93 lines
2.7 KiB
TypeScript
93 lines
2.7 KiB
TypeScript
|
|
import { NextResponse } from "next/server";
|
||
|
|
import prisma from "@/lib/prisma";
|
||
|
|
|
||
|
|
export async function GET(req: Request) {
|
||
|
|
const { searchParams } = new URL(req.url);
|
||
|
|
const boardId = searchParams.get("boardId");
|
||
|
|
const period = searchParams.get("period") || "daily"; // daily | weekly
|
||
|
|
|
||
|
|
// 날짜 범위 계산
|
||
|
|
const now = new Date();
|
||
|
|
const startOfToday = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0, 0);
|
||
|
|
const endOfToday = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59, 999);
|
||
|
|
const startOfWeek = new Date(now);
|
||
|
|
startOfWeek.setDate(now.getDate() - 7);
|
||
|
|
startOfWeek.setHours(0, 0, 0, 0);
|
||
|
|
|
||
|
|
const dateFilter = period === "daily"
|
||
|
|
? {
|
||
|
|
date: {
|
||
|
|
gte: startOfToday,
|
||
|
|
lte: endOfToday,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
: { date: { gte: startOfWeek } };
|
||
|
|
|
||
|
|
// 일일 조회수 테이블에서 조회수 합계 계산
|
||
|
|
const dailyViews = await prisma.dailyPostView.groupBy({
|
||
|
|
by: ["postId"],
|
||
|
|
where: dateFilter,
|
||
|
|
_sum: {
|
||
|
|
viewCount: true,
|
||
|
|
},
|
||
|
|
orderBy: {
|
||
|
|
_sum: {
|
||
|
|
viewCount: "desc",
|
||
|
|
},
|
||
|
|
},
|
||
|
|
take: 20, // 조회수 상위 20개만 가져와서 게시글 정보 조회
|
||
|
|
});
|
||
|
|
|
||
|
|
if (dailyViews.length === 0) {
|
||
|
|
return NextResponse.json({ items: [], period });
|
||
|
|
}
|
||
|
|
|
||
|
|
const postIds = dailyViews.map((dv) => dv.postId);
|
||
|
|
|
||
|
|
// 게시글 정보 조회
|
||
|
|
const posts = await prisma.post.findMany({
|
||
|
|
where: {
|
||
|
|
id: { in: postIds },
|
||
|
|
status: "published",
|
||
|
|
...(boardId ? { boardId } : {}),
|
||
|
|
},
|
||
|
|
select: {
|
||
|
|
id: true,
|
||
|
|
title: true,
|
||
|
|
createdAt: true,
|
||
|
|
boardId: true,
|
||
|
|
board: { select: { id: true, name: true, slug: true } },
|
||
|
|
isPinned: true,
|
||
|
|
status: true,
|
||
|
|
author: { select: { userId: true, nickname: true } },
|
||
|
|
stat: { select: { recommendCount: true, views: true, commentsCount: true } },
|
||
|
|
postTags: { select: { tag: { select: { name: true, slug: true } } } },
|
||
|
|
},
|
||
|
|
});
|
||
|
|
|
||
|
|
// 조회수와 게시글 매핑
|
||
|
|
const viewCountMap = new Map(
|
||
|
|
dailyViews.map((dv) => [dv.postId, dv._sum.viewCount ?? 0])
|
||
|
|
);
|
||
|
|
|
||
|
|
// 조회수 순으로 정렬 (조회수가 0보다 큰 것만)
|
||
|
|
const postsWithViews = posts
|
||
|
|
.map((post) => ({
|
||
|
|
...post,
|
||
|
|
viewCount: viewCountMap.get(post.id) ?? 0,
|
||
|
|
}))
|
||
|
|
.filter((post) => post.viewCount > 0) // 조회수가 0보다 큰 것만
|
||
|
|
.sort((a, b) => {
|
||
|
|
// 고정글 우선
|
||
|
|
if (a.isPinned && !b.isPinned) return -1;
|
||
|
|
if (!a.isPinned && b.isPinned) return 1;
|
||
|
|
// 조회수 순
|
||
|
|
if (b.viewCount !== a.viewCount) return b.viewCount - a.viewCount;
|
||
|
|
// 최신순
|
||
|
|
return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime();
|
||
|
|
})
|
||
|
|
.slice(0, 5); // 상위 5개만
|
||
|
|
|
||
|
|
return NextResponse.json({ items: postsWithViews, period });
|
||
|
|
}
|