Olivier Massot пре 2 година
родитељ
комит
25cf027d47

+ 1 - 0
config/opentalent/products.yaml

@@ -15,6 +15,7 @@ parameters:
           - Enum
           - Enum
           - LicenceCmfOrganizationER
           - LicenceCmfOrganizationER
           - DownloadRequest
           - DownloadRequest
+          - UploadRequest
           - SubdomainAvailability
           - SubdomainAvailability
         roles:
         roles:
           - ROLE_IMPORT
           - ROLE_IMPORT

+ 159 - 25
src/ApiResources/Core/File/UploadRequest.php

@@ -1,49 +1,83 @@
 <?php
 <?php
-declare (strict_types=1);
+
+declare(strict_types=1);
 
 
 namespace App\ApiResources\Core\File;
 namespace App\ApiResources\Core\File;
 
 
 use ApiPlatform\Metadata\ApiProperty;
 use ApiPlatform\Metadata\ApiProperty;
 use ApiPlatform\Metadata\ApiResource;
 use ApiPlatform\Metadata\ApiResource;
-use ApiPlatform\Metadata\Get;
+use ApiPlatform\Metadata\Post;
+use ApiPlatform\Metadata\Put;
+use App\Enum\Core\FileTypeEnum;
 use App\State\Processor\Core\UploadRequestProcessor;
 use App\State\Processor\Core\UploadRequestProcessor;
 use App\State\Provider\Core\DownloadRequestProvider;
 use App\State\Provider\Core\DownloadRequestProvider;
+use Ramsey\Uuid\Uuid;
+
 
 
 /**
 /**
- * A request for a file from the LocalStorage
+ * A request to upload the given content to a File
  */
  */
 #[ApiResource(
 #[ApiResource(
     operations: [
     operations: [
         new Post(
         new Post(
-            uriTemplate: '/upload/{fileId}',
-            requirements: ['fileId' => '\\d+'],
-            security: 'is_granted("ROLE_FILE")',
-            processor: UploadRequestProcessor::class
-        ),
-        new Put(
-            uriTemplate: '/upload/{fileId}',
-            requirements: ['fileId' => '\\d+'],
+            uriTemplate: '/upload',
+            requirements: ['id' => '\\d+'],
             security: 'is_granted("ROLE_FILE")',
             security: 'is_granted("ROLE_FILE")',
             processor: UploadRequestProcessor::class
             processor: UploadRequestProcessor::class
-        ),
-        new Post(
-            uriTemplate: '/internal/download/{fileId}',
-            requirements: ['fileId' => '\\d+'],
-            processor: UploadRequestProcessor::class
-        ),
-        new Put(
-            uriTemplate: '/internal/download/{fileId}',
-            requirements: ['fileId' => '\\d+'],
-            processor: UploadRequestProcessor::class
         )
         )
     ]
     ]
 )]
 )]
 class UploadRequest
 class UploadRequest
 {
 {
-    private int $fileId;
+    /**
+     * Only because id is required
+     * @var int | null
+     */
+    #[ApiProperty(identifier: true)]
+    private ?int $fileId = null;
 
 
+    /**
+     * Le nom du fichier
+     * @var string
+     */
+    private string $filename;
+
+    /**
+     * The content of the uploaded file
+     * @var string
+     */
     private string $content;
     private string $content;
 
 
+    /**
+     * Si vrai, le propriétaire du fichier ne sera pas l'utilisateur en cours, mais l'organisation à laquelle il
+     * appartient.
+     * @var bool
+     */
+    private bool $organizationOwned = false;
+
+    /**
+     * Le type de fichier uploadé, si connu
+     * @var FileTypeEnum
+     */
+    private FileTypeEnum $type;
+
+    /**
+     * Visibilité du fichier
+     * @var string
+     */
+    private string $visibility = 'NOBODY';
+
+    /**
+     * Type mime (il sera déduit automatiquement du nom du fichier s'il n'est pas fourni ici)
+     * @var string|null
+     */
+    private ?string $mimeType = null;
+
+    public function __construct()
+    {
+        $this->type = FileTypeEnum::NONE();
+    }
+
     /**
     /**
      * @return int
      * @return int
      */
      */
@@ -51,21 +85,121 @@ class UploadRequest
     {
     {
         return $this->fileId;
         return $this->fileId;
     }
     }
+
     /**
     /**
-     * @param int $fileId
+     * @param int $id
+     * @return self
      */
      */
-    public function setFileId(int $fileId) : void
+    public function setFileId(int $id) : self
     {
     {
-        $this->fileId = $fileId;
+        $this->fileId = $id;
+        return $this;
     }
     }
 
 
+    /**
+     * @return string
+     */
     public function getContent(): string
     public function getContent(): string
     {
     {
         return $this->content;
         return $this->content;
     }
     }
 
 
+    /**
+     * @param string $content
+     * @return void
+     */
     public function setContent(string $content): void
     public function setContent(string $content): void
     {
     {
         $this->content = $content;
         $this->content = $content;
     }
     }
+
+    /**
+     * @return bool
+     */
+    public function isOrganizationOwned(): bool
+    {
+        return $this->organizationOwned;
+    }
+
+    /**
+     * @param bool $organizationOwned
+     * @return self
+     */
+    public function setOrganizationOwned(bool $organizationOwned): self
+    {
+        $this->organizationOwned = $organizationOwned;
+        return $this;
+    }
+
+    /**
+     * @return string
+     */
+    public function getFilename(): string
+    {
+        return $this->filename;
+    }
+
+    /**
+     * @param string $filename
+     * @return self
+     */
+    public function setFilename(string $filename): self
+    {
+        $this->filename = $filename;
+        return $this;
+    }
+
+    /**
+     * @return FileTypeEnum
+     */
+    public function getType(): FileTypeEnum
+    {
+        return $this->type;
+    }
+
+    /**
+     * @param FileTypeEnum $type
+     * @return self
+     */
+    public function setType(FileTypeEnum $type): self
+    {
+        $this->type = $type;
+        return $this;
+    }
+
+    /**
+     * @return string
+     */
+    public function getVisibility(): string
+    {
+        return $this->visibility;
+    }
+
+    /**
+     * @param string $visibility
+     * @return self
+     */
+    public function setVisibility(string $visibility): self
+    {
+        $this->visibility = $visibility;
+        return $this;
+    }
+
+    /**
+     * @return string|null
+     */
+    public function getMimeType(): ?string
+    {
+        return $this->mimeType;
+    }
+
+    /**
+     * @param string|null $mimeType
+     * @return self
+     */
+    public function setMimeType(?string $mimeType): self
+    {
+        $this->mimeType = $mimeType;
+        return $this;
+    }
 }
 }

