1e783bd753
Auto Tag Develop / tag (push) Successful in 8s
Infra d'upload de fichiers générique et réutilisable dans `Shared` (spec M4 § 2.7). Ne touche pas au module Transport.
## Livré
- **Table `uploaded_document`** (migration racine `DoctrineMigrations`) : fichier téléversé immuable (PDF / images) — `original_filename`, `stored_path`, `mime_type`, `size_bytes`, `checksum` (sha256), `created_at`, `created_by`. COMMENT ON COLUMN sur toutes les colonnes + bloc dans `ColumnCommentsCatalog`.
- **Service `Shared\Infrastructure\Upload\FileUploader`** : validation MIME server-side via `getMimeType()` (jamais `getClientMimeType()`), whitelist explicite (PDF + images), bornage taille (10 Mo), checksum sha256, écriture disque `var/uploads/{yyyy}/{mm}/`.
- **Endpoint `POST /api/uploaded_documents`** (multipart, `deserialize:false`) + `UploadedDocumentProcessor` -> renvoie l'IRI ; MIME hors whitelist -> 422.
- Wiring : mapping Doctrine `Shared` + path API Platform `Shared`.
## Tests
- `FileUploaderTest` (unitaire) + `UploadedDocumentApiTest` (fonctionnel : 201/IRI/checksum, 422 MIME interdit, 422 sans fichier, 401 anonyme).
`make test` vert (701 tests), `php-cs-fixer` propre.
## Hors scope
Pas d'antivirus / S3 / purge (§ 9). Pas de `carrier.discharge_document_id` (ticket consommateur M4).
Ticket ERP-154.
---------
Co-authored-by: Matthieu <contact@malio.fr>
Reviewed-on: #108
87 lines
5.0 KiB
PHP
87 lines
5.0 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace DoctrineMigrations;
|
|
|
|
use Doctrine\DBAL\Schema\Schema;
|
|
use Doctrine\Migrations\AbstractMigration;
|
|
|
|
/**
|
|
* ERP-154 — Infra d'upload de fichiers generique et reutilisable (src/Shared).
|
|
*
|
|
* Cree la table `uploaded_document` : reference technique d'un fichier televerse
|
|
* (PDF / image), gere par le service Shared\Infrastructure\Upload\FileUploader.
|
|
* La « Decharge » du M4 transporteurs en sera le premier consommateur, mais ce
|
|
* ticket ne touche AUCUN module : la table vit cote Shared.
|
|
*
|
|
* Caracteristiques :
|
|
* - Document IMMUABLE : pas d'onglet edition, pas de updated_at / updated_by.
|
|
* Seules les colonnes created_at (UTC, remplie par le FileUploader via
|
|
* l'horloge injectee) et created_by (auteur HTTP, null hors HTTP) tracent
|
|
* l'origine. C'est pourquoi l'entite Shared n'implemente PAS
|
|
* Timestampable/Blamable (qui imposeraient les 4 colonnes).
|
|
* - checksum sha256 (64 caracteres hex) : controle d'integrite + future
|
|
* deduplication eventuelle (hors scope ici).
|
|
*
|
|
* Namespace racine `DoctrineMigrations` (regle ABSOLUE Starseed n°11) et non
|
|
* modulaire : la table porte une FK cross-module vers "user" (created_by). Le
|
|
* tri par version au sein du namespace racine garantit qu'elle joue APRES la
|
|
* creation de "user" sur base vide.
|
|
*
|
|
* Style DDL aligne sur le M1/M2/M3 : `INT GENERATED BY DEFAULT AS IDENTITY` et
|
|
* `TIMESTAMP(0) WITHOUT TIME ZONE` (mapping ORM `datetime_immutable`), pour que
|
|
* `schema:update --force` reste un no-op une fois l'entite mappee.
|
|
*
|
|
* COMMENT ON COLUMN inline (regle ABSOLUE n°12) : chaque colonne porte sa
|
|
* description ici. La table est aussi ajoutee a `ColumnCommentsCatalog` car
|
|
* l'entite UploadedDocument existe des ce ticket — `app:apply-column-comments`
|
|
* du `test-db-setup` rejoue donc ces COMMENT apres le `schema:update --force`.
|
|
*/
|
|
final class Version20260615130000 extends AbstractMigration
|
|
{
|
|
public function getDescription(): string
|
|
{
|
|
return 'ERP-154 : table uploaded_document (infra upload generique Shared) — fichier televerse immuable, checksum sha256.';
|
|
}
|
|
|
|
public function up(Schema $schema): void
|
|
{
|
|
$this->addSql(<<<'SQL'
|
|
CREATE TABLE uploaded_document (
|
|
id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL,
|
|
original_filename VARCHAR(255) NOT NULL,
|
|
stored_path VARCHAR(512) NOT NULL,
|
|
mime_type VARCHAR(100) NOT NULL,
|
|
size_bytes INT NOT NULL,
|
|
checksum VARCHAR(64) NOT NULL,
|
|
created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL,
|
|
created_by INT DEFAULT NULL,
|
|
PRIMARY KEY (id),
|
|
CONSTRAINT fk_uploaded_document_created_by
|
|
FOREIGN KEY (created_by) REFERENCES "user" (id) ON DELETE SET NULL
|
|
)
|
|
SQL);
|
|
|
|
// Postgres n'indexe pas automatiquement les colonnes de FK.
|
|
$this->addSql('CREATE INDEX idx_uploaded_document_created_by ON uploaded_document (created_by)');
|
|
// Recherche d'integrite / future deduplication par empreinte sha256.
|
|
$this->addSql('CREATE INDEX idx_uploaded_document_checksum ON uploaded_document (checksum)');
|
|
|
|
$this->addSql('COMMENT ON TABLE uploaded_document IS $_$Fichiers televerses (infra generique Shared, ERP-154) — documents immuables (PDF / images), 1er consommateur la Decharge M4.$_$');
|
|
$this->addSql('COMMENT ON COLUMN uploaded_document.id IS $_$Identifiant interne auto-incremente.$_$');
|
|
$this->addSql('COMMENT ON COLUMN uploaded_document.original_filename IS $_$Nom de fichier d origine fourni par le client (≤ 255) — metadonnee d affichage uniquement, jamais utilise pour le stockage disque.$_$');
|
|
$this->addSql('COMMENT ON COLUMN uploaded_document.stored_path IS $_$Chemin relatif du fichier sous var/uploads (ex: 2026/06/<hash>.pdf) — nom genere aleatoirement, jamais le nom client.$_$');
|
|
$this->addSql('COMMENT ON COLUMN uploaded_document.mime_type IS $_$Type MIME detecte SERVER-SIDE via getMimeType (jamais getClientMimeType, spoofable) — borne a la whitelist FileUploader (PDF + images).$_$');
|
|
$this->addSql('COMMENT ON COLUMN uploaded_document.size_bytes IS $_$Taille du fichier en octets — bornee par FileUploader::MAX_SIZE_BYTES.$_$');
|
|
$this->addSql('COMMENT ON COLUMN uploaded_document.checksum IS $_$Empreinte SHA-256 du contenu (64 caracteres hex) — controle d integrite + deduplication eventuelle (hors scope).$_$');
|
|
$this->addSql('COMMENT ON COLUMN uploaded_document.created_at IS $_$Horodatage UTC du televersement — rempli par FileUploader via l horloge injectee (pas via TimestampableBlamableSubscriber).$_$');
|
|
$this->addSql('COMMENT ON COLUMN uploaded_document.created_by IS $_$ID de l utilisateur ayant televerse le fichier — null hors HTTP (CLI, fixture). FK -> "user".id, ON DELETE SET NULL.$_$');
|
|
}
|
|
|
|
public function down(Schema $schema): void
|
|
{
|
|
$this->addSql('DROP TABLE IF EXISTS uploaded_document');
|
|
}
|
|
}
|