Files
gioco-mobile/server/README.md
T
davide 34708340f1 server: project config and infrastructure setup
Add package.json (Fastify, Prisma, Redis, JWT), TypeScript config,
Dockerfile, docker-compose with Postgres and Redis services, and
.gitignore with data directory rules.

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

124 lines
5.6 KiB
Markdown
Raw 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.
# 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 <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`):
```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.