All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 3m26s
数据正式落库, 不再仅靠浏览器内存: prisma/schema.prisma: - Artist 模型对齐当前前端数据形态: * 旧字段 slogan / birthday / cv / themeColor 改为可选 (前端早不用, 但保留兼容历史 seed) * 新增 age / gender / motto / personality / catchphrase / skills / track (来自人物小传) - 注释从 "001 ~ 035" 改 "001 ~ 036" prisma/seed.ts: - 整体重写: 从 src/lib/artist-bios.ts 的 ARTIST_SEEDS 灌真实 36 人 - 不再写假数据 (AURORA / LUMI / NEBULA...) - portrait / videoUrl 不入库 (前端 NEXT_PUBLIC_TOS_DOMAIN 拼接, 换桶不用 reseed) - ActivityConfig 默认 dailyQuota=10, perArtistLimit=0, voteEnabled=true, 活动期 30 天 src/lib/date-utils.ts (新增): - startOfUtcDay(): 修复"今日"在 MySQL @db.Date 列与 JS Date 之间的 TZ 漂移 - isSameUtcDay(): 共享给签到判断 修复 P2025 bug (vote / me / signin): - 用 startOfUtcDay 替代 startOfDay (后者用 setHours 取本地午夜, 对 @db.Date 列会因 TZ 漂移导致 upsert 后再用 userId_date 复合键查找失败) - /api/vote 的扣额度从 userId_date 改用 dq.id 主键 update, 双保险 - 三个路由的 startOfDay 重复实现合并到 lib/date-utils E2E 验证 (curl): 登录 → 投 5 给 002 → 余 5 ✓ 投 3 给 003 → 余 2 / totalVotes 8 ✓ /api/me supports 反映 002+003 真实 voteTotal ✓ 超额 (5 票 余 2) → 409 QUOTA_EXHAUSTED ✓ /api/ranking 票数实时反映 DB ✓ Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
111 lines
3.0 KiB
TypeScript
111 lines
3.0 KiB
TypeScript
/**
|
||
* Prisma 种子脚本 · 把 36 位真实艺人 + 活动配置写入 DB
|
||
*
|
||
* 数据来源:src/lib/artist-bios.ts (从《36 位虚拟艺人人物小传.docx》提取)
|
||
* 这份文件同时是前端 SSG 的静态源 + DB 的 seed 源,双写但单一来源,
|
||
* 改人物字段时只需要改 artist-bios.ts,然后重跑 seed。
|
||
*
|
||
* 运行:pnpm db:seed
|
||
*
|
||
* 注意:portrait / videoUrl 等 TOS URL 不写入 DB —— 前端从 NEXT_PUBLIC_TOS_DOMAIN
|
||
* 自动拼接,DB 字段保留为 NULL (灵活度高,换桶不需要重跑 seed)。
|
||
*/
|
||
|
||
import { PrismaClient, Prisma } from "@prisma/client";
|
||
import { ARTIST_SEEDS } from "../src/lib/artist-bios";
|
||
|
||
const prisma = new PrismaClient();
|
||
|
||
async function main() {
|
||
console.log("🌱 开始 seed 数据库...");
|
||
|
||
// 1. 活动配置 (upsert: 第一次创建, 后续仅延长 endAt)
|
||
const now = new Date();
|
||
const endAt = new Date(now);
|
||
endAt.setDate(endAt.getDate() + 30); // 默认活动期 30 天
|
||
|
||
await prisma.activityConfig.upsert({
|
||
where: { id: 1 },
|
||
create: {
|
||
id: 1,
|
||
startAt: now,
|
||
endAt,
|
||
voteEnabled: true,
|
||
dailyQuota: 10,
|
||
perArtistLimit: 0, // 不限单艺人
|
||
paidVoteEnabled: false,
|
||
},
|
||
update: {
|
||
endAt,
|
||
voteEnabled: true,
|
||
dailyQuota: 10,
|
||
perArtistLimit: 0,
|
||
},
|
||
});
|
||
console.log(" ✓ 活动配置已写入 (dailyQuota=10, voteEnabled=true)");
|
||
|
||
// 2. 36 位艺人 (upsert: 若 DB 里有旧 seed 的假数据, 覆盖为真实姓名/简介)
|
||
let created = 0;
|
||
let updated = 0;
|
||
for (const seed of ARTIST_SEEDS) {
|
||
const existing = await prisma.artist.findUnique({ where: { id: seed.no } });
|
||
|
||
const data = {
|
||
no: seed.no,
|
||
name: seed.name,
|
||
enName: seed.enName,
|
||
bio: seed.bio,
|
||
height: seed.height,
|
||
age: seed.age,
|
||
gender: seed.gender,
|
||
motto: seed.motto ?? null,
|
||
personality: seed.personality ?? null,
|
||
catchphrase: seed.catchphrase ?? null,
|
||
skills: seed.skills ?? null,
|
||
track: seed.track ?? null,
|
||
tags: seed.tags as unknown as Prisma.InputJsonValue,
|
||
};
|
||
|
||
if (existing) {
|
||
await prisma.artist.update({
|
||
where: { id: seed.no },
|
||
data,
|
||
});
|
||
updated++;
|
||
} else {
|
||
await prisma.artist.create({
|
||
data: {
|
||
id: seed.no,
|
||
...data,
|
||
status: "ACTIVE",
|
||
voteCount: 0,
|
||
currentRank: parseInt(seed.no, 10),
|
||
},
|
||
});
|
||
created++;
|
||
}
|
||
}
|
||
console.log(
|
||
` ✓ 36 人 seed 完成 (新建 ${created}, 更新 ${updated})`,
|
||
);
|
||
|
||
// 3. 报告 DB 当前状态
|
||
const stats = {
|
||
artists: await prisma.artist.count({ where: { status: "ACTIVE" } }),
|
||
users: await prisma.user.count(),
|
||
votes: await prisma.vote.count(),
|
||
config: await prisma.activityConfig.count(),
|
||
};
|
||
console.log("📊 DB 当前状态:", stats);
|
||
console.log("✅ Seed 完成");
|
||
}
|
||
|
||
main()
|
||
.catch((e) => {
|
||
console.error("❌ Seed 失败:", e);
|
||
process.exit(1);
|
||
})
|
||
.finally(async () => {
|
||
await prisma.$disconnect();
|
||
});
|