Modèle de données
Tables lrvo_media et lrvo_media_references, migration de suppression de la visibilité, cycle de vie d'un fichier.
Le module crée deux tables. Elles sont créées et migrées par npx lrvo-db init, qui appelle le createTables exporté par le module. Voir lrvo-db.
Le préfixe est lrvo_media, au singulier, et non lrvo_medias_. Il est hérité de l'époque où la médiathèque vivait dans le core. Le renommer casserait les bases existantes sans rien apporter.
lrvo_media
Une ligne par fichier. La ligne porte les métadonnées, le fichier vit dans le stockage objet.
| Colonne | Type | Rôle |
|---|---|---|
id | text NOT NULL PRIMARY KEY | Identifiant du média. C'est ce que les contenus référencent. |
filename | text NOT NULL UNIQUE | Nom public, celui de l'URL. Modifiable. |
storage_key | text NOT NULL UNIQUE | Clé de l'objet dans le stockage. Jamais exposée, jamais réécrite. |
original_name | text NOT NULL | Nom du fichier au téléversement, conservé tel quel. |
mime_type | text NOT NULL | Type MIME servi par la route de fichier. |
size_bytes | bigint NOT NULL | Taille de l'objet stocké, après optimisation. |
uploaded_by | text REFERENCES lrvo_auth_users(id) ON DELETE SET NULL | Auteur du téléversement. |
alt_text | text | Texte alternatif, source de vérité pour tous les rendus. |
caption | text | Légende, même statut. |
created_at | timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP | |
updated_at | timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP | Bumpé par toute édition, y compris un remplacement de fichier. |
Index sur mime_type, sur uploaded_by, et sur created_at en descendant, qui est l'ordre de la médiathèque.
La suppression d'un compte utilisateur vide uploaded_by mais ne touche pas au média. Un fichier ne disparaît pas parce que la personne qui l'a déposé a quitté le projet.
Pourquoi deux noms
filename et storage_key sont indépendants. Renommer un média réécrit le premier et laisse le second intact, ce qui n'invalide aucune référence puisque les contenus référencent id. Voir Téléversement.
alt_text et caption sont la source de vérité
Ces deux colonnes ne sont pas des valeurs par défaut : ce sont les valeurs. Les blocs image du BlockEditor ne conservent pas de copie locale, ils relisent la fiche du média à chaque rendu.
Corriger un texte alternatif dans la médiathèque le corrige donc partout où l'image est utilisée, sans toucher aux contenus. C'est la contrepartie de la normalisation stricte décrite dans Intégration.
lrvo_media_references
Une ligne par lien entre un média et un contenu.
| Colonne | Type | Rôle |
|---|---|---|
media_id | text NOT NULL REFERENCES lrvo_media(id) ON DELETE RESTRICT | Média référencé. |
source_type | text NOT NULL | Nature du contenu, chaîne libre. |
source_id | text NOT NULL | Identifiant du contenu, en chaîne. |
field | text NOT NULL DEFAULT '' | Champ porteur du lien. |
created_at | timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP |
Clé primaire composée : (media_id, source_type, source_id, field). Index sur (source_type, source_id), qui est le sens de lecture de tous les appelants.
La restriction est le cœur de la table
ON DELETE RESTRICT est ce qui distingue cette table d'un simple journal. La base refuse la suppression d'un média encore référencé, et le module refuse en amont avec un message explicite donnant le nombre de contenus concernés.
Conséquence pour un module consommateur : détacher avant de supprimer un contenu, sans quoi la référence reste et bloque la suppression du média sans que rien n'indique pourquoi.
Valeurs de source_type et field
Ni l'une ni l'autre n'est validée. Ce sont des chaînes libres, sans registre.
source_type | Écrit par |
|---|---|
post | @lrvo/module-posts |
pages | @lrvo/module-pages |
L'incohérence de nombre entre les deux est historique. Elle est sans effet fonctionnel, chaque module ne lisant que ses propres lignes.
Pour field, deux familles cohabitent. Les champs déclarés y écrivent leur nom, celui que le projet a choisi en déclarant son type de contenu : @lrvo/module-posts enregistre field.name, pas le type du champ. Les images posées dans le BlockEditor y écrivent toutes la valeur fixe block_image. C'est ce qui permet de resynchroniser les blocs sans toucher aux champs, et inversement.
Migration
Une seule migration, exécutée à chaque lrvo-db init, y compris sur une base déjà à jour :
DROP INDEX IF EXISTS lrvo_media_visibility_idx;
ALTER TABLE lrvo_media DROP COLUMN IF EXISTS visibility;La visibilité par média a été retirée. Tous les médias sont publics, et la route de fichier ne vérifie ni session ni permission. Voir Configuration.
Cycle de vie d'un fichier
| Action | Base | Stockage objet |
|---|---|---|
| Téléversement | Ligne insérée après l'envoi. | Objet créé, supprimé si l'insertion échoue. |
| Renommage | filename réécrit. | Inchangé. |
| Remplacement | storage_key, original_name, mime_type, size_bytes réécrits. | Nouvel objet créé, ancien supprimé ensuite. |
| Suppression | Ligne supprimée si aucune référence. | Objet supprimé ensuite. |
Les suppressions au stockage sont faites au mieux : leur échec est journalisé mais ne fait pas échouer l'opération. Un objet orphelin ne casse rien, il occupe de l'espace.
Notes
dropTablessupprime les deux tables en cascade, dans l'ordre qui respecte la clé étrangère. Il ne supprime aucun fichier du stockage objet.- Aucune table ne conserve d'historique. Un remplacement de fichier écrase la ligne, l'ancien fichier est perdu.
- La taille enregistrée est celle du fichier après optimisation, pas celle du fichier d'origine. Le poids affiché dans la médiathèque est donc le poids réellement servi.