171 lines
5.9 KiB
PHP
171 lines
5.9 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Shared\Domain\Entity;
|
|
|
|
use ApiPlatform\Metadata\ApiResource;
|
|
use ApiPlatform\Metadata\Get;
|
|
use ApiPlatform\Metadata\Post;
|
|
use App\Shared\Infrastructure\ApiPlatform\State\UploadedDocumentProcessor;
|
|
use DateTimeImmutable;
|
|
use Doctrine\ORM\Mapping as ORM;
|
|
use Symfony\Component\Security\Core\User\UserInterface;
|
|
use Symfony\Component\Serializer\Attribute\Groups;
|
|
|
|
/**
|
|
* Reference technique d'un fichier televerse (infra generique Shared, ERP-154).
|
|
*
|
|
* Entite IMMUABLE : un document n'est jamais modifie apres creation (pas d'onglet
|
|
* edition cote front). Elle ne porte donc QUE `created_at` / `created_by` — pas
|
|
* la paire `updated_*` — et n'implemente volontairement pas Timestampable /
|
|
* Blamable (qui imposeraient les 4 colonnes). `created_at` est rempli par le
|
|
* FileUploader via l'horloge injectee ; `created_by` est positionne par le
|
|
* processor depuis l'utilisateur authentifie (null hors HTTP).
|
|
*
|
|
* Pas de `#[Auditable]` : c'est un enregistrement d'infrastructure (et non un
|
|
* agregat metier edite), sa tracabilite est portee par created_at / created_by.
|
|
*
|
|
* Operations API :
|
|
* - Post (/uploaded_documents, multipart) : `deserialize: false` — le binaire
|
|
* n'est pas deserialise dans l'entite, le UploadedDocumentProcessor lit le
|
|
* fichier de la requete, delegue au FileUploader (validation MIME server-side,
|
|
* bornage taille, checksum, ecriture disque) puis persiste. MIME hors
|
|
* whitelist -> 422.
|
|
* - Get (/uploaded_documents/{id}) : necessaire pour qu'API Platform genere
|
|
* l'IRI renvoyee par le Post. Protege par IS_AUTHENTICATED_FULLY uniquement
|
|
* (pas de RBAC ni de cloisonnement tenant ici) : cette ressource est une
|
|
* infra GENERIQUE qui ne porte aucune notion de proprietaire metier. Le
|
|
* cloisonnement d'acces (qui peut voir quel document) est volontairement
|
|
* delegue au module CONSOMMATEUR (ex: la Decharge M4), qui exposera le
|
|
* document via sa propre ressource cloisonnee plutot que via cet endpoint
|
|
* technique. Ne renvoie que des metadonnees (jamais le binaire).
|
|
*
|
|
* Pas de GetCollection exposee (non requise) — la regle de pagination ne
|
|
* s'applique donc pas ici.
|
|
*/
|
|
#[ORM\Entity]
|
|
#[ORM\Table(name: 'uploaded_document')]
|
|
#[ApiResource(
|
|
operations: [
|
|
new Get(
|
|
security: "is_granted('IS_AUTHENTICATED_FULLY')",
|
|
),
|
|
new Post(
|
|
// Entree multipart : le binaire arrive en multipart/form-data sous
|
|
// le champ « file ». Sans cet inputFormats, API Platform rejette la
|
|
// requete en 415.
|
|
inputFormats: ['multipart' => ['multipart/form-data']],
|
|
// Le fichier n'est pas deserialisable dans l'entite : le processor
|
|
// lit le binaire de la requete. La validation est portee par le
|
|
// FileUploader (MIME server-side, taille), pas par les contraintes.
|
|
deserialize: false,
|
|
validate: false,
|
|
security: "is_granted('IS_AUTHENTICATED_FULLY')",
|
|
processor: UploadedDocumentProcessor::class,
|
|
),
|
|
],
|
|
normalizationContext: ['groups' => ['uploaded_document:read']],
|
|
)]
|
|
class UploadedDocument
|
|
{
|
|
#[ORM\Id]
|
|
#[ORM\GeneratedValue]
|
|
#[ORM\Column(type: 'integer')]
|
|
#[Groups(['uploaded_document:read'])]
|
|
private ?int $id = null;
|
|
|
|
// `uploaded_document:reference` : groupe minimal d'EMBARQUEMENT (nom de fichier
|
|
// seul, sans `storedPath`/`checksum`) pour qu'une entite parente (ex: Carrier)
|
|
// affiche le libelle du document au lieu d'un simple IRI. La parente l'ajoute a
|
|
// son `normalizationContext`.
|
|
#[ORM\Column(name: 'original_filename', length: 255)]
|
|
#[Groups(['uploaded_document:read', 'uploaded_document:reference'])]
|
|
private string $originalFilename;
|
|
|
|
#[ORM\Column(name: 'stored_path', length: 512)]
|
|
#[Groups(['uploaded_document:read'])]
|
|
private string $storedPath;
|
|
|
|
#[ORM\Column(name: 'mime_type', length: 100)]
|
|
#[Groups(['uploaded_document:read'])]
|
|
private string $mimeType;
|
|
|
|
#[ORM\Column(name: 'size_bytes', type: 'integer')]
|
|
#[Groups(['uploaded_document:read'])]
|
|
private int $sizeBytes;
|
|
|
|
#[ORM\Column(name: 'checksum', length: 64)]
|
|
#[Groups(['uploaded_document:read'])]
|
|
private string $checksum;
|
|
|
|
#[ORM\Column(name: 'created_at', type: 'datetime_immutable')]
|
|
#[Groups(['uploaded_document:read'])]
|
|
private DateTimeImmutable $createdAt;
|
|
|
|
#[ORM\ManyToOne(targetEntity: UserInterface::class)]
|
|
#[ORM\JoinColumn(name: 'created_by', referencedColumnName: 'id', nullable: true, onDelete: 'SET NULL')]
|
|
private ?UserInterface $createdBy = null;
|
|
|
|
public function __construct(
|
|
string $originalFilename,
|
|
string $storedPath,
|
|
string $mimeType,
|
|
int $sizeBytes,
|
|
string $checksum,
|
|
DateTimeImmutable $createdAt,
|
|
) {
|
|
$this->originalFilename = $originalFilename;
|
|
$this->storedPath = $storedPath;
|
|
$this->mimeType = $mimeType;
|
|
$this->sizeBytes = $sizeBytes;
|
|
$this->checksum = $checksum;
|
|
$this->createdAt = $createdAt;
|
|
}
|
|
|
|
public function getId(): ?int
|
|
{
|
|
return $this->id;
|
|
}
|
|
|
|
public function getOriginalFilename(): string
|
|
{
|
|
return $this->originalFilename;
|
|
}
|
|
|
|
public function getStoredPath(): string
|
|
{
|
|
return $this->storedPath;
|
|
}
|
|
|
|
public function getMimeType(): string
|
|
{
|
|
return $this->mimeType;
|
|
}
|
|
|
|
public function getSizeBytes(): int
|
|
{
|
|
return $this->sizeBytes;
|
|
}
|
|
|
|
public function getChecksum(): string
|
|
{
|
|
return $this->checksum;
|
|
}
|
|
|
|
public function getCreatedAt(): DateTimeImmutable
|
|
{
|
|
return $this->createdAt;
|
|
}
|
|
|
|
public function getCreatedBy(): ?UserInterface
|
|
{
|
|
return $this->createdBy;
|
|
}
|
|
|
|
public function setCreatedBy(?UserInterface $user): void
|
|
{
|
|
$this->createdBy = $user;
|
|
}
|
|
}
|