Files
gioco-mobile/server
davide 8a91a298a7 server: database schema, migrations and seed
Define Prisma schema (Player, City, Good, InventoryItem, Transaction,
Mission, WorldEvent), initial migration SQL, and seed script to
populate cities and goods with base game data.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 23:41:27 +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.