Files
thebet365/apps/api/scripts/build-wc2026-seed-json.mjs
Mars e7e938f261 feat: WC2026 赛事 seed、生产上线初始化脚本与目录归档
重构 seed 为 WC2026 72 场小组赛与 48 强优胜盘;新增 production 模式仅保留 admin 与赛事示例;提供 prod-init-db 全量重置脚本;管理端 i18n 分包与赛事归档能力。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-12 18:17:00 +08:00

153 lines
4.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 从 zhibo 导出的 world-cup-group-stage-matches.json 生成 seed 用裁剪 JSON 与球队映射 TS。
* 用法: node apps/api/scripts/build-wc2026-seed-json.mjs <源JSON路径>
*/
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const apiRoot = path.resolve(__dirname, '..');
const seedDataDir = path.join(apiRoot, 'src/infrastructure/database/seed-data');
const outrightTeamsPath = path.join(apiRoot, 'src/domains/catalog/wc2026-outright-teams.ts');
const sourcePath = process.argv[2];
if (!sourcePath) {
console.error('用法: node build-wc2026-seed-json.mjs <源JSON路径>');
process.exit(1);
}
const raw = JSON.parse(fs.readFileSync(path.resolve(sourcePath), 'utf8'));
const outrightSrc = fs.readFileSync(outrightTeamsPath, 'utf8');
const outrightTeams = [...outrightSrc.matchAll(/code: '([^']+)', names: \{ 'zh-CN': '([^']+)', 'en-US': '([^']+)'/g)].map((m) => ({
code: m[1],
zh: m[2],
en: m[3],
}));
function pickNames(names) {
return {
zh: names?.zh ?? null,
en: names?.en ?? null,
zhTw: names?.zhTw ?? null,
vi: names?.vi ?? null,
km: names?.km ?? null,
ms: names?.ms ?? null,
};
}
function pickTeam(team) {
return {
id: team.id,
name: team.name,
names: pickNames(team.names),
image: team.image ?? '',
};
}
function slimMatch(m) {
return {
officialMatchNo: m.officialMatchNo,
stage: m.stage,
groupName: m.groupName,
liveMatchId: m.liveMatchId,
additionMatchId: m.additionMatchId,
channelId: m.channelId,
matchName: m.matchName,
league: { type: m.league.type, en: m.league.en, zh: m.league.zh },
kickoff: {
utcTimeStart: m.kickoff.utcTimeStart,
utcTimeStop: m.kickoff.utcTimeStop,
utcIso: m.kickoff.utcIso,
chinaTime: m.kickoff.chinaTime,
venueTime: m.kickoff.venueTime,
venueTimezone: m.kickoff.venueTimezone,
},
homeTeam: pickTeam(m.homeTeam),
awayTeam: pickTeam(m.awayTeam),
status: { state: m.status.state, isHot: m.status.isHot ?? 0 },
venue: {
names: pickNames(m.venue?.names),
city: pickNames(m.venue?.city),
},
sortOrder: m.sortOrder,
isPublished: m.isPublished,
};
}
function resolveCanonicalCode(team) {
if (team.id == null) return null;
const en = (team.name || team.names?.en || '').toLowerCase();
const zh = team.names?.zh || '';
const hit = outrightTeams.find(
(o) =>
o.en.toLowerCase() === en ||
o.zh === zh ||
(o.en === 'Turkey' && en.includes('türkiye')) ||
(o.en === 'Czech' && en === 'czechia') ||
(o.en === 'Bosnia' && en.includes('bosnia')) ||
(o.en === 'Ivory Coast' && en.includes('côte')) ||
(o.en === 'DR Congo' && en.includes('congo')) ||
(o.en === 'Curacao' && en.includes('cura')),
);
return hit?.code ?? null;
}
const matches = (raw.matches || []).map(slimMatch);
const bundle = { count: matches.length, matches };
const teamById = new Map();
for (const m of raw.matches || []) {
for (const t of [m.homeTeam, m.awayTeam]) {
if (t?.id != null) teamById.set(t.id, t);
}
}
const zhiboToCode = {};
const logoByCode = {};
const unmatched = [];
for (const [id, team] of teamById) {
const code = resolveCanonicalCode(team);
if (code) {
zhiboToCode[id] = code;
if (team.image) logoByCode[code] = team.image;
} else {
unmatched.push({ id, name: team.name });
}
}
fs.mkdirSync(seedDataDir, { recursive: true });
const jsonOut = path.join(seedDataDir, 'wc2026-group-stage.json');
fs.writeFileSync(jsonOut, JSON.stringify(bundle, null, 2) + '\n', 'utf8');
const mapLines = Object.entries(zhiboToCode)
.sort(([a], [b]) => Number(a) - Number(b))
.map(([id, code]) => ` ${id}: '${code}',`)
.join('\n');
const logoLines = Object.entries(logoByCode)
.sort(([a], [b]) => a.localeCompare(b))
.map(([code, url]) => ` ${code}: '${url.replace(/'/g, "\\'")}',`)
.join('\n');
const mapTs = `/** 由 build-wc2026-seed-json.mjs 生成 — zhibo externalId → WC2026 canonical code */
export const WC2026_ZIBO_ID_TO_CODE: Record<number, string> = {
${mapLines}
};
/** zhibo 球队 logoseed 时写入 teams.logo_url */
export const WC2026_TEAM_LOGO_BY_CODE: Record<string, string> = {
${logoLines}
};
`;
const mapOut = path.join(seedDataDir, 'wc2026-zhibo-team-map.ts');
fs.writeFileSync(mapOut, mapTs, 'utf8');
console.log(`Wrote ${jsonOut} (${matches.length} matches)`);
console.log(`Wrote ${mapOut} (${Object.keys(zhiboToCode).length} team mappings)`);
if (unmatched.length) {
console.warn('Unmatched teams:', unmatched);
process.exit(1);
}