import { NextRequest, NextResponse } from "next/server"; import { getServerSession } from "next-auth"; import { authOptions } from "@/lib/auth"; import { prisma } from "@/lib/prisma"; export async function GET(req: NextRequest) { try { const session = await getServerSession(authOptions); if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); const { searchParams } = new URL(req.url); const listId = searchParams.get("listId"); const showCompleted = searchParams.get("showCompleted") === "true"; const tagName = searchParams.get("tag"); const isTrash = searchParams.get("isTrash") === "true"; const countTrash = searchParams.get("countTrash") === "true"; if (countTrash) { const trashCount = await prisma.task.count({ where: { userId: session.user.id, isDeleted: true } as any, }); return NextResponse.json({ count: trashCount }); } if (isTrash) { // Return all deleted tasks (top-level or subtasks) const trashTasks = await prisma.task.findMany({ where: { userId: session.user.id, isDeleted: true } as any, orderBy: [{ deletedAt: "desc" } as any, { createdAt: "desc" }], include: { children: { orderBy: [{ sortOrder: "asc" }], include: { tags: { include: { tag: true } } }, }, tags: { include: { tag: true } }, }, }); return NextResponse.json(trashTasks); } const where: Record = { userId: session.user.id, isDeleted: false, ...(listId ? { listId, parentId: null } : tagName ? {} : { parentId: null }), ...(showCompleted ? {} : { completed: false }), ...(tagName ? { tags: { some: { tag: { name: tagName } } } } : {}), }; const tasks = await prisma.task.findMany({ where: where as any, orderBy: [{ sortOrder: "asc" }, { createdAt: "asc" }], include: { children: { where: { isDeleted: false } as any, orderBy: [{ sortOrder: "asc" }], include: { children: { where: { isDeleted: false } as any, orderBy: [{ sortOrder: "asc" }] }, tags: { include: { tag: true } }, }, }, tags: { include: { tag: true } }, }, }); return NextResponse.json(tasks); } catch (err) { console.error("[tasks:GET]", err); return NextResponse.json({ error: "Failed to fetch tasks" }, { status: 500 }); } } export async function DELETE(req: NextRequest) { try { const session = await getServerSession(authOptions); if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); const { searchParams } = new URL(req.url); const trash = searchParams.get("trash") === "true"; if (trash) { // Empty all trash permanently await prisma.task.deleteMany({ where: { userId: session.user.id, isDeleted: true } as any, }); return NextResponse.json({ ok: true }); } return NextResponse.json({ error: "Invalid delete request" }, { status: 400 }); } catch (err) { console.error("[tasks:DELETE_BATCH]", err); return NextResponse.json({ error: "Failed to empty trash" }, { status: 500 }); } } export async function POST(req: NextRequest) { try { const session = await getServerSession(authOptions); if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); const body = await req.json().catch(() => null); if (!body) return NextResponse.json({ error: "Invalid body" }, { status: 400 }); const { title, listId, parentId, dueDate, priority, note } = body; if (!title || !listId) return NextResponse.json({ error: "title and listId required" }, { status: 400 }); const list = await prisma.list.findFirst({ where: { id: listId, userId: session.user.id } }); if (!list) return NextResponse.json({ error: "List not found" }, { status: 404 }); // IDOR 방어: parentId가 지정된 경우, 부모 태스크가 현재 사용자의 소유인지 검증 if (parentId) { const parentTask = await prisma.task.findFirst({ where: { id: parentId, userId: session.user.id }, }); if (!parentTask) { return NextResponse.json({ error: "Parent task not found or forbidden" }, { status: 403 }); } } const count = await prisma.task.count({ where: { listId, parentId: parentId || null } }); const task = await prisma.task.create({ data: { userId: session.user.id, listId, parentId: parentId || null, title: title.trim(), note: note?.trim() || null, dueDate: dueDate ? new Date(dueDate) : null, priority: Number(priority) || 0, sortOrder: count, }, include: { children: true, tags: { include: { tag: true } } }, }); return NextResponse.json(task, { status: 201 }); } catch (err) { console.error("[tasks:POST]", err); return NextResponse.json({ error: "Failed to create task" }, { status: 500 }); } }