+ 2 - 8
src/Service/Export/BaseExporter.php

@@ -10,7 +10,6 @@ use App\Repository\Access\AccessRepository;
 use App\Repository\Core\FileRepository;
 use App\Repository\Core\FileRepository;
 use App\Service\Export\Model\ExportModelInterface;
 use App\Service\Export\Model\ExportModelInterface;
 use App\Service\File\FileManager;
 use App\Service\File\FileManager;
-use App\Service\File\Storage\LocalStorage;
 use App\Service\ServiceIterator\EncoderIterator;
 use App\Service\ServiceIterator\EncoderIterator;
 use App\Service\Utils\StringsUtils;
 use App\Service\Utils\StringsUtils;
 use Doctrine\ORM\EntityManagerInterface;
 use Doctrine\ORM\EntityManagerInterface;
@@ -30,7 +29,6 @@ abstract class BaseExporter
     protected EncoderIterator $encoderIterator;
     protected EncoderIterator $encoderIterator;
     protected EntityManagerInterface $entityManager;
     protected EntityManagerInterface $entityManager;
     protected LoggerInterface $logger;
     protected LoggerInterface $logger;
-    private LocalStorage $localStorage;
     protected FileManager $fileManager;
     protected FileManager $fileManager;
 
 
     #[Required]
     #[Required]
@@ -49,9 +47,6 @@ abstract class BaseExporter
     public function setEntityManager(EntityManagerInterface $entityManager): void
     public function setEntityManager(EntityManagerInterface $entityManager): void
     { $this->entityManager = $entityManager; }
     { $this->entityManager = $entityManager; }
     #[Required]
     #[Required]
-    public function setLocalStorage(LocalStorage $localStorage): void
-    { $this->localStorage = $localStorage; }
-    #[Required]
     public function setFileManager(FileManager $fileManager): void
     public function setFileManager(FileManager $fileManager): void
     { $this->fileManager = $fileManager; }
     { $this->fileManager = $fileManager; }
     #[Required]
     #[Required]
@@ -101,8 +96,7 @@ abstract class BaseExporter
             $file = $this->prepareFile($exportRequest, false);
             $file = $this->prepareFile($exportRequest, false);
         }
         }
 
 
