全项目扫描修复: Docker数据卷修复+安全requireAdmin+12页SEO+API白名单+脚本超时+常量提取+假数据删除
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* 同步勋章定义到数据库
|
||||
* - 把 achievements 表填充/更新为代码中维护的 ACHIEVEMENT_DEFS
|
||||
* - 对存量用户批量评估一次(首屏打开即有数据)
|
||||
*/
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
import { PrismaMariaDb } from "@prisma/adapter-mariadb";
|
||||
import "dotenv/config";
|
||||
|
||||
const base = process.env.DATABASE_URL.replace("mysql://", "mariadb://");
|
||||
const cs = base + (base.includes("?") ? "&" : "?") + "connection_limit=5";
|
||||
const prisma = new PrismaClient({ adapter: new PrismaMariaDb(cs) });
|
||||
|
||||
const ACHIEVEMENT_DEFS = [
|
||||
{ key: "first_post", name: "初次发声", description: "发布第 1 个话题", icon: "🌱", tier: "bronze", points: 10, category: "post", sortOrder: 1, target: 1 },
|
||||
{ key: "first_reply", name: "热心回复", description: "发出第 1 条回复", icon: "💬", tier: "bronze", points: 10, category: "reply", sortOrder: 2, target: 1 },
|
||||
{ key: "first_like", name: "首获认可", description: "首次被点赞", icon: "👍", tier: "bronze", points: 10, category: "social", sortOrder: 3, target: 1 },
|
||||
{ key: "posts_10", name: "话题达人", description: "累计发布 10 个话题", icon: "📚", tier: "silver", points: 30, category: "post", sortOrder: 10, target: 10 },
|
||||
{ key: "replies_50", name: "热心答主", description: "累计发出 50 条回复", icon: "🗣️", tier: "silver", points: 30, category: "reply", sortOrder: 11, target: 50 },
|
||||
{ key: "likes_50", name: "社区明星", description: "累计被点赞 50 次", icon: "⭐", tier: "silver", points: 30, category: "social", sortOrder: 12, target: 50 },
|
||||
{ key: "checkin_7", name: "周满贯", description: "连续签到 7 天", icon: "🔥", tier: "silver", points: 30, category: "checkin", sortOrder: 13, target: 7 },
|
||||
{ key: "best_answer_1", name: "最佳答主", description: "首个回复被采纳为最佳答案", icon: "🏆", tier: "silver", points: 30, category: "milestone", sortOrder: 14, target: 1 },
|
||||
{ key: "posts_100", name: "百帖宗师", description: "累计发布 100 个话题", icon: "📕", tier: "gold", points: 100, category: "post", sortOrder: 20, target: 100 },
|
||||
{ key: "replies_500", name: "千言万语", description: "累计发出 500 条回复", icon: "📝", tier: "gold", points: 100, category: "reply", sortOrder: 21, target: 500 },
|
||||
{ key: "likes_500", name: "万人迷", description: "累计被点赞 500 次", icon: "💖", tier: "gold", points: 100, category: "social", sortOrder: 22, target: 500 },
|
||||
{ key: "checkin_30", name: "月之守护", description: "累计签到 30 天", icon: "🌙", tier: "gold", points: 100, category: "checkin", sortOrder: 23, target: 30 },
|
||||
{ key: "best_answer_10", name: "答疑名师", description: "10 个回复被采纳为最佳答案", icon: "👑", tier: "gold", points: 100, category: "milestone", sortOrder: 24, target: 10 },
|
||||
{ key: "pioneer", name: "先驱者", description: "社区前 100 位注册用户", icon: "🚀", tier: "special", points: 200, category: "milestone", sortOrder: 30, target: 1 },
|
||||
{ key: "night_owl", name: "深夜猫头鹰", description: "在 0~5 点发布 5 个话题", icon: "🦉", tier: "special", points: 50, category: "milestone", sortOrder: 31, target: 5 },
|
||||
];
|
||||
|
||||
async function evaluateForUser(userId) {
|
||||
const [topicCount, postCount, likeCount, bestAnswerCount, totalCheckIns] =
|
||||
await Promise.all([
|
||||
prisma.forumTopic.count({ where: { userId } }),
|
||||
prisma.forumPost.count({ where: { userId } }),
|
||||
prisma.forumPostLike.count({ where: { post: { userId } } }),
|
||||
prisma.forumPost.count({ where: { userId, isAnswer: true } }),
|
||||
prisma.forumCheckIn.count({ where: { userId } }),
|
||||
]);
|
||||
|
||||
const progressFor = (key) => {
|
||||
switch (key) {
|
||||
case "first_post": return Math.min(topicCount, 1);
|
||||
case "first_reply": return Math.min(postCount, 1);
|
||||
case "first_like": return Math.min(likeCount, 1);
|
||||
case "posts_10": return Math.min(topicCount, 10);
|
||||
case "replies_50": return Math.min(postCount, 50);
|
||||
case "likes_50": return Math.min(likeCount, 50);
|
||||
case "checkin_7": return Math.min(totalCheckIns, 7);
|
||||
case "best_answer_1": return Math.min(bestAnswerCount, 1);
|
||||
case "posts_100": return Math.min(topicCount, 100);
|
||||
case "replies_500": return Math.min(postCount, 500);
|
||||
case "likes_500": return Math.min(likeCount, 500);
|
||||
case "checkin_30": return Math.min(totalCheckIns, 30);
|
||||
case "best_answer_10": return Math.min(bestAnswerCount, 10);
|
||||
case "pioneer": return 1;
|
||||
case "night_owl": return 0;
|
||||
default: return 0;
|
||||
}
|
||||
};
|
||||
|
||||
const existing = await prisma.userAchievement.findMany({ where: { userId } });
|
||||
const map = new Map(existing.map((e) => [e.achievementKey, e]));
|
||||
|
||||
for (const def of ACHIEVEMENT_DEFS) {
|
||||
const progress = progressFor(def.key);
|
||||
const prev = map.get(def.key);
|
||||
const shouldUnlock = progress >= def.target;
|
||||
if (prev) {
|
||||
const data = { progress };
|
||||
if (shouldUnlock && !prev.unlockedAt) {
|
||||
data.unlockedAt = new Date();
|
||||
}
|
||||
await prisma.userAchievement.update({ where: { id: prev.id }, data });
|
||||
} else {
|
||||
await prisma.userAchievement.create({
|
||||
data: {
|
||||
userId,
|
||||
achievementKey: def.key,
|
||||
progress,
|
||||
unlockedAt: shouldUnlock ? new Date() : null,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log(`[seed-achievements] 同步 ${ACHIEVEMENT_DEFS.length} 个勋章定义...`);
|
||||
for (const def of ACHIEVEMENT_DEFS) {
|
||||
const data = {
|
||||
key: def.key,
|
||||
name: def.name,
|
||||
description: def.description,
|
||||
icon: def.icon,
|
||||
tier: def.tier,
|
||||
points: def.points,
|
||||
category: def.category,
|
||||
sortOrder: def.sortOrder,
|
||||
isActive: true,
|
||||
};
|
||||
await prisma.achievement.upsert({
|
||||
where: { key: def.key },
|
||||
create: data,
|
||||
update: data,
|
||||
});
|
||||
}
|
||||
console.log("[seed-achievements] 勋章定义已同步");
|
||||
|
||||
const users = await prisma.user.findMany({ select: { id: true } });
|
||||
console.log(`[seed-achievements] 评估 ${users.length} 个用户的勋章进度...`);
|
||||
for (const u of users) {
|
||||
await evaluateForUser(u.id);
|
||||
}
|
||||
console.log("[seed-achievements] 完成");
|
||||
}
|
||||
|
||||
main()
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
})
|
||||
.finally(async () => {
|
||||
await prisma.$disconnect();
|
||||
});
|
||||
Reference in New Issue
Block a user