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>
This commit is contained in:
2026-06-09 23:41:27 +02:00
parent 34708340f1
commit 8a91a298a7
4 changed files with 419 additions and 0 deletions
+81
View File
@@ -0,0 +1,81 @@
import { PrismaClient } from '@prisma/client';
import { computePrices } from '../src/modules/market/pricing.js';
import { randomFloat } from '../src/shared/validators.js';
const prisma = new PrismaClient();
/** Città iniziali (blueprint §19): economia e rischio differenziati. */
const CITIES = [
{ name: 'Milano', riskLevel: 3, policePressure: 1.1, economyModifier: 1.25 },
{ name: 'Napoli', riskLevel: 4, policePressure: 1.3, economyModifier: 1.0 },
{ name: 'Palermo', riskLevel: 4, policePressure: 1.25, economyModifier: 0.85 },
{ name: 'Torino', riskLevel: 2, policePressure: 0.9, economyModifier: 1.0 },
{ name: 'Bari', riskLevel: 3, policePressure: 1.0, economyModifier: 0.85 },
];
/** Item commerciabili iniziali (blueprint §19). */
const ITEMS = [
{ name: 'Gioielli', basePrice: 500, rarity: 2, illegalLevel: 2, weight: 1 },
{ name: 'Componenti elettronici', basePrice: 350, rarity: 1, illegalLevel: 1, weight: 2 },
{ name: 'Documenti falsi', basePrice: 700, rarity: 3, illegalLevel: 4, weight: 1 },
{ name: 'Auto rubate', basePrice: 1800, rarity: 4, illegalLevel: 5, weight: 10 },
{ name: 'Informazioni riservate', basePrice: 1200, rarity: 4, illegalLevel: 3, weight: 1 },
{ name: 'Armi leggere', basePrice: 1500, rarity: 3, illegalLevel: 5, weight: 4 },
];
async function main(): Promise<void> {
for (const city of CITIES) {
await prisma.city.upsert({
where: { name: city.name },
create: city,
update: {
riskLevel: city.riskLevel,
policePressure: city.policePressure,
economyModifier: city.economyModifier,
},
});
}
console.log(`Città: ${CITIES.length}`);
for (const item of ITEMS) {
await prisma.item.upsert({
where: { name: item.name },
create: item,
update: {
basePrice: item.basePrice,
rarity: item.rarity,
illegalLevel: item.illegalLevel,
weight: item.weight,
},
});
}
console.log(`Item: ${ITEMS.length}`);
// Listino iniziale per ogni coppia città/item, con domanda e offerta
// leggermente casuali. I prezzi già esistenti non vengono toccati.
const cities = await prisma.city.findMany();
const items = await prisma.item.findMany();
let createdPrices = 0;
for (const city of cities) {
for (const item of items) {
const demand = Math.round(randomFloat(0.8, 1.2) * 100) / 100;
const supply = Math.round(randomFloat(0.8, 1.2) * 100) / 100;
const prices = computePrices(item.basePrice, city.economyModifier, demand, supply, 1);
const result = await prisma.marketPrice.upsert({
where: { cityId_itemId: { cityId: city.id, itemId: item.id } },
create: { cityId: city.id, itemId: item.id, demand, supply, ...prices },
update: {},
});
if (result) createdPrices += 1;
}
}
console.log(`Prezzi di mercato: ${createdPrices}`);
console.log('Seed completato.');
}
main()
.catch((err) => {
console.error(err);
process.exit(1);
})
.finally(() => prisma.$disconnect());