Files
msgapp/src/app/api/attendance/rankings/route.ts

84 lines
2.9 KiB
TypeScript
Raw Normal View History

2025-11-09 22:05:22 +09:00
import { NextResponse } from "next/server";
import prisma from "@/lib/prisma";
function toYmdUTC(d: Date): string {
const yy = d.getUTCFullYear();
const mm = String(d.getUTCMonth() + 1).padStart(2, "0");
const dd = String(d.getUTCDate()).padStart(2, "0");
return `${yy}-${mm}-${dd}`;
}
export async function GET() {
// 전체 출석 누적 상위 (Top 10)
const overallGroups = await prisma.attendance.groupBy({
by: ["userId"],
_count: { _all: true },
orderBy: { _count: { _all: "desc" } },
take: 20,
});
const overallUserIds = overallGroups.map((g) => g.userId);
const overallUsers = await prisma.user.findMany({
where: { userId: { in: overallUserIds } },
select: { userId: true, nickname: true, profileImage: true, grade: true },
});
const userMeta = new Map(overallUsers.map((u) => [u.userId, u]));
const overall = overallGroups.map((g) => ({
userId: g.userId,
nickname: userMeta.get(g.userId)?.nickname ?? "회원",
count: g._count._all,
profileImage: userMeta.get(g.userId)?.profileImage ?? null,
grade: userMeta.get(g.userId)?.grade ?? 0,
}));
// 연속 출석 상위 (Top 10, 현재 연속 기준)
const now = new Date();
const todayUTC = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate(), 0, 0, 0, 0));
const past = new Date(todayUTC);
past.setUTCDate(past.getUTCDate() - 60); // 최근 60일 데이터만으로 streak 계산
const recent = await prisma.attendance.findMany({
where: { date: { gte: past } },
select: { userId: true, date: true },
orderBy: { userId: "asc" },
});
const map = new Map<string, Set<string>>();
for (const r of recent) {
const set = map.get(r.userId) ?? new Set<string>();
set.add(toYmdUTC(new Date(r.date)));
map.set(r.userId, set);
}
const streakArr: { userId: string; streak: number }[] = [];
for (const [userId, set] of map.entries()) {
let streak = 0;
let cursor = new Date(todayUTC);
while (streak < 60) {
const ymd = toYmdUTC(cursor);
if (set.has(ymd)) {
streak += 1;
cursor.setUTCDate(cursor.getUTCDate() - 1);
} else {
break;
}
}
if (streak > 0) streakArr.push({ userId, streak });
}
streakArr.sort((a, b) => b.streak - a.streak);
const topStreak = streakArr.slice(0, 20);
const streakUserIds = topStreak.map((s) => s.userId);
const streakUsers = await prisma.user.findMany({
where: { userId: { in: streakUserIds } },
select: { userId: true, nickname: true, profileImage: true, grade: true },
});
const streakMeta = new Map(streakUsers.map((u) => [u.userId, u]));
const streak = topStreak.map((s) => ({
userId: s.userId,
nickname: streakMeta.get(s.userId)?.nickname ?? "회원",
streak: s.streak,
profileImage: streakMeta.get(s.userId)?.profileImage ?? null,
grade: streakMeta.get(s.userId)?.grade ?? 0,
}));
return NextResponse.json({ overall, streak });
}