마이페이지, 리포트, 메일인증
This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
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 },
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user