Files
gioco-mobile/Blueprint_Server.md
2026-06-09 22:40:43 +02:00

1055 lines
18 KiB
Markdown
Raw Permalink 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.
# Blueprint Server — Contrabbandieri MMO Mobile
## 1. Obiettivo
Realizzare un backend funzionante per un gioco mobile multiplayer asincrono, persistente e trasferibile tra server Linux tramite Docker.
Il server deve gestire:
- autenticazione utenti;
- profilo giocatore;
- denaro, reputazione e livello;
- inventario;
- mercato dinamico;
- missioni generate dal server;
- città e prezzi locali;
- eventi globali;
- classifiche;
- notifiche realtime opzionali;
- deploy tramite Docker Compose.
Il client mobile non deve mai essere considerato affidabile. Tutte le regole di gioco vivono sul server.
---
## 2. Stack consigliato
### Backend
- Node.js 22 LTS
- TypeScript
- Fastify
- Prisma ORM
- PostgreSQL
- Redis
- Socket.IO
- Zod
- JWT
- Docker Compose
### Perché questo stack
Fastify è veloce, semplice e adatto a API REST. Prisma semplifica schema, migrazioni e accesso al database. PostgreSQL è affidabile per dati persistenti. Redis serve per cache, classifiche, cooldown e notifiche realtime. Socket.IO consente eventi live verso client mobile.
---
## 3. Architettura generale
```text
Mobile Client
|
| HTTPS REST / WebSocket
v
Nginx Reverse Proxy
|
v
Game API Node.js
| |
| v
| Redis
v
PostgreSQL
```
---
## 4. Struttura repository
```text
contrabbandieri-server/
├── docker-compose.yml
├── docker-compose.prod.yml
├── .env.example
├── README.md
├── data/
│ ├── postgres/
│ ├── redis/
│ └── backups/
├── package.json
├── tsconfig.json
├── prisma/
│ ├── schema.prisma
│ └── seed.ts
├── src/
│ ├── app.ts
│ ├── server.ts
│ ├── config/
│ │ └── env.ts
│ ├── db/
│ │ ├── prisma.ts
│ │ └── redis.ts
│ ├── modules/
│ │ ├── auth/
│ │ ├── players/
│ │ ├── market/
│ │ ├── inventory/
│ │ ├── missions/
│ │ ├── cities/
│ │ ├── events/
│ │ └── leaderboard/
│ ├── realtime/
│ │ └── socket.ts
│ ├── jobs/
│ │ ├── scheduler.ts
│ │ ├── updateMarketPrices.job.ts
│ │ ├── generateMissions.job.ts
│ │ └── generateWorldEvent.job.ts
│ ├── shared/
│ │ ├── authGuard.ts
│ │ ├── errors.ts
│ │ ├── result.ts
│ │ └── validators.ts
│ └── types/
└── nginx/
└── nginx.conf
```
---
## 5. Modello di dominio MVP
### Player
Rappresenta il giocatore.
Campi principali:
- id
- userId
- displayName
- money
- reputation
- level
- experience
- currentCityId
- createdAt
- updatedAt
### City
Ogni città ha mercato e missioni locali.
Esempi iniziali:
- Milano
- Napoli
- Palermo
Campi:
- id
- name
- riskLevel
- policePressure
- economyModifier
### Item
Merce commerciabile.
Esempi:
- Gioielli
- Componenti elettronici
- Auto rubate
- Informazioni riservate
- Armi leggere
Campi:
- id
- name
- basePrice
- rarity
- illegalLevel
- weight
### InventoryItem
Quantità posseduta da un player.
Campi:
- playerId
- itemId
- quantity
### MarketPrice
Prezzo locale per item e città.
Campi:
- cityId
- itemId
- buyPrice
- sellPrice
- demand
- supply
- updatedAt
### Mission
Missione generata dal server.
Campi:
- id
- cityId
- title
- type
- difficulty
- durationSeconds
- rewardMoney
- rewardXp
- risk
- requiredItemId
- requiredItemQuantity
- expiresAt
### PlayerMission
Missione iniziata da un player.
Campi:
- id
- playerId
- missionId
- status
- startedAt
- completesAt
- resolvedAt
### WorldEvent
Evento globale o locale che altera economia/rischio.
Campi:
- id
- title
- description
- type
- cityId nullable
- priceModifier
- policeModifier
- startsAt
- endsAt
---
## 6. Schema Prisma MVP
```prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
enum MissionStatus {
STARTED
COMPLETED
FAILED
}
enum MissionType {
DELIVERY
THEFT
SMUGGLING
INTEL
}
enum EventType {
MARKET_BOOM
POLICE_RAID
SHORTAGE
BLACKOUT
}
model User {
id String @id @default(uuid())
email String @unique
passwordHash String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
player Player?
}
model Player {
id String @id @default(uuid())
userId String @unique
displayName String @unique
money Int @default(1000)
reputation Int @default(0)
level Int @default(1)
experience Int @default(0)
currentCityId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id])
currentCity City @relation(fields: [currentCityId], references: [id])
inventory InventoryItem[]
missions PlayerMission[]
}
model City {
id String @id @default(uuid())
name String @unique
riskLevel Int
policePressure Float @default(1.0)
economyModifier Float @default(1.0)
createdAt DateTime @default(now())
players Player[]
prices MarketPrice[]
missions Mission[]
events WorldEvent[]
}
model Item {
id String @id @default(uuid())
name String @unique
basePrice Int
rarity Int
illegalLevel Int
weight Int @default(1)
createdAt DateTime @default(now())
inventory InventoryItem[]
prices MarketPrice[]
missions Mission[]
}
model InventoryItem {
playerId String
itemId String
quantity Int
player Player @relation(fields: [playerId], references: [id])
item Item @relation(fields: [itemId], references: [id])
@@id([playerId, itemId])
}
model MarketPrice {
cityId String
itemId String
buyPrice Int
sellPrice Int
demand Float @default(1.0)
supply Float @default(1.0)
updatedAt DateTime @updatedAt
city City @relation(fields: [cityId], references: [id])
item Item @relation(fields: [itemId], references: [id])
@@id([cityId, itemId])
}
model Mission {
id String @id @default(uuid())
cityId String
title String
type MissionType
difficulty Int
durationSeconds Int
rewardMoney Int
rewardXp Int
risk Float
requiredItemId String?
requiredItemQuantity Int?
expiresAt DateTime
createdAt DateTime @default(now())
city City @relation(fields: [cityId], references: [id])
requiredItem Item? @relation(fields: [requiredItemId], references: [id])
playerMissions PlayerMission[]
}
model PlayerMission {
id String @id @default(uuid())
playerId String
missionId String
status MissionStatus @default(STARTED)
startedAt DateTime @default(now())
completesAt DateTime
resolvedAt DateTime?
player Player @relation(fields: [playerId], references: [id])
mission Mission @relation(fields: [missionId], references: [id])
}
model WorldEvent {
id String @id @default(uuid())
title String
description String
type EventType
cityId String?
priceModifier Float @default(1.0)
policeModifier Float @default(1.0)
startsAt DateTime
endsAt DateTime
createdAt DateTime @default(now())
city City? @relation(fields: [cityId], references: [id])
}
```
---
## 7. API REST MVP
Base path:
```text
/api/v1
```
### Auth
```http
POST /auth/register
POST /auth/login
GET /auth/me
```
#### POST /auth/register
Request:
```json
{
"email": "player@example.com",
"password": "password123",
"displayName": "ShadowFox"
}
```
Response:
```json
{
"accessToken": "jwt",
"player": {
"id": "...",
"displayName": "ShadowFox",
"money": 1000,
"level": 1
}
}
```
### Player
```http
GET /player/me
POST /player/travel
```
#### POST /player/travel
Request:
```json
{
"cityId": "..."
}
```
Regola server:
- verifica che la città esista;
- calcola costo viaggio;
- verifica soldi disponibili;
- aggiorna città corrente;
- scala il denaro.
### Cities
```http
GET /cities
GET /cities/:cityId
```
### Market
```http
GET /market/current
POST /market/buy
POST /market/sell
```
#### POST /market/buy
Request:
```json
{
"itemId": "...",
"quantity": 3
}
```
Regole server:
- il player compra nel mercato della città corrente;
- il prezzo lo decide il server;
- il costo è `buyPrice * quantity`;
- se il player non ha soldi, errore;
- aggiorna inventario;
- aggiorna domanda/offerta.
#### POST /market/sell
Request:
```json
{
"itemId": "...",
"quantity": 2
}
```
Regole server:
- verifica inventario;
- paga `sellPrice * quantity`;
- riduce inventario;
- aggiorna domanda/offerta.
### Inventory
```http
GET /inventory
```
### Missions
```http
GET /missions/available
POST /missions/:missionId/start
POST /missions/:playerMissionId/claim
GET /missions/active
```
#### Avvio missione
Il server calcola `completesAt = now + durationSeconds`.
#### Claim missione
Regole:
- se `now < completesAt`, errore;
- se già risolta, errore;
- calcola successo in base a rischio, livello e reputazione;
- assegna premio o fallimento;
- aggiorna XP, soldi, reputazione.
### Events
```http
GET /events/current
```
### Leaderboard
```http
GET /leaderboard/money
GET /leaderboard/reputation
```
---
## 8. Realtime Socket.IO
Endpoint:
```text
/ws
```
Eventi server → client:
```text
market:updated
mission:completed
worldEvent:started
worldEvent:ended
player:notification
leaderboard:updated
```
Eventi client → server:
```text
client:ping
```
Nel prototipo, le azioni importanti restano REST. Socket.IO serve solo per notificare.
---
## 9. Regole mercato dinamico
Ogni città ha prezzi indipendenti.
Formula base:
```text
rawPrice = basePrice * city.economyModifier * demand / max(supply, 0.25) * eventModifier
```
Prezzo acquisto:
```text
buyPrice = round(rawPrice * 1.12)
```
Prezzo vendita:
```text
sellPrice = round(rawPrice * 0.88)
```
Limiti:
```text
minPrice = basePrice * 0.35
maxPrice = basePrice * 3.50
```
Ogni acquisto:
```text
demand += quantity * 0.02
supply -= quantity * 0.01
```
Ogni vendita:
```text
demand -= quantity * 0.01
supply += quantity * 0.02
```
Ogni ciclo scheduler:
```text
demand = demand * 0.98 + 1.0 * 0.02
supply = supply * 0.98 + 1.0 * 0.02
```
Questo fa tornare lentamente il mercato verso lequilibrio.
---
## 10. Scheduler
Implementare un processo interno nel backend.
### Ogni minuto
- aggiornare prezzi di mercato;
- completare notifiche missioni;
- cancellare missioni scadute;
- generare nuove missioni se sotto soglia.
### Ogni 15 minuti
- generare possibile evento locale;
- aggiornare classifiche Redis.
### Ogni ora
- generare possibile evento globale;
- pulizia dati temporanei.
Librerie consigliate:
```text
node-cron
```
Per versione avanzata:
```text
BullMQ + Redis
```
Per MVP basta `node-cron`.
---
## 11. Sicurezza
### Principi
- mai fidarsi del client;
- validare ogni payload con Zod;
- usare JWT con scadenza breve;
- password con Argon2 o bcrypt;
- rate limit su login e register;
- usare HTTPS tramite Nginx;
- non esporre PostgreSQL e Redis su internet;
- loggare azioni economiche importanti.
### Variabili segrete
Nel file `.env`, non committare mai:
```text
DATABASE_URL
JWT_SECRET
POSTGRES_PASSWORD
REDIS_URL
```
---
## 12. File `.env.example`
```env
NODE_ENV=development
PORT=3000
DATABASE_URL=postgresql://gameuser:gamepassword@db:5432/game
REDIS_URL=redis://redis:6379
JWT_SECRET=change_me_in_production
JWT_EXPIRES_IN=7d
POSTGRES_DB=game
POSTGRES_USER=gameuser
POSTGRES_PASSWORD=gamepassword
```
---
## 13. Docker Compose sviluppo
### Regola sui dati persistenti
Il server deve salvare tutti i dati persistenti in volumi montati nella stessa directory del progetto, non in volumi Docker anonimi o nominati.
Struttura dati locale:
```text
contrabbandieri-server/
└── data/
├── postgres/ # database PostgreSQL persistente
├── redis/ # snapshot Redis, se abilitati
└── backups/ # dump manuali o automatici
```
Vantaggi:
- puoi copiare l'intera cartella del progetto su un altro server;
- i dati sono visibili e controllabili dal filesystem Linux;
- backup e restore sono più semplici;
- Docker rimane solo il runtime, non il proprietario nascosto dei dati.
Prima del primo avvio:
```bash
mkdir -p data/postgres data/redis data/backups
```
Permessi consigliati se PostgreSQL ha problemi di scrittura:
```bash
sudo chown -R 999:999 data/postgres
sudo chown -R 999:999 data/redis
```
```yaml
services:
api:
build: .
container_name: contrabbandieri-api
restart: unless-stopped
ports:
- "3000:3000"
env_file:
- .env
depends_on:
- db
- redis
volumes:
- .:/app
- /app/node_modules
command: npm run dev
db:
image: postgres:16
container_name: contrabbandieri-db
restart: unless-stopped
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- ./data/postgres:/var/lib/postgresql/data
ports:
- "5432:5432"
redis:
image: redis:7
container_name: contrabbandieri-redis
restart: unless-stopped
ports:
- "6379:6379"
volumes:
- ./data/redis:/data
```
---
## 13.1 Backup e migrazione server
Poiché i volumi sono bind-mount locali, la migrazione minima consiste in:
1. fermare i container;
2. fare un dump PostgreSQL;
3. copiare repository, `.env` e cartella `data/backups`;
4. riavviare Docker Compose sul nuovo host.
Backup PostgreSQL:
```bash
docker compose exec db pg_dump -U "$POSTGRES_USER" "$POSTGRES_DB" > ./data/backups/game_$(date +%Y%m%d_%H%M).sql
```
Restore su nuovo server:
```bash
cat ./data/backups/game.sql | docker compose exec -T db psql -U "$POSTGRES_USER" "$POSTGRES_DB"
```
Spegnimento controllato prima di copiare dati grezzi:
```bash
docker compose down
tar -czf contrabbandieri-data.tar.gz data .env docker-compose.yml
```
Nota: il dump SQL è preferibile alla copia a caldo di `data/postgres`, perché evita inconsistenze se il database è in esecuzione.
---
## 14. Dockerfile backend
```dockerfile
FROM node:22-alpine AS base
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npx prisma generate
RUN npm run build
FROM node:22-alpine AS production
WORKDIR /app
ENV NODE_ENV=production
COPY package*.json ./
RUN npm ci --omit=dev
COPY --from=base /app/dist ./dist
COPY --from=base /app/prisma ./prisma
COPY --from=base /app/node_modules/.prisma ./node_modules/.prisma
EXPOSE 3000
CMD ["node", "dist/server.js"]
```
---
## 15. Package.json base
```json
{
"name": "contrabbandieri-server",
"version": "0.1.0",
"type": "module",
"scripts": {
"dev": "tsx watch src/server.ts",
"build": "tsc",
"start": "node dist/server.js",
"prisma:generate": "prisma generate",
"prisma:migrate": "prisma migrate dev",
"prisma:deploy": "prisma migrate deploy",
"prisma:seed": "tsx prisma/seed.ts"
},
"dependencies": {
"@fastify/cors": "latest",
"@fastify/jwt": "latest",
"@fastify/rate-limit": "latest",
"@prisma/client": "latest",
"argon2": "latest",
"dotenv": "latest",
"fastify": "latest",
"ioredis": "latest",
"node-cron": "latest",
"socket.io": "latest",
"zod": "latest"
},
"devDependencies": {
"@types/node": "latest",
"prisma": "latest",
"tsx": "latest",
"typescript": "latest"
}
}
```
---
## 16. Deploy su Linux
### Installazione base
```bash
sudo apt update
sudo apt install -y ca-certificates curl git ufw
```
### Docker
```bash
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
```
Disconnettersi e riconnettersi.
### Avvio
```bash
git clone <repo>
cd contrabbandieri-server
cp .env.example .env
nano .env
docker compose up -d --build
```
### Migrazioni
```bash
docker compose exec api npx prisma migrate deploy
docker compose exec api npx prisma db seed
```
---
## 17. Backup e migrazione
### Backup database
```bash
docker exec contrabbandieri-db pg_dump -U gameuser game > backup.sql
```
### Restore database
```bash
cat backup.sql | docker exec -i contrabbandieri-db psql -U gameuser game
```
### File da portare su altro server
```text
.env
backup.sql
docker-compose.yml
nginx/nginx.conf
```
Il codice può essere recuperato da Git.
---
## 18. MVP funzionale minimo
Ordine di implementazione:
1. setup Fastify + TypeScript;
2. Docker Compose con PostgreSQL e Redis;
3. Prisma schema;
4. seed città e item;
5. register/login JWT;
6. player profile;
7. inventory;
8. market current;
9. buy/sell;
10. missioni disponibili;
11. start/claim mission;
12. scheduler prezzi;
13. leaderboard;
14. Socket.IO notifiche.
---
## 19. Seed iniziale
Città:
```text
Milano: economia alta, rischio medio
Napoli: economia media, rischio alto
Palermo: economia bassa, rischio alto
Torino: economia media, rischio basso
Bari: economia bassa, rischio medio
```
Item:
```text
Gioielli, base 500
Componenti elettronici, base 350
Documenti falsi, base 700
Auto rubate, base 1800
Informazioni riservate, base 1200
Armi leggere, base 1500
```
Missioni template:
```text
Consegna discreta
Recupero merce
Scambio al porto
Infiltrazione uffici
Trasporto notturno
```
---
## 20. Estensioni successive
Dopo MVP:
- clan/gang;
- territori;
- PvP asincrono;
- aste tra giocatori;
- crafting oggetti;
- stagioni competitive;
- anti-cheat comportamentale;
- pannello admin web;
- log economico completo;
- bilanciamento automatico delleconomia.