-        // TODO: passer par le FileManager plutôt?
-        return $this->localStorage->write($file, $content, $requester);
+        return $this->fileManager->write($file, $content, $requester);
     }
     }
 
 
     /**
     /**
@@ -126,7 +120,7 @@ abstract class BaseExporter
             $filename .= '.' . $exportRequest->getFormat();
             $filename .= '.' . $exportRequest->getFormat();
         }
         }
 
 
-        return $this->localStorage->prepareFile(
+        return $this->fileManager->prepareFile(
             $requester,
             $requester,
             $filename,
             $filename,
             $this->getFileType(),
             $this->getFileType(),

+ 60 - 0
src/Service/File/FileManager.php

@@ -9,7 +9,10 @@ use ApiPlatform\Metadata\Get;
 use App\ApiResources\Core\File\DownloadRequest;
 use App\ApiResources\Core\File\DownloadRequest;
 use App\Entity\Access\Access;
 use App\Entity\Access\Access;
 use App\Entity\Core\File;
 use App\Entity\Core\File;
+use App\Entity\Organization\Organization;
+use App\Entity\Person\Person;
 use App\Enum\Core\FileHostEnum;
 use App\Enum\Core\FileHostEnum;
+use App\Enum\Core\FileTypeEnum;
 use App\Service\File\Exception\FileNotFoundException;
 use App\Service\File\Exception\FileNotFoundException;
 use App\Service\File\Storage\ApiLegacyStorage;
 use App\Service\File\Storage\ApiLegacyStorage;
 use App\Service\File\Storage\FileStorageInterface;
 use App\Service\File\Storage\FileStorageInterface;
@@ -61,6 +64,36 @@ class FileManager
         return $storage->read($file);
         return $storage->read($file);
     }
     }
 
 
+    /**
+     * Prepare a File record with a PENDING status.
+     * This record will hold all the data needed to create the file, except its content.
+     *
+     * @param Organization|Access|Person $owner Owner of the file, either an organization, a person or both (access)
+     * @param string $filename The file's name (mandatory)
+     * @param FileTypeEnum $type The type of the new file
+     * @param Access $createdBy Id of the access responsible for this creation
+     * @param bool $isTemporary Is it a temporary file that can be deleted after some time
+     * @param string|null $mimeType Mimetype of the file, if not provided, the method will try to guess it from its file name's extension
+     * @param string $visibility
+     * @param bool $flushFile Should the newly created file be flushed after having been persisted?
+     * @return File
+     */
+    public function prepareFile(
+        Organization | Access | Person $owner,
+        string $filename,
+        FileTypeEnum $type,
+        Access $createdBy,
+        bool $isTemporary = false,
+        string $visibility = 'NOBODY',
+        string $mimeType = null,
+        bool $flushFile = true
+    ): File
+    {
+        return $this
+            ->localStorage
+            ->prepareFile($owner, $filename, $type, $createdBy, $isTemporary, $visibility, $mimeType, $flushFile);
+    }
+
     /**
     /**
      * Write the $content into the file storage and update the given File object's size, slug, status (READY)...
      * Write the $content into the file storage and update the given File object's size, slug, status (READY)...
      *
      *
@@ -75,6 +108,33 @@ class FileManager
         return $storage->write($file, $content, $author);
         return $storage->write($file, $content, $author);
     }
     }
 
 
+    /**
+     * Convenient method to successively prepare and write a file
+     *
+     * @param Organization|Access|Person $owner
+     * @param string $filename
+     * @param FileTypeEnum $type
+     * @param string $content
+     * @param Access $author
+     * @param bool $isTemporary
+     * @param string|null $mimeType
+     * @param string $visibility
+     * @return File
+     */
+    public function makeFile (
+        Organization | Access | Person $owner,
+        string                         $filename,
+        FileTypeEnum                   $type,
+        string                         $content,
+        Access                         $author,
+        bool                           $isTemporary = false,
+        string                         $visibility = 'NOBODY',
+        string                         $mimeType = null
+    ): File {
+        return $this
+            ->localStorage
+            ->makeFile($owner, $filename, $type, $content, $author, $isTemporary, $visibility, $mimeType);
+    }
 
 
     /**
     /**
      * Get the IRI to download this file (eg: /api/download/1)
      * Get the IRI to download this file (eg: /api/download/1)

+ 1 - 1
src/Service/File/Storage/ApiLegacyStorage.php

@@ -21,7 +21,7 @@ class ApiLegacyStorage implements FileStorageInterface
     public function __construct(private ApiLegacyRequestService $apiLegacyRequestService)
     public function __construct(private ApiLegacyRequestService $apiLegacyRequestService)
     {}
     {}
 
 
-    public function exists(File $file, string $content, Access $author): File {
+    public function exists(File $file): bool {
         throw new \RuntimeException('not implemented error');
         throw new \RuntimeException('not implemented error');
     }
     }
 
 

+ 28 - 12
src/State/Processor/Core/UploadRequestProcessor.php

@@ -9,12 +9,14 @@ use ApiPlatform\Metadata\Operation;
 use ApiPlatform\State\ProcessorInterface;
 use ApiPlatform\State\ProcessorInterface;
 use ApiPlatform\State\ProviderInterface;
 use ApiPlatform\State\ProviderInterface;
 use App\ApiResources\Core\File\DownloadRequest;
 use App\ApiResources\Core\File\DownloadRequest;
+use App\ApiResources\Core\File\UploadRequest;
 use App\Entity\Access\Access;
 use App\Entity\Access\Access;
 use App\Enum\Core\FileStatusEnum;
 use App\Enum\Core\FileStatusEnum;
 use App\Repository\Core\FileRepository;
 use App\Repository\Core\FileRepository;
 use App\Service\File\Exception\FileNotFoundException;
 use App\Service\File\Exception\FileNotFoundException;
 use App\Service\File\FileManager;
 use App\Service\File\FileManager;
 use RuntimeException;
 use RuntimeException;
+use Symfony\Bundle\SecurityBundle\Security;
 use Symfony\Component\HttpFoundation\HeaderUtils;
 use Symfony\Component\HttpFoundation\HeaderUtils;
 use Symfony\Component\HttpFoundation\RedirectResponse;
 use Symfony\Component\HttpFoundation\RedirectResponse;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\HttpFoundation\Response;
@@ -25,7 +27,7 @@ use Symfony\Component\HttpFoundation\Response;
 final class UploadRequestProcessor implements ProcessorInterface
 final class UploadRequestProcessor implements ProcessorInterface
 {
 {
     public function __construct(
     public function __construct(
-        private readonly FileRepository $fileRepository,
+        private Security                $security,
         private readonly FileManager    $fileManager,
         private readonly FileManager    $fileManager,
     ) {}
     ) {}
 
 
@@ -33,27 +35,41 @@ final class UploadRequestProcessor implements ProcessorInterface
      * @param Operation $operation
      * @param Operation $operation
      * @param array<mixed> $uriVariables
      * @param array<mixed> $uriVariables
      * @param array<mixed> $context
      * @param array<mixed> $context
-     * @return Response|RedirectResponse
+     * @return UploadRequest
      * @throws FileNotFoundException
      * @throws FileNotFoundException
      */
      */
