全项目扫描修复: Docker数据卷修复+安全requireAdmin+12页SEO+API白名单+脚本超时+常量提取+假数据删除
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
const DAYS = 90;
|
||||
|
||||
export async function GET(_req: Request, { params }: { params: { id: string } }) {
|
||||
try {
|
||||
const userId = parseInt(params.id);
|
||||
if (isNaN(userId)) {
|
||||
return NextResponse.json({ error: "无效的用户ID" }, { status: 400 });
|
||||
}
|
||||
|
||||
const since = new Date();
|
||||
since.setDate(since.getDate() - DAYS);
|
||||
since.setHours(0, 0, 0, 0);
|
||||
|
||||
const [topics, posts, comments, checkIns] = await Promise.all([
|
||||
prisma.forumTopic.findMany({
|
||||
where: { userId, createdAt: { gte: since } },
|
||||
select: { createdAt: true },
|
||||
}),
|
||||
prisma.forumPost.findMany({
|
||||
where: { userId, createdAt: { gte: since } },
|
||||
select: { createdAt: true },
|
||||
}),
|
||||
prisma.comment.findMany({
|
||||
where: { userId, createdAt: { gte: since } },
|
||||
select: { createdAt: true },
|
||||
}),
|
||||
prisma.forumCheckIn.findMany({
|
||||
where: { userId, checkInDate: { gte: since } },
|
||||
select: { checkInDate: true, streak: true, pointsEarned: true },
|
||||
}),
|
||||
]);
|
||||
|
||||
const all = new Map<string, { count: number; topics: number; posts: number; comments: number }>();
|
||||
const bump = (d: Date, type: "topics" | "posts" | "comments") => {
|
||||
const k = d.toISOString().slice(0, 10);
|
||||
const cur = all.get(k) || { count: 0, topics: 0, posts: 0, comments: 0 };
|
||||
cur.count += 1;
|
||||
cur[type] += 1;
|
||||
all.set(k, cur);
|
||||
};
|
||||
|
||||
topics.forEach(t => bump(t.createdAt, "topics"));
|
||||
posts.forEach(p => bump(p.createdAt, "posts"));
|
||||
comments.forEach(c => bump(c.createdAt, "comments"));
|
||||
|
||||
const heatmap: Array<{ date: string; count: number; topics: number; posts: number; comments: number }> = [];
|
||||
for (let i = DAYS - 1; i >= 0; i--) {
|
||||
const d = new Date();
|
||||
d.setDate(d.getDate() - i);
|
||||
const k = d.toISOString().slice(0, 10);
|
||||
const v = all.get(k);
|
||||
heatmap.push({
|
||||
date: k,
|
||||
count: v?.count || 0,
|
||||
topics: v?.topics || 0,
|
||||
posts: v?.posts || 0,
|
||||
comments: v?.comments || 0,
|
||||
});
|
||||
}
|
||||
|
||||
const checkInList = checkIns
|
||||
.map(c => ({
|
||||
date: c.checkInDate.toISOString().slice(0, 10),
|
||||
streak: c.streak,
|
||||
points: c.pointsEarned,
|
||||
}))
|
||||
.sort((a, b) => a.date.localeCompare(b.date));
|
||||
|
||||
const totalActiveDays = heatmap.filter(d => d.count > 0).length;
|
||||
const totalActions = topics.length + posts.length + comments.length;
|
||||
|
||||
return NextResponse.json({
|
||||
heatmap,
|
||||
checkIns: checkInList,
|
||||
summary: {
|
||||
totalDays: DAYS,
|
||||
activeDays: totalActiveDays,
|
||||
activeRate: Math.round((totalActiveDays / DAYS) * 100),
|
||||
totalActions,
|
||||
topicsCount: topics.length,
|
||||
postsCount: posts.length,
|
||||
commentsCount: comments.length,
|
||||
},
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
const err = error as { message?: string };
|
||||
return NextResponse.json({ error: err.message || "获取活跃度数据失败" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user