mirror of
https://github.com/MaksTinyWorkshop/_Assistant_Lead_Tech
synced 2026-04-06 13:31:43 +02:00
ajout patterns
This commit is contained in:
@@ -8,7 +8,7 @@ Ce fichier contient **uniquement** des patterns back-end :
|
||||
|
||||
Objectif : éviter de réinventer la roue et réduire le temps de debug.
|
||||
|
||||
Dernière mise à jour : 10-03-2026
|
||||
Dernière mise à jour : 12-03-2026
|
||||
|
||||
---
|
||||
|
||||
@@ -31,6 +31,7 @@ Dernière mise à jour : 10-03-2026
|
||||
- [Sémantique explicite `Trial` vs `Paid` dans Subscription](#pattern-subscription-trial-vs-paid)
|
||||
- [Restauration d’achats Stripe en 3 étapes](#pattern-restauration-achats-stripe)
|
||||
- [Mapping explicite de `P2002` Prisma sur update de champ unique](#pattern-prisma-p2002-update-unique)
|
||||
- [Autorisation interne minimale sans RBAC complet](#pattern-autorisation-interne-minimale)
|
||||
|
||||
---
|
||||
|
||||
@@ -88,6 +89,7 @@ Si ce n’est pas confirmé comme fonctionnel et utile, **ça n’a rien à fair
|
||||
---
|
||||
|
||||
<a id="pattern-format-derreur-api-standardise"></a>
|
||||
|
||||
## Pattern : Format d’erreur API standardisé
|
||||
|
||||
- Objectif : fournir des erreurs prévisibles, exploitables et cohérentes pour tous les clients.
|
||||
@@ -125,6 +127,7 @@ Si ce n’est pas confirmé comme fonctionnel et utile, **ça n’a rien à fair
|
||||
---
|
||||
|
||||
<a id="pattern-middleware-correlation-requestid-traceid"></a>
|
||||
|
||||
## Pattern : Middleware de corrélation (requestId / traceId)
|
||||
|
||||
- Objectif : relier chaque requête aux logs et erreurs associées.
|
||||
@@ -156,6 +159,7 @@ Si ce n’est pas confirmé comme fonctionnel et utile, **ça n’a rien à fair
|
||||
---
|
||||
|
||||
<a id="pattern-idempotency-key-operations-sensibles"></a>
|
||||
|
||||
## Pattern : Idempotency key pour opérations sensibles
|
||||
|
||||
- Objectif : empêcher les doublons lors de retries ou timeouts.
|
||||
@@ -187,6 +191,7 @@ Si ce n’est pas confirmé comme fonctionnel et utile, **ça n’a rien à fair
|
||||
---
|
||||
|
||||
<a id="pattern-pagination-robuste-cursor-based"></a>
|
||||
|
||||
## Pattern : Pagination robuste (cursor-based) pour les listings
|
||||
|
||||
- Objectif : fournir des listings stables et performants sans incohérences entre pages.
|
||||
@@ -222,6 +227,7 @@ Si ce n’est pas confirmé comme fonctionnel et utile, **ça n’a rien à fair
|
||||
---
|
||||
|
||||
<a id="pattern-execution-asynchrone-taches-longues"></a>
|
||||
|
||||
## Pattern : Exécution asynchrone des tâches longues (queue + outbox light)
|
||||
|
||||
- Objectif : sortir les opérations longues ou fragiles du chemin request/response.
|
||||
@@ -259,6 +265,7 @@ Si ce n’est pas confirmé comme fonctionnel et utile, **ça n’a rien à fair
|
||||
---
|
||||
|
||||
<a id="pattern-soft-delete-archivage-explicite"></a>
|
||||
|
||||
## Pattern : Soft delete et archivage explicite
|
||||
|
||||
- Objectif : permettre la suppression logique sans perte immédiate de données.
|
||||
@@ -295,6 +302,7 @@ Si ce n’est pas confirmé comme fonctionnel et utile, **ça n’a rien à fair
|
||||
---
|
||||
|
||||
<a id="pattern-webhooks-sortants-robustes-idempotents"></a>
|
||||
|
||||
## Pattern : Webhooks sortants robustes et idempotents
|
||||
|
||||
- Objectif : garantir des intégrations fiables avec des systèmes externes.
|
||||
@@ -332,6 +340,7 @@ Si ce n’est pas confirmé comme fonctionnel et utile, **ça n’a rien à fair
|
||||
---
|
||||
|
||||
<a id="pattern-contracts-first-zod-infer-no-dto"></a>
|
||||
|
||||
## Pattern : Contracts-First / Zod-Infer / No-DTO (monorepo TypeScript fullstack)
|
||||
|
||||
- Objectif : avoir une seule source de vérité pour les contrats d’interface entre API et client, sans redéfinition manuelle de types.
|
||||
@@ -407,6 +416,7 @@ packages/contracts/src/
|
||||
---
|
||||
|
||||
<a id="pattern-guard-global-nestjs"></a>
|
||||
|
||||
## Pattern : Guard global NestJS — ordre d’enregistrement et décorateurs de bypass
|
||||
|
||||
- Objectif : protéger tous les endpoints par défaut, avec un mécanisme explicite pour les exceptions.
|
||||
@@ -455,6 +465,7 @@ if (skip) return true;
|
||||
---
|
||||
|
||||
<a id="pattern-provider-strategy-integrations-tierces"></a>
|
||||
|
||||
## Pattern : Provider-Strategy pour intégrations tierces — périmètre complet
|
||||
|
||||
- Objectif : isoler intégralement la logique propre à un prestataire (Stripe, Brevo, Firebase…) derrière une interface stable, pour éviter la contamination du domaine par le SDK tiers.
|
||||
@@ -502,6 +513,7 @@ async handleWebhook(rawBody: Buffer, signature: string): Promise<void> {
|
||||
---
|
||||
|
||||
<a id=”pattern-stripe-subscription-metadata”></a>
|
||||
|
||||
## Pattern : Stripe — metadata sur `subscription_data`, pas sur la Session
|
||||
|
||||
- Objectif : garantir que `userId` (ou tout identifiant métier) soit accessible dans les events `customer.subscription.*`, pas seulement dans `checkout.session.completed`.
|
||||
@@ -523,6 +535,7 @@ stripe.checkout.sessions.create({
|
||||
---
|
||||
|
||||
<a id=”pattern-webhook-parsing-unique”></a>
|
||||
|
||||
## Pattern : Webhooks entrants — parsing unique (single `constructWebhookEvent`)
|
||||
|
||||
- Objectif : appeler `constructWebhookEvent` une seule fois par requête, puis router vers des extracteurs purs.
|
||||
@@ -545,6 +558,7 @@ handlePackWebhookEvent(event): PackWebhookResult | null
|
||||
---
|
||||
|
||||
<a id=”pattern-contracts-error-codes”></a>
|
||||
|
||||
## Pattern : Contracts-First — error codes comme contrat obligatoire
|
||||
|
||||
- Objectif : maintenir les codes d’erreur API dans `packages/contracts` pour éviter les clients stringly-typed.
|
||||
@@ -563,6 +577,7 @@ handlePackWebhookEvent(event): PackWebhookResult | null
|
||||
---
|
||||
|
||||
<a id="pattern-redis-health-cache-court"></a>
|
||||
|
||||
## Pattern : RedisHealthService avec cache interne court
|
||||
|
||||
- Objectif : exposer un état Redis exploitable par les guards globaux sans ping Redis à chaque requête.
|
||||
@@ -596,6 +611,7 @@ handlePackWebhookEvent(event): PackWebhookResult | null
|
||||
---
|
||||
|
||||
<a id="pattern-subscription-trial-vs-paid"></a>
|
||||
|
||||
## Pattern : Sémantique explicite `Trial` vs `Paid` dans Subscription
|
||||
|
||||
- Objectif : aligner le modèle métier, les guards et les jeux de tests sur une définition unique de l’abonnement payant actif.
|
||||
@@ -627,6 +643,7 @@ handlePackWebhookEvent(event): PackWebhookResult | null
|
||||
---
|
||||
|
||||
<a id="pattern-restauration-achats-stripe"></a>
|
||||
|
||||
## Pattern : restauration d’achats Stripe en 3 étapes
|
||||
|
||||
- Objectif : reconstruire un état local cohérent à partir de Stripe sans dépendre d’une hypothèse fragile.
|
||||
@@ -658,6 +675,7 @@ handlePackWebhookEvent(event): PackWebhookResult | null
|
||||
---
|
||||
|
||||
<a id="pattern-prisma-p2002-update-unique"></a>
|
||||
|
||||
## Pattern : mapping explicite de `P2002` Prisma sur update de champ unique
|
||||
|
||||
- Objectif : transformer un conflit d’unicité prévisible en erreur métier exploitable plutôt qu’en 500 opaque.
|
||||
@@ -688,6 +706,43 @@ handlePackWebhookEvent(event): PackWebhookResult | null
|
||||
|
||||
---
|
||||
|
||||
<a id="pattern-autorisation-interne-minimale"></a>
|
||||
|
||||
## Pattern : Autorisation interne minimale sans RBAC complet
|
||||
|
||||
- Objectif : sécuriser une capacité interne sensible sans ouvrir trop tôt un chantier RBAC complet.
|
||||
- Contexte : application avec peu de rôles, besoin ponctuel d’une capacité admin ou opérateur clairement identifiée.
|
||||
- Quand l’utiliser : quand une story métier demande un pouvoir interne limité mais réel.
|
||||
- Quand l’éviter : si les permissions deviennent nombreuses, hiérarchiques ou contextuelles.
|
||||
- Avantage :
|
||||
- sécurisation rapide et lisible d’une capacité sensible
|
||||
- source de vérité backend explicite
|
||||
- chemin d’évolution propre vers un RBAC plus complet
|
||||
- Limites / vigilance :
|
||||
- ne pas laisser proliférer des rôles ad hoc non gouvernés
|
||||
- ne remplace pas un vrai modèle de permissions si le domaine grossit
|
||||
- Validé le : 10-03-2026
|
||||
- Contexte technique : NestJS / auth par session ou JWT / API métier interne
|
||||
|
||||
### Implémentation (exemple minimal)
|
||||
|
||||
```txt
|
||||
- introduire un enum de rôle minimal côté backend (ex. USER | ADMIN)
|
||||
- propager ce rôle dans la session ou le token d’auth
|
||||
- créer un décorateur + guard dédiés pour la capacité sensible
|
||||
- interdire les booléens front, emails hardcodés ou `if` dispersés dans les contrôleurs
|
||||
```
|
||||
|
||||
### Checklist
|
||||
|
||||
- Le rôle vit dans la source de vérité backend
|
||||
- Le rôle est propagé dans le mécanisme d’auth existant
|
||||
- Les endpoints sensibles passent par un guard dédié
|
||||
- Aucun contrôle d’accès critique n’est piloté par le front
|
||||
- Le passage à RBAC reste possible sans casser le contrat existant
|
||||
|
||||
---
|
||||
|
||||
### Notes importantes
|
||||
|
||||
- On préfère 5 patterns solides à 50 “bons conseils”.
|
||||
|
||||
@@ -12,7 +12,7 @@ Il sert de **mémoire durable** pour éviter :
|
||||
- de redélibérer éternellement sur des sujets déjà tranchés,
|
||||
- de propager des “bonnes pratiques” théoriques non éprouvées.
|
||||
|
||||
Dernière mise à jour : 09-03-2026
|
||||
Dernière mise à jour : 12-03-2026
|
||||
|
||||
---
|
||||
|
||||
@@ -22,6 +22,7 @@ Dernière mise à jour : 09-03-2026
|
||||
- [Séparation claire server state / client state](#pattern-separation-server-state-client-state)
|
||||
- [Formulaire robuste avec validation et erreurs explicites](#pattern-formulaire-robuste)
|
||||
- [Navigation réactive post-action async (React / Expo Router)](#pattern-navigation-reactive-post-action-async)
|
||||
- [Refresh idempotent sur store de liste paginée](#pattern-refresh-idempotent-liste-paginee)
|
||||
|
||||
---
|
||||
|
||||
@@ -290,6 +291,51 @@ const handleOAuth = async () => {
|
||||
|
||||
---
|
||||
|
||||
<a id="pattern-refresh-idempotent-liste-paginee"></a>
|
||||
## Pattern : Refresh idempotent sur store de liste paginée
|
||||
|
||||
### Synthèse
|
||||
|
||||
- **Objectif** : garantir qu’un pull-to-refresh recharge une liste paginée sans doublons, sans courses réseau et sans état intermédiaire incohérent.
|
||||
- **Contexte** : app mobile ou SPA avec store de domaine (ex. Zustand) et pagination incrémentale.
|
||||
- **Quand l’utiliser** : dès qu’une même liste supporte à la fois `loadMore` et `refresh`.
|
||||
- **Quand l’éviter** : listes purement statiques ou données entièrement remplacées sans pagination.
|
||||
|
||||
### Analyse
|
||||
|
||||
- **Avantages** :
|
||||
- évite les doublons lors des refresh concurrents
|
||||
- garde une transition atomique entre ancien et nouvel état
|
||||
- rend le comportement async testable côté store
|
||||
- **Limites / vigilance** :
|
||||
- impose une discipline claire entre `refresh` et `loadMore`
|
||||
- demande une clé d’identité stable pour dédupliquer les items
|
||||
|
||||
### Validation
|
||||
|
||||
- Validé le : 10-03-2026
|
||||
- Contexte technique : React Native / Expo / Zustand / listes paginées
|
||||
|
||||
### Implémentation (exemple minimal)
|
||||
|
||||
```txt
|
||||
- conserver une promesse de refresh partagée tant qu’un refresh est en vol
|
||||
- refuser ou réutiliser tout refresh concurrent au lieu d’en lancer un second
|
||||
- remplacer atomiquement la liste à la fin du refresh
|
||||
- dédupliquer les items par identifiant au merge des pages suivantes
|
||||
- empêcher `loadMore` de fusionner sur un snapshot devenu obsolète
|
||||
```
|
||||
|
||||
### Checklist
|
||||
|
||||
- [ ] Une seule promesse de refresh en vol à la fois
|
||||
- [ ] `refresh` et `loadMore` ont des garde-fous explicites
|
||||
- [ ] La liste est remplacée atomiquement après refresh
|
||||
- [ ] Les pages suivantes sont dédupliquées par identifiant stable
|
||||
- [ ] Tests sur refresh concurrent + refresh suivi de pagination
|
||||
|
||||
---
|
||||
|
||||
### Principes transverses
|
||||
|
||||
- Un pattern = une responsabilité claire
|
||||
|
||||
@@ -7,7 +7,7 @@ Ce fichier recense des risques front-end susceptibles de provoquer :
|
||||
- dette technique rapide,
|
||||
- régressions UX/perf/a11y.
|
||||
|
||||
Dernière mise à jour : 10-03-2026
|
||||
Dernière mise à jour : 12-03-2026
|
||||
|
||||
---
|
||||
|
||||
@@ -32,6 +32,7 @@ Dernière mise à jour : 10-03-2026
|
||||
- [Catch silencieux — erreur inconnue sans feedback utilisateur](#risque-catch-silencieux)
|
||||
- [Auto-reset d’un état dégradé sur toute réponse 2xx](#risque-auto-reset-etat-degrade)
|
||||
- [Refresh store en fire-and-forget après mutation](#risque-refresh-store-fire-and-forget)
|
||||
- [Loading infini sur écran gated par droits distants](#risque-loading-infini-ecran-gated)
|
||||
|
||||
---
|
||||
|
||||
@@ -255,3 +256,28 @@ Dernière mise à jour : 10-03-2026
|
||||
- Gestion d’erreur dédiée sur la phase de resynchronisation
|
||||
- N’utiliser le fire-and-forget que pour un effet secondaire réellement non bloquant
|
||||
- Contexte technique : React Native / Expo — 10-03-2026
|
||||
|
||||
---
|
||||
|
||||
<a id="risque-loading-infini-ecran-gated"></a>
|
||||
## Loading infini sur écran gated par droits distants
|
||||
|
||||
### Risques
|
||||
|
||||
- Un écran protégé reste bloqué dans un faux `loading` après une erreur de chargement des entitlements
|
||||
- Un effet relance automatiquement la récupération en boucle sans action utilisateur
|
||||
- L’utilisateur ne voit ni état d’erreur ni issue de sortie claire
|
||||
|
||||
### Symptômes
|
||||
|
||||
- Spinner infini sur un écran soumis à permissions distantes
|
||||
- `entitlements` ou autorisations laissés à `null` après erreur
|
||||
- `useEffect` ou logique d’entrée qui retrigger le fetch à chaque rendu
|
||||
|
||||
### Bonnes pratiques / mitigations
|
||||
|
||||
- Distinguer explicitement `loading`, `error`, `ready`
|
||||
- Ne pas réutiliser `null` comme état ambigu "pas encore chargé" et "chargement en erreur"
|
||||
- Bloquer les retries automatiques en boucle après erreur
|
||||
- Réautoriser un retry seulement via action utilisateur explicite ou nouvelle condition d’entrée
|
||||
- Contexte technique : React Native / Expo / store d’entitlements — 10-03-2026
|
||||
|
||||
@@ -8,27 +8,380 @@ Objectifs :
|
||||
- éviter de reposer les mêmes questions
|
||||
- assumer les compromis
|
||||
|
||||
Dernière mise à jour : 2026-03-10
|
||||
Dernière mise à jour : 2026-03-12
|
||||
|
||||
---
|
||||
|
||||
## Index
|
||||
## Pré-index par section
|
||||
|
||||
1. [Global](#section-global)
|
||||
2. [Infra](#section-infra)
|
||||
3. [Backend](#section-backend)
|
||||
4. [n8n](#section-n8n)
|
||||
|
||||
## Index détaillé
|
||||
|
||||
### 1. Global
|
||||
|
||||
<a id="section-global"></a>
|
||||
|
||||
- [Story sizing — foundations vs qualité non bloquante](#decision-story-sizing-foundations)
|
||||
- [Code review adversariale — contexte frais](#decision-code-review-adversariale)
|
||||
- [Workflows n8n complexes = mini-systèmes](#decision-n8n-mini-systemes)
|
||||
- [Le front-end est un logiciel en production](#decision-frontend-production)
|
||||
- [Le back-end est un logiciel en production](#decision-backend-production)
|
||||
- [Contrats d’API explicites et versionnés](#decision-contrats-api)
|
||||
- [Single source of truth des contrats — schémas runtime partagés (Zod) + z.infer (No-DTO)](#decision-contrats-sso-zod)
|
||||
- [User views — User public par défaut + MeUser explicite](#decision-user-views)
|
||||
|
||||
### 2. Infra
|
||||
|
||||
<a id="section-infra"></a>
|
||||
|
||||
- [Structure Docker et données persistantes](#decision-structure-docker)
|
||||
- [Accès réseau des VM de développement via Tailscale](#decision-reseau-tailscale-vm-dev)
|
||||
|
||||
### 3. Backend
|
||||
|
||||
<a id="section-backend"></a>
|
||||
|
||||
- [Le back-end est un logiciel en production](#decision-backend-production)
|
||||
- [Contrats d’API explicites et versionnés](#decision-contrats-api)
|
||||
- [Gestion standard des erreurs et des statuts HTTP](#decision-erreurs-http)
|
||||
- [Migrations et évolution de schéma maîtrisées](#decision-migrations)
|
||||
- [Observabilité minimale obligatoire](#decision-observabilite)
|
||||
- [Authentification et autorisation centrales](#decision-auth-central)
|
||||
- [Idempotence et gestion des retries](#decision-idempotence-retries)
|
||||
- [Structure Docker et données persistantes](#decision-structure-docker)
|
||||
- [Accès réseau des VM de développement via Tailscale](#decision-reseau-tailscale-vm-dev)
|
||||
|
||||
### 4. n8n
|
||||
|
||||
<a id="section-n8n"></a>
|
||||
|
||||
- [Workflows n8n complexes = mini-systèmes](#decision-n8n-mini-systemes)
|
||||
|
||||
---
|
||||
|
||||
## _Format standard d’une décision_
|
||||
|
||||
## <Titre>
|
||||
|
||||
- Date : YYYY-MM-DD
|
||||
- Statut : Proposed | Accepted | Deprecated
|
||||
- Périmètre : n8n | backend | infra | global
|
||||
|
||||
### Contexte
|
||||
|
||||
### Options envisagées
|
||||
|
||||
### Décision
|
||||
|
||||
### Justification
|
||||
|
||||
### Conséquences
|
||||
|
||||
---
|
||||
|
||||
## 1. Global
|
||||
|
||||
<a id="decision-story-sizing-foundations"></a>
|
||||
|
||||
## Story sizing — foundations bloquantes vs qualité non bloquante (CI mobile)
|
||||
|
||||
- Date : 2026-03-09
|
||||
- Statut : Accepted
|
||||
- Périmètre : global
|
||||
|
||||
### Contexte
|
||||
|
||||
Des items “infra” ont été mis dans des stories foundations d’epic alors qu’aucune story métier suivante n’en dépendait (ex : CI Maestro mobile iOS/Android).
|
||||
Résultat observé : story foundations “never-done”, friction et coût de contexte, sans bénéfice sur le throughput métier.
|
||||
|
||||
### Options envisagées
|
||||
|
||||
- Mettre toutes les améliorations de qualité dans foundations “par principe”
|
||||
- Séparer les prérequis réellement bloquants du reste (qualité non bloquante)
|
||||
|
||||
### Décision
|
||||
|
||||
On distingue explicitement :
|
||||
|
||||
- **Prérequis bloquants** : à inclure dans foundations (les stories suivantes en dépendent)
|
||||
- **Qualité non bloquante** : story indépendante, en parallèle ou après, sans bloquer le métier
|
||||
|
||||
### Justification
|
||||
|
||||
- Un epic doit pouvoir avancer sur le métier dès que les dépendances techniques minimales sont là
|
||||
- Les chantiers “qualité” (CI mobile, perf, audits…) ont souvent une inertie qui ne doit pas geler l’epic
|
||||
|
||||
### Conséquences
|
||||
|
||||
- Pour chaque AC “infra” en foundations : poser la question “la story X+1 est-elle bloquée si ce n’est pas fait ?”
|
||||
- Si la réponse est non : sortir l’AC en story dédiée (tag qualité / infra), et la planifier à part
|
||||
|
||||
---
|
||||
|
||||
<a id="decision-code-review-adversariale"></a>
|
||||
|
||||
## Code review adversariale — passe dédiée en contexte frais
|
||||
|
||||
- Date : 2026-03-09
|
||||
- Statut : Accepted
|
||||
- Périmètre : global
|
||||
|
||||
### Contexte
|
||||
|
||||
Certaines issues CRITICAL sont invisibles dans le contexte d’implémentation (biais de confirmation : “je sais comment c’est censé marcher”).
|
||||
Elles émergent uniquement en lecture froide : fondations manquantes, usages dépréciés, invariants non respectés (ex : sessions sans TTL).
|
||||
|
||||
### Options envisagées
|
||||
|
||||
- Review “au fil de l’eau” dans le même contexte que l’implémentation
|
||||
- Review dédiée, séparée, en contexte frais (nouvelle session / nouveau modèle / reviewer différent)
|
||||
|
||||
### Décision
|
||||
|
||||
La code review doit être une passe **séparée** de l’implémentation, en **contexte frais**, avec un objectif explicite : chercher des CRITICAL sans concession.
|
||||
|
||||
### Justification
|
||||
|
||||
- Les incohérences systémiques (sécurité, idempotence, TTL, drift de contracts) se détectent mieux en lecture froide
|
||||
- Réduit fortement le coût de debug tardif (prod/staging) et les refactors “de fondations”
|
||||
|
||||
### Conséquences
|
||||
|
||||
- Process recommandé :
|
||||
1. Dev termine l’implémentation, marque la story `review`
|
||||
2. Nouvelle session (contexte frais) : charger uniquement story + diff
|
||||
3. Review adversariale : lister CRITICAL + mitigations
|
||||
4. Corriger avant `done`
|
||||
|
||||
---
|
||||
|
||||
<a id="decision-frontend-production"></a>
|
||||
|
||||
## Le front-end est un logiciel en production
|
||||
|
||||
- Date : 2026-01-25
|
||||
- Statut : Accepted
|
||||
- Périmètre : global
|
||||
|
||||
### Contexte
|
||||
|
||||
Les applications front-end modernes (SPA, webapps) portent une part significative
|
||||
de la logique applicative : state, validations, permissions, erreurs, performance,
|
||||
sécurité côté client, expérience utilisateur.
|
||||
|
||||
Les traiter comme une simple “couche UI” conduit régulièrement à :
|
||||
|
||||
- une dette technique rapide,
|
||||
- des bugs difficiles à diagnostiquer,
|
||||
- une expérience utilisateur incohérente,
|
||||
- une maintenance coûteuse dans le temps.
|
||||
|
||||
### Options envisagées
|
||||
|
||||
- Traiter le front-end comme une couche de rendu légère, peu structurée
|
||||
- Traiter le front-end comme un logiciel à part entière, avec des exigences similaires au backend
|
||||
|
||||
### Décision
|
||||
|
||||
Le front-end est traité comme un **logiciel en production**.
|
||||
|
||||
Il est soumis aux mêmes principes que le backend :
|
||||
|
||||
- architecture explicite,
|
||||
- gestion des erreurs,
|
||||
- conventions de code,
|
||||
- tests au bon niveau,
|
||||
- attention portée à la maintenabilité et à l’évolution.
|
||||
|
||||
### Justification
|
||||
|
||||
- Le front concentre une logique métier et technique réelle
|
||||
- Les bugs front ont un impact direct sur les utilisateurs
|
||||
- La complexité augmente mécaniquement avec le produit
|
||||
- Les choix initiaux conditionnent fortement la maintenabilité future
|
||||
|
||||
### Conséquences
|
||||
|
||||
- Mise en place de patterns front validés et documentés
|
||||
- Gestion explicite des états UI, erreurs et formulaires
|
||||
- Attention portée à la performance et à l’accessibilité
|
||||
- Acceptation d’un léger surcoût initial pour réduire la dette long terme
|
||||
|
||||
---
|
||||
|
||||
<a id="decision-contrats-sso-zod"></a>
|
||||
|
||||
## Single source of truth des contrats — schémas runtime partagés (Zod) + z.infer (No-DTO)
|
||||
|
||||
- Date : 2026-03-10
|
||||
- Statut : Proposed
|
||||
- Périmètre : global
|
||||
|
||||
### Contexte
|
||||
|
||||
TypeScript ne valide pas les payloads HTTP au runtime (les types disparaissent à l’exécution).
|
||||
Quand on maintient à la fois des DTO/back (ex. Nest + decorators) et des types/contracts côté clients, on obtient une double source de vérité → drift, régressions, temps de debug.
|
||||
|
||||
### Options envisagées
|
||||
|
||||
- DTO NestJS + `class-validator` (validation runtime OK côté API, mais non partageable tel quel côté clients → duplication ou génération)
|
||||
- Schémas runtime partagés dans un package `contracts` + types dérivés (validation + types alignés)
|
||||
- OpenAPI/JSON Schema “first” + codegen (artefact contractuel unique, mais pipeline/outillage à maintenir)
|
||||
|
||||
### Décision
|
||||
|
||||
La source de vérité des contrats API↔clients est un package `contracts` contenant des **schémas runtime** (Zod), et les types TypeScript sont **dérivés** via `z.infer` (No-DTO / pas de redéfinition locale).
|
||||
|
||||
Principe opérationnel :
|
||||
|
||||
- validation concentrée aux frontières (ex. pipe/guard de validation côté API),
|
||||
- le reste du code consomme des **types** (et non des classes DTO redondantes),
|
||||
- les contrats restent “wire-level” (pas de métier, pas de stores, pas de classes Nest).
|
||||
|
||||
### Justification
|
||||
|
||||
- Un seul artefact sert à la fois de validation runtime et de typage compile-time
|
||||
- Propagation des changements plus fiable (compile + tests) → réduction du temps de debug
|
||||
|
||||
### Conséquences
|
||||
|
||||
- Dépendance assumée à Zod dans `contracts`
|
||||
- Les clients consomment les types; la validation côté client reste optionnelle (utile surtout sur entrées non-API : storage, deeplinks, etc.)
|
||||
- En contexte non-mobile (Nest/React), OpenAPI-first + codegen reste une alternative valide si multi-clients/externe ou besoin fort de contrat public/documenté
|
||||
|
||||
---
|
||||
|
||||
<a id="decision-user-views"></a>
|
||||
|
||||
## User views — User public par défaut + MeUser explicite
|
||||
|
||||
- Date : 2026-03-10
|
||||
- Statut : Proposed
|
||||
- Périmètre : global
|
||||
|
||||
### Contexte
|
||||
|
||||
Un “User” complet est rarement un bon contrat universel : il peut contenir des champs sensibles (email, adresse, téléphone, flags internes…).
|
||||
Les dérivations TypeScript seules (`Omit<User, "password">`) ne protègent pas au runtime et favorisent les fuites accidentelles.
|
||||
|
||||
### Options envisagées
|
||||
|
||||
- Un type `User` “god object” + dérivations TS (Omit/Pick) au cas par cas
|
||||
- Des “views” explicites par contexte (public, self, admin…), dérivées depuis les schémas runtime
|
||||
- Un modèle unique côté DB + sérialisation implicite (risque élevé de fuite)
|
||||
|
||||
### Décision
|
||||
|
||||
`User` (dans les contracts) est **public par défaut** et minimal (safe-by-default).
|
||||
La vue self/compte est un contrat séparé `MeUser`.
|
||||
Toute vue plus riche est créée explicitement et nommée (ex. `AdminUser`, `UserDirectoryEntry`).
|
||||
|
||||
Règles associées :
|
||||
|
||||
- les vues sont dérivées depuis les schémas runtime (extend/pick/omit) + `z.infer`
|
||||
- `password` (et assimilés) n’existe que dans des requêtes d’auth (login/register/reset/change), jamais dans un `User*`
|
||||
|
||||
### Justification
|
||||
|
||||
- Réduit le risque de fuite de données et clarifie les permissions/UX
|
||||
- Évite les champs optionnels ambigus et les contrats “implicites”
|
||||
|
||||
### Conséquences
|
||||
|
||||
- Chaque endpoint choisit explicitement la vue renvoyée (et côté DB, un `select` explicite par vue)
|
||||
- Les clients typent “public” vs “mon compte” distinctement
|
||||
- Des tests “no secret keys” sur réponses user/auth deviennent simples et efficaces
|
||||
|
||||
---
|
||||
|
||||
## 2. Infra
|
||||
|
||||
<a id="decision-structure-docker"></a>
|
||||
|
||||
## Convention de structure pour les projets Docker et les données persistantes
|
||||
|
||||
- Date : 2026-03-06
|
||||
- Statut : Accepted
|
||||
- Périmètre : infra
|
||||
|
||||
### Contexte
|
||||
|
||||
Sur un serveur de développement (NUC / VM docker-dev), Docker mélange facilement
|
||||
code applicatif, données persistantes, volumes et backups si aucune convention
|
||||
n’est définie.
|
||||
|
||||
Avec le temps cela rend :
|
||||
|
||||
- les sauvegardes ambiguës
|
||||
- le nettoyage risqué
|
||||
- la compréhension de l’infrastructure difficile
|
||||
- la reconstruction d’un environnement compliquée
|
||||
|
||||
Une structure simple et explicite permet d’éviter ces problèmes.
|
||||
|
||||
### Options envisagées
|
||||
|
||||
- Laisser Docker gérer implicitement les volumes (`/var/lib/docker/volumes`)
|
||||
- Laisser chaque projet organiser librement ses dossiers
|
||||
- Définir une convention globale claire séparant code, données et sauvegardes
|
||||
|
||||
### Décision
|
||||
|
||||
La structure standard suivante est adoptée sur les machines d’infrastructure
|
||||
(NUC / serveurs de développement) :
|
||||
|
||||
```
|
||||
/srv
|
||||
├ projects
|
||||
├ docker-data
|
||||
└ backups
|
||||
```
|
||||
|
||||
Principes :
|
||||
|
||||
- `/srv/projects`
|
||||
contient les projets applicatifs (code, `docker-compose.yml`, `.env`, scripts).
|
||||
|
||||
- `/srv/docker-data`
|
||||
contient les données persistantes des conteneurs (bases de données, uploads,
|
||||
état applicatif).
|
||||
|
||||
- `/srv/backups`
|
||||
contient les dumps, archives et exports destinés à la sauvegarde.
|
||||
|
||||
### Justification
|
||||
|
||||
- séparation claire **code / données / sauvegardes**
|
||||
- sauvegardes plus simples et plus fiables
|
||||
- nettoyage d’un projet possible sans risque pour les autres
|
||||
- lisibilité immédiate de l’infrastructure
|
||||
- reproductibilité de l’environnement
|
||||
|
||||
### Conséquences
|
||||
|
||||
Les conteneurs utilisent en priorité des **bind mounts explicites**, par exemple :
|
||||
|
||||
```txt
|
||||
/srv/docker-data/monapp-postgres:/var/lib/postgresql/data
|
||||
```
|
||||
|
||||
Les volumes Docker implicites (`/var/lib/docker/volumes`) sont évités quand la
|
||||
lisibilité et la maintenabilité priment.
|
||||
|
||||
Les données critiques à sauvegarder se trouvent principalement dans :
|
||||
|
||||
```
|
||||
/srv/projects
|
||||
/srv/docker-data
|
||||
```
|
||||
|
||||
Convention de nommage recommandée pour les dossiers de données :
|
||||
|
||||
```
|
||||
ex :
|
||||
rl799-postgres
|
||||
monapp-redis
|
||||
n8n-postgres
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -129,190 +482,7 @@ Cette convention est recommandée pour toutes les nouvelles VM du NUC, notamment
|
||||
|
||||
---
|
||||
|
||||
<a id="decision-story-sizing-foundations"></a>
|
||||
|
||||
## Story sizing — foundations bloquantes vs qualité non bloquante (CI mobile)
|
||||
|
||||
- Date : 2026-03-09
|
||||
- Statut : Accepted
|
||||
- Périmètre : global
|
||||
|
||||
### Contexte
|
||||
|
||||
Des items “infra” ont été mis dans des stories foundations d’epic alors qu’aucune story métier suivante n’en dépendait (ex : CI Maestro mobile iOS/Android).
|
||||
Résultat observé : story foundations “never-done”, friction et coût de contexte, sans bénéfice sur le throughput métier.
|
||||
|
||||
### Options envisagées
|
||||
|
||||
- Mettre toutes les améliorations de qualité dans foundations “par principe”
|
||||
- Séparer les prérequis réellement bloquants du reste (qualité non bloquante)
|
||||
|
||||
### Décision
|
||||
|
||||
On distingue explicitement :
|
||||
|
||||
- **Prérequis bloquants** : à inclure dans foundations (les stories suivantes en dépendent)
|
||||
- **Qualité non bloquante** : story indépendante, en parallèle ou après, sans bloquer le métier
|
||||
|
||||
### Justification
|
||||
|
||||
- Un epic doit pouvoir avancer sur le métier dès que les dépendances techniques minimales sont là
|
||||
- Les chantiers “qualité” (CI mobile, perf, audits…) ont souvent une inertie qui ne doit pas geler l’epic
|
||||
|
||||
### Conséquences
|
||||
|
||||
- Pour chaque AC “infra” en foundations : poser la question “la story X+1 est-elle bloquée si ce n’est pas fait ?”
|
||||
- Si la réponse est non : sortir l’AC en story dédiée (tag qualité / infra), et la planifier à part
|
||||
|
||||
---
|
||||
|
||||
<a id="decision-code-review-adversariale"></a>
|
||||
|
||||
## Code review adversariale — passe dédiée en contexte frais
|
||||
|
||||
- Date : 2026-03-09
|
||||
- Statut : Accepted
|
||||
- Périmètre : global
|
||||
|
||||
### Contexte
|
||||
|
||||
Certaines issues CRITICAL sont invisibles dans le contexte d’implémentation (biais de confirmation : “je sais comment c’est censé marcher”).
|
||||
Elles émergent uniquement en lecture froide : fondations manquantes, usages dépréciés, invariants non respectés (ex : sessions sans TTL).
|
||||
|
||||
### Options envisagées
|
||||
|
||||
- Review “au fil de l’eau” dans le même contexte que l’implémentation
|
||||
- Review dédiée, séparée, en contexte frais (nouvelle session / nouveau modèle / reviewer différent)
|
||||
|
||||
### Décision
|
||||
|
||||
La code review doit être une passe **séparée** de l’implémentation, en **contexte frais**, avec un objectif explicite : chercher des CRITICAL sans concession.
|
||||
|
||||
### Justification
|
||||
|
||||
- Les incohérences systémiques (sécurité, idempotence, TTL, drift de contracts) se détectent mieux en lecture froide
|
||||
- Réduit fortement le coût de debug tardif (prod/staging) et les refactors “de fondations”
|
||||
|
||||
### Conséquences
|
||||
|
||||
- Process recommandé :
|
||||
1. Dev termine l’implémentation, marque la story `review`
|
||||
2. Nouvelle session (contexte frais) : charger uniquement story + diff
|
||||
3. Review adversariale : lister CRITICAL + mitigations
|
||||
4. Corriger avant `done`
|
||||
|
||||
---
|
||||
|
||||
## Format standard d’une décision
|
||||
|
||||
## <Titre>
|
||||
|
||||
- Date : YYYY-MM-DD
|
||||
- Statut : Proposed | Accepted | Deprecated
|
||||
- Périmètre : n8n | backend | infra | global
|
||||
|
||||
### Contexte
|
||||
|
||||
### Options envisagées
|
||||
|
||||
### Décision
|
||||
|
||||
### Justification
|
||||
|
||||
### Conséquences
|
||||
|
||||
---
|
||||
|
||||
<a id="decision-n8n-mini-systemes"></a>
|
||||
|
||||
## Workflows n8n complexes = mini-systèmes
|
||||
|
||||
- Date : 2025-12-19
|
||||
- Statut : Accepted
|
||||
- Périmètre : n8n
|
||||
|
||||
### Contexte
|
||||
|
||||
Certains workflows n8n dépassent le simple enchaînement de nodes et deviennent
|
||||
de véritables systèmes applicatifs (orchestration, état, branches, retries, intégrations multiples).
|
||||
|
||||
### Options envisagées
|
||||
|
||||
- Traiter ces workflows comme de simples automatisations “low-code”
|
||||
- Les considérer comme du code à part entière (discipline, patterns, doc, prudence sur upgrades)
|
||||
|
||||
### Décision
|
||||
|
||||
Les considérer comme du code.
|
||||
|
||||
### Justification
|
||||
|
||||
- Complexité réelle (logique, état, orchestration)
|
||||
- Sensibilité aux versions n8n (upgrade-risk)
|
||||
- Besoin de maintenabilité et de capitalisation
|
||||
|
||||
### Conséquences
|
||||
|
||||
- Prudence accrue lors des upgrades
|
||||
- Documentation minimale mais ciblée
|
||||
- Patterns explicitement identifiés et partagés
|
||||
- On accepte d’utiliser du Code (JS) quand nécessaire
|
||||
|
||||
---
|
||||
|
||||
<a id="decision-frontend-production"></a>
|
||||
|
||||
## Le front-end est un logiciel en production
|
||||
|
||||
- Date : 2026-01-25
|
||||
- Statut : Accepted
|
||||
- Périmètre : global
|
||||
|
||||
### Contexte
|
||||
|
||||
Les applications front-end modernes (SPA, webapps) portent une part significative
|
||||
de la logique applicative : state, validations, permissions, erreurs, performance,
|
||||
sécurité côté client, expérience utilisateur.
|
||||
|
||||
Les traiter comme une simple “couche UI” conduit régulièrement à :
|
||||
|
||||
- une dette technique rapide,
|
||||
- des bugs difficiles à diagnostiquer,
|
||||
- une expérience utilisateur incohérente,
|
||||
- une maintenance coûteuse dans le temps.
|
||||
|
||||
### Options envisagées
|
||||
|
||||
- Traiter le front-end comme une couche de rendu légère, peu structurée
|
||||
- Traiter le front-end comme un logiciel à part entière, avec des exigences similaires au backend
|
||||
|
||||
### Décision
|
||||
|
||||
Le front-end est traité comme un **logiciel en production**.
|
||||
|
||||
Il est soumis aux mêmes principes que le backend :
|
||||
|
||||
- architecture explicite,
|
||||
- gestion des erreurs,
|
||||
- conventions de code,
|
||||
- tests au bon niveau,
|
||||
- attention portée à la maintenabilité et à l’évolution.
|
||||
|
||||
### Justification
|
||||
|
||||
- Le front concentre une logique métier et technique réelle
|
||||
- Les bugs front ont un impact direct sur les utilisateurs
|
||||
- La complexité augmente mécaniquement avec le produit
|
||||
- Les choix initiaux conditionnent fortement la maintenabilité future
|
||||
|
||||
### Conséquences
|
||||
|
||||
- Mise en place de patterns front validés et documentés
|
||||
- Gestion explicite des états UI, erreurs et formulaires
|
||||
- Attention portée à la performance et à l’accessibilité
|
||||
- Acceptation d’un léger surcoût initial pour réduire la dette long terme
|
||||
|
||||
---
|
||||
## 3. Backend
|
||||
|
||||
<a id="decision-backend-production"></a>
|
||||
|
||||
@@ -407,91 +577,6 @@ Minimum attendu :
|
||||
|
||||
---
|
||||
|
||||
<a id="decision-contrats-sso-zod"></a>
|
||||
|
||||
## Single source of truth des contrats — schémas runtime partagés (Zod) + z.infer (No-DTO)
|
||||
|
||||
- Date : 2026-03-10
|
||||
- Statut : Proposed
|
||||
- Périmètre : global
|
||||
|
||||
### Contexte
|
||||
|
||||
TypeScript ne valide pas les payloads HTTP au runtime (les types disparaissent à l’exécution).
|
||||
Quand on maintient à la fois des DTO/back (ex. Nest + decorators) et des types/contracts côté clients, on obtient une double source de vérité → drift, régressions, temps de debug.
|
||||
|
||||
### Options envisagées
|
||||
|
||||
- DTO NestJS + `class-validator` (validation runtime OK côté API, mais non partageable tel quel côté clients → duplication ou génération)
|
||||
- Schémas runtime partagés dans un package `contracts` + types dérivés (validation + types alignés)
|
||||
- OpenAPI/JSON Schema “first” + codegen (artefact contractuel unique, mais pipeline/outillage à maintenir)
|
||||
|
||||
### Décision
|
||||
|
||||
La source de vérité des contrats API↔clients est un package `contracts` contenant des **schémas runtime** (Zod), et les types TypeScript sont **dérivés** via `z.infer` (No-DTO / pas de redéfinition locale).
|
||||
|
||||
Principe opérationnel :
|
||||
|
||||
- validation concentrée aux frontières (ex. pipe/guard de validation côté API),
|
||||
- le reste du code consomme des **types** (et non des classes DTO redondantes),
|
||||
- les contrats restent “wire-level” (pas de métier, pas de stores, pas de classes Nest).
|
||||
|
||||
### Justification
|
||||
|
||||
- Un seul artefact sert à la fois de validation runtime et de typage compile-time
|
||||
- Propagation des changements plus fiable (compile + tests) → réduction du temps de debug
|
||||
|
||||
### Conséquences
|
||||
|
||||
- Dépendance assumée à Zod dans `contracts`
|
||||
- Les clients consomment les types; la validation côté client reste optionnelle (utile surtout sur entrées non-API : storage, deeplinks, etc.)
|
||||
- En contexte non-mobile (Nest/React), OpenAPI-first + codegen reste une alternative valide si multi-clients/externe ou besoin fort de contrat public/documenté
|
||||
|
||||
---
|
||||
|
||||
<a id="decision-user-views"></a>
|
||||
|
||||
## User views — User public par défaut + MeUser explicite
|
||||
|
||||
- Date : 2026-03-10
|
||||
- Statut : Proposed
|
||||
- Périmètre : global
|
||||
|
||||
### Contexte
|
||||
|
||||
Un “User” complet est rarement un bon contrat universel : il peut contenir des champs sensibles (email, adresse, téléphone, flags internes…).
|
||||
Les dérivations TypeScript seules (`Omit<User, "password">`) ne protègent pas au runtime et favorisent les fuites accidentelles.
|
||||
|
||||
### Options envisagées
|
||||
|
||||
- Un type `User` “god object” + dérivations TS (Omit/Pick) au cas par cas
|
||||
- Des “views” explicites par contexte (public, self, admin…), dérivées depuis les schémas runtime
|
||||
- Un modèle unique côté DB + sérialisation implicite (risque élevé de fuite)
|
||||
|
||||
### Décision
|
||||
|
||||
`User` (dans les contracts) est **public par défaut** et minimal (safe-by-default).
|
||||
La vue self/compte est un contrat séparé `MeUser`.
|
||||
Toute vue plus riche est créée explicitement et nommée (ex. `AdminUser`, `UserDirectoryEntry`).
|
||||
|
||||
Règles associées :
|
||||
|
||||
- les vues sont dérivées depuis les schémas runtime (extend/pick/omit) + `z.infer`
|
||||
- `password` (et assimilés) n’existe que dans des requêtes d’auth (login/register/reset/change), jamais dans un `User*`
|
||||
|
||||
### Justification
|
||||
|
||||
- Réduit le risque de fuite de données et clarifie les permissions/UX
|
||||
- Évite les champs optionnels ambigus et les contrats “implicites”
|
||||
|
||||
### Conséquences
|
||||
|
||||
- Chaque endpoint choisit explicitement la vue renvoyée (et côté DB, un `select` explicite par vue)
|
||||
- Les clients typent “public” vs “mon compte” distinctement
|
||||
- Des tests “no secret keys” sur réponses user/auth deviennent simples et efficaces
|
||||
|
||||
---
|
||||
|
||||
<a id="decision-erreurs-http"></a>
|
||||
|
||||
## Gestion standard des erreurs et des statuts HTTP
|
||||
@@ -703,92 +788,41 @@ Principes :
|
||||
|
||||
---
|
||||
|
||||
<a id="decision-structure-docker"></a>
|
||||
## 4. n8n
|
||||
|
||||
## Convention de structure pour les projets Docker et les données persistantes
|
||||
<a id="decision-n8n-mini-systemes"></a>
|
||||
|
||||
- Date : 2026-03-06
|
||||
## Workflows n8n complexes = mini-systèmes
|
||||
|
||||
- Date : 2025-12-19
|
||||
- Statut : Accepted
|
||||
- Périmètre : infra
|
||||
- Périmètre : n8n
|
||||
|
||||
### Contexte
|
||||
|
||||
Sur un serveur de développement (NUC / VM docker-dev), Docker mélange facilement
|
||||
code applicatif, données persistantes, volumes et backups si aucune convention
|
||||
n’est définie.
|
||||
|
||||
Avec le temps cela rend :
|
||||
|
||||
- les sauvegardes ambiguës
|
||||
- le nettoyage risqué
|
||||
- la compréhension de l’infrastructure difficile
|
||||
- la reconstruction d’un environnement compliquée
|
||||
|
||||
Une structure simple et explicite permet d’éviter ces problèmes.
|
||||
Certains workflows n8n dépassent le simple enchaînement de nodes et deviennent
|
||||
de véritables systèmes applicatifs (orchestration, état, branches, retries, intégrations multiples).
|
||||
|
||||
### Options envisagées
|
||||
|
||||
- Laisser Docker gérer implicitement les volumes (`/var/lib/docker/volumes`)
|
||||
- Laisser chaque projet organiser librement ses dossiers
|
||||
- Définir une convention globale claire séparant code, données et sauvegardes
|
||||
- Traiter ces workflows comme de simples automatisations “low-code”
|
||||
- Les considérer comme du code à part entière (discipline, patterns, doc, prudence sur upgrades)
|
||||
|
||||
### Décision
|
||||
|
||||
La structure standard suivante est adoptée sur les machines d’infrastructure
|
||||
(NUC / serveurs de développement) :
|
||||
|
||||
```
|
||||
/srv
|
||||
├ projects
|
||||
├ docker-data
|
||||
└ backups
|
||||
```
|
||||
|
||||
Principes :
|
||||
|
||||
- `/srv/projects`
|
||||
contient les projets applicatifs (code, `docker-compose.yml`, `.env`, scripts).
|
||||
|
||||
- `/srv/docker-data`
|
||||
contient les données persistantes des conteneurs (bases de données, uploads,
|
||||
état applicatif).
|
||||
|
||||
- `/srv/backups`
|
||||
contient les dumps, archives et exports destinés à la sauvegarde.
|
||||
Les considérer comme du code.
|
||||
|
||||
### Justification
|
||||
|
||||
- séparation claire **code / données / sauvegardes**
|
||||
- sauvegardes plus simples et plus fiables
|
||||
- nettoyage d’un projet possible sans risque pour les autres
|
||||
- lisibilité immédiate de l’infrastructure
|
||||
- reproductibilité de l’environnement
|
||||
- Complexité réelle (logique, état, orchestration)
|
||||
- Sensibilité aux versions n8n (upgrade-risk)
|
||||
- Besoin de maintenabilité et de capitalisation
|
||||
|
||||
### Conséquences
|
||||
|
||||
Les conteneurs utilisent en priorité des **bind mounts explicites**, par exemple :
|
||||
|
||||
```txt
|
||||
/srv/docker-data/monapp-postgres:/var/lib/postgresql/data
|
||||
```
|
||||
|
||||
Les volumes Docker implicites (`/var/lib/docker/volumes`) sont évités quand la
|
||||
lisibilité et la maintenabilité priment.
|
||||
|
||||
Les données critiques à sauvegarder se trouvent principalement dans :
|
||||
|
||||
```
|
||||
/srv/projects
|
||||
/srv/docker-data
|
||||
```
|
||||
|
||||
Convention de nommage recommandée pour les dossiers de données :
|
||||
|
||||
```
|
||||
ex :
|
||||
rl799-postgres
|
||||
monapp-redis
|
||||
n8n-postgres
|
||||
```
|
||||
- Prudence accrue lors des upgrades
|
||||
- Documentation minimale mais ciblée
|
||||
- Patterns explicitement identifiés et partagés
|
||||
- On accepte d’utiliser du Code (JS) quand nécessaire
|
||||
|
||||
---
|
||||
|
||||
@@ -93,3 +93,48 @@ Deux processus ont réécrit le même fichier sans coordination, le second
|
||||
- éviter deux passes d’écriture concurrentes sur le même fichier
|
||||
- relire le diff immédiatement après toute passe automatique
|
||||
- privilégier une séquence stricte : édition, puis lint/format, puis vérification
|
||||
|
||||
---
|
||||
|
||||
## tsx + NestJS : injection par type cassée silencieusement
|
||||
|
||||
### Contexte
|
||||
|
||||
Projet `app-alexandrie`, Epic 3, le 10-03-2026.
|
||||
Le backend NestJS tournait avec `tsx watch` dans un contexte ESM (`module: nodenext`), notamment pour rester compatible avec Prisma v7.
|
||||
|
||||
### Symptômes
|
||||
|
||||
- `TypeError: Cannot read properties of undefined (reading 'get')` dans le constructeur d’un service
|
||||
- `ConfigService` injecté par type mais `undefined` au runtime
|
||||
- `@Injectable()` et `ConfigModule` correctement configurés, sans erreur de compilation
|
||||
|
||||
### Cause probable
|
||||
|
||||
`tsx` repose sur esbuild pour transpiler TypeScript.
|
||||
Dans ce contexte, `emitDecoratorMetadata` est ignoré même s’il est activé dans `tsconfig.json`.
|
||||
NestJS ne peut donc plus résoudre correctement certaines injections par type, notamment `constructor(private readonly config: ConfigService)`.
|
||||
|
||||
### Correctif / règle à retenir
|
||||
|
||||
- ne pas supposer que `emitDecoratorMetadata` fonctionne avec `tsx`
|
||||
- dans ce contexte, éviter l’injection par type de `ConfigService` pour les services d’infra
|
||||
- lire explicitement les variables via `process.env`, après chargement amont de `ConfigModule.forRoot()`
|
||||
|
||||
Exemple :
|
||||
|
||||
```typescript
|
||||
// AVANT
|
||||
constructor(private readonly config: ConfigService) {
|
||||
const host = this.config.get('REDIS_HOST');
|
||||
}
|
||||
|
||||
// APRES
|
||||
constructor() {
|
||||
const host = process.env['REDIS_HOST'] ?? 'localhost';
|
||||
}
|
||||
```
|
||||
|
||||
### Alternative écartée
|
||||
|
||||
`nest start --watch` a été testé mais a introduit des conflits ESM/CJS dans ce contexte (`exports is not defined`).
|
||||
|
||||
@@ -55,17 +55,13 @@ FILE_UPDATE_PROPOSAL
|
||||
Fichier cible : 10_backend_patterns_valides.md
|
||||
|
||||
Pourquoi :
|
||||
Ordre d'enregistrement des Guards NestJS causant un bug
|
||||
`request.user undefined` observé à plusieurs reprises.
|
||||
Pattern réutilisable validé sur un projet réel.
|
||||
|
||||
Proposition :
|
||||
|
||||
## Ordre des Guards NestJS
|
||||
## Nom du pattern
|
||||
|
||||
Toujours enregistrer `AuthGuard` avant les Guards dépendants
|
||||
(`EmailVerifiedGuard`, `RoleGuard`, etc.).
|
||||
|
||||
Sinon `request.user` peut être undefined dans les guards suivants.
|
||||
Description courte, factuelle, orientée réutilisation.
|
||||
```
|
||||
|
||||
---
|
||||
@@ -104,129 +100,8 @@ Pour une feature de progression minimum viable :
|
||||
FILE_UPDATE_PROPOSAL
|
||||
Fichier cible : 10_frontend_patterns_valides.md
|
||||
|
||||
Pourquoi :
|
||||
Pattern frontend/mobile utile pour brancher un écran récapitulatif serveur sans dupliquer la logique réseau dans l'écran et sans navigation impérative via `store.getState()`.
|
||||
|
||||
Proposition :
|
||||
|
||||
## Écran récapitulatif branché sur store de domaine existant
|
||||
|
||||
Quand une vue récapitulative dépend d'un domaine déjà présent :
|
||||
- étendre le `service` et le `store` existants au lieu de créer un nouveau domaine artificiel
|
||||
- laisser l'écran purement déclaratif avec états `loading / error / empty / nominal`
|
||||
- déclencher le chargement via `useEffect` et actions du store
|
||||
- réserver `refresh` à une action idempotente du store
|
||||
- ajouter le point d'entrée depuis l'écran parent naturel (`library`, `profile`, etc.) au lieu de refondre la navigation globale
|
||||
|
||||
Réduit les régressions et garde le comportement async testable.
|
||||
|
||||
---
|
||||
|
||||
2026-03-10 — app-alexandrie
|
||||
|
||||
FILE_UPDATE_PROPOSAL
|
||||
Fichier cible : 10_frontend_patterns_valides.md
|
||||
|
||||
Pourquoi :
|
||||
Le pull-to-refresh mobile sur listes paginées a besoin d’une vraie idempotence côté store, sinon on crée des doublons et des courses réseau invisibles. Le pattern a été validé ici avec Zustand sans introduire de lib de cache serveur.
|
||||
|
||||
Proposition :
|
||||
Pattern "Refresh idempotent sur store de liste paginée" : conserver une promesse de refresh en vol partagée, refuser les refresh concurrents, remplacer atomiquement la liste à la fin du refresh, et dédupliquer les items par identifiant au merge des pages suivantes.
|
||||
|
||||
---
|
||||
|
||||
2026-03-10 — app-alexandrie
|
||||
|
||||
FILE_UPDATE_PROPOSAL
|
||||
Fichier cible : 10_frontend_risques_et_vigilance.md
|
||||
|
||||
Pourquoi :
|
||||
Un écran gated par des droits distants peut tomber dans un faux état `loading` infini si l’erreur de chargement des entitlements laisse les données à `null` et relance automatiquement l’effet. Le défaut a été détecté en review sur la Bibliothèque mobile.
|
||||
|
||||
Proposition :
|
||||
Risque "Loading infini sur écran gated" : si un écran dépend d’un store d’entitlements ou d’autorisations, distinguer explicitement `loading`, `error`, `ready`, et bloquer les retries automatiques en boucle après erreur tant qu’un retry utilisateur ou une nouvelle condition d’entrée n’a pas eu lieu.
|
||||
|
||||
# Rôle dans l'architecture
|
||||
|
||||
```
|
||||
Projet
|
||||
↓
|
||||
Proposition
|
||||
↓
|
||||
95_a_capitaliser.md
|
||||
↓
|
||||
Validation humaine
|
||||
↓
|
||||
Lead_tech
|
||||
```
|
||||
|
||||
Ce mécanisme permet :
|
||||
|
||||
- d'éviter la pollution de la base de connaissance
|
||||
- de capitaliser progressivement l'expérience des projets
|
||||
- de garder `Lead_tech` cohérent et fiable.
|
||||
|
||||
---
|
||||
|
||||
2026-03-10 — app-alexandrie
|
||||
|
||||
FILE_UPDATE_PROPOSAL
|
||||
Fichier cible : 10_backend_patterns_valides.md
|
||||
|
||||
Pourquoi :
|
||||
Quand une story métier demande un pouvoir interne limité, ajouter un rôle minimal persistant + un guard dédié permet de sécuriser l’authz sans ouvrir prématurément un RBAC complet. Le pattern a été validé ici pour la publication admin de contenus.
|
||||
|
||||
Proposition :
|
||||
Pattern "Autorisation interne minimale sans RBAC complet" : introduire un enum de rôle simple côté source de vérité backend (ex. `USER | ADMIN`), le propager dans la session/auth, puis créer un décorateur + guard dédiés à la capacité sensible. Éviter les booléens front, les emails hardcodés et les `if` dispersés dans les contrôleurs.
|
||||
|
||||
---
|
||||
|
||||
2026-03-10 — app-alexandrie
|
||||
|
||||
FILE_UPDATE_PROPOSAL
|
||||
Fichier cible : 10_frontend_patterns_valides.md
|
||||
|
||||
Pourquoi :
|
||||
Pour une capacité admin mince sur mobile, une route dédiée légère branchée sur le domaine existant et un refresh explicite du store après mutation permettent de rester testable et robuste sans lancer un back-office séparé.
|
||||
|
||||
Proposition :
|
||||
Pattern "UI admin légère sur domaine existant" : pour une action interne simple (publication, activation, modération légère), ajouter une route dédiée minimale qui réutilise le service/store métier existant, afficher le statut courant, bloquer les actions concurrentes avec un flag `isUpdating*`, et déclencher un refresh explicite des vues impactées après succès au lieu d’un `fire-and-forget`.
|
||||
|
||||
---
|
||||
2026-03-10 — app-alexandrie
|
||||
|
||||
FILE_UPDATE_PROPOSAL
|
||||
Fichier cible : 90_debug_et_postmortem.md
|
||||
|
||||
Pourquoi :
|
||||
`tsx` (esbuild) ignore `emitDecoratorMetadata` — l'injection par type NestJS est silencieusement `undefined` au runtime, même avec `emitDecoratorMetadata: true` dans tsconfig.json. Bug difficile à diagnostiquer car aucune erreur de compilation.
|
||||
|
||||
Proposition :
|
||||
|
||||
## tsx + NestJS : injection par type cassée silencieusement
|
||||
|
||||
**Contexte :** app-alexandrie, Epic 3, 2026-03-10
|
||||
|
||||
**Symptôme :** `TypeError: Cannot read properties of undefined (reading 'get')` dans le constructeur d'un service NestJS. `this.config` (de type `ConfigService`) est `undefined` malgré `@Injectable()` et `ConfigModule` correctement configuré.
|
||||
|
||||
**Cause racine :** `tsx` utilise esbuild pour transpiler TypeScript. esbuild ignore `emitDecoratorMetadata` même si activé dans tsconfig. NestJS ne peut donc pas résoudre les types pour l'injection de dépendances par type (`constructor(private config: ConfigService)`).
|
||||
|
||||
**Conditions :** runner = `tsx watch`, `module: nodenext` dans tsconfig (requis pour Prisma v7 ESM), `emitDecoratorMetadata: true` présent mais ignoré.
|
||||
|
||||
**Fix :** Remplacer l'injection `ConfigService` par `process.env['VAR']` directement dans le constructeur. `ConfigModule.forRoot` avec `isGlobal: true` charge dotenv en amont → `process.env` est peuplé avant l'instanciation des services.
|
||||
|
||||
```typescript
|
||||
// AVANT (cassé avec tsx)
|
||||
constructor(private readonly config: ConfigService) {
|
||||
const host = this.config.get('REDIS_HOST');
|
||||
}
|
||||
|
||||
// APRÈS (compatible tsx)
|
||||
constructor() {
|
||||
const host = process.env['REDIS_HOST'] ?? 'localhost';
|
||||
}
|
||||
```
|
||||
|
||||
**Alternative non retenue :** `nest start --watch` → conflits ESM/CJS avec Prisma v7 + `module: nodenext` (exports is not defined).
|
||||
|
||||
**Règle :** Dans un projet NestJS avec `tsx` comme runner, ne jamais injecter `ConfigService` par type. Toujours utiliser `process.env` directement dans les services d'infra (Redis, DB pools, etc.).
|
||||
|
||||
Reference in New Issue
Block a user