Files
gioco-mobile/server
davide 8ad83348f4 server: core bootstrap — config, db clients, shared utilities
Add env config (zod validation), game balance constants, Prisma and
Redis client singletons, auth guard middleware, error helpers,
request validators, Fastify JWT type augmentation, and app/server
entry points.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 23:41:33 +02:00
..

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

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):

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, commentata. Nessun numero "magico" altrove.

API REST (/api/v1)

Tutte le route tranne register/login richiedono Authorization: Bearer <jwt>.

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: "<jwt>" }.

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 polizia1)×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):

npm install
# in .env usa localhost al posto di db/redis
npx prisma migrate dev
npx prisma db seed
npm run dev

Backup

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