# 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 è l’unica sorgente di verità. Il client visualizza e chiama API. - L’apertura 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 - Idempotency-Key: ### 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 dall’utente - 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)