# 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= # 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".