-    public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): Response
+    public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): UploadRequest
     {
     {
-        if($operation instanceof Delete){
+        if ($operation instanceof Delete){
             throw new RuntimeException('not supported', 500);
             throw new RuntimeException('not supported', 500);
         }
         }
 
 
-        $fileId = $uriVariables['fileId'];
-        $file = $this->fileRepository->find($fileId);
+        /** @var Access $author */
+        $author = $this->security->getUser();
+
+        /** @var UploadRequest $uploadRequest */
+        $uploadRequest = $data;
 
 
-        if (empty($file)) {
-            throw new RuntimeException("File " . $fileId . " does not exist; abort.");
+        if (empty($uploadRequest->getFilename())) {
+            throw new \RuntimeException("Missing filename", 400);
+        }
+        if (empty($uploadRequest->getContent())) {
+            throw new \RuntimeException("Provided content is empty", 400);
         }
         }
 
 
-        $content = '';
+        $file = $this->fileManager->makeFile(
+            $uploadRequest->isOrganizationOwned() ? $author->getOrganization() : $author,
+            $uploadRequest->getFilename(),
+            $uploadRequest->getType(),
+            $uploadRequest->getContent(),
+            $author,
+            false,
+            $uploadRequest->getVisibility(),
+            $uploadRequest->getMimeType()
+        );
 
 
-        /** @var Access $access */
-        $author = $this->security->getUser();
+        $uploadRequest->setFileId($file->getId());
 
 
-        $this->fileManager->write($file, $content, $author);
+        return $uploadRequest;
     }
     }
 }
 }