Étape 21 : formulaire public d'inscription des bénévoles

Retire « constructeur de formulaire » des exclusions du CLAUDE.md (§ 5) —
décision explicite prise en conversation.

Un formulaire par édition (COORDINATION choisit les champs actifs parmi
neuf : email, téléphone, vœux de poste — max 2, vœux d'artiste — un par
soir de festival, vœu de compagnon en texte libre, disponibilité/
cotisation déclaratives, photo, commentaire ; nom/prénom toujours
collectés) ouvert/fermé à volonté, avec un lien à partager
(/inscription, aucun compte requis).

Backend : FormTemplate/FormField (migration 0013), quatre nouveaux champs
sur Volunteer (photo, companion_wish_note, self_declared_available,
willing_to_pay_fee), app/api/forms.py — les deux seuls endpoints publics
de toute l'API (GET .../form/public, POST .../form/public/submit),
protégés par hCaptcha (app/forms/captcha.py, désactivé sans
HCAPTCHA_SECRET_KEY) et un filet de sécurité global par édition
(form_submission_limiter). resolve_owned_ids/set_artists/
set_wished_positions déplacés vers app/api/_common.py, partagés avec
volunteers.py.

Frontend : écran de configuration (/inscriptions) avec lien copiable et
bascule ouvert/fermé, formulaire public (/inscription, hors RequireAuth)
avec widget hCaptcha chargé dynamiquement, capture photo native
(<input capture>), vœux de poste/artiste avec les mêmes règles que le
back-end. Réponses du formulaire visibles et éditables depuis la fiche
bénévole existante (ConfigVolunteers.tsx).

