import { NextResponse } from "next/server"; import { PrismaClient } from "@prisma/client"; import { verifyAccessToken } from "@/features/auth/authCrypto"; // /api/reports/leads // - 로그인한 사용자의 FormSubmission(리드) 목록을 기간/프로젝트 필터에 맞게 조회해, // 최신순으로 정렬된 리드 리스트와 총 개수를 반환한다. const prisma = new PrismaClient(); type RangeParam = "7d" | "30d" | "all"; function extractTokenFromCookieHeader(cookieHeader: string | null): string | null { if (!cookieHeader) return null; const parts = cookieHeader.split(";"); for (const part of parts) { const trimmed = part.trim(); if (trimmed.startsWith("pb_access=")) { const value = trimmed.substring("pb_access=".length); return decodeURIComponent(value); } } return null; } export async function GET(request: Request) { try { const url = new URL(request.url); const range = (url.searchParams.get("range") as RangeParam | null) ?? "all"; const projectIdFilter = url.searchParams.get("projectId"); const fromParam = url.searchParams.get("from"); const toParam = url.searchParams.get("to"); if (range !== "7d" && range !== "30d" && range !== "all") { return NextResponse.json({ message: "Invalid range parameter" }, { status: 400 }); } const cookieHeader = request.headers.get("cookie"); const token = extractTokenFromCookieHeader(cookieHeader); if (!token) { return NextResponse.json({ message: "인증 정보가 없습니다." }, { status: 401 }); } const payload = await verifyAccessToken(token); if (!payload) { return NextResponse.json({ message: "유효하지 않은 토큰입니다." }, { status: 401 }); } const userId = payload.sub; const [projects, submissions] = await Promise.all([ prisma.project.findMany({ where: { userId }, select: { id: true, title: true, slug: true, }, }), prisma.formSubmission.findMany({ where: { userId }, select: { id: true, projectId: true, projectSlug: true, createdAt: true, payloadJson: true, }, }), ]); let fromDate: Date | null = null; let toDate: Date | null = null; if (fromParam || toParam) { if (fromParam) { const parsedFrom = new Date(`${fromParam}T00:00:00.000Z`); if (Number.isNaN(parsedFrom.getTime())) { return NextResponse.json({ message: "Invalid from parameter" }, { status: 400 }); } fromDate = parsedFrom; } if (toParam) { const parsedTo = new Date(`${toParam}T23:59:59.999Z`); if (Number.isNaN(parsedTo.getTime())) { return NextResponse.json({ message: "Invalid to parameter" }, { status: 400 }); } toDate = parsedTo; } } else { const now = new Date(); if (range === "7d") { fromDate = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); } else if (range === "30d") { fromDate = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000); } } // 1단계: 기간/프로젝트 필터 적용 const filtered = submissions.filter((s) => { const createdAt = s.createdAt instanceof Date ? s.createdAt : new Date(s.createdAt as any); if (fromDate && createdAt < fromDate) { return false; } if (toDate && createdAt > toDate) { return false; } if (projectIdFilter && s.projectId !== projectIdFilter) { return false; } return true; }); // 2단계: 최신순 정렬 (createdAt desc) filtered.sort((a, b) => { const aDate = a.createdAt instanceof Date ? a.createdAt : new Date(a.createdAt as any); const bDate = b.createdAt instanceof Date ? b.createdAt : new Date(b.createdAt as any); return bDate.getTime() - aDate.getTime(); }); const projectById = new Map< string, { title: string; slug: string; } >(); for (const p of projects) { projectById.set(p.id, { title: p.title, slug: p.slug }); } const items = filtered.map((s) => { const createdAt = s.createdAt instanceof Date ? s.createdAt : new Date(s.createdAt as any); const projectMeta = s.projectId ? projectById.get(s.projectId) : null; return { id: s.id, projectId: s.projectId ?? null, projectSlug: s.projectSlug, projectTitle: projectMeta?.title ?? null, createdAt: createdAt.toISOString(), payload: (s as any).payloadJson ?? {}, }; }); const body = { total: items.length, items, }; return NextResponse.json(body, { status: 200 }); } catch (error: any) { return NextResponse.json( { message: "리드 리포트를 불러오는 중 오류가 발생했습니다.", error: error?.message, }, { status: 500 }, ); } }