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

marscoin / martianrepublic / 22642335023

03 Mar 2026 08:55PM UTC coverage: 4.588% (-0.3%) from 4.846%
22642335023

push

github

Lennart Lopin
security: Add gitleaks pre-commit hook, harden .gitignore for public repo

- Installed gitleaks 8.21.2 as pre-commit hook to prevent secret leaks
- Added .gitleaks.toml configuration
- Expanded .gitignore: user upload dirs (citizen/wallet/congress), backup files,
  IDE files, .ralphrc, Ralph logs
- Added PUBLIC REPO warning to Ralph PROMPT.md
- Removed hardcoded credentials from PROMPT.md
- Updated fix_plan.md with expanded 6-phase roadmap
- Added testing & security audit instructions to PROMPT.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

168 of 3662 relevant lines covered (4.59%)

0.23 hits per line

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

0.0
/app/Http/Controllers/Wallet/ApiController.php
1
<?php
2
namespace App\Http\Controllers\Wallet;
3

4
use App\Http\Controllers\Controller;
5
use Illuminate\Http\Request;
6
use Illuminate\Http\Response;
7
use Illuminate\Support\Facades\Auth;
8
use App\Includes\AppHelper;
9
use App\Models\Posts;
10
use App\Models\Profile;
11
use App\Models\User;
12
use App\Models\Proposals;
13
use App\Models\Publication;
14
use App\Models\Threads;
15
use App\Models\Citizen;
16
use App\Models\HDWallet;
17
use Illuminate\Support\Facades\Log;
18