Vérifié : pytest (273 passés), ruff, alembic check, tsc --noEmit, eslint,
vite build, smoke (rendu SSR de chaque écran + flux de bout en bout du
formulaire en mode démonstration) — tous verts.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
AdamThomas
2026-09-21 20:24:32 +02:00
co-authored by Claude Sonnet 5
parent 55c5d539a8
commit 82088f3b0c
41 changed files with 2197 additions and 81 deletions
+47 -6
View File
@@ -73,6 +73,16 @@ les tables sauf les tables d'association.
taille de t-shirt, régime, ni compétences. Porte trois listes de vœux,
exposées par la fiche (`artist_ids`, `wished_position_ids`,
`wished_companion_ids` ; `None`/absent = inchangé, `[]` = vider).
Renseignables aussi via le formulaire public (§ 9) : `photo`/
`photo_content_type` (image, servie séparément par
`GET .../volunteers/{id}/photo`, COORD/LEAD), `companion_wish_note` (« je
voudrais être avec » en texte libre — la personne visée n'existe pas
forcément encore en base au moment de l'inscription, donc pas de lien
direct vers un `VolunteerCompanionWish` ; la coordination la résout à la
main depuis la fiche une fois les deux bénévoles inscrits),
`self_declared_available`/`willing_to_pay_fee` (cases à cocher **purement
informatives**, sans lien avec `Availability` ni aucun workflow de
paiement).
- **`Artist`** : programmation de l'édition (`name`, `performance_date`,
`start_time`, `end_time` — `end_time <= start_time` ⇒ le passage finit le
lendemain). Unique (`edition_id`, `name`, `performance_date`). La
@@ -165,6 +175,27 @@ les tables sauf les tables d'association.
pour rien : juste ReportLab) : pages recto personnalisées suivies, planche
par planche, de la page de verso correspondante (même image répétée à
chaque emplacement) — impression recto-verso classique.
- **`FormTemplate`** : configuration du formulaire public d'inscription
d'une édition (une ligne, créée à la demande). `is_open` coupe les
nouvelles soumissions sans effacer la configuration.
- **`FormField`** : un champ activé sur le formulaire (`field_type` ∈
`EMAIL` / `PHONE` / `POSITION_WISHES` / `ARTIST_WISHES` / `COMPANION_WISH`
/ `AVAILABLE` / `WILLING_TO_PAY_FEE` / `PHOTO` / `COMMENT`, au plus un par
édition — `PUT .../form/fields` remplace tout). **Nom et prénom sont
toujours collectés**, indispensables à la création du `Volunteer` : ils ne
sont pas dans cette liste, contrairement aux neuf autres. Deux règles
propres à ce formulaire (pas à la fiche bénévole classique, qui reste sans
limite) : au plus **deux** vœux de poste, et au plus **un** artiste
souhaité par jour de festival (`app/api/forms.py:_check_one_artist_wish_per_day`).
`GET/PUT/PATCH /editions/{id}/form[...]` (COORDINATION) configurent ;
`GET /editions/{id}/form/public` et `POST .../form/public/submit` sont les
**deux seuls endpoints de toute l'API sans authentification** — protégés
par hCaptcha (`app/forms/captcha.py`, `HCAPTCHA_SECRET_KEY` ; vérification
désactivée si absente, comme `EMAIL_ENABLED=false`) et un filet de
sécurité global par édition (`app/auth/rate_limit.py:form_submission_limiter`,
hCaptcha reste la défense principale). Le bénévole créé par une soumission
apparaît immédiatement dans la liste de la coordination, comme n'importe
quel autre bénévole.
Conventions techniques :
@@ -202,6 +233,8 @@ Matrice appliquée par les endpoints :
| `availability` lecture / écriture | ✔ / ✔ | ✔ / — | la sienne / — |
| `matrix`, `import`, `accounts` | ✔ / ✔ / ✔ | ✔ / — / — | — |
| `badges` (config + génération) | ✔ | — | — |
| `form` (config) | ✔ | — | — |
| `form/public`, `form/public/submit` | *(aucune authentification — voir § 2, `FormField`)* |||
**Authentification — deux systèmes selon le compte**, une même session JWT
HS256 (`Authorization: Bearer <jwt>`, TTL `session_ttl_hours`) à l'arrivée.
@@ -417,8 +450,10 @@ défilement horizontal — jamais de colonnes étroites par défaut.
### Hors périmètre (le modèle doit juste pouvoir les accueillir sans refonte)
SMS · constructeur de formulaire · pointage sur site ·
fonctions d'IA. *(L'envoi d'emails par SMTP est en place — voir § 2.)*
SMS · pointage sur site · fonctions d'IA. *(L'envoi d'emails par SMTP est en
place — voir § 2. Le formulaire public d'inscription est en place — voir § 2
et § 9 ; ce n'était pas prévu à l'origine, retiré de cette liste à
l'étape 21.)*
---
@@ -449,7 +484,8 @@ fonctions d'IA. *(L'envoi d'emails par SMTP est en place — voir § 2.)*
│ │ │ # health, auth, accounts, editions,
│ │ │ # day_parts, imports, artists, volunteers,
│ │ │ # availability, positions, shifts,
│ │ │ # assignments, solver, matrix, notifications
│ │ │ # assignments, solver, matrix, notifications,
│ │ │ # badges, forms
│ │ ├── auth/ # mot de passe + lien magique + JWT + rôles
│ │ ├── email/ # SMTP (sender) + rendu des emails (templates)
│ │ ├── importing/ # import CSV/Excel (parser, text, service)
@@ -457,6 +493,7 @@ fonctions d'IA. *(L'envoi d'emails par SMTP est en place — voir § 2.)*
│ │ ├── validation/ # moteur pur (models, engine) + loader (base)
│ │ ├── solver/ # suggestions OR-Tools (models, engine, loader)
│ │ ├── badges/ # génération PDF des planches (pdf.py, ReportLab)
│ │ ├── forms/ # hCaptcha (captcha.py) — voir api/forms.py
│ │ ├── bootstrap.py # instance vierge : édition + 1er compte
│ │ ├── seed.py # jeu de démonstration (python -m app.seed)
│ │ ├── i18n/
@@ -529,10 +566,13 @@ Aucune dépendance locale requise hors Docker.
- Secrets/variables Actions requis (voir `deploy/README.md`) : secrets
`REGISTRY_TOKEN`, `SSH_KEY` ; variables `REGISTRY_HOST`,
`REGISTRY_NAMESPACE`, `REGISTRY_USER`, `SSH_HOST`, `SSH_USER`,
`DEPLOY_PATH`, `VITE_EDITION_ID`.
`DEPLOY_PATH`, `VITE_EDITION_ID`, `VITE_HCAPTCHA_SITE_KEY` (optionnelle —
clé de site hCaptcha, publique, figée au build du frontend comme
`VITE_EDITION_ID` ; vide ⇒ formulaire public sans vérification anti-robot).
- Le VPS porte un fichier `.env` (non versionné) avec `POSTGRES_USER`,
`POSTGRES_PASSWORD`, `POSTGRES_DB`, `JWT_SECRET`, `PUBLIC_BASE_URL` et les
variables `EMAIL_ENABLED` / `SMTP_*` (voir `deploy/README.md`).
`POSTGRES_PASSWORD`, `POSTGRES_DB`, `JWT_SECRET`, `PUBLIC_BASE_URL`,
`HCAPTCHA_SECRET_KEY` (optionnelle, secret hCaptcha — voir § 2, `FormField`)
et les variables `EMAIL_ENABLED` / `SMTP_*` (voir `deploy/README.md`).
- `backend` et `frontend` n'écoutent que sur `127.0.0.1` (voir
`docker-compose.prod.yml`) : `deploy/Caddyfile.benevoles` est le bloc à
ajouter au Caddyfile du serveur pour les exposer publiquement (`/api/*` vers
@@ -564,6 +604,7 @@ Aucune dépendance locale requise hors Docker.
| 18 | Gestion des référents depuis la fiche bénévole : `PATCH /accounts` (rôle LEAD ↔ VOLUNTEER), `lead_account_id` vérifié (doit être un compte référent de l'édition) | à valider |
| 19 | Revue de sécurité avant ouverture au public : refus de démarrer en production avec un `JWT_SECRET` par défaut/vide/court, avertissement si `EMAIL_ENABLED=false` en production, en-têtes de sécurité (nginx + Caddy), pas de jeton dans les logs d'accès du frontend, limiteur de tentatives sur `/auth/login` et `/auth/magic-link` | à valider |
| 20 | Planches de badges bénévoles (PDF) : recto personnalisable (fond + champs nom/poste(s)/catégorie/rôle, éditeur glisser-déposer), verso fixe (même image pour tous), écran « Badges » dédié (`/badges`, COORDINATION) | à valider |
| 21 | Formulaire public d'inscription (`/inscription`, sans authentification) : champs activables par la coordination (email, téléphone, vœux de poste — max 2, vœux d'artiste — un par soir, vœu de compagnon en texte libre, disponibilité/cotisation déclaratives, photo, commentaire), captcha hCaptcha, ouverture/fermeture, lien à partager, écran de configuration dédié (`/inscriptions`, COORDINATION) — les bénévoles créés rejoignent directement la liste | à valider |
**Reste hors périmètre** (modèle prêt à les accueillir) : SMS, constructeur
de formulaire, pointage sur site, IA. Frontend : squelette + 6 écrans +