feat(player): 完善 H5 投注端与 API 演示数据
- 球赛/串关/优胜冠军、赛事详情、历史投注与个人资料编辑 - 固定顶栏、公告与底栏,仅内容区滚动 - 底部导航与站点 favicon 使用 logo,登录页精简 - API 种子、冠军盘与历史注单增强 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -3,6 +3,569 @@ import * as bcrypt from 'bcryptjs';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
/** 为演示赛事补齐详情页玩法(与后台 markets 模板一致) */
|
||||
async function seedDemoMarkets(matchId: bigint) {
|
||||
const configs: Array<{
|
||||
marketType: string;
|
||||
period: string;
|
||||
lineValue?: number;
|
||||
sortOrder: number;
|
||||
selections: Array<{ code: string; name: string; odds: number }>;
|
||||
}> = [
|
||||
{
|
||||
marketType: 'FT_1X2',
|
||||
period: 'FT',
|
||||
sortOrder: 1,
|
||||
selections: [
|
||||
{ code: 'HOME', name: '主胜', odds: 2.5 },
|
||||
{ code: 'DRAW', name: '和', odds: 3.2 },
|
||||
{ code: 'AWAY', name: '客胜', odds: 2.8 },
|
||||
],
|
||||
},
|
||||
{
|
||||
marketType: 'FT_HANDICAP',
|
||||
period: 'FT',
|
||||
lineValue: -0.5,
|
||||
sortOrder: 2,
|
||||
selections: [
|
||||
{ code: 'HOME', name: '主 -0.5', odds: 1.9 },
|
||||
{ code: 'AWAY', name: '客 +0.5', odds: 1.9 },
|
||||
],
|
||||
},
|
||||
{
|
||||
marketType: 'FT_OVER_UNDER',
|
||||
period: 'FT',
|
||||
lineValue: 2.5,
|
||||
sortOrder: 3,
|
||||
selections: [
|
||||
{ code: 'OVER', name: '大 2.5', odds: 1.85 },
|
||||
{ code: 'UNDER', name: '小 2.5', odds: 1.95 },
|
||||
],
|
||||
},
|
||||
{
|
||||
marketType: 'FT_ODD_EVEN',
|
||||
period: 'FT',
|
||||
sortOrder: 4,
|
||||
selections: [
|
||||
{ code: 'ODD', name: '单', odds: 1.9 },
|
||||
{ code: 'EVEN', name: '双', odds: 1.9 },
|
||||
],
|
||||
},
|
||||
{
|
||||
marketType: 'HT_1X2',
|
||||
period: 'HT',
|
||||
sortOrder: 5,
|
||||
selections: [
|
||||
{ code: 'HOME', name: '半场主', odds: 3.0 },
|
||||
{ code: 'DRAW', name: '半场和', odds: 2.0 },
|
||||
{ code: 'AWAY', name: '半场客', odds: 3.5 },
|
||||
],
|
||||
},
|
||||
{
|
||||
marketType: 'HT_HANDICAP',
|
||||
period: 'HT',
|
||||
lineValue: -0.5,
|
||||
sortOrder: 6,
|
||||
selections: [
|
||||
{ code: 'HOME', name: '半场主 -0.5', odds: 1.9 },
|
||||
{ code: 'AWAY', name: '半场客 +0.5', odds: 1.9 },
|
||||
],
|
||||
},
|
||||
{
|
||||
marketType: 'HT_OVER_UNDER',
|
||||
period: 'HT',
|
||||
lineValue: 1.5,
|
||||
sortOrder: 7,
|
||||
selections: [
|
||||
{ code: 'OVER', name: '半场大 1.5', odds: 2.0 },
|
||||
{ code: 'UNDER', name: '半场小 1.5', odds: 1.75 },
|
||||
],
|
||||
},
|
||||
{
|
||||
marketType: 'FT_CORRECT_SCORE',
|
||||
period: 'FT',
|
||||
sortOrder: 8,
|
||||
selections: [
|
||||
{ code: 'SCORE_1_0', name: '1-0', odds: 4.86 },
|
||||
{ code: 'SCORE_2_0', name: '2-0', odds: 5.22 },
|
||||
{ code: 'SCORE_2_1', name: '2-1', odds: 7.92 },
|
||||
{ code: 'SCORE_3_0', name: '3-0', odds: 8.28 },
|
||||
{ code: 'SCORE_0_0', name: '0-0', odds: 8.64 },
|
||||
{ code: 'SCORE_1_1', name: '1-1', odds: 7.47 },
|
||||
{ code: 'SCORE_2_2', name: '2-2', odds: 24.3 },
|
||||
{ code: 'SCORE_3_3', name: '3-3', odds: 175.5 },
|
||||
{ code: 'SCORE_0_1', name: '0-1', odds: 14.4 },
|
||||
{ code: 'SCORE_0_2', name: '0-2', odds: 45.9 },
|
||||
{ code: 'SCORE_1_2', name: '1-2', odds: 23.4 },
|
||||
{ code: 'SCORE_0_3', name: '0-3', odds: 207 },
|
||||
],
|
||||
},
|
||||
{
|
||||
marketType: 'HT_CORRECT_SCORE',
|
||||
period: 'HT',
|
||||
sortOrder: 9,
|
||||
selections: [
|
||||
{ code: 'SCORE_1_0', name: '1-0', odds: 4.5 },
|
||||
{ code: 'SCORE_2_0', name: '2-0', odds: 8.0 },
|
||||
{ code: 'SCORE_0_0', name: '0-0', odds: 5.5 },
|
||||
{ code: 'SCORE_1_1', name: '1-1', odds: 7.0 },
|
||||
{ code: 'SCORE_0_1', name: '0-1', odds: 6.5 },
|
||||
{ code: 'SCORE_0_2', name: '0-2', odds: 18.0 },
|
||||
],
|
||||
},
|
||||
{
|
||||
marketType: 'SH_CORRECT_SCORE',
|
||||
period: 'SH',
|
||||
sortOrder: 10,
|
||||
selections: [
|
||||
{ code: 'SCORE_1_0', name: '1-0', odds: 4.5 },
|
||||
{ code: 'SCORE_2_0', name: '2-0', odds: 8.0 },
|
||||
{ code: 'SCORE_0_0', name: '0-0', odds: 5.5 },
|
||||
{ code: 'SCORE_1_1', name: '1-1', odds: 7.0 },
|
||||
{ code: 'SCORE_0_1', name: '0-1', odds: 6.5 },
|
||||
{ code: 'SCORE_0_2', name: '0-2', odds: 18.0 },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
for (const cfg of configs) {
|
||||
const exists = await prisma.market.findFirst({
|
||||
where: { matchId, marketType: cfg.marketType },
|
||||
include: { _count: { select: { selections: true } } },
|
||||
});
|
||||
if (exists) {
|
||||
const needRefresh =
|
||||
cfg.marketType.includes('CORRECT_SCORE') &&
|
||||
exists._count.selections < cfg.selections.length;
|
||||
if (needRefresh) {
|
||||
await prisma.market.delete({ where: { id: exists.id } });
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
await prisma.market.create({
|
||||
data: {
|
||||
matchId,
|
||||
marketType: cfg.marketType,
|
||||
period: cfg.period,
|
||||
lineValue: cfg.lineValue,
|
||||
allowSingle: true,
|
||||
allowParlay: true,
|
||||
sortOrder: cfg.sortOrder,
|
||||
status: 'OPEN',
|
||||
selections: {
|
||||
create: cfg.selections.map((s, i) => ({
|
||||
selectionCode: s.code,
|
||||
selectionName: s.name,
|
||||
odds: s.odds,
|
||||
sortOrder: i,
|
||||
status: 'OPEN',
|
||||
})),
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function upsertLeagueName(leagueId: bigint, names: Record<string, string>) {
|
||||
for (const [locale, value] of Object.entries(names)) {
|
||||
await prisma.entityTranslation.upsert({
|
||||
where: {
|
||||
entityType_entityId_locale_fieldName: {
|
||||
entityType: 'LEAGUE',
|
||||
entityId: leagueId,
|
||||
locale,
|
||||
fieldName: 'name',
|
||||
},
|
||||
},
|
||||
create: { entityType: 'LEAGUE', entityId: leagueId, locale, fieldName: 'name', value },
|
||||
update: { value },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function upsertTeam(
|
||||
code: string,
|
||||
names: Record<string, string>,
|
||||
) {
|
||||
const team = await prisma.team.upsert({
|
||||
where: { code },
|
||||
create: { code },
|
||||
update: {},
|
||||
});
|
||||
for (const [locale, value] of Object.entries(names)) {
|
||||
await prisma.entityTranslation.upsert({
|
||||
where: {
|
||||
entityType_entityId_locale_fieldName: {
|
||||
entityType: 'TEAM',
|
||||
entityId: team.id,
|
||||
locale,
|
||||
fieldName: 'name',
|
||||
},
|
||||
},
|
||||
create: { entityType: 'TEAM', entityId: team.id, locale, fieldName: 'name', value },
|
||||
update: { value },
|
||||
});
|
||||
}
|
||||
return team;
|
||||
}
|
||||
|
||||
async function ensurePublishedMatch(opts: {
|
||||
leagueId: bigint;
|
||||
homeTeamId: bigint;
|
||||
awayTeamId: bigint;
|
||||
startTime: Date;
|
||||
isHot?: boolean;
|
||||
displayOrder?: number;
|
||||
}) {
|
||||
let match = await prisma.match.findFirst({
|
||||
where: {
|
||||
leagueId: opts.leagueId,
|
||||
homeTeamId: opts.homeTeamId,
|
||||
awayTeamId: opts.awayTeamId,
|
||||
status: 'PUBLISHED',
|
||||
},
|
||||
});
|
||||
if (!match) {
|
||||
match = await prisma.match.create({
|
||||
data: {
|
||||
leagueId: opts.leagueId,
|
||||
homeTeamId: opts.homeTeamId,
|
||||
awayTeamId: opts.awayTeamId,
|
||||
startTime: opts.startTime,
|
||||
status: 'PUBLISHED',
|
||||
isHot: opts.isHot ?? false,
|
||||
displayOrder: opts.displayOrder ?? 0,
|
||||
publishTime: new Date(),
|
||||
},
|
||||
});
|
||||
} else {
|
||||
match = await prisma.match.update({
|
||||
where: { id: match.id },
|
||||
data: {
|
||||
startTime: opts.startTime,
|
||||
isHot: opts.isHot ?? match.isHot,
|
||||
displayOrder: opts.displayOrder ?? match.displayOrder,
|
||||
},
|
||||
});
|
||||
}
|
||||
await seedDemoMarkets(match.id);
|
||||
return match;
|
||||
}
|
||||
|
||||
function hoursFromNow(hours: number) {
|
||||
return new Date(Date.now() + hours * 3600 * 1000);
|
||||
}
|
||||
|
||||
async function seedSportsDemo() {
|
||||
const epl = await prisma.league.upsert({
|
||||
where: { code: 'EPL' },
|
||||
create: { code: 'EPL' },
|
||||
update: {},
|
||||
});
|
||||
await upsertLeagueName(epl.id, { 'zh-CN': '英超', 'en-US': 'Premier League' });
|
||||
|
||||
const wc = await prisma.league.upsert({
|
||||
where: { code: 'WC2026' },
|
||||
create: { code: 'WC2026' },
|
||||
update: {},
|
||||
});
|
||||
await upsertLeagueName(wc.id, {
|
||||
'zh-CN': '2026世界杯(在加拿大,墨西哥和美国)',
|
||||
'en-US': '2026 FIFA World Cup',
|
||||
});
|
||||
|
||||
const teams: Array<[string, Record<string, string>]> = [
|
||||
['MUN', { 'zh-CN': '曼联', 'en-US': 'Man United' }],
|
||||
['CHE', { 'zh-CN': '切尔西', 'en-US': 'Chelsea' }],
|
||||
['MEX', { 'zh-CN': '墨西哥', 'en-US': 'Mexico' }],
|
||||
['RSA', { 'zh-CN': '南非', 'en-US': 'South Africa' }],
|
||||
['CZE', { 'zh-CN': '捷克', 'en-US': 'Czech Republic' }],
|
||||
['KOR', { 'zh-CN': '韩国', 'en-US': 'South Korea' }],
|
||||
['CAN', { 'zh-CN': '加拿大', 'en-US': 'Canada' }],
|
||||
['BIH', { 'zh-CN': '波黑', 'en-US': 'Bosnia' }],
|
||||
['USA', { 'zh-CN': '美国', 'en-US': 'USA' }],
|
||||
['PAR', { 'zh-CN': '巴拉圭', 'en-US': 'Paraguay' }],
|
||||
['SUI', { 'zh-CN': '瑞士', 'en-US': 'Switzerland' }],
|
||||
['BRA', { 'zh-CN': '巴西', 'en-US': 'Brazil' }],
|
||||
['SCO', { 'zh-CN': '苏格兰', 'en-US': 'Scotland' }],
|
||||
['TUR', { 'zh-CN': '土耳其', 'en-US': 'Turkey' }],
|
||||
['ARG', { 'zh-CN': '阿根廷', 'en-US': 'Argentina' }],
|
||||
['FRA', { 'zh-CN': '法国', 'en-US': 'France' }],
|
||||
];
|
||||
|
||||
const teamMap = new Map<string, { id: bigint }>();
|
||||
for (const [code, names] of teams) {
|
||||
teamMap.set(code, await upsertTeam(code, names));
|
||||
}
|
||||
|
||||
const get = (code: string) => {
|
||||
const t = teamMap.get(code);
|
||||
if (!t) throw new Error(`Team ${code} missing`);
|
||||
return t;
|
||||
};
|
||||
|
||||
// 英超:明日开赛 → 早盘
|
||||
await ensurePublishedMatch({
|
||||
leagueId: epl.id,
|
||||
homeTeamId: get('MUN').id,
|
||||
awayTeamId: get('CHE').id,
|
||||
startTime: hoursFromNow(26),
|
||||
isHot: true,
|
||||
displayOrder: 1,
|
||||
});
|
||||
|
||||
// 英超:今晚开赛 → 今日
|
||||
await ensurePublishedMatch({
|
||||
leagueId: epl.id,
|
||||
homeTeamId: get('CHE').id,
|
||||
awayTeamId: get('MUN').id,
|
||||
startTime: hoursFromNow(8),
|
||||
isHot: false,
|
||||
displayOrder: 2,
|
||||
});
|
||||
|
||||
const wcFixtures: Array<{
|
||||
home: string;
|
||||
away: string;
|
||||
start: Date;
|
||||
hot?: boolean;
|
||||
order: number;
|
||||
}> = [
|
||||
{ home: 'MEX', away: 'RSA', start: new Date('2026-06-12T03:00:00Z'), hot: true, order: 1 },
|
||||
{ home: 'CZE', away: 'KOR', start: new Date('2026-06-12T07:00:00Z'), order: 2 },
|
||||
{ home: 'CAN', away: 'BIH', start: new Date('2026-06-13T00:00:00Z'), order: 3 },
|
||||
{ home: 'USA', away: 'PAR', start: new Date('2026-06-13T03:00:00Z'), hot: true, order: 4 },
|
||||
{ home: 'SUI', away: 'BRA', start: new Date('2026-06-14T16:00:00Z'), order: 5 },
|
||||
{ home: 'SCO', away: 'TUR', start: new Date('2026-06-14T19:00:00Z'), order: 6 },
|
||||
{ home: 'FRA', away: 'ARG', start: new Date('2026-06-15T20:00:00Z'), hot: true, order: 7 },
|
||||
];
|
||||
|
||||
for (const f of wcFixtures) {
|
||||
await ensurePublishedMatch({
|
||||
leagueId: wc.id,
|
||||
homeTeamId: get(f.home).id,
|
||||
awayTeamId: get(f.away).id,
|
||||
startTime: f.start,
|
||||
isHot: f.hot,
|
||||
displayOrder: f.order,
|
||||
});
|
||||
}
|
||||
|
||||
console.log(` Sports demo: ${wcFixtures.length + 2} published matches`);
|
||||
}
|
||||
|
||||
async function seedOutrightDemo() {
|
||||
const wc = await prisma.league.findUnique({ where: { code: 'WC2026' } });
|
||||
if (!wc) return;
|
||||
|
||||
const placeholder = await upsertTeam('OUT', { 'zh-CN': '冠军盘', 'en-US': 'Outright' });
|
||||
|
||||
const outrightOdds: Array<[string, Record<string, string>, number]> = [
|
||||
['FRA', { 'zh-CN': '法国', 'en-US': 'France' }, 4.95],
|
||||
['ESP', { 'zh-CN': '西班牙', 'en-US': 'Spain' }, 4.95],
|
||||
['ENG', { 'zh-CN': '英格兰', 'en-US': 'England' }, 6.3],
|
||||
['BRA', { 'zh-CN': '巴西', 'en-US': 'Brazil' }, 8.55],
|
||||
['ARG', { 'zh-CN': '阿根廷', 'en-US': 'Argentina' }, 8.55],
|
||||
['POR', { 'zh-CN': '葡萄牙', 'en-US': 'Portugal' }, 9.0],
|
||||
['GER', { 'zh-CN': '德国', 'en-US': 'Germany' }, 15.3],
|
||||
['NED', { 'zh-CN': '荷兰', 'en-US': 'Netherlands' }, 18.9],
|
||||
['NOR', { 'zh-CN': '挪威', 'en-US': 'Norway' }, 32.4],
|
||||
['BEL', { 'zh-CN': '比利时', 'en-US': 'Belgium' }, 35.1],
|
||||
['COL', { 'zh-CN': '哥伦比亚', 'en-US': 'Colombia' }, 45.9],
|
||||
['JPN', { 'zh-CN': '日本', 'en-US': 'Japan' }, 45.9],
|
||||
['URU', { 'zh-CN': '乌拉圭', 'en-US': 'Uruguay' }, 63.9],
|
||||
['USA', { 'zh-CN': '美国', 'en-US': 'USA' }, 63.9],
|
||||
['MAR', { 'zh-CN': '摩洛哥', 'en-US': 'Morocco' }, 63.9],
|
||||
['CRO', { 'zh-CN': '克罗地亚', 'en-US': 'Croatia' }, 81.0],
|
||||
['MEX', { 'zh-CN': '墨西哥', 'en-US': 'Mexico' }, 85.0],
|
||||
['SUI', { 'zh-CN': '瑞士', 'en-US': 'Switzerland' }, 90.0],
|
||||
['TUR', { 'zh-CN': '土耳其', 'en-US': 'Turkey' }, 95.0],
|
||||
['SEN', { 'zh-CN': '塞内加尔', 'en-US': 'Senegal' }, 100.0],
|
||||
];
|
||||
|
||||
for (const [code, names] of outrightOdds) {
|
||||
await upsertTeam(code, names);
|
||||
}
|
||||
|
||||
let match = await prisma.match.findFirst({
|
||||
where: { leagueId: wc.id, isOutright: true },
|
||||
});
|
||||
if (!match) {
|
||||
match = await prisma.match.create({
|
||||
data: {
|
||||
leagueId: wc.id,
|
||||
homeTeamId: placeholder.id,
|
||||
awayTeamId: placeholder.id,
|
||||
isOutright: true,
|
||||
startTime: new Date('2027-07-01T00:00:00Z'),
|
||||
status: 'PUBLISHED',
|
||||
publishTime: new Date(),
|
||||
isHot: true,
|
||||
displayOrder: 0,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const marketExists = await prisma.market.findFirst({
|
||||
where: { matchId: match.id, marketType: 'OUTRIGHT_WINNER' },
|
||||
});
|
||||
if (!marketExists) {
|
||||
await prisma.market.create({
|
||||
data: {
|
||||
matchId: match.id,
|
||||
marketType: 'OUTRIGHT_WINNER',
|
||||
period: 'OUTRIGHT',
|
||||
allowSingle: true,
|
||||
allowParlay: false,
|
||||
sortOrder: 1,
|
||||
status: 'OPEN',
|
||||
selections: {
|
||||
create: outrightOdds.map(([code, names, odds], i) => ({
|
||||
selectionCode: code,
|
||||
selectionName: names['zh-CN'],
|
||||
odds,
|
||||
sortOrder: i,
|
||||
status: 'OPEN',
|
||||
})),
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
console.log(' Outright demo: World Cup winner market');
|
||||
}
|
||||
|
||||
async function seedPlayerDemo() {
|
||||
const player = await prisma.user.findUnique({
|
||||
where: { username: 'player1' },
|
||||
include: { wallet: true },
|
||||
});
|
||||
if (!player?.wallet) return;
|
||||
|
||||
await prisma.wallet.update({
|
||||
where: { id: player.wallet.id },
|
||||
data: { availableBalance: 88888.88 },
|
||||
});
|
||||
|
||||
await prisma.walletTransaction.upsert({
|
||||
where: { transactionId: 'DEMO-DEP-001' },
|
||||
create: {
|
||||
transactionId: 'DEMO-DEP-001',
|
||||
userId: player.id,
|
||||
walletId: player.wallet.id,
|
||||
transactionType: 'DEPOSIT',
|
||||
amount: 50000,
|
||||
balanceBefore: 1000,
|
||||
balanceAfter: 51000,
|
||||
frozenBefore: 0,
|
||||
frozenAfter: 0,
|
||||
remark: '演示充值',
|
||||
},
|
||||
update: {},
|
||||
});
|
||||
|
||||
await prisma.walletTransaction.upsert({
|
||||
where: { transactionId: 'DEMO-DEP-002' },
|
||||
create: {
|
||||
transactionId: 'DEMO-DEP-002',
|
||||
userId: player.id,
|
||||
walletId: player.wallet.id,
|
||||
transactionType: 'DEPOSIT',
|
||||
amount: 37888.88,
|
||||
balanceBefore: 51000,
|
||||
balanceAfter: 88888.88,
|
||||
frozenBefore: 0,
|
||||
frozenAfter: 0,
|
||||
remark: '演示充值(二笔)',
|
||||
},
|
||||
update: {},
|
||||
});
|
||||
|
||||
const sampleSel = await prisma.marketSelection.findFirst({
|
||||
where: {
|
||||
status: 'OPEN',
|
||||
market: { marketType: 'FT_1X2', match: { status: 'PUBLISHED' } },
|
||||
},
|
||||
include: { market: { include: { match: true } } },
|
||||
});
|
||||
|
||||
if (sampleSel && !(await prisma.bet.findUnique({ where: { betNo: 'DEMO-BET-001' } }))) {
|
||||
const odds = Number(sampleSel.odds);
|
||||
const stake = 200;
|
||||
await prisma.bet.create({
|
||||
data: {
|
||||
betNo: 'DEMO-BET-001',
|
||||
userId: player.id,
|
||||
agentId: player.parentId,
|
||||
betType: 'SINGLE',
|
||||
stake,
|
||||
totalOdds: odds,
|
||||
potentialReturn: stake * odds,
|
||||
status: 'PENDING',
|
||||
requestId: 'seed-demo-bet-001',
|
||||
selections: {
|
||||
create: {
|
||||
matchId: sampleSel.market.matchId,
|
||||
marketId: sampleSel.marketId,
|
||||
selectionId: sampleSel.id,
|
||||
marketType: sampleSel.market.marketType,
|
||||
period: sampleSel.market.period,
|
||||
selectionNameSnapshot: sampleSel.selectionName,
|
||||
odds: sampleSel.odds,
|
||||
oddsVersion: sampleSel.oddsVersion,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const settledSel = await prisma.marketSelection.findFirst({
|
||||
where: {
|
||||
market: { marketType: 'FT_1X2' },
|
||||
selectionCode: 'DRAW',
|
||||
},
|
||||
include: { market: true },
|
||||
});
|
||||
|
||||
if (settledSel && !(await prisma.bet.findUnique({ where: { betNo: 'DEMO-BET-002' } }))) {
|
||||
const odds = Number(settledSel.odds);
|
||||
const stake = 50;
|
||||
await prisma.bet.create({
|
||||
data: {
|
||||
betNo: 'DEMO-BET-002',
|
||||
userId: player.id,
|
||||
agentId: player.parentId,
|
||||
betType: 'SINGLE',
|
||||
stake,
|
||||
totalOdds: odds,
|
||||
potentialReturn: stake * odds,
|
||||
actualReturn: stake * odds,
|
||||
status: 'WON',
|
||||
settlementStatus: 'SETTLED',
|
||||
settledAt: new Date(Date.now() - 86400000),
|
||||
requestId: 'seed-demo-bet-002',
|
||||
selections: {
|
||||
create: {
|
||||
matchId: settledSel.market.matchId,
|
||||
marketId: settledSel.marketId,
|
||||
selectionId: settledSel.id,
|
||||
marketType: settledSel.market.marketType,
|
||||
period: settledSel.market.period,
|
||||
selectionNameSnapshot: settledSel.selectionName,
|
||||
odds: settledSel.odds,
|
||||
oddsVersion: settledSel.oddsVersion,
|
||||
resultStatus: 'WIN',
|
||||
effectiveOdds: settledSel.odds,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
console.log(' Player demo: wallet + transactions + sample bets');
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log('Seeding database...');
|
||||
|
||||
@@ -107,70 +670,79 @@ async function main() {
|
||||
}
|
||||
}
|
||||
|
||||
const league = await prisma.league.upsert({
|
||||
where: { code: 'EPL' },
|
||||
create: { code: 'EPL' },
|
||||
update: {},
|
||||
});
|
||||
|
||||
await prisma.entityTranslation.upsert({
|
||||
where: { entityType_entityId_locale_fieldName: { entityType: 'LEAGUE', entityId: league.id, locale: 'zh-CN', fieldName: 'name' } },
|
||||
create: { entityType: 'LEAGUE', entityId: league.id, locale: 'zh-CN', fieldName: 'name', value: '英超' },
|
||||
update: {},
|
||||
});
|
||||
|
||||
for (const [code, name] of [['MUN', '曼联'], ['CHE', '切尔西']] as const) {
|
||||
const team = await prisma.team.upsert({ where: { code }, create: { code }, update: {} });
|
||||
await prisma.entityTranslation.upsert({
|
||||
where: { entityType_entityId_locale_fieldName: { entityType: 'TEAM', entityId: team.id, locale: 'zh-CN', fieldName: 'name' } },
|
||||
create: { entityType: 'TEAM', entityId: team.id, locale: 'zh-CN', fieldName: 'name', value: name },
|
||||
update: { value: name },
|
||||
});
|
||||
}
|
||||
|
||||
const mun = await prisma.team.findUnique({ where: { code: 'MUN' } });
|
||||
const che = await prisma.team.findUnique({ where: { code: 'CHE' } });
|
||||
|
||||
if (mun && che) {
|
||||
const existing = await prisma.match.findFirst({ where: { homeTeamId: mun.id, awayTeamId: che.id } });
|
||||
if (!existing) {
|
||||
const match = await prisma.match.create({
|
||||
data: {
|
||||
leagueId: league.id,
|
||||
homeTeamId: mun.id,
|
||||
awayTeamId: che.id,
|
||||
startTime: new Date(Date.now() + 86400000),
|
||||
status: 'PUBLISHED',
|
||||
isHot: true,
|
||||
publishTime: new Date(),
|
||||
},
|
||||
});
|
||||
await prisma.market.create({
|
||||
data: {
|
||||
matchId: match.id,
|
||||
marketType: 'FT_1X2',
|
||||
period: 'FT',
|
||||
selections: {
|
||||
create: [
|
||||
{ selectionCode: 'HOME', selectionName: 'Home', odds: 2.5 },
|
||||
{ selectionCode: 'DRAW', selectionName: 'Draw', odds: 3.2 },
|
||||
{ selectionCode: 'AWAY', selectionName: 'Away', odds: 2.8 },
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
await seedSportsDemo();
|
||||
await seedOutrightDemo();
|
||||
await seedPlayerDemo();
|
||||
|
||||
await prisma.content.create({
|
||||
data: {
|
||||
contentType: 'BANNER',
|
||||
status: 'ACTIVE',
|
||||
sortOrder: 1,
|
||||
linkType: 'ROUTE',
|
||||
linkTarget: '/football',
|
||||
translations: {
|
||||
create: [
|
||||
{ locale: 'zh-CN', title: '欢迎投注', body: '足球赛事火热进行中' },
|
||||
{ locale: 'en-US', title: 'Welcome', body: 'Football matches available' },
|
||||
{ locale: 'zh-CN', title: '欢迎投注', body: '足球赛事火热进行中', imageUrl: '/uploads/banners/welcome.svg' },
|
||||
{ locale: 'en-US', title: 'Welcome', body: 'Football matches available', imageUrl: '/uploads/banners/welcome.svg' },
|
||||
],
|
||||
},
|
||||
},
|
||||
}).catch(() => {});
|
||||
|
||||
await prisma.content.create({
|
||||
data: {
|
||||
contentType: 'BANNER',
|
||||
status: 'ACTIVE',
|
||||
sortOrder: 2,
|
||||
translations: {
|
||||
create: [
|
||||
{ locale: 'zh-CN', title: '首存礼遇', body: '新会员专属优惠', imageUrl: '/uploads/banners/promo.svg' },
|
||||
{ locale: 'en-US', title: 'First Deposit', body: 'New member offer', imageUrl: '/uploads/banners/promo.svg' },
|
||||
],
|
||||
},
|
||||
},
|
||||
}).catch(() => {});
|
||||
|
||||
await prisma.content.create({
|
||||
data: {
|
||||
contentType: 'BANNER',
|
||||
status: 'ACTIVE',
|
||||
sortOrder: 3,
|
||||
linkType: 'ROUTE',
|
||||
linkTarget: '/football',
|
||||
translations: {
|
||||
create: [
|
||||
{ locale: 'zh-CN', title: '热门赛事', body: '五大联赛天天有球', imageUrl: '/uploads/banners/hot-matches.svg' },
|
||||
{ locale: 'en-US', title: 'Hot Matches', body: 'Top leagues daily', imageUrl: '/uploads/banners/hot-matches.svg' },
|
||||
],
|
||||
},
|
||||
},
|
||||
}).catch(() => {});
|
||||
|
||||
await prisma.content.create({
|
||||
data: {
|
||||
contentType: 'TICKER',
|
||||
status: 'ACTIVE',
|
||||
sortOrder: 1,
|
||||
translations: {
|
||||
create: [
|
||||
{ locale: 'zh-CN', body: '欢迎来到 TheBet365 · 热门赛事每日更新 · 请理性投注' },
|
||||
{ locale: 'en-US', body: 'Welcome to TheBet365 · Daily hot matches · Bet responsibly' },
|
||||
],
|
||||
},
|
||||
},
|
||||
}).catch(() => {});
|
||||
|
||||
await prisma.content.create({
|
||||
data: {
|
||||
contentType: 'NOTICE',
|
||||
status: 'ACTIVE',
|
||||
sortOrder: 1,
|
||||
translations: {
|
||||
create: [
|
||||
{ locale: 'zh-CN', title: '系统维护通知:每周一 04:00-05:00 例行维护,敬请谅解' },
|
||||
{ locale: 'en-US', title: 'Maintenance: Every Mon 04:00-05:00 UTC' },
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user