# Contrabbandieri MMO — Server Backend per il gioco mobile multiplayer asincrono "Contrabbandieri". Tutte le regole di gioco vivono sul server: il client non è mai considerato affidabile. ## Stack Node.js 22 · TypeScript · Fastify · Prisma · PostgreSQL · Redis · Socket.IO · Zod · JWT · Docker Compose ## Avvio rapido ```bash cp .env.example .env # poi cambia JWT_SECRET e POSTGRES_PASSWORD mkdir -p data/postgres data/redis data/backups docker compose up -d --build ``` Al primo avvio il container `api` applica automaticamente le migrazioni (`prisma migrate deploy`). Poi esegui il seed (città, item, listini): ```bash docker compose exec api npx prisma db seed ``` Il server è su `http://localhost:3000` (health check: `GET /healthz`). ### Dati persistenti Tutti i dati vivono in bind-mount locali dentro `data/` (`postgres/`, `redis/`, `backups/`): copiare la cartella del progetto su un altro server trasferisce tutto. Per il backup vedi §Backup. ## Struttura ``` src/ ├── app.ts # Fastify, plugin, error handler, registrazione route ├── server.ts # entrypoint: HTTP + Socket.IO + scheduler ├── config/ │ ├── env.ts # variabili d'ambiente validate con Zod │ └── gameBalance.ts # ★ TUTTI i parametri di bilanciamento del gioco ├── db/ # client Prisma e Redis ├── modules/ # un modulo per dominio: routes + service │ ├── auth/ players/ cities/ market/ inventory/ │ ├── missions/ events/ leaderboard/ ├── jobs/ # scheduler node-cron + job periodici ├── realtime/socket.ts # Socket.IO (path /ws) └── shared/ # authGuard, errori, validazione, utility ``` **Bilanciamento**: ogni costante di gioco (costi viaggio, formule mercato, probabilità di successo missioni, eventi, XP) sta in [src/config/gameBalance.ts](src/config/gameBalance.ts), commentata. Nessun numero "magico" altrove. ## API REST (`/api/v1`) Tutte le route tranne register/login richiedono `Authorization: Bearer `. | Metodo | Path | Descrizione | |---|---|---| | POST | `/auth/register` | crea utente+giocatore, ritorna JWT (rate limit 5/min) | | POST | `/auth/login` | login, ritorna JWT (rate limit 5/min) | | GET | `/auth/me` | utente + giocatore correnti | | GET | `/player/me` | profilo, città corrente, XP per il prossimo livello | | POST | `/player/travel` | `{cityId}` — viaggia (costo scalato dal server) | | GET | `/cities` · `/cities/:cityId` | città con costo viaggio | | GET | `/market/current` | listino della città corrente + eventi attivi | | POST | `/market/buy` · `/market/sell` | `{itemId, quantity}` — prezzi decisi dal server | | GET | `/inventory` | inventario con peso totale | | GET | `/missions/available` | missioni nella città corrente | | POST | `/missions/:missionId/start` | avvia (consuma subito gli item richiesti) | | POST | `/missions/:playerMissionId/claim` | riscuote: successo/fallimento calcolato dal server | | GET | `/missions/active` | missioni in corso | | GET | `/events/current` | eventi mondo attivi | | GET | `/leaderboard/money` · `/leaderboard/reputation` | classifiche (Redis), `?limit=N` | Gli errori hanno sempre la forma `{ "error": { "code": "...", "message": "..." } }`. ## Realtime (Socket.IO) Path `/ws`, handshake con `auth: { token: "" }`. Eventi server→client: `market:updated`, `mission:completed`, `worldEvent:started`, `worldEvent:ended`, `player:notification`, `leaderboard:updated`. Client→server: `client:ping`. ## Regole di gioco (formule) I parametri citati sono tutti in `gameBalance.ts`. - **Mercato** (`rawPrice = basePrice * economyModifier * demand / max(supply, 0.25) * eventModifier`): buy = raw×1.12, sell = raw×0.88, limiti [0.35×, 3.5×] del prezzo base. Acquisti/vendite spostano domanda/offerta; ogni minuto il mercato decade verso l'equilibrio (fattore 0.98). - **Viaggio**: `costo = 50 + riskLevel(destinazione) × 25`. - **Missioni**: `successo = 0.95 − risk×0.6 + livello×0.01 + reputazione×0.0005 − (pressione polizia−1)×0.05`, limitato a [5%, 95%]. Successo: denaro + XP + reputazione (+2×difficoltà). Fallimento: −3 reputazione (mai sotto 0). Massimo 3 missioni attive per giocatore. - **XP**: per passare dal livello L a L+1 servono `100 × L^1.5` XP. - **Eventi**: ogni 15 min possibile evento locale (25%), ogni ora possibile evento globale (20%); modificano prezzi e pressione di polizia finché attivi. ## Scheduler - **Ogni minuto**: ricalcolo prezzi, notifica missioni completate, pulizia missioni scadute, generazione missioni (minimo 5 attive per città). - **Ogni 15 minuti**: possibile evento locale, refresh classifiche Redis. - **Ogni ora**: possibile evento globale, notifica eventi terminati. ## Sviluppo senza Docker Servono PostgreSQL e Redis raggiungibili (puoi usare solo i container `db` e `redis`): ```bash npm install # in .env usa localhost al posto di db/redis npx prisma migrate dev npx prisma db seed npm run dev ``` ## Backup ```bash # dump (a caldo, sicuro) docker compose exec db pg_dump -U gameuser game > ./data/backups/game_$(date +%Y%m%d_%H%M).sql # restore cat ./data/backups/game_XXX.sql | docker compose exec -T db psql -U gameuser game # migrazione completa su altro host: stop, archivio, riavvio docker compose down tar -czf contrabbandieri-data.tar.gz data .env docker-compose.yml ``` ## Produzione Il `Dockerfile` ha uno stage `production` (build TypeScript, solo dipendenze runtime, `node dist/server.js`). Prima del deploy: cambia `JWT_SECRET` e `POSTGRES_PASSWORD`, non esporre le porte 5432/6379, metti un reverse proxy HTTPS (es. Nginx) davanti alla porta 3000.