1055 lines
18 KiB
Markdown
1055 lines
18 KiB
Markdown
# 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 l’equilibrio.
|
||
|
||
---
|
||
|
||
## 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 dell’economia.
|