first commit

This commit is contained in:
a.alinaghipour
2026-04-05 15:53:20 +03:30
commit aa9ed69dd2
96 changed files with 7721 additions and 0 deletions

252
prisma/schema.prisma Normal file
View File

@@ -0,0 +1,252 @@
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
enum Role {
USER
ADMIN
}
enum Position {
GK
DEF
MID
FWD
}
enum MatchStage {
GROUP
ROUND_OF_16
QUARTER_FINAL
SEMI_FINAL
THIRD_PLACE
FINAL
}
enum MatchStatus {
SCHEDULED
LIVE
FINISHED
}
enum TeamStatus {
ACTIVE
INACTIVE
}
enum PaymentStatus {
PENDING
SUCCESS
FAILED
}
enum EventType {
GOAL
ASSIST
YELLOW_CARD
RED_CARD
SECOND_YELLOW
SUBSTITUTION_IN
SUBSTITUTION_OUT
INJURY_NO_SUB
CLEAN_SHEET
PENALTY_SAVED
PENALTY_MISSED
OWN_GOAL
EXTRA_TIME_BONUS
MOTM
}
model Country {
id String @id @default(cuid())
name String @unique
code String @unique
flagUrl String?
defaultFormation String @default("4-3-3")
group Group? @relation(fields: [groupId], references: [id])
groupId String?
isEliminated Boolean @default(false)
players Player[]
homeMatches Match[] @relation("HomeTeam")
awayMatches Match[] @relation("AwayTeam")
}
model Group {
id String @id @default(cuid())
name String @unique
countries Country[]
}
model Player {
id String @id @default(cuid())
name String
position Position
countryId String
country Country @relation(fields: [countryId], references: [id])
price Float @default(5.0)
totalPoints Int @default(0)
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
matchStats PlayerMatchStat[]
teamPlayers TeamPlayer[]
events MatchEvent[]
}
model Match {
id String @id @default(cuid())
homeTeamId String
awayTeamId String
homeTeam Country @relation("HomeTeam", fields: [homeTeamId], references: [id])
awayTeam Country @relation("AwayTeam", fields: [awayTeamId], references: [id])
homeScore Int?
awayScore Int?
stage MatchStage @default(GROUP)
status MatchStatus @default(SCHEDULED)
matchDate DateTime
roundId String?
round Round? @relation(fields: [roundId], references: [id])
playerStats PlayerMatchStat[]
events MatchEvent[]
lineups MatchLineup[]
createdAt DateTime @default(now())
}
model Round {
id String @id @default(cuid())
number Int @unique
name String
isActive Boolean @default(false)
deadline DateTime
matches Match[]
createdAt DateTime @default(now())
}
model MatchEvent {
id String @id @default(cuid())
matchId String
playerId String
match Match @relation(fields: [matchId], references: [id], onDelete: Cascade)
player Player @relation(fields: [playerId], references: [id], onDelete: Cascade)
type EventType
minute Int?
extraInfo String?
createdAt DateTime @default(now())
}
model MatchLineup {
id String @id @default(cuid())
matchId String
countryId String
match Match @relation(fields: [matchId], references: [id], onDelete: Cascade)
formation String
playerIds String[]
}
model PlayerMatchStat {
id String @id @default(cuid())
playerId String
matchId String
player Player @relation(fields: [playerId], references: [id], onDelete: Cascade)
match Match @relation(fields: [matchId], references: [id], onDelete: Cascade)
goals Int @default(0)
assists Int @default(0)
yellowCards Int @default(0)
redCards Int @default(0)
minutesPlayed Int @default(0)
cleanSheet Boolean @default(false)
penaltySaved Int @default(0)
penaltyMissed Int @default(0)
ownGoals Int @default(0)
isMotm Boolean @default(false)
extraTimeBonus Int @default(0)
points Int @default(0)
@@unique([playerId, matchId])
}
model ScoringRule {
id String @id @default(cuid())
position Position
eventType EventType
points Int
updatedAt DateTime @updatedAt
updatedBy String?
@@unique([position, eventType])
}
model User {
id String @id @default(cuid())
name String?
email String @unique
password String
role Role @default(USER)
createdAt DateTime @default(now())
team Team?
sessions Session[]
payments Payment[]
}
model Session {
id String @id @default(cuid())
sessionToken String @unique
userId String
expires DateTime
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
model Team {
id String @id @default(cuid())
name String
userId String @unique
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
budget Float @default(100.0)
totalPoints Int @default(0)
formation String @default("4-3-3")
status TeamStatus @default(INACTIVE)
createdAt DateTime @default(now())
players TeamPlayer[]
}
model TeamPlayer {
teamId String
playerId String
isCaptain Boolean @default(false)
isViceCaptain Boolean @default(false)
isBench Boolean @default(false)
positionIndex Int @default(0)
team Team @relation(fields: [teamId], references: [id], onDelete: Cascade)
player Player @relation(fields: [playerId], references: [id], onDelete: Cascade)
@@id([teamId, playerId])
}
model Package {
id String @id @default(cuid())
name String
budgetBonus Float
price Int
description String?
isActive Boolean @default(true)
payments Payment[]
}
model Payment {
id String @id @default(cuid())
userId String
packageId String
user User @relation(fields: [userId], references: [id])
package Package @relation(fields: [packageId], references: [id])
amount Int
authority String? @unique
refId String?
status PaymentStatus @default(PENDING)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}

235
prisma/seed.ts Normal file
View File

@@ -0,0 +1,235 @@
import { PrismaClient } from "@prisma/client";
import bcrypt from "bcryptjs";
// DEFAULT_RULES رو مستقیم اینجا تعریف می‌کنیم تا از @/ alias استفاده نکنیم
const DEFAULT_RULES = {
GK: { GOAL: 10, ASSIST: 3, YELLOW_CARD: -1, RED_CARD: -3, SECOND_YELLOW: -3, CLEAN_SHEET: 6, PENALTY_SAVED: 5, PENALTY_MISSED: -2, OWN_GOAL: -2, MOTM: 3, EXTRA_TIME_BONUS: 1, INJURY_NO_SUB: -1 },
DEF: { GOAL: 8, ASSIST: 3, YELLOW_CARD: -1, RED_CARD: -3, SECOND_YELLOW: -3, CLEAN_SHEET: 4, PENALTY_SAVED: 0, PENALTY_MISSED: -2, OWN_GOAL: -2, MOTM: 3, EXTRA_TIME_BONUS: 1, INJURY_NO_SUB: -1 },
MID: { GOAL: 5, ASSIST: 3, YELLOW_CARD: -2, RED_CARD: -3, SECOND_YELLOW: -3, CLEAN_SHEET: 1, PENALTY_SAVED: 0, PENALTY_MISSED: -2, OWN_GOAL: -2, MOTM: 3, EXTRA_TIME_BONUS: 1, INJURY_NO_SUB: -1 },
FWD: { GOAL: 4, ASSIST: 3, YELLOW_CARD: -1, RED_CARD: -3, SECOND_YELLOW: -3, CLEAN_SHEET: 0, PENALTY_SAVED: 0, PENALTY_MISSED: -2, OWN_GOAL: -2, MOTM: 3, EXTRA_TIME_BONUS: 1, INJURY_NO_SUB: -1 },
} as const;
const prisma = new PrismaClient();
// ترکیب پیش‌فرض تیم‌های ملی معروف (بر اساس آخرین اطلاعات)
const COUNTRY_FORMATIONS: Record<string, string> = {
BRA: "4-3-3", FRA: "4-3-3", ARG: "4-3-3", ENG: "4-3-3",
ESP: "4-3-3", GER: "4-2-3-1", POR: "4-3-3", NED: "4-3-3",
BEL: "4-3-3", CRO: "4-3-3", MAR: "4-3-3", IRN: "4-5-1",
URU: "4-3-3", SEN: "4-3-3", KOR: "4-3-3", JPN: "4-3-3",
MEX: "4-3-3", USA: "4-3-3", CAN: "4-3-3", AUS: "4-3-3",
POL: "4-3-3", DEN: "4-3-3", SUI: "4-2-3-1", SRB: "3-4-3",
WAL: "5-3-2", TUN: "4-3-3", CMR: "4-3-3", GHA: "4-2-3-1",
ECU: "4-3-3", QAT: "5-3-2", KSA: "4-3-3", CRC: "5-4-1",
};
const COUNTRIES = [
{ name: "برزیل", code: "BRA", flag: "🇧🇷", group: "G" },
{ name: "سربیا", code: "SRB", flag: "🇷🇸", group: "G" },
{ name: "سوئیس", code: "SUI", flag: "🇨🇭", group: "G" },
{ name: "کامرون", code: "CMR", flag: "🇨🇲", group: "G" },
{ name: "فرانسه", code: "FRA", flag: "🇫🇷", group: "D" },
{ name: "استرالیا", code: "AUS", flag: "🇦🇺", group: "D" },
{ name: "دانمارک", code: "DEN", flag: "🇩🇰", group: "D" },
{ name: "تونس", code: "TUN", flag: "🇹🇳", group: "D" },
{ name: "آرژانتین", code: "ARG", flag: "🇦🇷", group: "C" },
{ name: "عربستان", code: "KSA", flag: "🇸🇦", group: "C" },
{ name: "مکزیک", code: "MEX", flag: "🇲🇽", group: "C" },
{ name: "لهستان", code: "POL", flag: "🇵🇱", group: "C" },
{ name: "ایران", code: "IRN", flag: "🇮🇷", group: "B" },
{ name: "انگلیس", code: "ENG", flag: "🏴󠁧󠁢󠁥󠁮󠁧󠁿", group: "B" },
{ name: "آمریکا", code: "USA", flag: "🇺🇸", group: "B" },
{ name: "ولز", code: "WAL", flag: "🏴󠁧󠁢󠁷󠁬󠁳󠁿", group: "B" },
{ name: "آلمان", code: "GER", flag: "🇩🇪", group: "E" },
{ name: "ژاپن", code: "JPN", flag: "🇯🇵", group: "E" },
{ name: "اسپانیا", code: "ESP", flag: "🇪🇸", group: "E" },
{ name: "کاستاریکا", code: "CRC", flag: "🇨🇷", group: "E" },
{ name: "بلژیک", code: "BEL", flag: "🇧🇪", group: "F" },
{ name: "کانادا", code: "CAN", flag: "🇨🇦", group: "F" },
{ name: "مراکش", code: "MAR", flag: "🇲🇦", group: "F" },
{ name: "کرواسی", code: "CRO", flag: "🇭🇷", group: "F" },
{ name: "پرتغال", code: "POR", flag: "🇵🇹", group: "H" },
{ name: "غنا", code: "GHA", flag: "🇬🇭", group: "H" },
{ name: "اروگوئه", code: "URU", flag: "🇺🇾", group: "H" },
{ name: "کره جنوبی", code: "KOR", flag: "🇰🇷", group: "H" },
{ name: "هلند", code: "NED", flag: "🇳🇱", group: "A" },
{ name: "سنگال", code: "SEN", flag: "🇸🇳", group: "A" },
{ name: "اکوادور", code: "ECU", flag: "🇪🇨", group: "A" },
{ name: "قطر", code: "QAT", flag: "🇶🇦", group: "A" },
];
const PLAYERS_DATA = [
{ name: "آلیسون بکر", pos: "GK", code: "BRA", price: 6.0, pts: 42 },
{ name: "تیاگو سیلوا", pos: "DEF", code: "BRA", price: 6.5, pts: 38 },
{ name: "مارکینیوس", pos: "DEF", code: "BRA", price: 6.0, pts: 35 },
{ name: "کاسمیرو", pos: "MID", code: "BRA", price: 8.0, pts: 52 },
{ name: "نیمار", pos: "FWD", code: "BRA", price: 11.5, pts: 68 },
{ name: "وینیسیوس جونیور", pos: "FWD", code: "BRA", price: 10.5, pts: 72 },
{ name: "ریچارلیسون", pos: "FWD", code: "BRA", price: 8.5, pts: 55 },
{ name: "هوگو لوریس", pos: "GK", code: "FRA", price: 6.0, pts: 40 },
{ name: "رافائل واران", pos: "DEF", code: "FRA", price: 6.5, pts: 36 },
{ name: "کیلیان امباپه", pos: "FWD", code: "FRA", price: 12.5, pts: 88 },
{ name: "آنتوان گریزمان", pos: "MID", code: "FRA", price: 9.5, pts: 62 },
{ name: "اولیویه ژیرو", pos: "FWD", code: "FRA", price: 7.5, pts: 48 },
{ name: "اوسمان دمبله", pos: "FWD", code: "FRA", price: 8.0, pts: 44 },
{ name: "امیلیانو مارتینز", pos: "GK", code: "ARG", price: 6.5, pts: 55 },
{ name: "لیونل مسی", pos: "FWD", code: "ARG", price: 12.5, pts: 92 },
{ name: "خولیان آلوارز", pos: "FWD", code: "ARG", price: 8.5, pts: 60 },
{ name: "رودریگو دپاول", pos: "MID", code: "ARG", price: 7.5, pts: 45 },
{ name: "نیکولاس اوتامندی", pos: "DEF", code: "ARG", price: 5.5, pts: 32 },
{ name: "جوردن پیکفورد", pos: "GK", code: "ENG", price: 5.5, pts: 38 },
{ name: "جود بلینگهام", pos: "MID", code: "ENG", price: 10.5, pts: 75 },
{ name: "هری کین", pos: "FWD", code: "ENG", price: 11.0, pts: 70 },
{ name: "بوکایو ساکا", pos: "MID", code: "ENG", price: 8.5, pts: 58 },
{ name: "فیل فودن", pos: "MID", code: "ENG", price: 9.0, pts: 62 },
{ name: "جان استونز", pos: "DEF", code: "ENG", price: 6.0, pts: 34 },
{ name: "اونای سیمون", pos: "GK", code: "ESP", price: 5.5, pts: 36 },
{ name: "پدری", pos: "MID", code: "ESP", price: 9.0, pts: 60 },
{ name: "گاوی", pos: "MID", code: "ESP", price: 8.5, pts: 55 },
{ name: "آلوارو موراتا", pos: "FWD", code: "ESP", price: 7.5, pts: 46 },
{ name: "دنی اولمو", pos: "MID", code: "ESP", price: 7.0, pts: 42 },
{ name: "مانوئل نویر", pos: "GK", code: "GER", price: 6.0, pts: 38 },
{ name: "توماس مولر", pos: "MID", code: "GER", price: 8.0, pts: 50 },
{ name: "کای هاورتز", pos: "MID", code: "GER", price: 8.5, pts: 52 },
{ name: "یامله موسیالا", pos: "MID", code: "GER", price: 9.0, pts: 65 },
{ name: "آنتونیو رودیگر", pos: "DEF", code: "GER", price: 5.5, pts: 30 },
{ name: "دیوگو کوستا", pos: "GK", code: "POR", price: 5.5, pts: 35 },
{ name: "کریستیانو رونالدو", pos: "FWD", code: "POR", price: 11.0, pts: 65 },
{ name: "برونو فرناندز", pos: "MID", code: "POR", price: 10.0, pts: 70 },
{ name: "رافائل لئائو", pos: "FWD", code: "POR", price: 8.5, pts: 55 },
{ name: "روبن دیاز", pos: "DEF", code: "POR", price: 6.0, pts: 36 },
{ name: "علیرضا بیرانوند", pos: "GK", code: "IRN", price: 5.0, pts: 28 },
{ name: "مهدی طارمی", pos: "FWD", code: "IRN", price: 8.0, pts: 50 },
{ name: "سردار آزمون", pos: "FWD", code: "IRN", price: 7.5, pts: 44 },
{ name: "علی کریمی", pos: "MID", code: "IRN", price: 6.0, pts: 32 },
{ name: "رامین رضاییان", pos: "DEF", code: "IRN", price: 5.0, pts: 22 },
{ name: "یاسین بونو", pos: "GK", code: "MAR", price: 6.5, pts: 58 },
{ name: "اشرف حکیمی", pos: "DEF", code: "MAR", price: 8.0, pts: 62 },
{ name: "حکیم زیاش", pos: "MID", code: "MAR", price: 7.5, pts: 48 },
{ name: "یوسف النصیری", pos: "FWD", code: "MAR", price: 7.0, pts: 45 },
{ name: "دومینیک لیواکوویچ", pos: "GK", code: "CRO", price: 6.0, pts: 50 },
{ name: "لوکا مودریچ", pos: "MID", code: "CRO", price: 9.5, pts: 68 },
{ name: "ایوان پریشیچ", pos: "MID", code: "CRO", price: 7.5, pts: 48 },
{ name: "آندره کراماریچ", pos: "FWD", code: "CRO", price: 7.0, pts: 44 },
{ name: "آندریس نوپرت", pos: "GK", code: "NED", price: 5.5, pts: 36 },
{ name: "ویرخیل فان دایک", pos: "DEF", code: "NED", price: 7.0, pts: 48 },
{ name: "دنزل دامفریس", pos: "DEF", code: "NED", price: 7.5, pts: 52 },
{ name: "کودی گاکپو", pos: "FWD", code: "NED", price: 8.0, pts: 58 },
{ name: "تیبو کورتوا", pos: "GK", code: "BEL", price: 6.5, pts: 44 },
{ name: "کوین دبروینه", pos: "MID", code: "BEL", price: 11.0, pts: 72 },
{ name: "رومله لوکاکو", pos: "FWD", code: "BEL", price: 9.5, pts: 55 },
];
async function main() {
console.log("🌱 Seeding...");
// ─── ادمین و کاربران ─────────────────────────────────
const adminPwd = await bcrypt.hash("admin123", 10);
await prisma.user.upsert({ where: { email: "admin@worldcup.com" }, update: {},
create: { email: "admin@worldcup.com", name: "ادمین", password: adminPwd, role: "ADMIN" } });
const userPwd = await bcrypt.hash("user123", 10);
const users = [];
for (const u of [{ email: "ali@test.com", name: "علی احمدی" }, { email: "sara@test.com", name: "سارا رضایی" }, { email: "reza@test.com", name: "رضا محمدی" }]) {
const user = await prisma.user.upsert({ where: { email: u.email }, update: {}, create: { ...u, password: userPwd } });
users.push(user);
}
// ─── گروه‌ها ──────────────────────────────────────────
const groupMap: Record<string, string> = {};
for (const name of ["A","B","C","D","E","F","G","H"]) {
const g = await prisma.group.upsert({ where: { name }, update: {}, create: { name } });
groupMap[name] = g.id;
}
// ─── تیم‌های ملی ─────────────────────────────────────
const countryMap: Record<string, string> = {};
for (const c of COUNTRIES) {
const country = await prisma.country.upsert({ where: { code: c.code }, update: { flagUrl: c.flag, defaultFormation: COUNTRY_FORMATIONS[c.code] ?? "4-3-3" },
create: { name: c.name, code: c.code, flagUrl: c.flag, groupId: groupMap[c.group], defaultFormation: COUNTRY_FORMATIONS[c.code] ?? "4-3-3" } });
countryMap[c.code] = country.id;
}
// ─── بازیکنان ─────────────────────────────────────────
for (const p of PLAYERS_DATA) {
const countryId = countryMap[p.code];
if (!countryId) continue;
await prisma.player.create({ data: { name: p.name, position: p.pos as any, countryId, price: p.price, totalPoints: p.pts } });
}
// ─── قوانین امتیازدهی پیش‌فرض ────────────────────────
const positions = ["GK", "DEF", "MID", "FWD"] as const;
for (const pos of positions) {
const rules = DEFAULT_RULES[pos];
for (const [eventType, points] of Object.entries(rules)) {
await prisma.scoringRule.upsert({
where: { position_eventType: { position: pos, eventType: eventType as any } },
update: { points: points as number },
create: { position: pos, eventType: eventType as any, points: points as number },
});
}
}
// ─── دورها ────────────────────────────────────────────
const round1 = await prisma.round.upsert({ where: { number: 1 }, update: {},
create: { number: 1, name: "دور اول - مرحله گروهی", isActive: true, deadline: new Date("2026-06-15T10:00:00Z") } });
const round2 = await prisma.round.upsert({ where: { number: 2 }, update: {},
create: { number: 2, name: "دور دوم - مرحله گروهی", deadline: new Date("2026-06-22T10:00:00Z") } });
const round3 = await prisma.round.upsert({ where: { number: 3 }, update: {},
create: { number: 3, name: "دور سوم - مرحله گروهی", deadline: new Date("2026-06-29T10:00:00Z") } });
const round4 = await prisma.round.upsert({ where: { number: 4 }, update: {},
create: { number: 4, name: "دور چهارم - یک‌هشتم نهایی", deadline: new Date("2026-07-05T10:00:00Z") } });
// ─── بازی‌ها ──────────────────────────────────────────
const matchesData = [
// دور اول - بازی اول هر تیم
{ home: "QAT", away: "ECU", hS: 0, aS: 2, st: "FINISHED", date: "2026-06-20T16:00:00Z", rId: round1.id },
{ home: "ENG", away: "IRN", hS: 6, aS: 2, st: "FINISHED", date: "2026-06-21T13:00:00Z", rId: round1.id },
{ home: "ARG", away: "KSA", hS: 1, aS: 2, st: "FINISHED", date: "2026-06-22T10:00:00Z", rId: round1.id },
{ home: "FRA", away: "AUS", hS: 4, aS: 1, st: "FINISHED", date: "2026-06-22T19:00:00Z", rId: round1.id },
{ home: "GER", away: "JPN", hS: 1, aS: 2, st: "FINISHED", date: "2026-06-23T13:00:00Z", rId: round1.id },
{ home: "ESP", away: "CRC", hS: 7, aS: 0, st: "FINISHED", date: "2026-06-23T19:00:00Z", rId: round1.id },
{ home: "BEL", away: "CAN", hS: 1, aS: 0, st: "FINISHED", date: "2026-06-23T16:00:00Z", rId: round1.id },
{ home: "BRA", away: "SRB", hS: 2, aS: 0, st: "FINISHED", date: "2026-06-24T19:00:00Z", rId: round1.id },
{ home: "POR", away: "GHA", hS: 3, aS: 2, st: "FINISHED", date: "2026-06-24T16:00:00Z", rId: round1.id },
{ home: "MAR", away: "CRO", hS: 0, aS: 0, st: "FINISHED", date: "2026-06-23T10:00:00Z", rId: round1.id },
{ home: "NED", away: "SEN", hS: 2, aS: 0, st: "FINISHED", date: "2026-06-21T16:00:00Z", rId: round1.id },
// دور دوم - بازی دوم هر تیم
{ home: "IRN", away: "WAL", hS: 2, aS: 0, st: "FINISHED", date: "2026-06-25T13:00:00Z", rId: round2.id },
{ home: "FRA", away: "DEN", hS: 2, aS: 1, st: "FINISHED", date: "2026-06-26T19:00:00Z", rId: round2.id },
{ home: "ARG", away: "MEX", hS: 2, aS: 0, st: "FINISHED", date: "2026-06-26T22:00:00Z", rId: round2.id },
{ home: "BRA", away: "SUI", hS: 1, aS: 0, st: "FINISHED", date: "2026-06-28T19:00:00Z", rId: round2.id },
{ home: "ENG", away: "USA", hS: 0, aS: 0, st: "FINISHED", date: "2026-06-25T19:00:00Z", rId: round2.id },
// دور سوم - بازی سوم گروهی
{ home: "BRA", away: "CMR", hS: 1, aS: 0, st: "SCHEDULED", date: "2026-07-02T19:00:00Z", rId: round3.id },
{ home: "FRA", away: "TUN", hS: null, aS: null, st: "SCHEDULED", date: "2026-07-01T16:00:00Z", rId: round3.id },
// دور چهارم - یک‌هشتم
{ home: "NED", away: "ARG", hS: 2, aS: 2, st: "FINISHED", date: "2026-12-09T19:00:00Z", rId: round4.id, stage: "ROUND_OF_16" },
{ home: "FRA", away: "ENG", hS: 2, aS: 1, st: "FINISHED", date: "2026-12-10T19:00:00Z", rId: round4.id, stage: "ROUND_OF_16" },
{ home: "ARG", away: "FRA", hS: 3, aS: 3, st: "FINISHED", date: "2026-12-18T15:00:00Z", rId: round4.id, stage: "FINAL" },
];
for (const m of matchesData) {
const homeId = countryMap[m.home], awayId = countryMap[m.away];
if (!homeId || !awayId) continue;
await prisma.match.create({ data: {
homeTeamId: homeId, awayTeamId: awayId,
homeScore: m.hS ?? null, awayScore: m.aS ?? null,
status: m.st as any, stage: (m.stage ?? "GROUP") as any,
matchDate: new Date(m.date), roundId: m.rId,
}});
}
// ─── پکیج‌ها ──────────────────────────────────────────
for (const pkg of [
{ id: "pkg-silver", name: "پکیج نقره‌ای", budgetBonus: 10, price: 50000, description: "۱۰ میلیون به بودجه اضافه کن" },
{ id: "pkg-gold", name: "پکیج طلایی", budgetBonus: 20, price: 90000, description: "۲۰ میلیون به بودجه اضافه کن" },
{ id: "pkg-diamond", name: "پکیج الماس", budgetBonus: 30, price: 120000, description: "۳۰ میلیون به بودجه اضافه کن" },
]) {
await prisma.package.upsert({ where: { id: pkg.id }, update: {}, create: pkg });
}
console.log("✅ Seed done! admin@worldcup.com / admin123 | ali@test.com / user123");
}
main().catch(console.error).finally(() => prisma.$disconnect());