• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

marscoin / martianrepublic / 23823524774

31 Mar 2026 11:03PM UTC coverage: 10.419%. Remained the same
23823524774

push

github

Martian Congress
refactor: A1 — split ApiController (1,147 lines) into 5 controllers

The god controller is dead. Long live focused controllers:

- FeedApiController (287 lines) — allPublic, allCitizen, allApplicants,
  allFeed, showCitizen, scitizen
- AuthApiController (260 lines) — marsAuth, checkAuth, wauth, token
- ForumApiController (205 lines) — threads, comments, categories
- ContentApiController (230 lines) — pinpic, pinvideo, pinjson (IPFS)
- UserManagementController (95 lines) — blockUser, deleteUser, eula

ApiController.php is now 18 lines (empty shell with migration notes).
All routes updated to array notation pointing to new controllers.
Zero method logic changed — pure structural refactor.

127 tests, 0 PHPStan errors, API endpoints verified.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

31 of 637 new or added lines in 5 files covered. (4.87%)

599 of 5749 relevant lines covered (10.42%)

1.47 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/app/Http/Controllers/ContentApiController.php
1
<?php
2

3
namespace App\Http\Controllers;
4

5
use App\Includes\AppHelper;
6
use App\Models\Citizen;
7
use Illuminate\Http\Request;
8
use Illuminate\Http\Response;
9
use Illuminate\Support\Facades\Auth;
10
use Illuminate\Support\Facades\Log;
11

