UI-UX/prisma/seed.ts
iye a9f4799f71
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 3m26s
feat(db): wire real persistence for votes / users / quota / supports
数据正式落库, 不再仅靠浏览器内存:

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>
2026-05-13 17:32:38 +08:00

111 lines
3.0 KiB
TypeScript
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.

/**
* 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();
});