2026-05-25: 等级体系升级(20级)+论坛二级分类+会员空间
This commit is contained in:
@@ -0,0 +1,206 @@
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { authOptions } from "@/lib/auth";
|
||||
import { NextResponse } from "next/server";
|
||||
import { rewardPoints } from "@/lib/points-reward";
|
||||
|
||||
export async function GET(req: Request) {
|
||||
const { searchParams } = new URL(req.url);
|
||||
const toolId = searchParams.get("toolId");
|
||||
const skillId = searchParams.get("skillId");
|
||||
const parentId = searchParams.get("parentId");
|
||||
const page = parseInt(searchParams.get("page") || "1", 10);
|
||||
const pageSize = parseInt(searchParams.get("pageSize") || "20", 10);
|
||||
|
||||
if (!toolId && !skillId) {
|
||||
return NextResponse.json({ error: "必须提供 toolId 或 skillId" }, { status: 400 });
|
||||
}
|
||||
|
||||
const where: any = { status: "ACTIVE" };
|
||||
if (toolId) where.toolId = parseInt(toolId);
|
||||
if (skillId) where.skillId = parseInt(skillId);
|
||||
if (parentId) where.parentId = parseInt(parentId);
|
||||
else where.parentId = null;
|
||||
|
||||
const [comments, totalCount] = await Promise.all([
|
||||
prisma.comment.findMany({
|
||||
where,
|
||||
include: {
|
||||
user: { select: { id: true, name: true, email: true, avatarUrl: true, level: true, points: true } },
|
||||
likes: { select: { userId: true } },
|
||||
replies: {
|
||||
where: { status: "ACTIVE" },
|
||||
include: {
|
||||
user: { select: { id: true, name: true, email: true, avatarUrl: true, level: true, points: true } },
|
||||
likes: { select: { userId: true } },
|
||||
},
|
||||
orderBy: { createdAt: "asc" },
|
||||
take: 5,
|
||||
},
|
||||
_count: { select: { replies: true } },
|
||||
},
|
||||
orderBy: [
|
||||
{ isPinned: "desc" },
|
||||
{ createdAt: "desc" },
|
||||
],
|
||||
skip: (page - 1) * pageSize,
|
||||
take: pageSize,
|
||||
}),
|
||||
prisma.comment.count({ where }),
|
||||
]);
|
||||
|
||||
const session = await getServerSession(authOptions);
|
||||
const currentUserId = session?.user?.id ? parseInt(session.user.id) : null;
|
||||
|
||||
const formatted = comments.map((c: any) => ({
|
||||
id: c.id,
|
||||
content: c.content,
|
||||
rating: c.rating,
|
||||
likeCount: c.likeCount,
|
||||
isPinned: c.isPinned,
|
||||
createdAt: c.createdAt,
|
||||
updatedAt: c.updatedAt,
|
||||
replyCount: c._count.replies,
|
||||
liked: currentUserId ? c.likes.some((l: any) => l.userId === currentUserId) : false,
|
||||
user: {
|
||||
id: c.user.id,
|
||||
name: c.user.name || c.user.email.split("@")[0],
|
||||
avatarUrl: c.user.avatarUrl,
|
||||
level: c.user.level,
|
||||
points: c.user.points,
|
||||
},
|
||||
replies: c.replies.map((r: any) => ({
|
||||
id: r.id,
|
||||
content: r.content,
|
||||
likeCount: r.likeCount,
|
||||
createdAt: r.createdAt,
|
||||
liked: currentUserId ? r.likes.some((l: any) => l.userId === currentUserId) : false,
|
||||
user: {
|
||||
id: r.user.id,
|
||||
name: r.user.name || r.user.email.split("@")[0],
|
||||
avatarUrl: r.user.avatarUrl,
|
||||
level: r.user.level,
|
||||
points: r.user.points,
|
||||
},
|
||||
})),
|
||||
}));
|
||||
|
||||
return NextResponse.json({ comments: formatted, totalCount, page, pageSize });
|
||||
}
|
||||
|
||||
export async function POST(req: Request) {
|
||||
const session = await getServerSession(authOptions);
|
||||
if (!session?.user?.id) {
|
||||
return NextResponse.json({ error: "请先登录" }, { status: 401 });
|
||||
}
|
||||
|
||||
const body = await req.json();
|
||||
const { toolSlug, skillSlug, content, rating, parentId } = body;
|
||||
const userId = parseInt(session.user.id);
|
||||
|
||||
if (!content || !content.trim()) {
|
||||
return NextResponse.json({ error: "评论内容不能为空" }, { status: 400 });
|
||||
}
|
||||
if (content.trim().length > 2000) {
|
||||
return NextResponse.json({ error: "评论内容不能超过2000个字符" }, { status: 400 });
|
||||
}
|
||||
|
||||
let toolId: number | undefined;
|
||||
let skillId: number | undefined;
|
||||
|
||||
if (toolSlug) {
|
||||
const tool = await prisma.tool.findUnique({ where: { slug: toolSlug } });
|
||||
if (!tool) return NextResponse.json({ error: "工具不存在" }, { status: 404 });
|
||||
toolId = tool.id;
|
||||
} else if (skillSlug) {
|
||||
const skill = await prisma.skill.findUnique({ where: { slug: skillSlug } });
|
||||
if (!skill) return NextResponse.json({ error: "技能不存在" }, { status: 404 });
|
||||
skillId = skill.id;
|
||||
} else {
|
||||
return NextResponse.json({ error: "必须提供 toolSlug 或 skillSlug" }, { status: 400 });
|
||||
}
|
||||
|
||||
if (!parentId && rating && (rating < 1 || rating > 5)) {
|
||||
return NextResponse.json({ error: "评分必须为1-5" }, { status: 400 });
|
||||
}
|
||||
|
||||
const comment = await prisma.$transaction(async (tx) => {
|
||||
const newComment = await tx.comment.create({
|
||||
data: {
|
||||
userId,
|
||||
toolId,
|
||||
skillId,
|
||||
parentId: parentId || null,
|
||||
content: content.trim(),
|
||||
rating: parentId ? null : rating || null,
|
||||
},
|
||||
include: {
|
||||
user: { select: { id: true, name: true, email: true, avatarUrl: true, level: true, points: true } },
|
||||
},
|
||||
});
|
||||
|
||||
if (!parentId) {
|
||||
if (toolId) {
|
||||
await tx.tool.update({
|
||||
where: { id: toolId },
|
||||
data: { commentCount: { increment: 1 } },
|
||||
});
|
||||
}
|
||||
if (skillId) {
|
||||
await tx.skill.update({
|
||||
where: { id: skillId },
|
||||
data: { commentCount: { increment: 1 } },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const parentComment = parentId
|
||||
? await tx.comment.findUnique({ where: { id: parentId }, select: { userId: true } })
|
||||
: null;
|
||||
|
||||
if (parentComment && parentComment.userId !== userId) {
|
||||
const replyTo = await tx.user.findUnique({
|
||||
where: { id: parentId ? (await tx.comment.findUnique({ where: { id: parentId } }))?.userId : undefined },
|
||||
select: { id: true, name: true },
|
||||
});
|
||||
if (replyTo) {
|
||||
await tx.notification.create({
|
||||
data: {
|
||||
userId: replyTo.id,
|
||||
type: "COMMENT_REPLY",
|
||||
title: `${session.user.name || "用户"} 回复了你的评论`,
|
||||
content: content.trim().slice(0, 100),
|
||||
link: toolSlug ? `/tools/${toolSlug}` : `/skills/${skillSlug}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return newComment;
|
||||
});
|
||||
|
||||
if (!parentId) {
|
||||
await rewardPoints(userId, "comment", `评论${toolSlug || skillSlug}`, toolSlug ? "tool" : "skill", toolId || skillId);
|
||||
}
|
||||
|
||||
const formatted = {
|
||||
id: comment.id,
|
||||
content: comment.content,
|
||||
rating: comment.rating,
|
||||
likeCount: 0,
|
||||
isPinned: comment.isPinned,
|
||||
createdAt: comment.createdAt,
|
||||
updatedAt: comment.updatedAt,
|
||||
replyCount: 0,
|
||||
liked: false,
|
||||
user: {
|
||||
id: comment.user.id,
|
||||
name: comment.user.name || comment.user.email.split("@")[0],
|
||||
avatarUrl: comment.user.avatarUrl,
|
||||
level: comment.user.level,
|
||||
points: comment.user.points,
|
||||
},
|
||||
};
|
||||
|
||||
return NextResponse.json(formatted, { status: 201 });
|
||||
}
|
||||
Reference in New Issue
Block a user