Skip to content

feat(file-storage): setup s3 storage - #481

Open
BanelhaqB wants to merge 6 commits into
developfrom
feat/2321-migration-systeme-stockage-s3
Open

feat(file-storage): setup s3 storage#481
BanelhaqB wants to merge 6 commits into
developfrom
feat/2321-migration-systeme-stockage-s3

Conversation

@BanelhaqB

Copy link
Copy Markdown
Contributor

📝 Pull Request Description

Brief description of changes applied

Migration du système de stockage de fichiers vers S3, avec conservation du stockage local comme backend par défaut.

  • Port FileStorageService rendu agnostique du backend : upload() renvoie désormais un locator opaque, persisté dans File.uri et réinterprété par le seul adaptateur qui l'a produit (chemin absolu en local, clé d'objet en S3). Les méthodes delete(UUID) / deleteByPath(String) fusionnent en un unique delete(String locator).
  • Nouvel adaptateur S3FileStorageService (@ConditionalOnProperty file.storage.type=s3) implémentant upload / get / delete via le SDK AWS v2, avec contentType stocké sur l'objet.
  • Configuration : S3ClientConfig (endpoint override, credentials statiques, path-style access) et S3StorageProperties (préfixe file.storage.s3).
  • Images par défaut servies hors backend de stockage : StorageController les lit via le ResourceLoader Spring (file: ou classpath:), pour qu'un asset statique livré avec le déploiement ne dépende plus de la disponibilité du bucket.
  • URLs de fichiers construites via FileStorageConstants.publicUrlOf(fileId), puisque File.uri ne contient plus une valeur exposable à un client.

Reference to an Issue, Feature, Task, User Story or another PR

Closes avenirs-esr/AVENIRS-Project#2321


Documentation

Nouvelles propriétés de configuration :

Propriété Défaut Rôle
file.storage.type local Backend actif : local ou s3
file.storage.s3.endpoint URL de base de l'API S3
file.storage.s3.region eu-west-3 Région envoyée dans la signature
file.storage.s3.bucket Bucket de l'environnement
file.storage.s3.access-key À chiffrer via Jasypt (ENC(...))
file.storage.s3.secret-key À chiffrer via Jasypt (ENC(...))
file.storage.s3.path-style-access true Adressage <endpoint>/<bucket> (requis MinIO / Ceph)

Les blocs S3 sont présents commentés dans application.properties. Changement de format à noter : file.storage.profile.default-path et file.storage.cover.default-path sont désormais des Spring resource locations et doivent être préfixées (file: ou classpath:).


Target Branch

develop


Additional Notes

  • region a une valeur par défaut car le SDK AWS refuse de construire un client sans région, même quand le backend l'ignore.
  • path-style-access est à true par défaut : les déploiements MinIO / Ceph servis depuis un hôte nu ou une IP l'exigent.
  • L'adaptateur S3 est annoté @Primary : lorsque file.storage.type=s3, il prend le pas sans retirer le bean local du contexte.
  • Aucun changement de schéma de base de données. La colonne File.uri change en revanche de sémantique selon le backend actif.

Known Limitations or Side Effects

  • Pas de migration des fichiers existants. Basculer un environnement déjà alimenté sur file.storage.type=s3 rend inaccessibles les fichiers déjà écrits sur disque : leur uri reste un chemin local que l'adaptateur S3 interprétera comme une clé d'objet. Un transfert des contenus et une réécriture des uri sont à prévoir hors de cette PR.
  • delete() sur S3 est silencieux pour une clé absente (comportement natif de S3), là où l'adaptateur local signale le fichier manquant.
  • Les identifiants S3 sont passés en credentials statiques ; pas de support des rôles IAM ni de la chaîne de credentials par défaut du SDK.
  • CHANGELOG.md n'a pas été mis à jour dans cette PR alors que des propriétés sont ajoutées (voir checklist).

✅ Checklist

Please make sure you have addressed the following before submitting:

  • a11y tested (if the PR includes frontend changes) — sans objet, backend uniquement
  • Tests provided (including unit and integration tests for both frontend and backend) — S3FileStorageServiceTest, S3FileStorageServiceIT, FileStorageBackendSelectionTest, StorageControllerIT
  • i18n handled (texts are translated) — sans objet
  • Performance tests
  • No unnecessary code (e.g., debug logs, commented code)
  • Code style and formatting rules respected (linting, conventions, etc.)
  • Semantic Versioning respected
  • Add to CHANGELOG.md file all properties and database change and details for the update process