12
class ContentApiController extends Controller
13
{
NEW
14
    public function pinpic(Request $request)
×
15
    {
NEW
16
        $uid = Auth::user()->id;
×
NEW
17
        $hash = '';
×
NEW
18
        $dataPic = $request->input('picture');
×
NEW
19
        $type = $request->input('type');
×
NEW
20
        $public_address = $request->input('address');
×
21

22
        // --- SECURITY: Sanitize the public_address to prevent directory traversal ---
NEW
23
        $safeAddress = AppHelper::sanitizePathSegment($public_address);
×
NEW
24
        if ($safeAddress === null) {
×
NEW
25
            return response()->json(['error' => 'Invalid address format.'], 400);
×
26
        }
27

28
        // --- SECURITY: Validate the base64 image data (extension, MIME, size, PHP code) ---
NEW
29
        $validation = AppHelper::validateBase64Image($dataPic);
×
NEW
30
        if (! $validation['valid']) {
×
NEW
31
            Log::warning('pinpic upload rejected: '.$validation['error'].' (user: '.$uid.')');
×
32

NEW
33
            return response()->json(['error' => $validation['error']], 422);
×
34
        }
35

NEW
36
        $safeExtension = $validation['extension'];
×
NEW
37
        $decodedData = $validation['data'];
×
38

NEW
39
        $file_path = './assets/citizen/'.$safeAddress.'/';
×
NEW
40
        if (! file_exists($file_path)) {
×
NEW
41
            mkdir($file_path, 0755, true);
×
42
        }
43

44
        // --- SECURITY: Write .htaccess to prevent PHP execution in upload dir ---
NEW
45
        AppHelper::writeUploadHtaccess($file_path);
×
46

47
        // Use validated extension, not user-supplied one
NEW
48
        $file_path = './assets/citizen/'.$safeAddress.'/profile_pic.'.$safeExtension;
×
49

NEW
50
        file_put_contents($file_path, $decodedData);
×
NEW
51
        $hash = AppHelper::upload($file_path, config('blockchain.ipfs.api_url').'/api/v0/add?pin=true');
×
52

NEW
53
        $citcache = Citizen::where('userid', '=', $uid)->first();
×
NEW
54
        if (is_null($citcache)) {
×
NEW
55
            $citcache = new Citizen;
×
56
        }
NEW
57
        $citcache->userid = $uid;
×
NEW
58
        $citcache->avatar_link = config('blockchain.ipfs.gateway_url').$hash;
×
NEW
59
        $citcache->save();
×
60

NEW
61
        return (new Response(json_encode(['Hash' => $hash]), 200))
×
NEW
62
            ->header('Content-Type', 'application/json;');
×
63

64
    }
65

NEW
66
    public function pinvideo(Request $request)
×
67
    {
68

NEW
69
        Log::debug('pinvid');
×
NEW
70
        $uid = Auth::user()->id;
×
NEW
71
        $hash = '';
×
NEW
72
        $dataPic = $request->input('file');
×
NEW
73
        $type = $request->input('type');
×
NEW
74
        $public_address = $request->input('address');
×
75

76
        // --- SECURITY: Sanitize the public_address to prevent directory traversal ---
NEW
77
        $safeAddress = AppHelper::sanitizePathSegment($public_address);
×
NEW
78
        if ($safeAddress === null) {
×
NEW
79
            return response()->json(['error' => 'Invalid address format.'], 400);
×
80
        }
81

NEW
82
        Log::debug('public: '.$safeAddress);
×
NEW
83
        if ($request->hasFile('file')) {
×
84
            // --- SECURITY: Validate the uploaded file (extension, MIME, size, PHP code) ---
NEW
85
            $uploadedFile = $request->file('file');
×
NEW
86
            $validation = AppHelper::validateUploadedFile($uploadedFile, [
×
NEW
87
                'webm' => ['video/webm', 'audio/webm'],
×
NEW
88
            ]);
×
NEW
89
            if (! $validation['valid']) {
×
NEW
90
                Log::warning('pinvideo upload rejected: '.$validation['error'].' (user: '.$uid.')');
×
91

NEW
92
                return response()->json(['error' => $validation['error']], 422);
×
93
            }
94

NEW
95
            Log::debug('storing file');
×
NEW
96
            $file_path = './assets/citizen/'.$safeAddress.'/';
×
NEW
97
            if (! file_exists($file_path)) {
×
NEW
98
                mkdir($file_path, 0755, true);
×
NEW
99
                Log::debug('making dir');
×
100
            }
101

102
            // --- SECURITY: Write .htaccess to prevent PHP execution in upload dir ---
NEW
103
            AppHelper::writeUploadHtaccess($file_path);
×
104

NEW
105
            $file_path = './assets/citizen/'.$safeAddress.'/';
×
NEW
106
            $request->file('file')->move($file_path, 'profile_video.webm');
×
NEW
107
            $file_path = $file_path.'profile_video.webm';
×
NEW
108
            $hash = AppHelper::upload($file_path, config('blockchain.ipfs.api_url').'/api/v0/add?pin=true');
×
NEW
109
            Log::debug('upload complete: '.$hash);
×
NEW
110
            $citcache = Citizen::where('userid', '=', $uid)->first();
×
NEW
111
            if (is_null($citcache)) {
×
NEW
112
                $citcache = new Citizen;
×
113
            }
NEW
114
            $citcache->userid = $uid;
×
NEW
115
            $citcache->liveness_link = config('blockchain.ipfs.gateway_url').$hash;
×
NEW
116
            $citcache->save();
×
NEW
117
            Log::debug('saved cit data');
×
118

NEW
119
            return (new Response(json_encode(['Hash' => $hash]), 200))
×
NEW
120
                ->header('Content-Type', 'application/json;');
×
121
        } else {
NEW
122
            Log::debug('no file found!');
×
123
        }
124

125
    }
126

127
    /**
128
     * Handles JSON storage and pinning to a distributed file system.
129
     *
130
     * @hideFromAPIDocumentation
131
     */
NEW
132
    public function pinjson(Request $request)
×
133
    {
NEW
134
        Log::info('in function');
×
NEW
135
        $public_address = $request->input('address');
×
NEW
136
        $type = $request->input('type');
×
NEW
137
        $json = $request->input('payload');
×
138

139
        // --- SECURITY: Sanitize the public_address to prevent directory traversal ---
NEW
140
        $safeAddress = AppHelper::sanitizePathSegment($public_address);
×
NEW
141
        if ($safeAddress === null) {
×
NEW
142
            return response()->json(['error' => 'Invalid address format.'], 400);
×
143
        }
144

145
        // --- SECURITY: Sanitize the type parameter to prevent path traversal ---
NEW
146
        $safeType = AppHelper::sanitizePathSegment($type);
×
NEW
147
        if ($safeType === null) {
×
NEW
148
            return response()->json(['error' => 'Invalid type format.'], 400);
×
149
        }
150

151
        // --- SECURITY: Check file size (max 5MB) ---
NEW
152
        if (strlen($json) > 5242880) {
×
NEW
153
            return response()->json(['error' => 'Payload exceeds maximum size of 5MB.'], 422);
×
154
        }
155

156
        // --- SECURITY: Reject payloads containing PHP code ---
NEW
157
        if (AppHelper::containsPhpCode($json)) {
×
NEW
158
            Log::warning('pinjson rejected: payload contains PHP code');
×
159

NEW
160
            return response()->json(['error' => 'Payload contains potentially dangerous content.'], 422);
×
161
        }
162

163
        // --- SECURITY: Validate that payload is valid JSON ---
NEW
164
        $decodedJson = json_decode($json);
×
NEW
165
        if ($json !== '' && json_last_error() !== JSON_ERROR_NONE) {
×
NEW
166
            return response()->json(['error' => 'Invalid JSON payload.'], 422);
×
167
        }
168

NEW
169
        $projectRoot = config('app.project_root', base_path());
×
NEW
170
        $base_path = $projectRoot.'/assets/citizen/'.$safeAddress;
×
171

172
        // Check and create the directory if it doesn't exist
NEW
173
        Log::info($base_path);
×
NEW
174
        clearstatcache();
×
NEW
175
        if (! is_dir($base_path)) {
×
NEW
176
            Log::info('Trying to create directory: '.$base_path);
×
NEW
177
            if (! mkdir($base_path, 0755, true)) {
×
NEW
178
                Log::error('Failed to create directory: '.$base_path);
×
179

NEW
180
                return response()->json(['error' => 'Failed to create directory. Check permissions.'], 500);
×
181
            }
NEW
182
            Log::info('Directory created: '.$base_path);
×
183
        }
184

185
        // Check if the directory is writable, regardless of whether it was just created or already existed
NEW
186
        if (! is_writable($base_path)) {
×
NEW
187
            Log::error('Directory not writable: '.$base_path);
×
188

NEW
189
            return response()->json(['error' => 'Directory is not writable. Check permissions.'], 500);
×
190
        }
191

192
        // --- SECURITY: Write .htaccess to prevent PHP execution in upload dir ---
NEW
193
        AppHelper::writeUploadHtaccess($base_path);
×
194

NEW
195
        $file_path = $base_path.'/'.$safeType.'.json';
×
196

197
        // Attempt to write the JSON data to the file
NEW
198
        if (file_put_contents($file_path, $json) === false) {
×
NEW
199
            return response()->json(['error' => 'Failed to write to file.'], 500);
×
200
        }
201

202
        try {
NEW
203
            Log::info('PermaJson: '.$file_path);
×
204

205
            // Check if the type contains the word 'log'
NEW
206
            if (strpos($safeType, 'log') !== false) {
×
207
                // The type contains 'log', use uploadFolder
NEW
208
                $apiResponse = AppHelper::uploadFolder($file_path, config('blockchain.ipfs.api_url').'/api/v0/add?pin=true&recursive=true&wrap-with-directory=true&quieter');
×
209
            } else {
210
                // The type does not contain 'log', use upload
NEW
211
                $apiResponse = AppHelper::upload($file_path, config('blockchain.ipfs.api_url').'/api/v0/add?pin=true');
×
212
            }
213

NEW
214
            if (is_string($apiResponse)) {
×
NEW
215
                $formattedResponse = ['Hash' => $apiResponse];
×
216
            } else {
NEW
217
                Log::error('Upload error: Formatting');
×
218

NEW
219
                return response()->json(['error' => 'formatting error'], 500);
×
220
            }
221

NEW
222
            return response()->json($formattedResponse, 200)->header('Content-Type', 'application/json;');
×
NEW
223
        } catch (\Exception $e) {
×
224
            // Handle exceptions during the upload and pinning process
NEW
225
            Log::error('Upload error: '.$e->getMessage());
×
226

NEW
227
            return response()->json(['error' => $e->getMessage()], 500);
×
228
        }
229
    }
230
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc