From 145c7f89dc4be4f74ac87c827966de94f308336a Mon Sep 17 00:00:00 2001 From: Davide Grilli Date: Tue, 9 Jun 2026 22:40:43 +0200 Subject: [PATCH] initial commit --- Blueprint_Client.md | 1054 +++++++++++++++++++++++++++++++++++++++++++ Blueprint_Server.md | 1054 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 2108 insertions(+) create mode 100644 Blueprint_Client.md create mode 100644 Blueprint_Server.md diff --git a/Blueprint_Client.md b/Blueprint_Client.md new file mode 100644 index 0000000..4632768 --- /dev/null +++ b/Blueprint_Client.md @@ -0,0 +1,1054 @@ +# 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 +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. diff --git a/Blueprint_Server.md b/Blueprint_Server.md new file mode 100644 index 0000000..4632768 --- /dev/null +++ b/Blueprint_Server.md @@ -0,0 +1,1054 @@ +# 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 +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.