InboxSftpRequest.php
1 <?php 2 3 namespace App\Http\Requests; 4 5 use Illuminate\Foundation\Http\FormRequest; 6 use App\Http\Services\DossierService; 7 use App\Models\Dossier; 8 use App\Models\User; 9 use Carbon\Carbon; 10 use Illuminate\Support\Facades\Storage; 11 use Illuminate\Support\Str; 12 13 class InboxSftpRequest extends FormRequest 14 { 15 private const FILENAME_PATTERN = '/^(\d{4}-\d{2}-\d{2})_(\d{4}-\d+)_([a-zA-Z0-9_-]+)\..*$/'; 16 private const EXTENSIONS = ['.zip', '.gpg', '.asc', '.zip.gpg', '.zip.asc']; 17 private ?User $user; 18 private ?Dossier $dossier; 19 20 public function authorize() 21 { 22 return true; 23 } 24 25 public function rules() 26 { 27 return [ 28 'username' => ['required', 'exists:users,username'], 29 'filename' => [ 30 'required', 31 'string', 32 $this->validateFilenamePattern(), 33 $this->validateFileExtension(), 34 ], 35 'path' => [ 36 'required', 37 'string', 38 $this->validateFileExistsInSftp() 39 ] 40 ]; 41 } 42 43 private function validateFileExistsInSftp() 44 { 45 return function ($attribute, $value, $fail) { 46 if (!Storage::disk('sftp')->exists($value)) { 47 $fail('The specified file does not exist in SFTP.'); 48 } 49 }; 50 } 51 52 private function validateFilenamePattern() 53 { 54 return function ($attribute, $value, $fail) { 55 if (!preg_match(self::FILENAME_PATTERN, $value, $matches)) { 56 $fail('Filename must follow the pattern: YYYY-MM-DD_YYYY-CODE_description.extension'); 57 return; 58 } 59 60 [, $date, $dossierCode, $description] = $matches; 61 62 if (!$this->isValidDate($date)) { 63 $fail('Invalid date format or date does not exist.'); 64 return; 65 } 66 67 if (!$this->isValidDossierCode($dossierCode)) { 68 $fail('Invalid dossier code format. Must be YYYY-N where N is between 0 and 999.'); 69 return; 70 } 71 72 if (!$this->isNewDossier($dossierCode) && !$this->dossierExistsForUser($dossierCode)) { 73 $fail('No dossier found with the specified code for this user.'); 74 } 75 }; 76 } 77 78 private function validateFileExtension() 79 { 80 return function ($attribute, $value, $fail) { 81 foreach (self::EXTENSIONS as $extension) { 82 if (Str::endsWith($value, $extension)) { 83 return; 84 } 85 } 86 87 return $fail('File extension is not allowed. Allowed extensions are: ' . implode(', ', self::EXTENSIONS)); 88 }; 89 } 90 91 private function isValidDate($date) 92 { 93 try { 94 $parsedDate = Carbon::createFromFormat('Y-m-d', $date); 95 return $parsedDate && $parsedDate->format('Y-m-d') === $date; 96 } catch (\Exception $e) { 97 return false; 98 } 99 } 100 101 private function isValidDossierCode($dossierCode) 102 { 103 if (!preg_match('/^(\d{4})-(\d+)$/', $dossierCode, $matches)) { 104 return false; 105 } 106 107 [, $year, $code] = $matches; 108 $codeNumber = intval($code); 109 110 return is_numeric($year) && $codeNumber >= 0; 111 } 112 113 private function isNewDossier($dossierCode) 114 { 115 if (!preg_match('/^(\d{4})-(\d+)$/', $dossierCode, $matches)) { 116 return false; 117 } 118 119 [, $year, $code] = $matches; 120 return intval($code) === 0; 121 } 122 123 private function dossierExistsForUser($dossierCode) 124 { 125 $this->user = User::where('username', $this->input('username'))->first(); 126 if (!$this->user) { 127 return false; 128 } 129 130 $this->dossier = DossierService::findByCode($dossierCode); 131 if (!$this->dossier) { 132 return false; 133 } 134 135 return $this->user->isInDossier($this->dossier) 136 or $this->dossier->inboxFiles()->where('user_id', $this->user->id)->exists(); 137 } 138 139 public function validated($key = null, $default = null) 140 { 141 $data = parent::validated($key, $default); 142 $data['dossier'] = $this->dossier ?? null; 143 $data['user'] = User::where('username', $this->input('username'))->first(); 144 return $data; 145 } 146 }