feat(admin,api,player): 赛事分组管理、盘口独立页与多语言展示优化
- 管理端按联赛展示单场,新增赛事/单场流程与列表展开状态保持 - 盘口赔率迁至独立页面,保存按钮仅在有修改时高亮 - API 新增联赛列表与子场查询,按 locale 返回队名并修复编译 - 波胆其它选项与促销标签等 i18n 补齐,文案更易懂
This commit is contained in:
@@ -193,46 +193,284 @@ export class MatchesService {
|
||||
return null;
|
||||
}
|
||||
|
||||
async createPlatformMatch(data: {
|
||||
async createPlatformLeague(data: {
|
||||
leagueEn: string;
|
||||
leagueZh: string;
|
||||
leagueMs?: string;
|
||||
logoUrl?: string;
|
||||
displayOrder?: number;
|
||||
}) {
|
||||
const leagueEn = data.leagueEn.trim();
|
||||
const leagueZh = data.leagueZh.trim();
|
||||
if (!leagueEn && !leagueZh) {
|
||||
throw new BadRequestException('请填写赛事名称(中文或英文至少一项)');
|
||||
}
|
||||
const league = await this.upsertLeagueFromZhiboExport({
|
||||
type: 'FOOTBALL',
|
||||
en: leagueEn || leagueZh,
|
||||
zh: leagueZh || leagueEn,
|
||||
});
|
||||
if (data.leagueMs?.trim()) {
|
||||
await this.upsertEntityTranslations('LEAGUE', league.id, {
|
||||
'ms-MY': data.leagueMs.trim(),
|
||||
});
|
||||
}
|
||||
const updates: { logoUrl?: string; displayOrder?: number } = {};
|
||||
if (data.logoUrl?.trim()) updates.logoUrl = data.logoUrl.trim();
|
||||
if (data.displayOrder != null) updates.displayOrder = data.displayOrder;
|
||||
if (Object.keys(updates).length) {
|
||||
await this.prisma.league.update({ where: { id: league.id }, data: updates });
|
||||
}
|
||||
const [en, zh, ms] = await Promise.all([
|
||||
this.getTranslationExact('LEAGUE', league.id, 'en-US'),
|
||||
this.getTranslationExact('LEAGUE', league.id, 'zh-CN'),
|
||||
this.getTranslationExact('LEAGUE', league.id, 'ms-MY'),
|
||||
]);
|
||||
const fresh = await this.prisma.league.findUniqueOrThrow({ where: { id: league.id } });
|
||||
return {
|
||||
id: fresh.id.toString(),
|
||||
code: fresh.code,
|
||||
logoUrl: fresh.logoUrl,
|
||||
displayOrder: fresh.displayOrder,
|
||||
leagueEn: en,
|
||||
leagueZh: zh,
|
||||
leagueMs: ms,
|
||||
};
|
||||
}
|
||||
|
||||
async listAdminLeagues(opts: {
|
||||
page: number;
|
||||
pageSize: number;
|
||||
keyword?: string;
|
||||
status?: string;
|
||||
}) {
|
||||
const skip = (opts.page - 1) * opts.pageSize;
|
||||
const kw = opts.keyword?.trim();
|
||||
let idFilter: bigint[] | undefined;
|
||||
|
||||
if (kw || opts.status) {
|
||||
const ids = new Set<bigint>();
|
||||
if (kw) {
|
||||
const trRows = await this.prisma.entityTranslation.findMany({
|
||||
where: {
|
||||
entityType: 'LEAGUE',
|
||||
fieldName: 'name',
|
||||
value: { contains: kw, mode: 'insensitive' },
|
||||
},
|
||||
select: { entityId: true },
|
||||
});
|
||||
for (const r of trRows) ids.add(r.entityId);
|
||||
}
|
||||
const matchWhere: Prisma.MatchWhereInput = {
|
||||
deletedAt: null,
|
||||
isOutright: false,
|
||||
};
|
||||
if (opts.status) matchWhere.status = opts.status;
|
||||
if (kw) {
|
||||
matchWhere.OR = [
|
||||
{ matchName: { contains: kw, mode: 'insensitive' } },
|
||||
{ homeTeam: { code: { contains: kw, mode: 'insensitive' } } },
|
||||
{ awayTeam: { code: { contains: kw, mode: 'insensitive' } } },
|
||||
];
|
||||
}
|
||||
const matchLeagues = await this.prisma.match.findMany({
|
||||
where: matchWhere,
|
||||
select: { leagueId: true },
|
||||
distinct: ['leagueId'],
|
||||
});
|
||||
for (const m of matchLeagues) ids.add(m.leagueId);
|
||||
idFilter = [...ids];
|
||||
if (!idFilter.length) {
|
||||
return { items: [], total: 0, page: opts.page, pageSize: opts.pageSize };
|
||||
}
|
||||
}
|
||||
|
||||
const where: Prisma.LeagueWhereInput = { deletedAt: null };
|
||||
if (idFilter) where.id = { in: idFilter };
|
||||
|
||||
const [leagues, total] = await Promise.all([
|
||||
this.prisma.league.findMany({
|
||||
where,
|
||||
orderBy: [{ displayOrder: 'asc' }, { id: 'desc' }],
|
||||
skip,
|
||||
take: opts.pageSize,
|
||||
}),
|
||||
this.prisma.league.count({ where }),
|
||||
]);
|
||||
|
||||
const items = await Promise.all(
|
||||
leagues.map(async (league) => {
|
||||
const [leagueEn, leagueZh, leagueMs, matchCount] = await Promise.all([
|
||||
this.getTranslationExact('LEAGUE', league.id, 'en-US'),
|
||||
this.getTranslationExact('LEAGUE', league.id, 'zh-CN'),
|
||||
this.getTranslationExact('LEAGUE', league.id, 'ms-MY'),
|
||||
this.prisma.match.count({
|
||||
where: {
|
||||
leagueId: league.id,
|
||||
deletedAt: null,
|
||||
isOutright: false,
|
||||
...(opts.status ? { status: opts.status } : {}),
|
||||
},
|
||||
}),
|
||||
]);
|
||||
return {
|
||||
id: league.id.toString(),
|
||||
code: league.code,
|
||||
logoUrl: league.logoUrl,
|
||||
displayOrder: league.displayOrder,
|
||||
leagueEn,
|
||||
leagueZh,
|
||||
leagueMs,
|
||||
matchCount,
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
return { items, total, page: opts.page, pageSize: opts.pageSize };
|
||||
}
|
||||
|
||||
async listAdminLeagueMatches(
|
||||
leagueId: bigint,
|
||||
opts: { status?: string; keyword?: string; locale?: string },
|
||||
) {
|
||||
const where: Prisma.MatchWhereInput = {
|
||||
leagueId,
|
||||
deletedAt: null,
|
||||
isOutright: false,
|
||||
};
|
||||
if (opts.status) where.status = opts.status;
|
||||
const kw = opts.keyword?.trim();
|
||||
if (kw) {
|
||||
where.OR = [
|
||||
{ matchName: { contains: kw, mode: 'insensitive' } },
|
||||
{ homeTeam: { code: { contains: kw, mode: 'insensitive' } } },
|
||||
{ awayTeam: { code: { contains: kw, mode: 'insensitive' } } },
|
||||
];
|
||||
}
|
||||
const items = await this.prisma.match.findMany({
|
||||
where,
|
||||
include: { homeTeam: true, awayTeam: true },
|
||||
orderBy: [{ displayOrder: 'asc' }, { startTime: 'desc' }],
|
||||
});
|
||||
const locale = opts.locale ?? 'zh-CN';
|
||||
return Promise.all(
|
||||
items.map(async (m) => {
|
||||
const [homeTeamName, awayTeamName] = await Promise.all([
|
||||
this.getTranslation('TEAM', m.homeTeamId, locale),
|
||||
this.getTranslation('TEAM', m.awayTeamId, locale),
|
||||
]);
|
||||
return {
|
||||
id: m.id.toString(),
|
||||
status: m.status,
|
||||
isOutright: m.isOutright,
|
||||
isHot: m.isHot,
|
||||
displayOrder: m.displayOrder,
|
||||
startTime: m.startTime,
|
||||
matchName: m.matchName,
|
||||
homeTeamName,
|
||||
awayTeamName,
|
||||
homeTeam: { code: m.homeTeam.code },
|
||||
awayTeam: { code: m.awayTeam.code },
|
||||
};
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
async createPlatformMatch(data: {
|
||||
leagueId?: bigint;
|
||||
leagueEn?: string;
|
||||
leagueZh?: string;
|
||||
leagueMs?: string;
|
||||
homeTeamZh: string;
|
||||
homeTeamEn: string;
|
||||
homeTeamMs?: string;
|
||||
awayTeamZh: string;
|
||||
awayTeamEn: string;
|
||||
awayTeamMs?: string;
|
||||
startTime: Date;
|
||||
isHot?: boolean;
|
||||
displayOrder?: number;
|
||||
matchName?: string;
|
||||
stage?: string;
|
||||
groupName?: string;
|
||||
leagueLogoUrl?: string;
|
||||
homeTeamLogoUrl?: string;
|
||||
awayTeamLogoUrl?: string;
|
||||
createdBy?: bigint;
|
||||
}) {
|
||||
const homeEn = data.homeTeamEn.trim();
|
||||
const homeZh = data.homeTeamZh.trim();
|
||||
const homeMs = data.homeTeamMs?.trim() ?? '';
|
||||
const awayEn = data.awayTeamEn.trim();
|
||||
const awayZh = data.awayTeamZh.trim();
|
||||
if ((!homeEn && !homeZh) || (!awayEn && !awayZh)) {
|
||||
throw new BadRequestException('请填写主客队中英文名至少各一项');
|
||||
const awayMs = data.awayTeamMs?.trim() ?? '';
|
||||
if ((!homeEn && !homeZh && !homeMs) || (!awayEn && !awayZh && !awayMs)) {
|
||||
throw new BadRequestException('请填写主客队名称(中文、英文或马来文至少一项)');
|
||||
}
|
||||
|
||||
const league = await this.upsertLeagueFromZhiboExport({
|
||||
type: 'FOOTBALL',
|
||||
en: data.leagueEn.trim(),
|
||||
zh: data.leagueZh.trim(),
|
||||
});
|
||||
let league;
|
||||
if (data.leagueId) {
|
||||
league = await this.prisma.league.findFirst({
|
||||
where: { id: data.leagueId, deletedAt: null },
|
||||
});
|
||||
if (!league) throw new NotFoundException('赛事不存在');
|
||||
} else {
|
||||
const leagueEn = data.leagueEn?.trim() ?? '';
|
||||
const leagueZh = data.leagueZh?.trim() ?? '';
|
||||
const leagueMs = data.leagueMs?.trim() ?? '';
|
||||
if (!leagueEn && !leagueZh && !leagueMs) {
|
||||
throw new BadRequestException('请填写赛事名称(中文、英文或马来文至少一项)');
|
||||
}
|
||||
league = await this.upsertLeagueFromZhiboExport({
|
||||
type: 'FOOTBALL',
|
||||
en: leagueEn || leagueZh || leagueMs,
|
||||
zh: leagueZh || leagueEn || leagueMs,
|
||||
});
|
||||
if (leagueMs) {
|
||||
await this.upsertEntityTranslations('LEAGUE', league.id, {
|
||||
'ms-MY': leagueMs,
|
||||
});
|
||||
}
|
||||
if (data.leagueLogoUrl?.trim()) {
|
||||
await this.prisma.league.update({
|
||||
where: { id: league.id },
|
||||
data: { logoUrl: data.leagueLogoUrl.trim() },
|
||||
});
|
||||
}
|
||||
}
|
||||
const [homeTeam, awayTeam] = await Promise.all([
|
||||
this.upsertTeamFromZhiboExport({
|
||||
id: null,
|
||||
name: homeEn || homeZh,
|
||||
names: { zh: homeZh || null, en: homeEn || null, zhTw: '', vi: null, km: null, ms: null },
|
||||
image: '',
|
||||
name: homeEn || homeZh || homeMs,
|
||||
names: {
|
||||
zh: homeZh || null,
|
||||
en: homeEn || null,
|
||||
zhTw: '',
|
||||
vi: null,
|
||||
km: null,
|
||||
ms: homeMs || null,
|
||||
},
|
||||
image: data.homeTeamLogoUrl?.trim() || '',
|
||||
}),
|
||||
this.upsertTeamFromZhiboExport({
|
||||
id: null,
|
||||
name: awayEn || awayZh,
|
||||
names: { zh: awayZh || null, en: awayEn || null, zhTw: '', vi: null, km: null, ms: null },
|
||||
image: '',
|
||||
name: awayEn || awayZh || awayMs,
|
||||
names: {
|
||||
zh: awayZh || null,
|
||||
en: awayEn || null,
|
||||
zhTw: '',
|
||||
vi: null,
|
||||
km: null,
|
||||
ms: awayMs || null,
|
||||
},
|
||||
image: data.awayTeamLogoUrl?.trim() || '',
|
||||
}),
|
||||
]);
|
||||
|
||||
const matchName =
|
||||
data.matchName?.trim() ||
|
||||
`${homeEn || homeZh || homeMs} - ${awayEn || awayZh || awayMs}`;
|
||||
|
||||
return this.createMatch({
|
||||
leagueId: league.id,
|
||||
homeTeamId: homeTeam.id,
|
||||
@@ -243,7 +481,9 @@ export class MatchesService {
|
||||
createdBy: data.createdBy,
|
||||
status: 'DRAFT',
|
||||
zhibo: {
|
||||
matchName: `${homeEn || homeZh} - ${awayEn || awayZh}`,
|
||||
matchName,
|
||||
stage: data.stage?.trim() || undefined,
|
||||
groupName: data.groupName?.trim() || undefined,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -251,7 +491,7 @@ export class MatchesService {
|
||||
private async requireAdminMatch(matchId: bigint) {
|
||||
const match = await this.prisma.match.findFirst({
|
||||
where: { id: matchId, deletedAt: null },
|
||||
include: { homeTeam: true, awayTeam: true },
|
||||
include: { homeTeam: true, awayTeam: true, league: true },
|
||||
});
|
||||
if (!match) throw new NotFoundException('赛事不存在');
|
||||
return match;
|
||||
@@ -259,27 +499,66 @@ export class MatchesService {
|
||||
|
||||
async getAdminMatchDetail(matchId: bigint) {
|
||||
const match = await this.requireAdminMatch(matchId);
|
||||
const [leagueEn, leagueZh, homeEn, homeZh, awayEn, awayZh] = await Promise.all([
|
||||
this.getTranslation('LEAGUE', match.leagueId, 'en-US'),
|
||||
this.getTranslation('LEAGUE', match.leagueId, 'zh-CN'),
|
||||
this.getTranslation('TEAM', match.homeTeamId, 'en-US'),
|
||||
this.getTranslation('TEAM', match.homeTeamId, 'zh-CN'),
|
||||
this.getTranslation('TEAM', match.awayTeamId, 'en-US'),
|
||||
this.getTranslation('TEAM', match.awayTeamId, 'zh-CN'),
|
||||
const markets = await this.prisma.market.findMany({
|
||||
where: { matchId },
|
||||
include: { selections: { orderBy: { sortOrder: 'asc' } } },
|
||||
orderBy: { sortOrder: 'asc' },
|
||||
});
|
||||
const [leagueEn, leagueZh, leagueMs, homeEn, homeZh, homeMs, awayEn, awayZh, awayMs] =
|
||||
await Promise.all([
|
||||
this.getTranslationExact('LEAGUE', match.leagueId, 'en-US'),
|
||||
this.getTranslationExact('LEAGUE', match.leagueId, 'zh-CN'),
|
||||
this.getTranslationExact('LEAGUE', match.leagueId, 'ms-MY'),
|
||||
this.getTranslationExact('TEAM', match.homeTeamId, 'en-US'),
|
||||
this.getTranslationExact('TEAM', match.homeTeamId, 'zh-CN'),
|
||||
this.getTranslationExact('TEAM', match.homeTeamId, 'ms-MY'),
|
||||
this.getTranslationExact('TEAM', match.awayTeamId, 'en-US'),
|
||||
this.getTranslationExact('TEAM', match.awayTeamId, 'zh-CN'),
|
||||
this.getTranslationExact('TEAM', match.awayTeamId, 'ms-MY'),
|
||||
]);
|
||||
return {
|
||||
id: match.id.toString(),
|
||||
status: match.status,
|
||||
isOutright: match.isOutright,
|
||||
isHot: match.isHot,
|
||||
displayOrder: match.displayOrder,
|
||||
startTime: match.startTime.toISOString(),
|
||||
leagueId: match.leagueId.toString(),
|
||||
leagueCode: match.league.code,
|
||||
leagueEn,
|
||||
leagueZh,
|
||||
leagueMs,
|
||||
leagueLogoUrl: match.league.logoUrl ?? '',
|
||||
homeTeamEn: homeEn,
|
||||
homeTeamZh: homeZh,
|
||||
homeTeamMs: homeMs,
|
||||
homeTeamCode: match.homeTeam.code,
|
||||
homeTeamLogoUrl: match.homeTeam.logoUrl ?? '',
|
||||
awayTeamEn: awayEn,
|
||||
awayTeamZh: awayZh,
|
||||
awayTeamMs: awayMs,
|
||||
awayTeamCode: match.awayTeam.code,
|
||||
awayTeamLogoUrl: match.awayTeam.logoUrl ?? '',
|
||||
matchName: match.matchName ?? '',
|
||||
stage: match.stage ?? '',
|
||||
groupName: match.groupName ?? '',
|
||||
markets: markets.map((m) => ({
|
||||
id: m.id.toString(),
|
||||
marketType: m.marketType,
|
||||
period: m.period,
|
||||
lineValue: m.lineValue != null ? Number(m.lineValue) : null,
|
||||
status: m.status,
|
||||
promoLabel: m.promoLabel ?? '',
|
||||
sortOrder: m.sortOrder,
|
||||
selections: m.selections.map((s) => ({
|
||||
id: s.id.toString(),
|
||||
selectionCode: s.selectionCode,
|
||||
selectionName: s.selectionName,
|
||||
odds: Number(s.odds),
|
||||
status: s.status,
|
||||
sortOrder: s.sortOrder,
|
||||
})),
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -288,13 +567,22 @@ export class MatchesService {
|
||||
data: {
|
||||
leagueEn: string;
|
||||
leagueZh: string;
|
||||
leagueMs?: string;
|
||||
homeTeamZh: string;
|
||||
homeTeamEn: string;
|
||||
homeTeamMs?: string;
|
||||
awayTeamZh: string;
|
||||
awayTeamEn: string;
|
||||
awayTeamMs?: string;
|
||||
startTime: Date;
|
||||
isHot?: boolean;
|
||||
displayOrder?: number;
|
||||
matchName?: string;
|
||||
stage?: string;
|
||||
groupName?: string;
|
||||
leagueLogoUrl?: string;
|
||||
homeTeamLogoUrl?: string;
|
||||
awayTeamLogoUrl?: string;
|
||||
updatedBy?: bigint;
|
||||
},
|
||||
) {
|
||||
@@ -306,23 +594,55 @@ export class MatchesService {
|
||||
throw new BadRequestException('当前状态不可编辑');
|
||||
}
|
||||
|
||||
const matchName = `${data.homeTeamEn.trim() || data.homeTeamZh.trim()} - ${data.awayTeamEn.trim() || data.awayTeamZh.trim()}`;
|
||||
const matchName =
|
||||
data.matchName?.trim() ||
|
||||
`${data.homeTeamEn.trim() || data.homeTeamZh.trim() || data.homeTeamMs?.trim() || ''} - ${data.awayTeamEn.trim() || data.awayTeamZh.trim() || data.awayTeamMs?.trim() || ''}`;
|
||||
|
||||
await Promise.all([
|
||||
this.upsertEntityTranslations('LEAGUE', match.leagueId, {
|
||||
'zh-CN': data.leagueZh.trim(),
|
||||
'en-US': data.leagueEn.trim(),
|
||||
'ms-MY': (data.leagueMs ?? '').trim(),
|
||||
}),
|
||||
this.upsertEntityTranslations('TEAM', match.homeTeamId, {
|
||||
'zh-CN': data.homeTeamZh.trim(),
|
||||
'en-US': data.homeTeamEn.trim(),
|
||||
'ms-MY': (data.homeTeamMs ?? '').trim(),
|
||||
}),
|
||||
this.upsertEntityTranslations('TEAM', match.awayTeamId, {
|
||||
'zh-CN': data.awayTeamZh.trim(),
|
||||
'en-US': data.awayTeamEn.trim(),
|
||||
'ms-MY': (data.awayTeamMs ?? '').trim(),
|
||||
}),
|
||||
]);
|
||||
|
||||
const logoUpdates: Promise<unknown>[] = [];
|
||||
if (data.leagueLogoUrl !== undefined) {
|
||||
logoUpdates.push(
|
||||
this.prisma.league.update({
|
||||
where: { id: match.leagueId },
|
||||
data: { logoUrl: data.leagueLogoUrl.trim() || null },
|
||||
}),
|
||||
);
|
||||
}
|
||||
if (data.homeTeamLogoUrl !== undefined) {
|
||||
logoUpdates.push(
|
||||
this.prisma.team.update({
|
||||
where: { id: match.homeTeamId },
|
||||
data: { logoUrl: data.homeTeamLogoUrl.trim() || null },
|
||||
}),
|
||||
);
|
||||
}
|
||||
if (data.awayTeamLogoUrl !== undefined) {
|
||||
logoUpdates.push(
|
||||
this.prisma.team.update({
|
||||
where: { id: match.awayTeamId },
|
||||
data: { logoUrl: data.awayTeamLogoUrl.trim() || null },
|
||||
}),
|
||||
);
|
||||
}
|
||||
if (logoUpdates.length) await Promise.all(logoUpdates);
|
||||
|
||||
return this.prisma.match.update({
|
||||
where: { id: matchId },
|
||||
data: {
|
||||
@@ -330,6 +650,8 @@ export class MatchesService {
|
||||
isHot: data.isHot ?? match.isHot,
|
||||
displayOrder: data.displayOrder ?? match.displayOrder,
|
||||
matchName,
|
||||
stage: data.stage !== undefined ? data.stage.trim() || null : match.stage,
|
||||
groupName: data.groupName !== undefined ? data.groupName.trim() || null : match.groupName,
|
||||
updatedBy: data.updatedBy,
|
||||
},
|
||||
});
|
||||
@@ -484,6 +806,13 @@ export class MatchesService {
|
||||
});
|
||||
}
|
||||
|
||||
private async getTranslationExact(entityType: string, entityId: bigint, locale: string) {
|
||||
const row = await this.prisma.entityTranslation.findFirst({
|
||||
where: { entityType, entityId, locale, fieldName: 'name' },
|
||||
});
|
||||
return row?.value ?? '';
|
||||
}
|
||||
|
||||
async getTranslation(entityType: string, entityId: bigint, locale: string) {
|
||||
const translations = await this.prisma.entityTranslation.findMany({
|
||||
where: { entityType, entityId },
|
||||
@@ -500,25 +829,63 @@ export class MatchesService {
|
||||
leagueId: bigint;
|
||||
homeTeamId: bigint;
|
||||
awayTeamId: bigint;
|
||||
homeTeam?: { code: string };
|
||||
awayTeam?: { code: string };
|
||||
markets?: unknown[];
|
||||
startTime: Date;
|
||||
status?: string;
|
||||
isHot?: boolean;
|
||||
displayOrder?: number;
|
||||
matchName?: string | null;
|
||||
stage?: string | null;
|
||||
groupName?: string | null;
|
||||
homeTeam?: { code: string; logoUrl?: string | null };
|
||||
awayTeam?: { code: string; logoUrl?: string | null };
|
||||
league?: { logoUrl?: string | null };
|
||||
markets?: Array<Record<string, unknown>>;
|
||||
};
|
||||
const [leagueName, homeName, awayName] = await Promise.all([
|
||||
this.getTranslation('LEAGUE', m.leagueId, locale),
|
||||
this.getTranslation('TEAM', m.homeTeamId, locale),
|
||||
this.getTranslation('TEAM', m.awayTeamId, locale),
|
||||
]);
|
||||
return {
|
||||
...match,
|
||||
const base = {
|
||||
id: m.id.toString(),
|
||||
leagueId: m.leagueId.toString(),
|
||||
leagueName,
|
||||
leagueLogoUrl: m.league?.logoUrl ?? null,
|
||||
homeTeamName: homeName,
|
||||
awayTeamName: awayName,
|
||||
homeTeamCode: m.homeTeam?.code ?? '',
|
||||
awayTeamCode: m.awayTeam?.code ?? '',
|
||||
homeTeamLogoUrl: m.homeTeam?.logoUrl ?? null,
|
||||
awayTeamLogoUrl: m.awayTeam?.logoUrl ?? null,
|
||||
startTime: m.startTime.toISOString(),
|
||||
isHot: m.isHot ?? false,
|
||||
displayOrder: m.displayOrder ?? 0,
|
||||
matchName: m.matchName ?? null,
|
||||
stage: m.stage ?? null,
|
||||
groupName: m.groupName ?? null,
|
||||
status: m.status ?? 'PUBLISHED',
|
||||
};
|
||||
if (m.markets) {
|
||||
return {
|
||||
...base,
|
||||
markets: m.markets.map((market) => ({
|
||||
id: (market.id as bigint).toString(),
|
||||
marketType: market.marketType as string,
|
||||
period: market.period as string,
|
||||
lineValue: market.lineValue != null ? Number(market.lineValue) : null,
|
||||
allowParlay: (market.allowParlay as boolean | undefined) ?? true,
|
||||
promoLabel: (market.promoLabel as string | null | undefined) ?? null,
|
||||
selections: ((market.selections as Array<Record<string, unknown>>) ?? []).map((s) => ({
|
||||
id: (s.id as bigint).toString(),
|
||||
selectionCode: s.selectionCode as string,
|
||||
selectionName: s.selectionName as string,
|
||||
odds: Number(s.odds),
|
||||
oddsVersion: (s.oddsVersion as bigint).toString(),
|
||||
})),
|
||||
})),
|
||||
};
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
async listPublished(locale = 'en-US', leagueId?: bigint) {
|
||||
@@ -528,27 +895,37 @@ export class MatchesService {
|
||||
status: 'PUBLISHED',
|
||||
isOutright: false,
|
||||
sportType: 'FOOTBALL',
|
||||
deletedAt: null,
|
||||
startTime: { gt: now },
|
||||
...(leagueId ? { leagueId } : {}),
|
||||
},
|
||||
include: {
|
||||
league: true,
|
||||
homeTeam: true,
|
||||
awayTeam: true,
|
||||
markets: {
|
||||
where: { status: 'OPEN' },
|
||||
include: { selections: { where: { status: 'OPEN' } } },
|
||||
include: { selections: { where: { status: 'OPEN' }, orderBy: { sortOrder: 'asc' } } },
|
||||
orderBy: { sortOrder: 'asc' },
|
||||
},
|
||||
},
|
||||
orderBy: [{ isHot: 'desc' }, { startTime: 'asc' }],
|
||||
orderBy: [{ isHot: 'desc' }, { displayOrder: 'asc' }, { startTime: 'asc' }],
|
||||
});
|
||||
|
||||
return Promise.all(matches.map((m) => this.enrichMatch(m, locale)));
|
||||
}
|
||||
|
||||
async getMatchDetail(matchId: bigint, locale = 'en-US') {
|
||||
const match = await this.prisma.match.findUnique({
|
||||
where: { id: matchId },
|
||||
const match = await this.prisma.match.findFirst({
|
||||
where: {
|
||||
id: matchId,
|
||||
deletedAt: null,
|
||||
sportType: 'FOOTBALL',
|
||||
isOutright: false,
|
||||
status: { in: ['PUBLISHED', 'CLOSED'] },
|
||||
},
|
||||
include: {
|
||||
league: true,
|
||||
homeTeam: true,
|
||||
awayTeam: true,
|
||||
markets: {
|
||||
@@ -560,9 +937,6 @@ export class MatchesService {
|
||||
},
|
||||
});
|
||||
if (!match) throw new NotFoundException('Match not found');
|
||||
if (match.sportType !== 'FOOTBALL') {
|
||||
throw new NotFoundException('Match not found');
|
||||
}
|
||||
return this.enrichMatch(match, locale);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user