The port used to speak filesystem: get(path), deleteByPath(path) and an
upload returning an absolute path. Plugging S3 under that vocabulary would
have made the abstraction lie about what it does.

Upload now returns a locator that is opaque to the domain: it is persisted
as File.uri and handed back to the very same adapter to read or delete the
content. Each adapter picks its own format, so the local adapter keeps
writing and reading the exact same absolute paths as before and no stored
row becomes invalid.

Also drops delete(UUID), which no production code ever called.

refs: #2321
FileDtoMapper exposed File.uri straight as the DTO url, and the shared
FileDTOMapper concatenated the request origin with it. Both leaked a
storage locator to clients: in production the second one produced URLs
like https://host/workspace/app/target/storage/<uuid>.png, which no route
serves — the Apache overlay has no alias for that path.

Both now point at the /storage/{fileId} endpoint, the way FileDataMapper
already did. The construction is centralised in FileStorageConstants so
the three call sites cannot drift apart again.

This changes the url field returned by the trace, feedback, user photo and
activity file endpoints.

refs: #2321
S3FileStorageService stores objects under the {uuid}.{extension} key and
hands that key back as the locator. The content type is set on the object
so a client reading it back is told what it is rather than getting
application/octet-stream.

The two real adapters are mutually exclusive on file.storage.type and both
keep @primary, which the seeder mock relies on to stay resolvable through
its own qualifier. Local storage remains the default, so nothing changes
until the property is set to s3.

Behaviour difference worth knowing: S3 answers successfully when deleting a
key that does not exist, so the adapter cannot report a missing file the way
the local one does.

Default profile and cover pictures are now read as Spring resource
locations instead of going through the storage backend. They ship with the
deployment and belong to no user, so routing a static asset through the
bucket would have tied it to the backend availability.

Keys are flat for now. Prefixing them per domain needs the upload signatures
to carry the calling context, which is the streaming refactor's job.

refs: #2321
S3FileStorageServiceTest pins the contract against a mocked client: key
format, content type, and the translation of a missing key into
FileNotFoundException.

S3FileStorageServiceIT runs the same operations against a MinIO container,
including the delete-then-read round trip and the fact that deleting an
unknown key succeeds. It instantiates the adapter directly rather than
extending ContainerConfigurationTest, since booting the application context
would cover nothing more here.

FileStorageBackendSelectionTest guards the file.storage.type switch: both
adapters are marked primary, so a regression registering the two at once
would break every injection point.

The testcontainers MinIO module is not published for the 2.x line Spring
Boot 4 manages, so the container is a plain GenericContainer instead.

refs: #2321
The hosted bucket lives in Paris, so us-east-1 was a misleading default for
the online backend. Local MinIO keeps us-east-1, which is its conventional
value and which it ignores anyway.

refs: #2321
@BanelhaqB BanelhaqB changed the title fix(file-storage): default the S3 region to eu-west-3 feat(file-storage): setup s3 storage Sep 3, 2026

@Nathan-Pignon Nathan-Pignon left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quelques suggestions mais sinon c'est good pour moi, merci Bilel !

Comment on lines +86 to +88
file.storage.s3.bucket=b4d61f49-e303-4f97-8736-924372b513ff
file.storage.s3.access-key=ENC(CNyV3T3tgXUc7DBXo5thx3L9uMEipDFzF+7i1c8pOj0=)
file.storage.s3.secret-key=ENC(NVre2sSEsYv2cvLlz5v5MKcQewIZg/6WEpALQ+smK4WoohZ6j1Hj26+ClqzROF9s9yVWtswwAG0=)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Je sais pas ce que tu en penses mais j'aurais peut-être mis ces trois props dans le env.properties ? Puis mettre à jour le script qui génère le env.properties ducoup

Enable the S3 properties with the encrypted dev credentials. The
backend stays inactive until file.storage.type is switched to s3.

refs: #2321
@BanelhaqB
BanelhaqB force-pushed the feat/2321-migration-systeme-stockage-s3 branch from 5c4f610 to 1e07272 Compare September 3, 2026 16:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Migration du système de stockage sur du S3

2 participants