update
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Backfill CaseKeyProgress from open_case transactions.
|
||||
* Run after: npx prisma db push
|
||||
*
|
||||
* npm run db:backfill-case-keys --prefix server
|
||||
*/
|
||||
import { prisma } from '../src/db.js';
|
||||
import { progressFromTotalOpens } from '../src/caseKeys.js';
|
||||
|
||||
async function main() {
|
||||
const txs = await prisma.transaction.findMany({
|
||||
where: { type: 'open_case' },
|
||||
select: { userId: true, meta: true },
|
||||
orderBy: { createdAt: 'asc' },
|
||||
});
|
||||
|
||||
const counts = new Map();
|
||||
for (const tx of txs) {
|
||||
let meta;
|
||||
try {
|
||||
meta = JSON.parse(tx.meta || '{}');
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
const caseId = Number(meta.caseId);
|
||||
if (!caseId) continue;
|
||||
const key = `${tx.userId}:${caseId}`;
|
||||
counts.set(key, (counts.get(key) || 0) + 1);
|
||||
}
|
||||
|
||||
let upserted = 0;
|
||||
for (const [key, totalOpens] of counts) {
|
||||
const [userId, caseId] = key.split(':').map(Number);
|
||||
const { keys, opensSinceLastKey } = progressFromTotalOpens(totalOpens);
|
||||
|
||||
await prisma.caseKeyProgress.upsert({
|
||||
where: { userId_caseId: { userId, caseId } },
|
||||
create: { userId, caseId, keys, opensSinceLastKey, totalOpens },
|
||||
update: { keys, opensSinceLastKey, totalOpens },
|
||||
});
|
||||
upserted += 1;
|
||||
}
|
||||
|
||||
console.log(`Backfilled ${upserted} user/case key progress rows from ${txs.length} open_case transactions`);
|
||||
}
|
||||
|
||||
main()
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
})
|
||||
.finally(() => prisma.$disconnect());
|
||||
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Backfill CatalogEntry from open_case transactions.
|
||||
* Run after: npx prisma db push
|
||||
*
|
||||
* npm run db:backfill-catalog --prefix server
|
||||
*/
|
||||
import { prisma } from '../src/db.js';
|
||||
|
||||
async function main() {
|
||||
const txs = await prisma.transaction.findMany({
|
||||
where: { type: 'open_case' },
|
||||
select: { userId: true, meta: true, createdAt: true },
|
||||
orderBy: { createdAt: 'asc' },
|
||||
});
|
||||
|
||||
const pairs = new Map();
|
||||
for (const tx of txs) {
|
||||
let meta;
|
||||
try {
|
||||
meta = JSON.parse(tx.meta || '{}');
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
const caseId = Number(meta.caseId);
|
||||
const itemId = Number(meta.itemId);
|
||||
if (!caseId || !itemId) continue;
|
||||
const key = `${tx.userId}:${caseId}:${itemId}`;
|
||||
if (!pairs.has(key)) {
|
||||
pairs.set(key, {
|
||||
userId: tx.userId,
|
||||
caseId,
|
||||
itemId,
|
||||
filledAt: tx.createdAt,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let created = 0;
|
||||
let skipped = 0;
|
||||
for (const row of pairs.values()) {
|
||||
try {
|
||||
await prisma.catalogEntry.create({
|
||||
data: {
|
||||
userId: row.userId,
|
||||
caseId: row.caseId,
|
||||
itemId: row.itemId,
|
||||
filledAt: row.filledAt,
|
||||
},
|
||||
});
|
||||
created += 1;
|
||||
} catch {
|
||||
skipped += 1;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(
|
||||
`Backfilled catalog: ${created} new entries, ${skipped} already present (${pairs.size} unique drops from ${txs.length} opens)`
|
||||
);
|
||||
}
|
||||
|
||||
main()
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
})
|
||||
.finally(() => prisma.$disconnect());
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Backfill existing InventoryItem rows with FT mid float + catalog marketValue.
|
||||
* Run after: npx prisma db push
|
||||
*
|
||||
* npm run db:backfill-wear --prefix server
|
||||
*/
|
||||
import { prisma } from '../src/db.js';
|
||||
import { FT_MID_FLOAT } from '../src/wear.js';
|
||||
|
||||
async function main() {
|
||||
const rows = await prisma.inventoryItem.findMany({
|
||||
include: { item: true },
|
||||
});
|
||||
|
||||
let updated = 0;
|
||||
for (const inv of rows) {
|
||||
if (inv.valueCents !== 0) continue;
|
||||
await prisma.inventoryItem.update({
|
||||
where: { id: inv.id },
|
||||
data: {
|
||||
floatValue: FT_MID_FLOAT,
|
||||
wear: 'Field-Tested',
|
||||
paintSeed: 0,
|
||||
valueCents: inv.item.marketValue,
|
||||
},
|
||||
});
|
||||
updated += 1;
|
||||
}
|
||||
|
||||
console.log(`Backfilled ${updated} / ${rows.length} inventory items`);
|
||||
}
|
||||
|
||||
main()
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
})
|
||||
.finally(() => prisma.$disconnect());
|
||||
+90
-14
@@ -11,12 +11,20 @@ model User {
|
||||
id Int @id @default(autoincrement())
|
||||
username String @unique
|
||||
passwordHash String
|
||||
balance Int @default(0)
|
||||
balance BigInt @default(0)
|
||||
osuBalance Int @default(0)
|
||||
lastAdAt DateTime?
|
||||
role String @default("user")
|
||||
avatarUrl String @default("")
|
||||
bio String @default("")
|
||||
autoSellEnabled Boolean @default(false)
|
||||
autoSellThresholdCents BigInt @default(1000)
|
||||
prestigeCount Int @default(0)
|
||||
prestigeChanceBonus Int @default(0)
|
||||
prestigeGainBonus Int @default(0)
|
||||
prestigeKeyOpenReduction Int @default(0)
|
||||
lastConnection DateTime?
|
||||
playTimeSeconds BigInt @default(0)
|
||||
createdAt DateTime @default(now())
|
||||
inventory InventoryItem[]
|
||||
transactions Transaction[]
|
||||
@@ -25,16 +33,22 @@ model User {
|
||||
duelRoomsCreated DuelRoom[] @relation("DuelCreator")
|
||||
duelWins DuelRoom[] @relation("DuelWinner")
|
||||
duelDeposits DuelDeposit[]
|
||||
caseKeyProgress CaseKeyProgress[]
|
||||
catalogEntries CatalogEntry[]
|
||||
prestigeLogs PrestigeLog[]
|
||||
achievements UserAchievement[]
|
||||
}
|
||||
|
||||
model Case {
|
||||
id Int @id @default(autoincrement())
|
||||
name String
|
||||
imageUrl String @default("")
|
||||
price Int
|
||||
price BigInt
|
||||
active Boolean @default(true)
|
||||
createdAt DateTime @default(now())
|
||||
items CaseItem[]
|
||||
keyProgress CaseKeyProgress[]
|
||||
catalogEntries CatalogEntry[]
|
||||
}
|
||||
|
||||
model Item {
|
||||
@@ -42,10 +56,11 @@ model Item {
|
||||
name String
|
||||
imageUrl String @default("")
|
||||
rarity String
|
||||
marketValue Int
|
||||
marketValue BigInt
|
||||
createdAt DateTime @default(now())
|
||||
cases CaseItem[]
|
||||
inventory InventoryItem[]
|
||||
catalogEntries CatalogEntry[]
|
||||
}
|
||||
|
||||
model CaseItem {
|
||||
@@ -60,13 +75,18 @@ model CaseItem {
|
||||
}
|
||||
|
||||
model InventoryItem {
|
||||
id Int @id @default(autoincrement())
|
||||
userId Int
|
||||
itemId Int
|
||||
locked Boolean @default(false)
|
||||
obtainedAt DateTime @default(now())
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
item Item @relation(fields: [itemId], references: [id], onDelete: Cascade)
|
||||
id Int @id @default(autoincrement())
|
||||
userId Int
|
||||
itemId Int
|
||||
locked Boolean @default(false)
|
||||
floatValue Float @default(0.265)
|
||||
wear String @default("Field-Tested")
|
||||
paintSeed Int @default(0)
|
||||
valueCents BigInt @default(0)
|
||||
favorite Boolean @default(false)
|
||||
obtainedAt DateTime @default(now())
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
item Item @relation(fields: [itemId], references: [id], onDelete: Cascade)
|
||||
jackpotDeposits JackpotDeposit[]
|
||||
duelDeposits DuelDeposit[]
|
||||
}
|
||||
@@ -75,7 +95,7 @@ model Transaction {
|
||||
id Int @id @default(autoincrement())
|
||||
userId Int
|
||||
type String
|
||||
amount Int
|
||||
amount BigInt
|
||||
meta String @default("{}")
|
||||
createdAt DateTime @default(now())
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
@@ -97,7 +117,7 @@ model JackpotDeposit {
|
||||
roundId Int
|
||||
userId Int
|
||||
inventoryItemId Int
|
||||
valueCents Int
|
||||
valueCents BigInt
|
||||
createdAt DateTime @default(now())
|
||||
round JackpotRound @relation(fields: [roundId], references: [id], onDelete: Cascade)
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
@@ -112,7 +132,7 @@ model DuelRoom {
|
||||
status String @default("open")
|
||||
maxPlayers Int
|
||||
maxItemsPerPlayer Int
|
||||
minItemValue Int @default(0)
|
||||
minItemValue BigInt @default(0)
|
||||
minPlayersToStart Int @default(2)
|
||||
winnerUserId Int?
|
||||
createdAt DateTime @default(now())
|
||||
@@ -128,7 +148,7 @@ model DuelDeposit {
|
||||
roomId Int
|
||||
userId Int
|
||||
inventoryItemId Int
|
||||
valueCents Int
|
||||
valueCents BigInt
|
||||
createdAt DateTime @default(now())
|
||||
room DuelRoom @relation(fields: [roomId], references: [id], onDelete: Cascade)
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
@@ -136,3 +156,59 @@ model DuelDeposit {
|
||||
|
||||
@@unique([roomId, inventoryItemId])
|
||||
}
|
||||
|
||||
model CaseKeyProgress {
|
||||
id Int @id @default(autoincrement())
|
||||
userId Int
|
||||
caseId Int
|
||||
keys Int @default(1)
|
||||
opensSinceLastKey Int @default(0)
|
||||
totalOpens Int @default(0)
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
case Case @relation(fields: [caseId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([userId, caseId])
|
||||
}
|
||||
|
||||
model CatalogEntry {
|
||||
id Int @id @default(autoincrement())
|
||||
userId Int
|
||||
caseId Int
|
||||
itemId Int
|
||||
filledAt DateTime @default(now())
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
case Case @relation(fields: [caseId], references: [id], onDelete: Cascade)
|
||||
item Item @relation(fields: [itemId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([userId, caseId, itemId])
|
||||
}
|
||||
|
||||
model PrestigeLog {
|
||||
id Int @id @default(autoincrement())
|
||||
userId Int
|
||||
choice String
|
||||
createdAt DateTime @default(now())
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
}
|
||||
|
||||
model Achievement {
|
||||
id Int @id @default(autoincrement())
|
||||
key String @unique
|
||||
name String
|
||||
description String
|
||||
icon String @default("")
|
||||
sortOrder Int @default(0)
|
||||
createdAt DateTime @default(now())
|
||||
unlocks UserAchievement[]
|
||||
}
|
||||
|
||||
model UserAchievement {
|
||||
id Int @id @default(autoincrement())
|
||||
userId Int
|
||||
achievementId Int
|
||||
unlockedAt DateTime @default(now())
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
achievement Achievement @relation(fields: [achievementId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([userId, achievementId])
|
||||
}
|
||||
|
||||
+13
-3
@@ -25,6 +25,8 @@ const ITEMS = [
|
||||
];
|
||||
|
||||
async function main() {
|
||||
await prisma.userAchievement.deleteMany();
|
||||
await prisma.achievement.deleteMany();
|
||||
await prisma.transaction.deleteMany();
|
||||
await prisma.inventoryItem.deleteMany();
|
||||
await prisma.caseItem.deleteMany();
|
||||
@@ -32,12 +34,19 @@ async function main() {
|
||||
await prisma.item.deleteMany();
|
||||
await prisma.user.deleteMany();
|
||||
|
||||
const adminHash = await bcrypt.hash('admin123', 10);
|
||||
const { ACHIEVEMENT_DEFS } = await import('../src/achievements.js');
|
||||
for (const def of ACHIEVEMENT_DEFS) {
|
||||
await prisma.achievement.create({ data: def });
|
||||
}
|
||||
|
||||
const adminUsername = String(process.env.ADMIN_USERNAME || 'admin').trim() || 'admin';
|
||||
const adminPassword = String(process.env.ADMIN_PASSWORD || 'admin123');
|
||||
const adminHash = await bcrypt.hash(adminPassword, 10);
|
||||
const userHash = await bcrypt.hash('player123', 10);
|
||||
|
||||
await prisma.user.create({
|
||||
data: {
|
||||
username: 'admin',
|
||||
username: adminUsername,
|
||||
passwordHash: adminHash,
|
||||
balance: 0,
|
||||
role: 'admin',
|
||||
@@ -144,8 +153,9 @@ async function main() {
|
||||
}
|
||||
|
||||
console.log('Seed complete.');
|
||||
console.log(' admin / admin123');
|
||||
console.log(` admin: ${adminUsername} / ${adminPassword}`);
|
||||
console.log(' player / player123 (balance: 100.00, osu: 100)');
|
||||
console.log(` achievements: ${ACHIEVEMENT_DEFS.length} definitions`);
|
||||
}
|
||||
|
||||
main()
|
||||
|
||||
Reference in New Issue
Block a user