Modèle de données
Les onze tables lrvo_drive_*, la table unique des dossiers et fichiers, et les choix de schéma.
Onze tables, toutes préfixées lrvo_drive_. Elles sont créées par npx lrvo-db init et supprimées par npx lrvo-db drop.
lrvo_drive_space
Les espaces. Un seul de type shared, un par utilisateur de type personal.
| Colonne | Type | Note |
|---|---|---|
id | text | Clé primaire. |
type | text | shared ou personal. |
name | text | |
owner_id | text | Vers lrvo_auth_users(id). NULL pour l'espace partagé, obligatoire pour un espace personnel. |
used_bytes | bigint | Compteur incrémental, mis à jour dans la même transaction que l'écriture. |
Aucune colonne de quota : le plafond d'un espace vient de l'environnement, et il est posé sur l'espace à la lecture. Une valeur écrite ici survivrait au changement de variable.
L'unicité passe par deux index partiels : un seul espace shared, un seul personal par propriétaire. Une contrainte UNIQUE classique ne sait pas exprimer une condition sur une autre colonne.
lrvo_drive_node
Dossiers et fichiers dans la même table, discriminés par kind. C'est le choix structurant du module : une seule hiérarchie, une seule cible de partage, une seule corbeille, un seul journal.
| Colonne | Type | Note |
|---|---|---|
id | text | Clé primaire. |
space_id | text | Vers lrvo_drive_space, en cascade. |
parent_id | text | Auto-référence, en cascade. NULL à la racine. |
kind | text | folder ou file. |
name | text | Affiché tel quel : accents, espaces et majuscules conservés. |
mime_type, size_bytes | Fichiers seulement. | |
storage_key | text | Clé de la version courante. Unique, jamais exposée. |
thumbnail_key | text | Miniature webp, quand elle existe. |
version_no | integer | Numéro de la version courante. |
created_by, updated_by, deleted_by | text | Vers lrvo_auth_users(id), mis à NULL si le compte disparaît. |
deleted_at | timestamptz | Non nul quand l'élément est à la corbeille. |
trashed_root | boolean | Voir plus bas. |
Trois contraintes de forme : un nœud n'est pas son propre parent, un fichier a toujours une clé de stockage, un dossier n'en a jamais.
Unicité du nom
CREATE UNIQUE INDEX lrvo_drive_node_name_unique
ON lrvo_drive_node (space_id, COALESCE(parent_id, ''), lower(name))
WHERE deleted_at IS NULL;Deux détails comptent. Le COALESCE est indispensable : en Postgres, NULL est distinct de lui-même dans une contrainte UNIQUE, donc la racine y échapperait. Et la clause WHERE laisse coexister un fichier en corbeille et un nouveau fichier du même nom, ce qui évite de refuser un dépôt à cause d'un élément que l'utilisateur croit supprimé.
trashed_root
Marque le seul nœud sur lequel l'utilisateur a réellement cliqué pour supprimer. Tout son sous-arbre porte la même deleted_at, mais lui seul porte ce drapeau.
Sans lui, un dossier supprimé apparaîtrait dans la corbeille avec chacun de ses cent fichiers, et les restaurer un par un serait le seul recours. Avec lui, la corbeille n'affiche que les racines, la restauration remonte le sous-arbre d'un bloc, et la rétention se compte une seule fois.
lrvo_drive_version
Une ligne par dépôt, y compris le premier. lrvo_drive_node.storage_key reflète la version courante, qui a toujours sa ligne ici.
La rétention des N dernières versions se réduit donc à supprimer les lignes au delà de N en excluant la courante. Une restauration ne recopie pas l'objet : deux versions peuvent partager une clé, ce dont la purge tient compte en dédoublonnant avant de supprimer.
lrvo_drive_share
Les liens publics. Porte le jeton, le mode, les quatre droits, le condensat du mot de passe, l'expiration, le plafond de téléchargements, les adresses de notification et la cadence.
revoked_at marque une révocation plutôt que de supprimer la ligne : l'historique continue de nommer le lien.
lrvo_drive_share_session
Une session ouverte après saisie du mot de passe. Une table plutôt qu'un témoin signé : cela ne dépend d'aucun secret d'environnement, et cela se révoque. Modifier le mot de passe d'un lien supprime ses sessions.
lrvo_drive_share_user
Les partages internes : un élément, un utilisateur, quatre droits. Unique sur le couple.
lrvo_drive_activity
Le journal. node_id et share_id sont sans clé étrangère, et le nom, le chemin et le libellé de l'acteur sont dénormalisés au moment de l'événement.
C'est délibéré : l'historique doit rester lisible après la purge du fichier ou la suppression du partage. Une clé étrangère effacerait ou bloquerait exactement ce qu'on veut conserver.
ip_hash porte un condensat de l'adresse d'un visiteur, jamais l'adresse.
Six index couvrent les filtres de la page d'historique, un par critère.
lrvo_drive_notification
La file des avis de dépôt. Une ligne par fichier reçu sur un partage qui notifie, marquée envoyée seulement après un envoi réussi. Un échec la laisse en attente, donc la tâche suivante réessaie.
lrvo_drive_upload
Les sessions d'envoi par tranches. Une ligne est écrite à l'ouverture de la session, avant même que le stockage n'ouvre le téléversement en plusieurs parties, et passe à finalized ou abandoned selon l'issue.
Cet ordre d'écriture est délibéré : un incident entre les deux laisse une ligne sans rien dans le seau, ce que le balayage résout sans frais. L'ordre inverse laisserait des parts facturées que plus rien ne référence.
chunk_size et part_count sont figés à l'ouverture : le stockage exige que toutes les parts sauf la dernière fassent la même taille, donc une reprise doit reprendre le découpage d'origine. replace_existing retient si le dépôt doit créer une version plutôt qu'un doublon ; les partages ne le posent jamais.
expires_at est repoussé à chaque tranche acceptée. Il borne donc une inactivité, pas la durée totale d'un envoi : un fichier de deux gigaoctets sur une ligne lente ne doit pas expirer en plein vol. Une ligne restée pending après expiration signale un téléversement laissé ouvert, dont le balayage horaire libère les parts.
lrvo_drive_upload_part
Une ligne par tranche reçue, avec son etag, celui que le stockage exige pour l'assemblage.
Une table plutôt qu'une colonne jsonb sur la session, et la raison vaut d'être dite : le navigateur envoie plusieurs tranches en parallèle. Un tableau jsonb imposerait une lecture puis une écriture, donc un verrou de ligne, donc la sérialisation de ce qu'on venait de paralléliser. Une ligne par tranche avec ON CONFLICT s'écrit sans se gêner, et SUM(size_bytes) donne le total reçu sans avoir à faire confiance à un compteur.
Les lignes disparaissent à l'assemblage : dix mille tranches par fichier terminé ne méritent pas d'être conservées.
lrvo_drive_job_lock
Le verrou des tâches planifiées, à durée de vie plutôt qu'à libération obligatoire : une instance qui tombe en plein traitement ne bloque pas la tâche indéfiniment. Porte aussi le résultat de la dernière exécution, affiché dans l'écran de configuration.
Migrations
| Identifiant | Effet |
|---|---|
001-multipart-uploads | Colonnes de session sur lrvo_drive_upload, table lrvo_drive_upload_part, et expiration des lignes héritées du protocole d'URL signée. |
Lancez npx lrvo-db migrate après chaque mise à jour du paquet. Voir Écrire une migration.