import { sql } from 'drizzle-orm' import { db } from '../db/client.js' import { findAuthenticatedUser } from '../lib/authSession.js' import { users } from '../db/schema.js' async function requireAdminUser(request, reply) { const user = await findAuthenticatedUser(request) if (!user) { reply.code(401).send({ message: '인증이 필요합니다.', }) return null } if (user.role !== 'admin') { reply.code(403).send({ message: '관리자만 접근할 수 있습니다.', }) return null } return user } export async function registerAdminRoutes(app) { app.get('/api/admin/overview', async (request, reply) => { const adminUser = await requireAdminUser(request, reply) if (!adminUser) { return } const [summary] = await db .select({ totalUsers: sql`count(*)::int`, totalAdmins: sql`count(*) filter (where ${users.role} = 'admin')::int`, verifiedUsers: sql`count(*) filter (where ${users.emailVerifiedAt} is not null)::int`, activeUsers30d: sql`count(*) filter (where ${users.lastLoginAt} >= now() - interval '30 days')::int`, newUsers7d: sql`count(*) filter (where ${users.createdAt} >= now() - interval '7 days')::int`, }) .from(users) const plannerCountResult = await db.execute( sql`select count(*)::int as count from planner_entries`, ) const goalCountResult = await db.execute( sql`select count(*)::int as count from goals`, ) const userRowsResult = await db.execute(sql` select u.id, u.nickname, u.email, u.role, u.created_at as "createdAt", u.email_verified_at as "emailVerifiedAt", u.last_login_at as "lastLoginAt", count(distinct pe.id)::int as "plannerEntryCount", count(distinct g.id)::int as "goalCount", max(pe.entry_date) as "lastEntryDate", max(pe.updated_at) as "lastEntryUpdatedAt" from users u left join planner_entries pe on pe.user_id = u.id left join goals g on g.user_id = u.id group by u.id order by coalesce(u.last_login_at, u.created_at) desc, u.id desc `) const recentLoginsResult = await db.execute(sql` select id, nickname, email, role, last_login_at as "lastLoginAt" from users where last_login_at is not null order by last_login_at desc limit 5 `) return { summary: { totalUsers: summary?.totalUsers ?? 0, totalAdmins: summary?.totalAdmins ?? 0, verifiedUsers: summary?.verifiedUsers ?? 0, activeUsers30d: summary?.activeUsers30d ?? 0, newUsers7d: summary?.newUsers7d ?? 0, totalPlannerEntries: plannerCountResult.rows[0]?.count ?? 0, totalGoals: goalCountResult.rows[0]?.count ?? 0, }, users: userRowsResult.rows.map((row) => ({ ...row, isActiveRecently: row.lastLoginAt ? new Date(row.lastLoginAt).getTime() >= Date.now() - 30 * 24 * 60 * 60 * 1000 : false, })), recentLogins: recentLoginsResult.rows, } }) }