19
class ApiController extends Controller {
20

21
        /**
22
         * Setup the layout used by the controller.
23
         *
24
         * @return void
25
         */
26

27
        public function __construct() {
×
28
            if (!Auth::check())
×
29
                        redirect('/login');
×
30
        }
31

32

33

34
        /**
35
         * Internal
36
         *
37
         * @ignore
38
         * @hideFromAPIDocumentation
39
         */
40
        public function permapinpic(Request $request){
×
41

42
                if (Auth::check()) {
×
43
                        $uid = Auth::user()->id;
×
44
                        $hash = "";
×
45
                        $dataPic = $request->input('picture');
×
46
                        $type = $request->input('type');
×
47
                        $public_address = $request->input('address');
×
48

49
                        // --- SECURITY: Sanitize the public_address to prevent directory traversal ---
50
                        $safeAddress = AppHelper::sanitizePathSegment($public_address);
×
51
                        if ($safeAddress === null) {
×
52
                                return response()->json(['error' => 'Invalid address format.'], 400);
×
53
                        }
54

55
                        // --- SECURITY: Validate the base64 image data (extension, MIME, size, PHP code) ---
56
                        $validation = AppHelper::validateBase64Image($dataPic);
×
57
                        if (!$validation['valid']) {
×
58
                                Log::warning('permapinpic upload rejected: ' . $validation['error'] . ' (user: ' . $uid . ')');
×
59
                                return response()->json(['error' => $validation['error']], 422);
×
60
                        }
61

62
                        $safeExtension = $validation['extension'];
×
63
                        $decodedData = $validation['data'];
×
64

65
                        $file_path = "./assets/citizen/" . $safeAddress . "/";
×
66
                        if (!file_exists($file_path)) {
×
67
                                mkdir($file_path, 0755, true);
×
68
                        }
69

70
                        // --- SECURITY: Write .htaccess to prevent PHP execution in upload dir ---
71
                        AppHelper::writeUploadHtaccess($file_path);
×
72

73
                        // Use validated extension, not user-supplied one
74
                        $file_path = "./assets/citizen/" . $safeAddress . "/profile_pic." . $safeExtension;
×
75

76
                        file_put_contents($file_path, $decodedData);
×
77
                        $hash = AppHelper::upload($file_path, "http://127.0.0.1:5001/api/v0/add?pin=true");
×
78

79
                        $citcache = Citizen::where('userid', '=', $uid)->first();
×
80
                        if(is_null($citcache)) $citcache = new Citizen;
×
81
                        $citcache->userid = $uid;
×
82
                        $citcache->avatar_link = "https://ipfs.marscoin.org/ipfs/".$hash;
×
83
                        $citcache->save();
×
84

85
                        return (new Response(json_encode(array("Hash" => $hash)), 200))
×
86
              ->header('Content-Type', "application/json;");
×
87

88
                        //}
89
                }else{
90
            return redirect('/login');
×
91
        }
92
        }
93

94

95
        /**
96
         * Internal
97
         *
98
         * @ignore
99
         * @hideFromAPIDocumentation
100
         */
101
        public function permapinvideo(Request $request){
×
102

103
                if (Auth::check()) {
×
104
                        $uid = Auth::user()->id;
×
105
                        $hash = "";
×
106
                        $dataPic = $request->input('file');
×
107
                        $type = $request->input('type');
×
108
                        $public_address = $request->input('address');
×
109

110
                        // --- SECURITY: Sanitize the public_address to prevent directory traversal ---
111
                        $safeAddress = AppHelper::sanitizePathSegment($public_address);
×
112
                        if ($safeAddress === null) {
×
113
                                return response()->json(['error' => 'Invalid address format.'], 400);
×
114
                        }
115

116
                        if ($request->hasFile('file'))
×
117
                        {
118
                                // --- SECURITY: Validate the uploaded file (extension, MIME, size, PHP code) ---
119
                                $uploadedFile = $request->file('file');
×
120
                                $validation = AppHelper::validateUploadedFile($uploadedFile, [
×
121
                                        'webm' => ['video/webm', 'audio/webm'],
×
122
                                ]);
×
123
                                if (!$validation['valid']) {
×
124
                                        Log::warning('permapinvideo upload rejected: ' . $validation['error'] . ' (user: ' . $uid . ')');
×
125
                                        return response()->json(['error' => $validation['error']], 422);
×
126
                                }
127

128
                                $file_path = "./assets/citizen/" . $safeAddress . "/";
×
129
                                if (!file_exists($file_path)) {
×
130
                                        mkdir($file_path, 0755, true);
×
131
                                }
132

133
                                // --- SECURITY: Write .htaccess to prevent PHP execution in upload dir ---
134
                                AppHelper::writeUploadHtaccess($file_path);
×
135

136
                                $file_path = "./assets/citizen/" . $safeAddress  . "/";
×
137
                                $request->file('file')->move($file_path, "profile_video.webm" );
×
138
                                $file_path = $file_path . "profile_video.webm";
×
139
                                $hash = AppHelper::upload($file_path, "http://127.0.0.1:5001/api/v0/add?pin=true");
×
140

141
                                $citcache = Citizen::where('userid', '=', $uid)->first();
×
142
                                if(is_null($citcache)) $citcache = new Citizen;
×
143
                                $citcache->userid = $uid;
×
144
                                $citcache->liveness_link = "https://ipfs.marscoin.org/ipfs/".$hash;
×
145
                                $citcache->save();
×
146

147
                                return (new Response(json_encode(array("Hash" => $hash)), 200))
×
148
                                ->header('Content-Type', "application/json;");
×
149
                        }
150

151

152
                }else{
153
            return redirect('/login');
×
154
        }
155

156
        }
157

158

159
        /**
160
         * Internal
161
         *
162
         * @hideFromAPIDocumentation
163
         */
164
        public function permapinlog(Request $request)
×
165
        {
166
                if (!Auth::check()) {
×
167
                        return redirect('/login');
×
168
                }
169

170
                $public_address = $request->input('address');
×
171
                $title = $request->input('title');
×
172
                $entry = $request->input('entry');
×
173
                $uid = Auth::user()->id;
×
174

175
                // --- SECURITY: Sanitize the public_address to prevent directory traversal ---
176
                $safeAddress = AppHelper::sanitizePathSegment($public_address);
×
177
                if ($safeAddress === null) {
×
178
                        return response()->json(['error' => 'Invalid address format.'], 400);
×
179
                }
180

181
                // --- SECURITY: Sanitize title for use in path (md5 hash is safe, but validate title exists) ---
182
                if (empty($title)) {
×
183
                        return response()->json(['error' => 'Title is required.'], 400);
×
184
                }
185

186
                $file_path = "./assets/citizen/" . $safeAddress . "/logbook/" . md5($title);
×
187

188
                if (!file_exists($file_path)) {
×
189
                        mkdir($file_path, 0755, true); // More secure permissions
×
190
                }
191

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

195
                // --- SECURITY: Check content for PHP code ---
196
                $logContent = $title . "\n\n" . $entry;
×
197
                if (AppHelper::containsPhpCode($logContent)) {
×
198
                        Log::warning('permapinlog rejected: content contains PHP code (user: ' . $uid . ')');
×
199
                        return response()->json(['error' => 'Content contains potentially dangerous code.'], 422);
×
200
                }
201

202
                // --- SECURITY: Check content size (max 5MB) ---
203
                if (strlen($logContent) > 5242880) {
×
204
                        return response()->json(['error' => 'Content exceeds maximum size of 5MB.'], 422);
×
205
                }
206

207
                $file = $file_path . "/log.markdown";
×
208
                file_put_contents($file, $logContent);
×
209

210
                $files = $request->file('filenames');
×
211
                if ($files && is_array($files)) {
×
212
                        foreach ($files as $f) {
×
213
                                // --- SECURITY: Validate each uploaded file ---
214
                                $validation = AppHelper::validateUploadedFile($f);
×
215
                                if (!$validation['valid']) {
×
216
                                        Log::warning('permapinlog file rejected: ' . $validation['error'] . ' (user: ' . $uid . ', file: ' . $f->getClientOriginalName() . ')');
×
217
                                        // Skip invalid files but continue processing valid ones
218
                                        continue;
×
219
                                }
220

221
                                $name = $f->hashName(); // Generates a unique, random name...
×
222

223
                                // --- SECURITY: Ensure the generated filename does not have a dangerous extension ---
224
                                $ext = strtolower(pathinfo($name, PATHINFO_EXTENSION));
×
225
                                if (AppHelper::isExtensionBlocked($ext)) {
×
226
                                        Log::warning('permapinlog blocked dangerous file extension: ' . $ext . ' (user: ' . $uid . ')');
×
227
                                        continue;
×
228
                                }
229

230
                                $f->move($file_path, $name);
×
231
                        }
232
                }
233

234
                try {
235
                        $hash = AppHelper::uploadFolder($file_path, 'http://127.0.0.1:5001/api/v0/add?pin=true&recursive=true&wrap-with-directory=true&quieter'); // Example: use a config value or env variable
×
236
                        AppHelper::insertPublicationCache($uid, $file_path, $hash, $title);
×
237
                } catch (\Exception $e) {
×
238
                        // Handle error; possibly log it and return a user-friendly message
239
                        return response()->json(["error" => $e->getMessage()], 500);
×
240
                }
241

242
                return response()->json(["Hash" => $hash, "Path" => $file_path], 200)
×
243
                        ->header('Content-Type', "application/json;");
×
244
        }
245

246

247

248

249
        public function removepinlog(Request $request)
×
250
        {
251
                if (!Auth::check()) {
×
252
                        return redirect('/login');
×
253
                }
254

255
                $cid = $request->input('cid'); // The CID to unpin
×
256

257
                if (!$cid) {
×
258
                        return response()->json(["error" => "CID is required"], 400);
×
259
                }
260

261
                // Validate CID format to prevent injection into the IPFS API URL
262
                if (!AppHelper::isValidCID($cid)) {
×
263
                        return response()->json(["error" => "Invalid CID format"], 400);
×
264
                }
265

266
                // Verify the publication belongs to the current user
267
                $uid = Auth::user()->id;
×
268
                $publication = Publication::where('ipfs_hash', $cid)->first();
×
269
                if ($publication && $publication->userid != $uid) {
×
270
                        return response()->json(["error" => "Unauthorized: you can only remove your own publications."], 403);
×
271
                }
272

273
                $ipfsApiUrl = 'http://127.0.0.1:5001/api/v0/pin/rm?arg=' . urlencode($cid) . "&recursive=true";
×
274
                Log::debug($ipfsApiUrl);
×
275
                try {
276
                        // Initialize cURL session
277
                        $ch = curl_init();
×
278
                        curl_setopt($ch, CURLOPT_URL, $ipfsApiUrl);
×
279
                        curl_setopt($ch, CURLOPT_VERBOSE, true);
×
280
                        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
×
281
                        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST"); // Ensure this is POST
×
282

283
                        $response = curl_exec($ch);
×
284
                        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
×
285

286
                        Log::debug($response);
×
287

288
                        if ($httpCode != 200) {
×
289
                                // Error handling, IPFS might return error messages as JSON
290
                                $errorMsg = "Failed to unpin CID. HTTP status code: $httpCode";
×
291
                                if ($responseJson = json_decode($response)) {
×
292
                                        if (!empty($responseJson->Message)) {
×
293
                                                $errorMsg = $responseJson->Message;
×
294
                                        }
295
                                }
296
                                throw new \Exception($errorMsg);
×
297
                        }
298

299
                        $publicationDeleted = Publication::where('ipfs_hash', $cid)->delete();
×
300
                        if (!$publicationDeleted) {
×
301
                                throw new \Exception("Failed to delete publication from the database.");
×
302
                        }
303

304
                } catch (\Exception $e) {
×
305
                        // Handle error; possibly log it and return a user-friendly message
306
                        return response()->json(["error" => $e->getMessage()], 500);
×
307
                }
308

309
                // Close cURL session
310
                curl_close($ch);
×
311

312
                // Respond to the client
313
                return response()->json(["message" => "Successfully unpinned CID: $cid"], 200)
×
314
                        ->header('Content-Type', "application/json;");
×
315
        }
316

317

318

319
        /**
320
         * Handles JSON storage and pinning to a distributed file system.
321
         *
322
         * @hideFromAPIDocumentation
323
         */
324
        public function permapinjson(Request $request)
×
325
        {
326
                if (!Auth::check()) {
×
327
                        return redirect('/login');
×
328
                }
329
                Log::info("in function");
×
330
                $public_address = $request->input('address');
×
331
                $type = $request->input('type');
×
332
                $json = $request->input('payload');
×
333

334
                // --- SECURITY: Sanitize the public_address to prevent directory traversal ---
335
                $safeAddress = AppHelper::sanitizePathSegment($public_address);
×
336
                if ($safeAddress === null) {
×
337
                        return response()->json(['error' => 'Invalid address format.'], 400);
×
338
                }
339

340
                // --- SECURITY: Sanitize the type parameter to prevent path traversal ---
341
                $safeType = AppHelper::sanitizePathSegment($type);
×
342
                if ($safeType === null) {
×
343
                        return response()->json(['error' => 'Invalid type format.'], 400);
×
344
                }
345

346
                // --- SECURITY: Check file size (max 5MB) ---
347
                if (strlen($json) > 5242880) {
×
348
                        return response()->json(['error' => 'Payload exceeds maximum size of 5MB.'], 422);
×
349
                }
350

351
                // --- SECURITY: Reject payloads containing PHP code ---
352
                if (AppHelper::containsPhpCode($json)) {
×
353
                        Log::warning('permapinjson rejected: payload contains PHP code');
×
354
                        return response()->json(['error' => 'Payload contains potentially dangerous content.'], 422);
×
355
                }
356

357
                // --- SECURITY: Validate that payload is valid JSON ---
358
                $decodedJson = json_decode($json);
×
359
                if ($json !== '' && json_last_error() !== JSON_ERROR_NONE) {
×
360
                        return response()->json(['error' => 'Invalid JSON payload.'], 422);
×
361
                }
362

363
                $projectRoot = config('app.project_root', base_path());
×
364
                $base_path =  $projectRoot . "/assets/citizen/" . $safeAddress;
×
365

366
                // Check and create the directory if it doesn't exist
367
                Log::info($base_path);
×
368
                clearstatcache();
×
369
                if (!is_dir($base_path)) {
×
370
                        Log::info("Trying to create directory: " . $base_path);
×
371
                        if (!mkdir($base_path, 0755, true)) {
×
372
                                Log::error("Failed to create directory: " . $base_path);
×
373
                                return response()->json(["error" => "Failed to create directory. Check permissions."], 500);
×
374
                        }
375
                        Log::info("Directory created: " . $base_path);
×
376
                }
377

378
                // Check if the directory is writable, regardless of whether it was just created or already existed
379
                if (!is_writable($base_path)) {
×
380
                        Log::error("Directory not writable: " . $base_path);
×
381
                        return response()->json(["error" => "Directory is not writable. Check permissions."], 500);
×
382
                }
383

384
                // --- SECURITY: Write .htaccess to prevent PHP execution in upload dir ---
385
                AppHelper::writeUploadHtaccess($base_path);
×
386

387
                $file_path = $base_path . "/" . $safeType . ".json";
×
388

389
                // Attempt to write the JSON data to the file
390
                if (file_put_contents($file_path, $json) === false) {
×
391
                        return response()->json(["error" => "Failed to write to file."], 500);
×
392
                }
393

394
                try {
395
                        Log::info("PermaJson: " . $file_path);
×
396

397
                        // Check if the type contains the word 'log'
398
                        if (strpos($safeType, 'log') !== false) {
×
399
                                // The type contains 'log', use uploadFolder
400
                                $apiResponse = AppHelper::uploadFolder($file_path, "http://127.0.0.1:5001/api/v0/add?pin=true&recursive=true&wrap-with-directory=true&quieter");
×
401
                        } else {
402
                                // The type does not contain 'log', use upload
403
                                $apiResponse = AppHelper::upload($file_path, "http://127.0.0.1:5001/api/v0/add?pin=true");
×
404
                        }
405

406
                        if (is_string($apiResponse)) {
×
407
                                $formattedResponse = ['Hash' => $apiResponse];
×
408
                        } else {
409
                                Log::error("Upload error: Formatting");
×
410
                                return response()->json(["error"=>"formatting error"], 500);
×
411
                        }
412

413
                        return response()->json($formattedResponse, 200)->header('Content-Type', "application/json;");
×
414
                } catch (\Exception $e) {
×
415
                        // Handle exceptions during the upload and pinning process
416
                        Log::error("Upload error: " . $e->getMessage());
×
417
                        return response()->json(["error" => $e->getMessage()], 500);
×
418
                }
419
        }
420

421

422

423

424
        /**
425
         * Internal
426
         *
427
         * @hideFromAPIDocumentation
428
         */
429
        public function setfeed(Request $request)
×
430
        {
431
                if (Auth::check()) {
×
432
                        $uid = Auth::user()->id;
×
433
                        $txid = $request->input('txid');
×
434
                        $action_tag = $request->input('type');
×
435
                        $public_address = $request->input('address');
×
436
                        $embedded_link = $request->input('embedded_link');
×
437
                        $message = $request->input('message');
×
438

439
                        AppHelper::insertBlockchainCache($public_address, $uid, $action_tag, $message, $embedded_link, $txid);
×
440

441
                        $profile = Profile::where('userid', '=', $uid)->first();
×
442
                        $profile->general_public = 1;
×
443
                        $profile->save();
×
444

445
                        return (new Response(json_encode(array("Hash" => $txid)), 200))
×
446
              ->header('Content-Type', "application/json;");
×
447

448
                }else{
449
            return redirect('/login');
×
450
        }
451
        }
452

453

454
        /**
455
         * Internal
456
         *
457
         * @hideFromAPIDocumentation
458
         */
459
        public function getBalance($address)
×
460
        {
461
                if (Auth::check()) {
×
462
                        $balance = AppHelper::getMarscoinBalance($address);
×
463
                        return (new Response(json_encode(array("balance" => $balance)), 200))
×
464
              ->header('Content-Type', "application/json;");
×
465

466

467
                }else{
468
            return redirect('/login');
×
469
        }
470
        }
471

472

473

474
        /**
475
         * Internal
476
         *
477
         * @hideFromAPIDocumentation
478
         */
479
        public function dismissAlert(Request $request)
×
480
    {
481
        $alertType = $request->alertType;
×
482
        session()->put($alertType, true);
×
483

484
        return response()->json(['success' => true]);
×
485
    }
486

487

488

489
        /**
490
         * Internal
491
         *
492
         * @hideFromAPIDocumentation
493
         */
494
        public function getPrice(Request $request)
×
495
        {
496
                if (Auth::check()) {
×
497
                        $price = AppHelper::getMarscoinPrice();
×
498
                        return (new Response(json_encode(array("mars_price" => $price)), 200))
×
499
              ->header('Content-Type', "application/json;");
×
500

501

502
                }else{
503
            return redirect('/login');
×
504
        }
505
        }
506

507

508
        /**
509
         * Internal
510
         *
511
         * @hideFromAPIDocumentation
512
         */
513
        public function getTransactions(Request $request)
×
514
        {
515
                if (Auth::check()) {
×
516

517
                        $address = $request->input('address');
×
518
                        $json = AppHelper::file_get_contents_curl("http://explore1.marscoin.org/api/txs/?address={$address}");
×
519

520
                        return (new Response($json))
×
521
              ->header('Content-Type', "application/json;");
×
522

523

524
                }else{
525
            return redirect('/login');
×
526
        }
527
        }
528

529
        /**
530
         * Internal
531
         *
532
         * @hideFromAPIDocumentation
533
         */
534
        public function setfullname(Request $request)
×
535
        {
536
                if (Auth::check()) {
×
537
                        $uid = Auth::user()->id;
×
538
                        $firstname = $request->input('firstname');
×
539
                        $lastname = $request->input('lastname');
×
540
                        if(!isset($firstname))
×
541
                                return;
×
542
                        if(!isset($lastname))
×
543
                                return;
×
544

545
                        $fullname = $firstname . " " . $lastname;
×
546

547
                        $citcache = Citizen::where('userid', '=', $uid)->first();
×
548
                        if(is_null($citcache)) $citcache = new Citizen;
×
549

550
                        $citcache->userid = $uid;
×
551
                        $citcache->firstname = $firstname;
×
552
                        $citcache->lastname = $lastname;
×
553
                        $citcache->save();
×
554

555
                        $user = User::where('id', '=', $uid)->first();
×
556
                        $user->fullname = $fullname;
×
557
                        $user->save();
×
558
                        return;
×
559
                }
560
        }
561

562
        /**
563
         *
564
         * @hideFromAPIDocumentation
565
         */
566
        public function cacheonboarding(Request $request)
×
567
        {
568
                if (Auth::check()) {
×
569
                        $uid = Auth::user()->id;
×
570
                        $shortbio = $request->input('shortbio');
×
571
                        $displayname = $request->input('displayname');
×
572
                        $publicaddress = $request->input('publicaddress');
×
573

574
                        $citcache = Citizen::where('userid', '=', $uid)->first();
×
575
                        if(is_null($citcache)) $citcache = new Citizen;
×
576

577
                        $citcache->userid = $uid;
×
578
                        $citcache->shortbio = $shortbio;
×
579
                        $citcache->displayname = $displayname;
×
580
                        $citcache->public_address = $publicaddress;
×
581
                        $citcache->save();
×
582

583
                        return;
×
584
                }
585
        }
586

587

588
        public function rejectApplication(Request $request)
×
589
        {
590
                if (Auth::check())
×
591
                {
592
                        // Check if the user has citizen status
593
                        $user = Auth::user();
×
594
                        $profile = Profile::where('userid', $user->id)->first();
×
595
                        $reporter = Citizen::where('userid', '=',  $user->id)->first();
×
596

597
                        $rejectionReasons = [
×
598
                                'avatar_link' => 'Missing Personal Image',
×
599
                                'liveness_link' => 'Incomplete Video',
×
600
                                'duplicate' => 'Duplicate Entry'
×
601
                        ];
×
602

603
                        if ($profile && $profile->citizen)
×
604
                        {
605
                                // Fetch the request data
606
                                $applicantUserId = $request->input('applicantUserId');
×
607
                                $fieldToUpdate = $request->input('field');
×
608

609
                                // Validate the field to update
610
                                if (!in_array($fieldToUpdate, ['avatar_link', 'liveness_link'])) {
×
611
                                        return response()->json(['error' => 'Invalid field specified.'], 400);
×
612
                                }
613

614
                                // Update the citizen table, setting the specified field to NULL for the applicant
615
                                Citizen::where('userid', $applicantUserId)->update([$fieldToUpdate => NULL]);
×
616

617
                                $applicant = Citizen::where('userid', '=',  $applicantUserId)->first();
×
618

619
                                // Insert a new forum post indicating the rejection
620
                                $content = "The application of {$applicant->public_address} has been rejected due to " . $rejectionReasons[$fieldToUpdate] . ".";
×
621

622

623
                                $fullname = $reporter->firstname . "" . $reporter->lastname;
×
624
                                Posts::create([
×
625
                                        'thread_id' => 27, // Thread ID for application commentary
×
626
                                        'author_id' => $user->id, // The ID of the citizen performing the rejection
×
627
                                        'content' => $content,
×
628
                                        'authorName' => $fullname,
×
629
                                        'created_at' => now(),
×
630
                                        'updated_at' => now(),
×
631
                                ]);
×
632

633
                                return response()->json(['success' => 'Application has been rejected and recorded.']);
×
634
                        } else {
635
                                // User is not a citizen or not authenticated
636
                                return response()->json(['error' => 'Unauthorized access.'], 403);
×
637
                        }
638
                } else {
639
                        // User is not logged in
640
                        return response()->json(['error' => 'User not authenticated.'], 401);
×
641
                }
642

643
        }
644

645
        /**
646
         *
647
         * @hideFromAPIDocumentation
648
         */
649
        public function closewallet(Request $request)
×
650
        {
651
                if (Auth::check()) {
×
652
                        $uid = Auth::user()->id;
×
653
                        $profile = Profile::where('userid', '=', $uid)->first();
×
654
                        $profile->wallet_open = 0;
×
655
                        $profile->save();
×
656
                        return;
×
657
                }
658
        }
659

660
        /**
661
         *
662
         * @hideFromAPIDocumentation
663
         */
664
        public function renameWallet(Request $request)
×
665
        {
666
                $request->validate([
×
667
                        'hdwallet_id' => 'required',
×
668
                        'new_name' => 'required|string|max:500',
×
669
                ]);
×
670

671
                if (Auth::check()) {
×
672
                        $wallet = HDWallet::where('id', $request->hdwallet_id)
×
673
                                                                ->where('user_id', Auth::id())
×
674
                                                                ->firstOrFail();
×
675
                        $wallet->wallet_type = $request->new_name;
×
676
                        $wallet->save();
×
677
                        return response()->json(['success' => 'Wallet renamed successfully']);
×
678
                }
679

680
                return response()->json(['error' => 'Unauthorized'], 403);
×
681
        }
682

683

684
        /**
685
         * @hideFromAPIDocumentation
686
         */
687
        public function setendorsed(Request $request)
×
688
        {
689
                if (Auth::check()) {
×
690
                        $endorserId = Auth::user()->id;
×
691
                        $targetUserId = $request->input("id");
×
692

693
                        // Prevent self-endorsement
694
                        if ((int) $endorserId === (int) $targetUserId) {
×
695
                                return response()->json(['error' => 'Cannot endorse yourself.'], 400);
×
696
                        }
697

698
                        // Only citizens can endorse
699
                        $endorserProfile = Profile::where('userid', '=', $endorserId)->first();
×
700
                        if (!$endorserProfile || !$endorserProfile->citizen) {
×
701
                                return response()->json(['error' => 'Only citizens can endorse.'], 403);
×
702
                        }
703

704
                        $profile = Profile::where('userid', '=', $targetUserId)->first();
×
705
                        if (!$profile) {
×
706
                                return response()->json(['error' => 'User not found.'], 404);
×
707
                        }
708
                        $profile->endorse_cnt = ($profile->endorse_cnt ?? 0) + 1;
×
709
                        $profile->save();
×
710
                        return response()->json(['success' => true]);
×
711
                }
712
        }
713

714

715
        /**
716
         * @hideFromAPIDocumentation
717
         */
718
        public function cacheproposal(Request $request)
×
719
        {
720
                if (Auth::check()) {
×
721
                        $uid = Auth::user()->id;
×
722
                        $txid = $request->input('txid');
×
723
                        $public_address = $request->input('address');
×
724
                        $embedded_link = $request->input('embedded_link');
×
725
                        $json = $request->input('message');
×
726
                        $data = json_decode($json);
×
727
                        $citcache = Citizen::where('userid', '=', $uid)->first();
×
728

729
                        if (!$this->isValidCID($embedded_link)) {
×
730
                                return response()->json(['error' => 'Invalid IPFS hash'], 400);
×
731
                        }
732

733
                        $proposal = new Proposals;
×
734
                        $proposal->user_id = $uid;
×
735
                        $proposal->title = $data->data->title;
×
736
                        $proposal->description = $data->data->description;
×
737
                        $proposal->category = $data->data->category;
×
738
                        $proposal->author = Auth::user()->fullname;
×
739
                        $proposal->ipfs_hash = $embedded_link;
×
740
                        $proposal->participation = $data->data->participation;
×
741
                        $proposal->threshold = $data->data->threshold;
×
742
                        $proposal->duration = $data->data->duration;
×
743
                        $proposal->expiration = $data->data->expiration;
×
744
                        $proposal->txid = $txid;
×
745
                        $proposal->public_address = $public_address;
×
746

747

748
                        $proposal->save();
×
749
                        $prop_id = $proposal->id;
×
750

751
                        $post = new Posts;
×
752
                        $post->thread->id = 2;
×
753
                        $post->author_id = $uid;
×
754
                        $post->content = $proposal->description;
×
755
                        $post->authorName = $citcache->firstname . " " . $citcache->lastname;
×
756
                        $post->save();
×
757

758
                        $post_id = $post->id;
×
759

760
                        $threads = new Threads;
×
761
                        $threads->category_id = 2;
×
762
                        $threads->author_id = $uid;
×
763
                        $threads->title = $data->data->title;
×
764
                        $threads->first_post_id = $post_id;
×
765
                        $threads->proposal_id = $prop_id;
×
766
                        $threads->save();
×
767

768
                        $thd_id = $threads->id;
×
769

770
                        $proposal->where('id', $prop_id)->update(['discussion' => $thd_id]);
×
771

772
                        return (new Response(json_encode(array("Proposal" => $prop_id, "Discussion" => $thd_id)), 200))
×
773
              ->header('Content-Type', "application/json;");
×
774

775

776
                }else{
777
            return redirect('/login');
×
778
        }
779
        }
780

781

782
        protected function isValidCID($input)
×
783
        {
784
                // Extract CID from the URL if embedded in a typical IPFS link format
785
                $parsedUrl = parse_url($input);
×
786
                $path = $parsedUrl['path'] ?? '';
×
787
                // Regex to extract potential CIDs from paths; adjust according to typical link structures
788
                preg_match('/(Qm[a-zA-Z1-9]{44}|b[a-z2-7]{58})/', $path, $matches);
×
789
                $hash = $matches[0] ?? '';
×
790

791
                // Regex to check CIDv0; starts with 'Qm' and followed by 44 characters from Base58 set
792
                $cidv0Regex = '/^Qm[a-zA-Z1-9]{44}$/';
×
793
                // Regex for basic validation of CIDv1; broader due to variability in encoding and version
794
                $cidv1Regex = '/^b[a-z2-7]{58}$/';  // Example for base32 encoding
×
795

796
                // Validate extracted hash
797
                return preg_match($cidv0Regex, $hash) || preg_match($cidv1Regex, $hash);
×
798
        }
799

800

801

802
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc