Modèle de données
Tables lrvo_forms_form et lrvo_forms_submission, structure des champs et des réglages, absence de migration.
Le module crée deux tables, toutes deux préfixées lrvo_forms_. Elles sont créées par npx lrvo-db init, qui appelle le createTables exporté par le module. Voir lrvo-db.
Le module n'a aucune migration de schéma. Les tables sont créées si elles sont absentes, et laissées strictement intactes sinon. Une évolution du schéma demanderait une intervention manuelle, ce qui n'a jamais été nécessaire jusqu'ici.
lrvo_forms_form
| Colonne | Type | Rôle |
|---|---|---|
id | SERIAL PRIMARY KEY | Identifiant. C'est ce que stocke le bloc du BlockEditor. |
slug | VARCHAR(200) NOT NULL UNIQUE | Identifiant d'URL, utilisé par la page publique et par FormEmbed. |
title | VARCHAR(500) NOT NULL | Titre affiché. |
description | TEXT | Facultative, affichée sous le titre. |
fields | JSONB NOT NULL DEFAULT '[]' | La liste ordonnée des champs. |
settings | JSONB NOT NULL DEFAULT '{}' | Les réglages du formulaire. |
status | VARCHAR(20) NOT NULL DEFAULT 'active' | active ou inactive. |
created_at | TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP | |
updated_at | TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP | Écrit à chaque modification. |
Index sur status.
Le statut n'a pas de contrainte de vérification en base : il est validé côté module, qui refuse toute valeur autre que active ou inactive. Une écriture directe en SQL pourrait donc y placer autre chose.
Slug
Dérivé du slug soumis ou, à défaut, du titre : minuscules, accents retirés, espaces en tirets, puis suppression de tout caractère hors [a-z0-9-].
L'unicité est globale. En cas de collision, le module suffixe -2, puis -3.
Le titre est obligatoire à la création comme à la mise à jour, où il ne peut pas être vidé.
Structure de fields
Un tableau d'objets, dans l'ordre de rendu. Chaque champ porte au minimum id, type, label, width, et name pour les types qui collectent une valeur.
Il n'existe aucun schéma de base sur ce JSON, et donc aucune contrainte d'intégrité. Un champ mal formé n'est pas détecté à l'écriture, il produira une anomalie au rendu.
Voir Champs pour la structure complète par type.
Structure de settings
Toujours normalisé à l'écriture : chaque branche absente est remplie avec sa valeur par défaut, de sorte qu'un formulaire lu porte toujours l'arbre complet.
| Clé | Défaut | Contenu |
|---|---|---|
submitLabel | Envoyer | Texte du bouton. |
success.mode | message | message ou redirect. Toute autre valeur retombe sur message. |
success.message | Remerciement générique | Affiché après envoi. |
success.redirectUrl | vide | Destination en mode redirection. |
notify.enabled | false | Notification à l'équipe. |
notify.recipients | [] | Adresses destinataires. |
notify.subject | vide | Sujet, un défaut est calculé s'il est vide. |
confirmation.enabled | false | Accusé de réception au visiteur. |
confirmation.toField | vide | Nom du champ courriel à utiliser. |
confirmation.subject | vide | Sujet, un défaut est calculé s'il est vide. |
confirmation.message | vide | Corps personnalisé. |
antiBot.minSeconds | 3 | Délai minimal de remplissage, jamais négatif. |
lrvo_forms_submission
| Colonne | Type | Rôle |
|---|---|---|
id | SERIAL PRIMARY KEY | Identifiant. |
form_id | INTEGER NOT NULL REFERENCES lrvo_forms_form(id) ON DELETE CASCADE | Formulaire d'origine. |
data | JSONB NOT NULL DEFAULT '{}' | Réponses, indexées par nom de champ. |
files | JSONB NOT NULL DEFAULT '[]' | Métadonnées des pièces jointes. |
meta | JSONB NOT NULL DEFAULT '{}' | Contexte de la requête. |
status | VARCHAR(20) NOT NULL DEFAULT 'new' | new, read ou spam. |
created_at | TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP | Date de réception. |
Index sur form_id, sur status, et sur le couple (form_id, status) qui est l'axe de filtrage de l'admin.
Il n'y a pas de colonne de modification. Une soumission n'est jamais éditée, seul son statut change.
data
Les clés sont les name des champs, pas leurs identifiants ni leurs libellés. Les valeurs sont des chaînes, sauf pour deux types : checkbox produit un booléen, checkboxes produit un tableau.
Renommer un champ après réception de soumissions rompt donc le lien avec les réponses déjà reçues, qui restent stockées sous l'ancienne clé.
files
Un tableau d'objets { fieldName, key, filename, size, contentType }. key est la clé de l'objet dans le stockage, de la forme forms/<identifiant du formulaire>/<nom unique>.
Le contenu du fichier n'est jamais en base. Il est servi uniquement par la route de téléchargement de l'admin, protégée par forms.view.
meta
{ ip, userAgent, referer }. L'adresse est lue dans x-forwarded-for, première entrée, puis dans x-real-ip. Chacune des trois vaut null quand l'en-tête correspondant est absent.
Elles sont prises telles quelles, sans la vérification LRVO_TRUST_PROXY appliquée à la clé de limitation de débit. Un appelant peut donc les falsifier : elles éclairent un diagnostic, elles ne prouvent rien.
Ces trois valeurs restent des données personnelles. En tenir compte dans la politique de rétention du projet.
Cycle de vie
| Action | Effet en base | Effet au stockage |
|---|---|---|
| Soumission rejetée après téléversement | Aucune ligne créée. | Aucun. Les fichiers déjà envoyés deviennent orphelins. |
| Suppression d'une soumission | Ligne supprimée. | Aucun. Les fichiers deviennent orphelins. |
| Suppression d'un formulaire | Formulaire et soumissions supprimés en cascade. | Aucun. Idem. |
| Duplication d'un formulaire | Nouveau formulaire, titre suffixé, slug ré-unicifié, statut inactive. Aucune soumission copiée. | Aucun. |
L'absence de nettoyage du stockage est à connaître : un projet qui reçoit beaucoup de pièces jointes accumule des objets sans référence. Prévoir une purge si le volume le justifie.
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 purge automatique n'existe sur les soumissions. Une politique de rétention est à mettre en place au niveau du projet.
- Le commentaire d'en-tête du module mentionne une clé
sourceUrldansmeta. Elle n'est jamais écrite, le tableau de cette page fait foi.