mirror of
https://github.com/MaksTinyWorkshop/_Assistant_Lead_Tech
synced 2026-04-06 21:41:42 +02:00
capitalisation: intégrer 12 entrées depuis app-alexandrie et app-template-resto
- backend/risques/nestjs : guard multi-statut READ_METHODS avant statut - backend/patterns/nestjs : fusionner lastSeenAt dans la réconciliation - backend/risques/contracts : pas de process.env dans services/helpers - backend/risques/nextjs : self-request Server Action + EXDEV atomic write - backend/risques/prisma : champ enum-like stocké en String - frontend/risques/general : Alert.prompt iOS-only - frontend/risques/tests : 3 anti-patterns (helpers copiés, test indirect, test façade) - workflow/risques/story-tracking : 2 entrées (hors périmètre, File List approximative) - skill capitalisation-triage : nouveau format de rapport (tableaux par domaine) - 95_a_capitaliser.md : purgé
This commit is contained in:
@@ -136,3 +136,32 @@ if (count !== null && count > QUOTA_MAX) {
|
||||
- [ ] Mode dégradé permissif si `count === null` (Redis down)
|
||||
- [ ] Clé nommée `{app}:quota:{action}:{userId}:{yyyy-mm-dd}` (date UTC)
|
||||
- [ ] Anti-pattern évité : `incrBy` + `setEx` séparés (race condition si count === 1 concurrent)
|
||||
|
||||
---
|
||||
|
||||
<a id="pattern-fusionner-lastseenat-reconciliation"></a>
|
||||
## Pattern : Fusionner `lastSeenAt` dans l'update de réconciliation — évite N requêtes DB par requête
|
||||
|
||||
- Objectif : éviter deux appels Prisma distincts (réconciliation + lastSeenAt) sur chaque requête authentifiée.
|
||||
- Contexte : service de réconciliation d'état de session appelé à chaque request via guard ou middleware.
|
||||
- Quand l'utiliser : dès qu'un `lastSeenAt` est mis à jour systématiquement et qu'un update conditionnel coexiste.
|
||||
- Avantage : 1 requête DB par requête authentifiée au lieu de 2.
|
||||
- Validé le : 30-03-2026
|
||||
- Contexte technique : NestJS / Prisma — app-alexandrie
|
||||
|
||||
### Implémentation (exemple minimal)
|
||||
|
||||
```typescript
|
||||
// ❌ 2 requêtes par requête authentifiée
|
||||
private async reconcileSessionStatus(session) {
|
||||
if (statusChanged) await prisma.session.update({ data: { status, graceEndsAt } });
|
||||
}
|
||||
await prisma.session.update({ data: { lastSeenAt: now } }); // 2ème update systématique
|
||||
|
||||
// ✅ 1 requête — lastSeenAt toujours inclus dans le même appel
|
||||
private async reconcileSessionStatus(session, now = new Date()) {
|
||||
await prisma.session.update({
|
||||
data: { lastSeenAt: now, ...(statusChanged && { status, graceEndsAt }) }
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
@@ -163,3 +163,27 @@ Tableau de correspondance :
|
||||
|
||||
- **Règle** : HTTP 403 = "tu n'as pas le droit d'effectuer cette action". HTTP 400 = "ta requête est mal formée".
|
||||
- Contexte technique : NestJS / HTTP — 20-03-2026
|
||||
|
||||
---
|
||||
|
||||
<a id="risque-process-env-direct-service"></a>
|
||||
## Feature flags / config : lecture directe de `process.env` dans les services ou helpers métier
|
||||
|
||||
### Risques
|
||||
|
||||
- Tests verts malgré une dépendance implicite à l'état global du process
|
||||
- La validation Zod de l'env (ConfigService) existe mais est contournée au runtime via un helper non injecté
|
||||
- Story conforme "pas de process.env direct en service métier" mais violation dans un helper utilisé par le service
|
||||
|
||||
### Symptômes
|
||||
|
||||
- `process.env.FEATURE_FLAG_X` dans un helper métier plutôt que dans un module ConfigService
|
||||
- Tests passent mais comportement diverge selon l'env du process
|
||||
|
||||
### Bonnes pratiques / mitigations
|
||||
|
||||
- Ne jamais lire `process.env` directement dans les services ni les helpers métier.
|
||||
- Injecter `ConfigService` (NestJS) et centraliser la lecture via une fonction pure recevant la config injectée.
|
||||
- **Checklist review** : rechercher `process.env` dans `src/` hors `config/` ou `main.ts` — tout hit est suspect.
|
||||
|
||||
- Contexte technique : NestJS / ConfigService — 30-03-2026
|
||||
|
||||
@@ -100,3 +100,36 @@ throw new HttpException(
|
||||
- Interdire les méthodes "cachées" consommées hors contrat
|
||||
- Tester au moins une implémentation par le contrat abstrait
|
||||
- Contexte technique : TypeScript / provider strategy — 10-03-2026
|
||||
|
||||
---
|
||||
|
||||
<a id="risque-guard-multistatut-read-methods"></a>
|
||||
## Guard multi-statut : ordre READ_METHODS avant statut et whitelist des writes critiques
|
||||
|
||||
### Risques
|
||||
|
||||
- Un guard qui vérifie le statut BLOCKED avant la méthode HTTP bloque aussi les GET, enfermant l'utilisateur sans moyen de sortir (ex : révoquer un appareil).
|
||||
- Sans whitelist explicite des writes autorisés en état bloqué, le circuit de sortie est fermé.
|
||||
|
||||
### Symptômes
|
||||
|
||||
- L'utilisateur avec `sessionStatus === 'BLOCKED'` ne peut pas accéder à `GET /auth/devices`
|
||||
- Tests passent mais l'ordre des conditions est inversé
|
||||
|
||||
### Bonnes pratiques / mitigations
|
||||
|
||||
```typescript
|
||||
// ❌ DANGEREUX — BLOCKED bloque aussi les GET
|
||||
if (user.sessionStatus === 'BLOCKED') throw new HttpException(...);
|
||||
if (READ_METHODS.has(request.method)) return true; // jamais atteint pour BLOCKED
|
||||
|
||||
// ✅ CORRECT
|
||||
if (READ_METHODS.has(request.method)) return true;
|
||||
if (isDeviceManagementPath(request.path)) return true; // whitelist writes critiques
|
||||
if (user.sessionStatus === 'BLOCKED') throw new HttpException(...);
|
||||
```
|
||||
|
||||
- **Règle** : dans tout guard multi-statut, vérifier `READ_METHODS.has(request.method)` EN PREMIER, avant tout check de statut.
|
||||
- **Règle** : définir une whitelist explicite des writes autorisés même en état bloqué.
|
||||
|
||||
- Contexte technique : NestJS / guard statut — app-alexandrie 30-03-2026
|
||||
|
||||
@@ -73,3 +73,50 @@ return buildLocalizedPath(locale, "home");
|
||||
- Retourner `null` (accès non bloqué) plutôt que de boucler
|
||||
|
||||
- Contexte technique : Next.js App Router / feature flags tenant — app-template-resto 17-03-2026
|
||||
|
||||
---
|
||||
|
||||
<a id="risque-server-action-self-request"></a>
|
||||
## Server Action : self-request / loopback HTTP vers sa propre API
|
||||
|
||||
### Risques
|
||||
|
||||
- Latence réseau inutile (aller-retour HTTP interne)
|
||||
- Fragilité sur `APP_URL` en environnement Docker (loopback non résolu)
|
||||
- Double authentification (cookies reconstitués manuellement)
|
||||
- Surface d'attaque SSRF
|
||||
|
||||
### Symptômes
|
||||
|
||||
- `fetch(process.env.APP_URL + '/api/...')` dans une Server Action
|
||||
- Reconstitution manuelle de cookies de session dans l'appel
|
||||
|
||||
### Bonnes pratiques / mitigations
|
||||
|
||||
- Une Server Action peut importer et appeler directement des modules marqués `server-only` sans `fetch` interne — les deux s'exécutent côté serveur.
|
||||
- **Règle** : si tu te retrouves à reconstituer des cookies de session pour faire un `fetch` vers ta propre API depuis une Server Action, c'est le signal que l'appel doit être direct.
|
||||
|
||||
- Contexte technique : Next.js App Router / Server Actions — app-template-resto 31-03-2026
|
||||
|
||||
---
|
||||
|
||||
<a id="risque-exdev-atomic-write"></a>
|
||||
## Écriture atomique (tmp + rename) : EXDEV si tmp sur device différent
|
||||
|
||||
### Risques
|
||||
|
||||
- `rename()` lève `EXDEV` si le fichier temporaire et la destination sont sur deux devices différents (cas courant Docker bind-mount).
|
||||
- L'erreur survient uniquement en production (Docker) — invisible en dev local.
|
||||
|
||||
### Symptômes
|
||||
|
||||
- `Error: EXDEV: cross-device link not permitted, rename '/tmp/...' → '/data/...'`
|
||||
- Upload de fichier fonctionnel en dev, cassé en production Docker
|
||||
|
||||
### Bonnes pratiques / mitigations
|
||||
|
||||
- Créer le fichier temporaire dans **le même répertoire** que la destination finale, pas dans `os.tmpdir()`.
|
||||
- S'assurer que le répertoire cible existe avant l'écriture du fichier temporaire.
|
||||
- **Règle** : `os.tmpdir()` est interdit pour les fichiers qui seront renommés vers un volume Docker bind-mounté.
|
||||
|
||||
- Contexte technique : Node.js / Docker / fichiers media — app-template-resto 31-03-2026
|
||||
|
||||
@@ -304,3 +304,26 @@ if (cursor) {
|
||||
- **Règle** : ajouter un test unitaire "cursor invalide → 400" sur tout endpoint paginé par cursor
|
||||
|
||||
- Contexte technique : NestJS / pagination — app-alexandrie 24-03-2026
|
||||
|
||||
---
|
||||
|
||||
<a id="risque-enum-like-string-prisma"></a>
|
||||
## Champ enum-like stocké en `String` Prisma — perte de contrainte DB et typage dégradé
|
||||
|
||||
### Risques
|
||||
|
||||
- Aucune contrainte en base sur les valeurs acceptées — insertion de valeurs invalides possible sans erreur DB.
|
||||
- Cast manuel `as EnumType` dans le service masque l'absence de validation Prisma.
|
||||
|
||||
### Symptômes
|
||||
|
||||
- `as SomeEnum` dans un service ou repository sur un champ qui provient de la DB
|
||||
- Getter `get model(): any` dans PrismaService pour contourner le typage
|
||||
|
||||
### Bonnes pratiques / mitigations
|
||||
|
||||
1. Tout champ à valeurs finies doit être déclaré avec un `enum` Prisma dès la création du modèle — jamais en `String`.
|
||||
2. Si un modèle existant utilise `String`, créer une migration de conversion : `ALTER COLUMN ... TYPE "EnumType" USING ...::"EnumType"`.
|
||||
3. **Signal review** : tout cast `as EnumType` sur une valeur issue de Prisma = dette à corriger immédiatement.
|
||||
|
||||
- Contexte technique : Prisma / PostgreSQL — app-alexandrie 31-03-2026
|
||||
|
||||
Reference in New Issue
Block a user