/ app / Http / Requests / InboxSftpRequest.php
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  }