Files
TetaRdPG/SPRINT1.md
Tetardtek da3237bf3f feat: Sprint 1 — backend fondations TetaRdPG
Auth SuperOAuth (JWT validation + httpOnly cookie), entités users/characters/level_thresholds,
lazy calculation endurance, seed 100 niveaux, config prod-ready (trust proxy, helmet, CORS, rate limit).
Validé : health 200, auth flow, character CRUD, endurance lazy, 401 sans cookie.
2026-03-15 05:51:02 +01:00

319 lines
10 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# TetaRdPG — Brief Sprint 1
> Statut : ⬜ À démarrer
> Objectif : Backend jouable en local — Auth + Personnage + Endurance
> Stack : TypeScript · NestJS · PostgreSQL · Redis · Docker Compose
---
## Contexte
Le GDD est complet sur les systèmes core (voir `GDD.md`).
Ce sprint pose les fondations backend : auth déléguée à SuperOAuth, personnage joueur, gestion d'endurance.
**Pas de Twitch. Pas de combat.** Ce sera Sprint 2.
Architecture locale d'abord — mais production-ready dès le départ (VPS probable).
---
## Scope — contrainte d'autonomie agents
```
Répertoire de travail : /home/tetardtek/Dev/Gitea/TetaRdPG/
Interdit d'écriture : brain/, originsdigital/, super-oauth/, tout autre projet
SuperOAuth : service externe consommé via env vars — jamais modifié
Brain : lecture seule si besoin de patterns — zéro écriture
```
> Toute écriture hors de ce répertoire = violation de scope → STOP immédiat, signaler à l'humain.
---
## Périmètre Sprint 1
### ✅ In scope
- Projet NestJS + TypeScript initialisé
- Docker Compose local : PostgreSQL + Redis + backend
- Auth via SuperOAuth (consommer le service existant — ne pas réimplémenter)
- Entités DB : `users`, `characters`, `level_thresholds` (seed)
- API : création personnage, lecture personnage, état endurance
- Endurance : lazy calculation (pas de timer actif)
- Config production-ready dès le départ (trust proxy, CORS env, rate limiting, httpOnly cookies)
- Health endpoint `/api/health`
### ❌ Out of scope
- Twitch OAuth / EventSub
- Combat PvE
- Forge / Artisanat
- Frontend
- Déploiement VPS
---
## Auth — Intégration SuperOAuth
SuperOAuth est le service d'auth mutualisé de Tetardtek.
Le client (TetaRdPG) ne gère jamais les credentials OAuth — il délègue et valide le JWT.
**Variables d'environnement requises :**
```env
SUPER_OAUTH_URL=http://localhost:3000 # local — https://superoauth.tetardtek.com en prod
SUPER_OAUTH_JWT_SECRET=<secret partagé> # même secret que SuperOAuth
```
**Flow :**
```
Joueur clique "Se connecter"
→ Frontend redirige vers SuperOAuth (/auth/twitch ou /auth/discord)
→ SuperOAuth gère l'OAuth provider
→ SuperOAuth émet un JWT signé avec SUPER_OAUTH_JWT_SECRET
→ TetaRdPG backend reçoit le JWT → valide la signature → stocke en httpOnly cookie
→ Toutes les routes protégées : AuthGuard vérifie le cookie
```
**Endpoints auth à implémenter :**
```
POST /api/auth/session → reçoit JWT de SuperOAuth → valide → set cookie httpOnly
GET /api/auth/me → lit cookie → retourne profil user
POST /api/auth/logout → clear cookie
```
**AuthGuard NestJS :**
- Lit le cookie `session`
- Vérifie la signature JWT avec `SUPER_OAUTH_JWT_SECRET`
- Injecte le user dans le request context
- Retourne 401 si invalide ou absent
---
## Schéma DB
### `users`
```sql
id UUID PRIMARY KEY DEFAULT gen_random_uuid()
oauth_id VARCHAR(255) UNIQUE NOT NULL -- ID chez le provider (Twitch ID, Discord ID)
provider VARCHAR(50) NOT NULL -- 'twitch' | 'discord' | 'google' | 'github'
username VARCHAR(255) NOT NULL
avatar_url VARCHAR(500)
created_at TIMESTAMP DEFAULT NOW()
updated_at TIMESTAMP DEFAULT NOW()
```
### `characters`
```sql
id UUID PRIMARY KEY DEFAULT gen_random_uuid()
user_id UUID NOT NULL REFERENCES users(id)
name VARCHAR(100) NOT NULL
level INTEGER DEFAULT 1
xp INTEGER DEFAULT 0
gold INTEGER DEFAULT 0
-- Stats (cap : 101)
force INTEGER DEFAULT 1
agilite INTEGER DEFAULT 1
intelligence INTEGER DEFAULT 1
chance INTEGER DEFAULT 1
vitalite INTEGER DEFAULT 1
hp_current INTEGER DEFAULT 100
hp_max INTEGER DEFAULT 100
-- Endurance (lazy calculation)
endurance_saved INTEGER DEFAULT 100
last_endurance_ts TIMESTAMP DEFAULT NOW()
endurance_max INTEGER DEFAULT 100 -- 150 avec équipement (v2)
created_at TIMESTAMP DEFAULT NOW()
updated_at TIMESTAMP DEFAULT NOW()
```
### `level_thresholds` (seed — immuable)
```sql
level INTEGER PRIMARY KEY -- 1 à 100
xp_required INTEGER -- 100 × level^1.5
```
> Précalculé au seed — jamais recalculé à la requête.
---
## Endurance — Pattern lazy calculation
**Règle absolue : pas de timer par joueur.**
```typescript
// À chaque lecture de l'endurance :
const elapsedMinutes = (Date.now() - character.lastEnduranceTs.getTime()) / 60_000;
const recharge = Math.floor(elapsedMinutes / 6); // 10 pts/heure = 1 pt / 6 min
const enduranceCurrent = Math.min(
character.enduranceSaved + recharge,
character.enduranceMax
);
// Lors d'une action (ex: combat) :
// 1. Calculer l'endurance actuelle (ci-dessus)
// 2. Vérifier que enduranceCurrent >= coût de l'action
// 3. Stocker : endurance_saved = enduranceCurrent - coût, last_endurance_ts = NOW()
```
> Ce pattern est fondamental. L'endurance n'existe en DB que comme deux colonnes.
> Tout le reste est calculé. Zéro cron job. Zéro timer. Scalable à N joueurs.
---
## API — Endpoints Sprint 1
```
GET /api/health → { status: 'ok', timestamp }
POST /api/auth/session → valide JWT SuperOAuth → set cookie
GET /api/auth/me → profil user connecté
POST /api/auth/logout → clear cookie
POST /api/characters → crée un personnage (5 pts stats à répartir)
GET /api/characters/me → personnage du user connecté + endurance calculée
GET /api/characters/me/endurance → endurance actuelle calculée lazy
```
---
## Config production-ready (pattern OriginsDigital)
Ces éléments sont **obligatoires dès le Sprint 1** — pas des ajouts post-lancement.
```typescript
// main.ts
app.set('trust proxy', 1); // VPS derrière Apache / reverse proxy
// CORS — depuis l'env, multi-origin supporté
const allowedOrigins = (process.env.FRONTEND_URL ?? 'http://localhost:5173')
.split(',')
.map(o => o.trim());
// Cookies httpOnly pour le JWT
// Rate limiting sur /api/auth/*
// Logging structuré (Pino ou Winston)
// Helmet pour les headers de sécurité
```
**Variables d'environnement — `.env.example` :**
```env
PORT=4000
NODE_ENV=development
DATABASE_URL=postgresql://tetardpg:password@localhost:5432/tetardpg
REDIS_URL=redis://localhost:6379
FRONTEND_URL=http://localhost:5173
SUPER_OAUTH_URL=http://localhost:3000
SUPER_OAUTH_JWT_SECRET=
COOKIE_SECRET=
```
---
## Docker Compose local
```yaml
# docker-compose.yml
services:
postgres:
image: postgres:16-alpine
environment:
POSTGRES_DB: tetardpg
POSTGRES_USER: tetardpg
POSTGRES_PASSWORD: password
ports:
- "5432:5432"
volumes:
- pgdata:/var/lib/postgresql/data
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
pgdata:
```
> Le backend tourne hors Docker en local (hot reload). Seuls PostgreSQL et Redis sont conteneurisés.
> En prod VPS : tout dans Docker (backend inclus) — Dockerfile à préparer dès Sprint 1.
---
## Structure NestJS cible
```
src/
├── main.ts → bootstrap, trust proxy, CORS, helmet
├── app.module.ts
├── auth/
│ ├── auth.module.ts
│ ├── auth.controller.ts → /api/auth/*
│ ├── auth.service.ts → valide JWT SuperOAuth, gère session
│ └── guards/
│ └── auth.guard.ts → vérifie cookie sur routes protégées
├── character/
│ ├── character.module.ts
│ ├── character.controller.ts → /api/characters/*
│ ├── character.service.ts
│ └── entities/
│ ├── character.entity.ts
│ └── level-threshold.entity.ts
├── user/
│ ├── user.module.ts
│ └── user.entity.ts
└── common/
├── health.controller.ts → /api/health
└── logger/
```
---
## Chaîne d'agents — Sprint 1
```
tech-lead → gate d'entrée (valide l'approche, contention map)
migration → schema DB + seed level_thresholds (AVANT tout build)
build ×3 → [auth module] [character module] [docker-compose + config]
security → validation JWT handling, httpOnly, CORS, rate limiting
integrator → critères de validation (voir ci-dessous)
```
**Critères de validation integrator :**
- [ ] `docker-compose up` → PostgreSQL + Redis up
- [ ] `npm run start:dev` → backend démarre sans erreur
- [ ] `GET /api/health` → 200 `{ status: 'ok' }`
- [ ] Auth flow SuperOAuth → cookie httpOnly posé
- [ ] `GET /api/auth/me` → profil user retourné
- [ ] `POST /api/characters` → personnage créé en DB
- [ ] `GET /api/characters/me` → endurance calculée correctement
- [ ] Requête sans cookie → 401
- [ ] `.env.example` complet
- [ ] `Dockerfile` présent (non testé en prod — validé au Sprint 2)
---
## ⚡ Coach — lecture obligatoire avant de démarrer
Ce sprint est le premier code réel de TetaRdPG. C'est aussi le premier test de la chaîne sur du vrai code.
**Ce qui va bien se passer :** la structure est claire, les patterns sont connus (SuperOAuth + OriginsDigital), les entités sont définies.
**Ce qui va être l'enjeu réel :**
- Le pattern lazy endurance — ne pas le simplifier en timer "parce que c'est plus simple". C'est le coeur du système idle.
- La séparation auth user / character — un user peut ne pas avoir de personnage. Gérer ce cas dès le départ.
- `trust proxy: 1` — si oublié, les rate limiters et les IP logs seront faux dès le VPS.
**Signal de graduation à surveiller :**
Si le pattern lazy calculation est implémenté correctement sans intervention du coach → le concept de "calcul à la demande vs état persisté en continu" est acquis. C'est une compétence backend avancée.
**Objectif pédagogique du sprint :**
Produire un backend NestJS de qualité professionnelle — structure de modules, séparation des responsabilités, config 12-factor (env vars), sécurité dès le départ. Pas juste "ça marche en local".