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