Files
benevoles/BLUEPRINT.md
T
AdamThomasandClaude Opus 5.5 5b493c9737 Ajout de BLUEPRINT.md : socle réutilisable pour une nouvelle appli
Philosophie, stack, architecture, style et conventions de l'appli,
débarrassés du domaine métier.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 18:05:16 +01:00

19 KiB
Raw Blame History

BLUEPRINT — Socle réutilisable

Ce fichier capture la philosophie, la stack, l'architecture, le style et les conventions de l'application « Gestion des bénévoles », débarrassés de son domaine métier (festival, créneaux, cotisations…). Il sert de point de départ pour une nouvelle application construite sur les mêmes bases.

Mode d'emploi pour une nouvelle appli :

  1. Copier ce fichier dans le nouveau dépôt.
  2. Le donner à Claude avec le brief métier : « Crée l'appli X sur les bases de BLUEPRINT.md ».
  3. La première étape produit un CLAUDE.md propre au nouveau projet (même plan que celui de benevoles : glossaire, modèle, règles, périmètre, arborescence, commandes, conventions, déploiement, journal des étapes), qui devient ensuite le contrat de travail.

Les passages marqués ⟨à adapter⟩ dépendent du métier ; tout le reste est le socle à reprendre tel quel.


1. Philosophie

  • Un outil pour une petite organisation réelle (association, 50–200 personnes), pas une plateforme générique. On résout le besoin concret, sans sur-ingénierie, mais avec un vrai niveau de qualité (tests, sécurité, RGPD, déploiement automatisé).
  • Le métier parle français, le code parle anglais. Un glossaire FR ↔ EN fige les noms une fois pour toutes ; aucun nom métier français dans le code, aucun texte d'interface en dur nulle part.
  • CLAUDE.md est le contrat de travail. Il décrit le modèle, les règles, les choix et leurs raisons. Il est mis à jour à chaque étape, en même temps que le code. Quand un bug est corrigé, la cause et la règle qui l'empêche de revenir y sont notées (« bug corrigé une fois — faire Y recréerait la même classe de bug »).
  • Étapes courtes et validées. Une étape = une fonctionnalité cohérente, montrée à l'utilisateur, puis validée avant de continuer. Un commit atomique par étape, message en français. Le journal des étapes (§ 10 du CLAUDE.md) garde l'historique des décisions.
  • Le cœur métier est pur et testé d'abord. Les règles critiques vivent dans un module isolé, sans base de données ni HTTP (fonction pure : entrée typée → résultat typé), écrit en TDD : un cas passant + un cas déclenchant par règle. Un loader.py est le seul point de contact avec la base.
  • L'outil assiste, l'humain décide. Les calculs « intelligents » (solveur, suggestions) proposent ; ils ne persistent jamais rien d'eux-mêmes. La validation reste seule juge au moment de l'écriture.
  • Erreurs = codes, pas phrases. Le backend renvoie {"detail": {"code": "domaine.raison", ...params}} ; la traduction se fait côté locales. Les violations de règles sont typées (BLOCKING / WARNING) avec des paramètres structurés.
  • Simplicité assumée et documentée. Quand on simplifie (ex. « vérifié seulement contre l'existant, pas entre deux suggestions »), on l'écrit explicitement comme simplification assumée.
  • Retirer est une fonctionnalité. On n'hésite pas à supprimer un concept devenu inutile (table, règle, écran) — migration, API, front et doc nettoyés ensemble, plutôt que de laisser du code mort.
  • Aucune dépendance locale hors Docker. Tout se lance, se teste et se migre via docker compose.

2. Stack technique

Backend

Rôle Choix
Langage Python 3.12
Gestion des dépendances uv (pyproject.toml, groupe dev)
API FastAPI + Uvicorn
ORM / schémas SQLModel (SQLAlchemy + Pydantic)
Base PostgreSQL 16 (driver psycopg[binary] v3)
Migrations Alembic (autogenerate + alembic check pour vérifier modèle ↔ migrations)
Configuration pydantic-settings (app/config.py, valeurs réelles dans .env non versionné)
Auth pyjwt (HS256) + bcrypt
Tests pytest + pytest-cov, vraie base PostgreSQL de test
Lint / format ruff (line-length 100, règles E F I UP B)
Outils ponctuels openpyxl (import/export Excel), reportlab + pypdf (PDF), pillow (validation d'images), httpx (API tierces), ortools (optimisation) — ⟨à adapter⟩ selon le besoin

Frontend

Rôle Choix
Framework React 18 + TypeScript (strict)
Build Vite 5
Style Tailwind CSS 3 (thème étendu, aucune lib de composants)
Données serveur TanStack React Query 5 (hooks centralisés dans src/api/hooks.ts)
Routing React Router 6
Graphiques SVG maison (components/charts.tsx), aucune bibliothèque
Qualité ESLint 9 + tsc --noEmit + script smoke (rendu headless via tsx)

Dépendances runtime volontairement minimales : react, react-dom, react-router-dom, @tanstack/react-query. Rien d'autre sans bonne raison.

Infra

  • Docker Compose en dev (db + backend en hot reload + frontend Vite).
  • Production : images backend (uvicorn) et frontend (build statique servi par nginx), ports exposés uniquement sur 127.0.0.1, Caddy du VPS en reverse proxy HTTPS (/api/* → backend avec préfixe retiré, le reste → frontend).
  • Dépôt Gitea auto-hébergé + Gitea Actions (act_runner) : tests → build des images (registre Gitea) → SSH sur le VPS → pull → alembic upgrade head → up -d.
  • Fuseau horaire : TZ=Europe/Paris + tzdata dans le conteneur.

3. Architecture

Arborescence type

.
├── CLAUDE.md                  # contrat de travail (tenu à jour à chaque étape)
├── README.md
├── Makefile                   # make demo / make blank / make down / make logs
├── docker-compose.yml         # dev
├── .env.example               # toutes les variables, sans vraies valeurs
├── .gitea/workflows/ci-cd.yml
├── deploy/                    # docker-compose.prod.yml, bloc Caddyfile, README de mise en service
├── docker/postgres-init/      # crée la base de test au premier démarrage
├── backend/
│   ├── pyproject.toml · Dockerfile · alembic.ini
│   ├── app/
│   │   ├── main.py            # instance FastAPI, montage des routers
│   │   ├── config.py          # Settings (pydantic-settings), get_settings() en cache
│   │   ├── db.py              # engine + get_session
│   │   ├── logging_config.py  # logs sans PII + identifiant de requête
│   │   ├── request_logging.py # middleware : une ligne par requête, X-Request-ID, 500 tracées
│   │   ├── api/               # un router par ressource + schemas.py + deps.py
│   │   ├── auth/              # passwords, magic_link, tokens (JWT), deps (rôles), rate_limit
│   │   ├── email/             # sender (SMTP), templates (texte), html (rendu thémé), check (diagnostic)
│   │   ├── models/            # enums.py, base.py (TimestampMixin, local_now), domain.py
│   │   ├── validation/        # cœur métier pur : models.py, engine.py, loader.py
│   │   ├── <module_métier>/   # même découpage pour chaque calcul isolé ⟨à adapter⟩
│   │   ├── i18n/              # t(key, **params) + locales/fr.json
│   │   ├── bootstrap.py       # instance vierge : 1re entité racine + 1er compte admin
│   │   ├── seed.py            # jeu de démonstration réaliste et déterministe
│   │   └── migrations/
│   └── tests/                 # conftest.py (vraie base), factories.py, test_<module>.py
└── frontend/
    ├── Dockerfile · Dockerfile.prod · nginx.conf
    ├── tailwind.config.js     # palette et primitives du design
    ├── scripts/smoke.tsx      # rendu headless + vérifications structurelles
    └── src/
        ├── api/               # types.ts (interface Api), schema.ts (miroir des schémas backend),
        │                      # http-client.ts, mock/ (même surface, en mémoire), hooks.ts, session.ts
        ├── app/               # AppShell.tsx, router.tsx
        ├── auth/              # auth-context, RequireAuth, roles (homePath par rôle)
        ├── components/        # ui.tsx (primitives), state.tsx (chargement/erreur), charts.tsx
        ├── i18n/              # t(key, params) + locales/fr.json
        ├── lib/               # logique pure testable (calculs, dates, env, cx)
        └── pages/             # un écran par fichier, sous-dossiers si gros écran

Principes d'architecture backend

  • Multi-tenant par une entité racine (ici Edition) : toute entité métier porte son <racine>_id, toutes les routes sont préfixées /<racines>/{id}/... et la dépendance get_<racine> renvoie 404 error.not_found si elle n'existe pas. ⟨à adapter⟩ (organisation, projet, saison…).
  • Clés primaires UUID, champs d'audit created_at/updated_at sur toutes les tables sauf associations (TimestampMixin).
  • Énumérations en VARCHAR + CHECK (native_enum=False), jamais d'ENUM natif PostgreSQL (migrations plus simples).
  • ON DELETE CASCADE au niveau base sur les liens vers la racine et entre entités : la suppression RGPD d'une personne est un vrai hard delete qui emporte tout ce qui la concerne.
  • Dates naïves en heure locale (local_now()), interprétées Europe/Paris. Pas de mélange UTC/local.
  • Modules isolés pour la logique non triviale : models.py (types purs, dataclasses), engine.py (fonction pure), loader.py (seul accès base). Testés sans HTTP.
  • Un router par ressource, schémas Pydantic XCreate / XRead / XUpdate dans api/schemas.py. En PATCH : None/absent = inchangé, [] = vider une liste.
  • Secrets jamais renvoyés par l'API (xxx_set: bool à la place ; chaîne vide en écriture = effacer).
  • Tâches de fond (BackgroundTasks) pour les emails, en best effort : un échec SMTP est journalisé mais ne fait jamais échouer l'appel HTTP.
  • Nettoyage paresseux plutôt que tâches planifiées (purge des données périmées au passage d'une requête), tant que le volume le permet.
  • Instance vierge et démo : bootstrap.py (CLI, crée la racine + le 1er compte) et seed.py (jeu réaliste avec des cas intéressants : trous, conflits, données de chaque fonctionnalité).

Principes d'architecture frontend

  • Une interface Api unique, deux implémentations : client HTTP réel et mock en mémoire de même surface (VITE_API_MODE=mock|http). Le front se développe et se démontre sans backend ; passer de l'un à l'autre ne change aucun écran. Le mock calcule les mêmes résultats que le backend.
  • schema.ts = miroir de backend/app/api/schemas.py, gardé synchrone à la main.
  • Toutes les données serveur passent par React Query (hooks.ts), jamais de fetch dans les pages.
  • Logique pure dans src/lib/, souvent miroir d'une fonction backend (ex. festivalDay ↔ day_boundary) : la règle est écrite à l'identique des deux côtés et le CLAUDE.md le signale.
  • Auth : JWT en localStorage, Authorization: Bearer, une 401 déconnecte ; <RequireAuth> sur toutes les pages métier ; page d'accueil et onglets visibles selon le rôle.
  • Variables Vite figées au build (VITE_*) : l'image de prod est construite pour un environnement donné.
  • Téléchargements (PDF, Excel) : blob → <a download> généré côté client.
  • Chaque erreur affiche sa référence (X-Request-ID) sous le message, pour retrouver la ligne de log correspondante.

4. Sécurité et RGPD (non négociables)

  • Deux systèmes d'authentification, une même session JWT :
    • mot de passe (bcrypt, ≥ 10 caractères) pour les administrateurs ;
    • lien magique (jeton aléatoire haché SHA-256 en base, usage unique, TTL court) pour les autres comptes ; réponse 202 constante, sans jamais révéler si un compte existe.
  • Rôles appliqués côté backend par endpoint, documentés dans une matrice ressource × rôle dans le CLAUDE.md. ⟨à adapter⟩ (ici COORDINATOR / LEAD / VOLUNTEER).
  • Limiteurs de tentatives en mémoire (connexion, lien magique, formulaires publics, sondages vers API tierces) — 429 error.too_many_requests.
  • Démarrage refusé en production avec un JWT_SECRET par défaut, vide ou trop court.
  • Pages publiques protégées par jeton dans l'URL (secrets.token_urlsafe, comparaison secrets.compare_digest, 404 générique), régénérable pour révoquer un lien qui a fuité ; hCaptcha optionnel (désactivé si la clé est absente).
  • Fichiers importés revalidés (décodage réel d'image avec PIL, pas seulement le Content-Type) ; exports Excel protégés contre l'injection de formule (CWE-1236).
  • Aucune donnée nominative dans les logs (ni nom, ni email, ni téléphone, ni sujet d'email personnalisable) : on logue des identifiants. Journal d'accès d'uvicorn désactivé (il écrivait les query strings, donc les jetons).
  • Logs de diagnostic : une ligne par requête (méthode, route modèle
    • identifiants, statut, durée, code d'erreur métier, compte) ; WARNING en 4xx, ERROR + trace complète en 5xx ; X-Request-ID propagé jusque dans les tâches de fond. Erreur 422 : emplacement + type, jamais la valeur saisie.
  • Rétention paramétrable des données nominatives sur l'entité racine ; endpoint de suppression complète d'une personne.
  • En-têtes de sécurité dans nginx et Caddy ; backend et frontend n'écoutent que sur 127.0.0.1.
  • Emails : SMTP auto-hébergé, EMAIL_ENABLED=false en dev/tests (simple journalisation), multipart/alternative (texte brut = source de vérité des tests + HTML au thème de l'appli, tableaux et styles en ligne, logo en pièce jointe inline par Content-ID plutôt qu'une URL qui fuiterait un jeton). Commande de diagnostic SMTP dédiée.

5. Style visuel (design « néo-brutaliste »)

Identité forte, lisible, sans bibliothèque de composants : bordures franches noires, ombres portées dures sans flou, libellés en capitales, fond crème, accents saturés.

Palette (tailwind.config.js)

Jeton Valeur Usage
cream #F5EFE1 fond général
ink #14110E encre, bordures, ombres
paper #FFFDF7 panneaux, cartes
sand / sand-line / sand-dark #EFE8D6 / #E0D8C4 / #E7E0CE barres secondaires, séparateurs
yellow / yellow-soft / yellow-hover #FFD23F / #FFE68A / #FFDF77 action principale, état incomplet
orange / orange-dark #FF5C39 / #E0421F urgence, danger
green #0E9F6E succès, survol de l'action principale
blue / blue-strong #A8C4FF / #2A6BFF information
mauve / mauve-hover #CBA6E3 / #BE93D9 dépassement / surplus

Typographie

  • font-display : Archivo (400 → 900), titres et boutons en font-black/font-extrabold, uppercase, tracking-wide, petites tailles (11–12 px).
  • font-mono : DM Mono, pour les heures, chiffres, codes.
  • Chargées depuis Google Fonts dans index.html.

Primitives (components/ui.tsx)

  • Ombres : shadow-brutal-sm (2 px), brutal (3 px), brutal-md (4 px), brutal-lg (6 px) — toutes Npx Npx 0 #14110E.
  • Panel : border-[3px] border-ink bg-paper + ombre portée.
  • PanelHeader : bandeau bg-ink text-cream en capitales (variante hachurée bg-hatch-ink text-yellow).
  • Btn : border-2 border-ink, capitales, variantes solid (jaune → vert au survol), ghost (crème → jaune), danger (crème → orange), dark (encre → jaune).
  • Trames d'état en backgroundImage : hachures orange (critique), mauve (surplus), encre (brouillon), sable (fermé), pointillés.
  • Infobulles (HelpTip) en position: fixed positionnées par JS (useTooltipPosition), bornées à l'écran — jamais en absolute dans un conteneur qui défile.
  • Graphiques SVG maison, palette vérifiée daltonisme/contraste sur paper, chaque graphique doublé d'une vue tableau.
  • Écrans mobiles dédiés aux rôles « terrain » (consultation simple), écrans denses pour l'administration.

Aucun texte dans les primitives : les libellés viennent toujours de l'i18n.


6. Conventions de code

  • Code, base, fichiers en anglais ; français uniquement dans les locales, les commentaires de contexte métier et les commits.
  • i18n partout, même en monolingue : backend app.i18n.t(key, **params) (str.format), frontend t(key, params) ({nom}).
  • Commentaires qui expliquent le pourquoi (contraintes, bug évité, choix non évident), pas le quoi.
  • Tests backend : vraie base PostgreSQL (créée par docker/postgres-init), chaque test dans une transaction annulée (savepoints, join_transaction_mode="create_savepoint"), fixtures autouse qui coupent le SMTP et réinitialisent les limiteurs, factories.py pour les données. Un fichier test_<module>.py par module ou router ; tests d'autorisation dédiés (test_authz.py).
  • Tests frontend : typecheck + lint + smoke (rendu SSR des écrans avec le mock, et vérifications structurelles quand un bug visuel a déjà eu lieu — ex. positions de grille rendues).
  • Migrations numérotées (0001, 0002…), une par étape qui touche le modèle, vérifiées par alembic check.
  • Pas de nouvelle dépendance sans raison écrite (commentaire dans pyproject.toml si contrainte de version).

7. Commandes types

Action Commande
Tout lancer avec la démo make demo
Instance vierge make blank EMAIL=… PASSWORD=… NAME=…
Dev docker compose up --build (API : http://localhost:8000/docs, front : http://localhost:5173)
Tests backend docker compose run --rm backend uv run pytest
Lint / format docker compose run --rm backend uv run ruff check . / ruff format .
Migration docker compose run --rm backend uv run alembic revision --autogenerate -m "…"
Appliquer / vérifier … alembic upgrade head puis … alembic check
Front npm run typecheck && npm run lint && npm run smoke

8. Méthode de travail avec Claude

  1. Étape 1 — squelette : monorepo, Docker Compose, backend qui démarre (/health), frontend vide, CI/CD, CLAUDE.md initial avec glossaire.
  2. Étape 2 — modèle de données complet + migration initiale.
  3. Ensuite : import des données existantes (analyse puis exécution, mode simulation, rapport ligne par ligne, détection de doublons), CRUD, cœur métier en TDD, seed de démo, authentification, puis fonctionnalités une par une.
  4. À chaque étape : code + tests + migration + mise à jour du CLAUDE.md (modèle, règles, journal) + front (mock compris) → montrer le résultat → attendre la validation → commit atomique en français.
  5. Revues de sécurité régulières avant toute ouverture au public, notées comme étapes à part entière.