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 }); }