Files
card-game/SOP.md

482 lines
12 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.
# SOP.md — Prototipo Gioco Carte (Server Docker + Client Test)
> Obiettivo: realizzare un prototipo end-to-end dove:
> - Utente si registra/login con email+password
> - Ha una valuta (Gold) con cui apre bauli
> - Aprendo un baule ottiene carte con rarità
> - La collezione è "unique-only": la carta viene salvata solo se non già posseduta
> - Se la carta è già posseduta: conversione in Gold (o altra regola definita)
> - I giocatori possono vedere la collezione pubblica di altri giocatori
> - Server e DB girano su Docker
> - Esiste un client temporaneo testabile (CLI o Postman) per test E2E
---
## 0) Decisioni bloccanti (DA DEFINIRE PRIMA DI CODIFICARE)
Compilare questa sezione PRIMA di procedere.
- [ ] STACK_BACKEND: TODO (FastAPI / NestJS)
- [ ] DB: PostgreSQL (default)
- [ ] ORM: TODO (SQLAlchemy+Alembic / Prisma / TypeORM)
- [ ] AUTH: TODO (JWT access + refresh / solo access)
- [ ] CLIENT_TEST: TODO (CLI Python / Postman / entrambi)
- [ ] CHEST:
- gold_initial: 1000
- chest_cost: 100
- cards_per_open: 3
- rarities: Common 70%, Rare 25%, Legendary 5%
- duplicate_conversion_gold: 20
- [ ] PRIVACY:
- collection_public_default: true
> Regola: se un valore è “TODO” non procedere oltre senza definirlo.
---
## 1) Regole Architetturali (NON negoziabili)
- Tutta la logica RNG e assegnazione premi sta nel backend (mai nel client).
- Il backend è lunica sorgente di verità. Il client visualizza e chiama API.
- Lapertura baule deve essere:
- atomica (transazione DB)
- idempotente (retry rete non deve duplicare premi né spesa)
- La collezione non deve avere duplicati:
- vincolo DB: PRIMARY KEY (user_id, card_id)
---
## 2) Convenzioni Repo e Commit
### 2.1 Struttura repository
Creare repo con questa struttura:
card-game/
- backend/
- client-test/ (CLI o Postman)
- infra/
- spec/
- docker-compose.yml
- .env.example
- README.md
- SOP.md
### 2.2 Regole commit
- Ogni step sotto corrisponde a 1 commit (o più commit piccoli) con un messaggio chiaro:
- feat: ...
- test: ...
- chore: ...
- Ogni commit deve avere un “Gate” (test di verifica) che deve passare.
---
## 3) Stack locale con Docker (Backend + DB)
### 3.1 File obbligatori
- docker-compose.yml
- backend/Dockerfile
- backend/.dockerignore
- .env.example
### 3.2 Variabili ambiente minime (.env)
Definire:
- APP_ENV=local
- DB_HOST=db
- DB_PORT=5432
- DB_NAME=cardgame
- DB_USER=cardgame
- DB_PASSWORD=cardgame
- JWT_SECRET=change-me
- JWT_ACCESS_TTL_MIN=15
- JWT_REFRESH_TTL_DAYS=30
> Non committare mai `.env` vero. Solo `.env.example`.
---
# STEPS (commit-based)
## STEP 01 — Bootstrap repo + Docker “Hello World”
### Obiettivo
- `docker compose up --build` avvia tutto
- backend espone `GET /health``{ "status": "ok" }`
### Azioni
1) Creare docker-compose.yml con:
- service `db` (postgres)
- service `api` (backend)
2) Implementare endpoint `/health`
3) Documentare README con comandi minimi
### Output
- docker-compose.yml
- backend/Dockerfile
- backend/app/main.(py|ts)
- README.md
### Commit
- `feat: bootstrap docker compose and health endpoint`
### Gate / Test
- Comando: `docker compose up --build`
- Verifica:
- `curl http://localhost:8000/health`
- Risposta deve essere `{"status":"ok"}`
---
## STEP 02 — Connessione DB + endpoint db-check
### Obiettivo
Backend connesso realmente a Postgres.
### Azioni
1) Aggiungere driver DB e configurazione.
2) Implementare `GET /db-check` che esegue `SELECT 1`.
### Commit
- `feat: add database connection and db-check endpoint`
### Gate / Test
- `curl http://localhost:8000/db-check`
- Risposta: `{ "db": "ok" }`
---
## STEP 03 — Schema DB v1 (tabelle core)
### Obiettivo
Creare schema DB minimo completo per il prototipo.
### Tabelle richieste (v1)
#### users
- id (uuid / serial)
- email (unique, not null)
- password_hash
- created_at
#### user_profiles
- user_id (PK/FK users.id)
- nickname (unique, not null)
- is_collection_public (bool, default true)
- created_at
#### wallets
- user_id (PK/FK)
- balance (int, not null)
#### wallet_transactions
- id
- user_id
- type (CREDIT/DEBIT)
- amount
- reason (REGISTER_BONUS, CHEST_OPEN, DUPLICATE_CONVERT, etc.)
- reference_id (nullable, e.g. chest_open_id)
- created_at
#### cards (catalogo)
- id (string or uuid)
- name
- rarity (COMMON/RARE/LEGENDARY)
- created_at
#### chests (catalogo)
- id
- name
- cost_gold
- cards_per_open
- created_at
#### user_cards (collezione unique-only)
- user_id
- card_id
- obtained_at
- PRIMARY KEY (user_id, card_id)
#### chest_opens (audit)
- id
- user_id
- chest_id
- spent_gold
- created_at
#### chest_open_items (audit dettagli)
- id
- chest_open_id
- card_id (nullable se conversion-only)
- rarity
- outcome_type (NEW / CONVERTED)
- converted_gold (nullable)
- created_at
#### idempotency_keys
- key (string)
- user_id
- endpoint (e.g. "POST:/chests/{id}/open")
- response_body (json/text)
- created_at
- UNIQUE (key, user_id, endpoint)
### Azioni
- Implementare migrazioni (consigliato) o create_all (solo prototipo).
- Assicurare vincoli unici e FK.
### Commit
- `feat: add database schema v1`
### Gate / Test
- Avviare stack e verificare che tabelle esistano (psql o query).
- Verificare vincolo UNIQUE email e nickname.
- Verificare PK composita su user_cards.
---
## STEP 04 — Seed catalogo (cards + chests)
### Obiettivo
Popolare catalogo con dati minimi.
### Azioni
- Inserire almeno:
- 10 cards (mix rarità)
- 1 chest base (cost=100, cards_per_open=3)
- Seed deve essere ripetibile (non duplicare se rilanciato).
### Commit
- `feat: seed initial catalog (cards, chests)`
### Gate / Test
- `GET /catalog/cards` ritorna lista > 0
- `GET /catalog/chests` ritorna lista > 0
---
## STEP 05 — Auth: register + login (email/password)
### Obiettivo
Utente può registrarsi e loggarsi.
### Azioni
Implementare:
- `POST /auth/register`:
- valida email e password (minimo)
- hash password
- crea user
- crea profile con nickname (TODO: decidere quando si setta nickname)
- crea wallet con gold_initial
- crea wallet_transaction REGISTER_BONUS
- `POST /auth/login`:
- verifica credenziali
- emette token (access + refresh se previsto)
> Nota: nickname
- Opzione A: richiesto in register (consigliato per social immediato)
- Opzione B: scelto dopo login (richiede endpoint `/me/profile` update)
### Commit
- `feat: implement register and login`
### Gate / Test (manuale)
- Register nuovo: 201
- Register email già usata: 409
- Login corretto: 200 + token
- Login password errata: 401
---
## STEP 06 — Endpoint me: wallet + collection + profile
### Obiettivo
Utente autenticato può vedere:
- saldo
- collezione
- profilo
### Azioni
Implementare:
- `GET /me/wallet`
- `GET /me/collection`
- `GET /me/profile`
### Commit
- `feat: add me endpoints (wallet, collection, profile)`
### Gate / Test
- Dopo register/login:
- wallet = gold_initial
- collection = []
- profile contiene nickname
---
## STEP 07 — Core: Open Chest (transazione + unique-only + conversione)
### Obiettivo
Endpoint che apre il baule con logica corretta e audit completo.
### Endpoint
- `POST /chests/{chestId}/open`
Header:
- Authorization: Bearer <access_token>
- Idempotency-Key: <uuid>
### Algoritmo (obbligatorio)
Dentro una transazione DB:
1) Validare chestId
2) Verificare saldo >= cost
3) Debit saldo (wallet)
4) Per i=1..cards_per_open:
4.1) Roll rarità secondo pesi
4.2) Selezionare una carta casuale tra quelle di quella rarità NON possedute dallutente
- Se pool vuoto:
- outcome = CONVERTED (converted_gold = duplicate_conversion_gold)
- credit wallet
- wallet_transaction DUPLICATE_CONVERT
- Se pool non vuoto:
- inserire in user_cards (PK impedisce dup)
- outcome = NEW
5) Salvare chest_opens + chest_open_items
6) Commit
### Idempotenza
- Se la stessa Idempotency-Key per lo stesso user+endpoint è già stata usata:
- ritornare ESATTAMENTE la response salvata
- NON scalare saldo
- NON creare nuovi record
### Commit
- `feat: implement open chest with transaction, unique-only, conversion, audit`
### Gate / Test (manuale)
Caso A (happy path):
- wallet iniziale 1000
- open chest
- wallet diminuisce di 100 (+ eventuali conversioni)
- collection cresce con nuove carte
- chest_opens creato
- chest_open_items creati = cards_per_open
Caso B (saldo insufficiente):
- set wallet a 50 (o creare user con meno gold)
- open chest → 403
Caso C (idempotenza):
- inviare due volte la stessa request con stessa Idempotency-Key
- wallet deve scalare una volta sola
- response identica
Caso D (no duplicates):
- aprire fino a completare tutte le carte (o tutte della rarità)
- ulteriori aperture producono conversioni, senza violare vincoli DB
---
## STEP 08 — Social: profilo pubblico + collezione altrui
### Obiettivo
Chiunque autenticato può vedere collezioni altrui (se pubbliche).
### Endpoint
- `GET /users/{nickname}`
- `GET /users/{nickname}/collection`
### Regole privacy
- se `is_collection_public=false` → 403 (o response “private” definita)
### Commit
- `feat: add public user profile and collection endpoints`
### Gate / Test
- Utente A apre carte
- Utente B fa GET su collezione A → vede lista
- Se A setta privacy false (TODO se implementare endpoint) → B riceve 403
---
## STEP 09 — Test automatici backend (minimo indispensabile)
### Obiettivo
Test ripetibili per le feature core.
### Test richiesti
- register/login
- wallet iniziale
- open chest happy path
- open chest saldo insufficiente
- open chest idempotenza
- unique-only: non si possono inserire duplicati
### Commit
- `test: add backend tests for core flows`
### Gate / Test
- `pytest` (o test runner equivalente) verde
---
## STEP 10 — Client di test temporaneo (CLI o Postman)
### Obiettivo
Avere un modo semplice per fare E2E senza Unity.
### Opzione 1: CLI (consigliata)
- script che:
- register
- login
- open chest con Idempotency-Key
- stampa wallet e collection
- view other collection
### Opzione 2: Postman collection
- file esportato con env (base_url, tokens)
### Commit
- `feat: add test client (cli/postman) for end-to-end runs`
### Gate / Test
- Eseguire scenario:
1) crea userA e userB
2) userA open chest 3 volte
3) userB legge collezione userA
4) verificare che non ci siano duplicati in userA
---
## STEP 11 — Hardening minimo (prototipo, ma non fragile)
### Obiettivo
Ridurre bug e abusi evidenti.
### Azioni
- rate limit su login e open chest
- logging strutturato per chest_open
- gestione errori uniforme (JSON: code, message)
### Commit
- `chore: add basic rate limiting and structured logs`
### Gate / Test
- aprire 20 volte in 1 secondo → deve limitare o rispondere coerentemente
- log contiene open_id e user_id
---
# 4) Definition of Done (DoD) Prototipo
Il prototipo è “DONE” quando:
- [ ] docker compose up avvia stack
- [ ] register/login funzionano
- [ ] wallet non scende mai sotto zero
- [ ] open chest è transazionale e idempotente
- [ ] user_cards non contiene duplicati (vincolo DB)
- [ ] collezione pubblica di altri utenti è consultabile
- [ ] esistono test automatici minimi
- [ ] esiste client di test per E2E
---
# 5) Prompt template per agente IA (uso consigliato)
## Prompt generale per ogni step
“In questo step implementa SOLO ciò che è richiesto, senza inventare funzionalità extra.
Rispetta i Gate/Test e aggiorna README se cambiano comandi.
Dopo ogni modifica, elenca i file creati/modificati.”
## Prompt per open chest (molto importante)
“Implementa `POST /chests/{chestId}/open` ESATTAMENTE secondo SOP.
Vincoli: transazione DB, idempotenza con Idempotency-Key, unique-only, conversione duplicati, audit.
Non cambiare comportamento e non introdurre logiche non esplicitate.”
---
# 6) Domande aperte (non procedere se non risolte)
- STACK_BACKEND = ?
- AUTH (refresh token sì/no) = ?
- CLIENT_TEST = ?
- Conversione duplicati: sempre 20 gold o dipende da rarità? (se dipende, definire tabella)
- Nickname: in register o endpoint separato? (definire)