Project Presentation Images¶
Flux d'upload des images de la présentation d'un projet (image principale, images secondaires, images de lots).
Le modèle est déclaratif : l'upload ne fait que déposer un fichier sur S3 et le confirmer ; le placement des images (quelle clé est principale / secondaire / sur quel lot) est décidé au save de la présentation (PATCH …/presentation/:id, voir project-presentation.md), pas ici. Il n'y a donc plus d'endpoint de suppression d'image : retirer une image = l'enlever de la liste envoyée au save.
Source : project-presentation-images.controller.ts.
Tous les endpoints requièrent un porteur authentifié via better-auth (ProjectFinancingRequestOwnerGuard). La route portant un :projectId, le guard vérifie aussi que le projet appartient au porteur (403) et qu'il existe (404).
Cycle de vie d'une image¶
request-upload→ le back génère une clé S3 + une URL PUT presignée par fichier.- Le navigateur PUT le fichier directement sur S3 (jamais via l'API).
confirm-upload→ le back vérifie seulement que l'objet existe et appartient au projet. Il ne pose aucun tag : une image confirmée n'est jamais candidate à la suppression (elle survit tant qu'un save ne la retire pas).- Le front met les clés dans le body du save de la présentation.
- Au save : les clés qui disparaissent de la galerie sont taguées
pending; les clés conservées n'ont aucun tag. - Une lifecycle rule S3 (filtre
pending=true) expire les objets restéspendingaprès leur TTL (images retirées d'une galerie).
Le tag
pendinga un seul sens : « image retirée de la galerie, à nettoyer ». Une image uploadée mais jamais confirmée (onglet fermé en plein upload) reste sans tag, donc non nettoyée — fuite de stockage mineure, hors périmètre de ce flux.
POST …/presentation/:presentationId/images/request-upload¶
Génère un lot d'URLs PUT presignées. La cible (main/secondary/lot) n'est pas connue ici — elle est décidée au save.
Request body¶
requestUploadProjectPresentationImagesBodySchema :
Contraintes : 1 à 20 fichiers, 10 MB max chacun.
Response¶
{ "uploads": [ { "imageKey": "ProjectFinancingRequestS3Key", "presignedUrl": "https://...", "expiresAt": "ISO date" } ] }
imageKey est la clé S3 brandée à conserver pour le confirm-upload puis le save.
POST …/presentation/:presentationId/images/confirm-upload¶
Valide que les clés uploadées appartiennent au projet et existent sur S3, puis les tague pending. Ne touche pas à la présentation.
Request body¶
confirmUploadProjectPresentationImagesBodySchema :
Contrainte : 1 à 20 clés. Chaque clé doit commencer par projects/{projectId}/presentation-images/.
Response¶
Erreurs¶
| Code | Statut | Cause |
|---|---|---|
validation-body |
400 | Body invalide (batch hors limites) |
s3-key-invalid-prefix |
400 | Une clé n'appartient pas au projet |
image-key-not-found |
400 | Une clé ne correspond à aucun objet S3 |
s3-put-tagging-failed |
500 | Échec du tag pending |
Placement et suppression des images¶
Il n'y a pas d'endpoint de placement ni de suppression dédiés. Le front envoie l'état voulu de la galerie dans le body du PATCH …/presentation/:id :
{
"mainImageKey": "key | null",
"secondaryImageKeys": ["key", "..."],
"lots": [ { "id": "...", "name": "...", "imageKey": "key | null" } ]
}
mainImageKey/lots[].imageKey: une clé = set,null= efface, absent = inchangé.secondaryImageKeys: la liste exacte voulue (ordre inclus) ;[]efface toutes les secondaires.
Le détail du contrat de lecture ({ key, url }) et d'écriture est dans project-presentation.md.
Liens¶
- Service :
project-presentation-images.service.ts - Vue :
get-project-presentation.view.ts - Endpoints DTOs :
projectPresentationImages.endpoint.ts - Guard :
project-financing-request-owner.guard.ts