import { MessageSquare, Shield, Terminal, Users } from "lucide-react"; import { redirect } from "next/navigation"; import { StatsCard } from "@/components/admin/stats-card"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { canAccess, getAdminContext, PERMS } from "@/lib/permissions"; import { prisma } from "@/lib/prisma"; async function getActivityData() { const now = Math.floor(Date.now() / 1000); const todayStart = now - (now % 86400); const weekAgo = now - 7 * 86400; // Daily activity breakdown — 4 GROUP BY queries instead of 28 sequential queries const rangeStart = todayStart - 6 * 86400; const [chatRows, cmdRows, banRows, regRows] = await Promise.all([ prisma.$queryRaw<{ day_bucket: bigint; count: bigint }[]>` SELECT FLOOR(timestamp / 86400) AS day_bucket, COUNT(*) AS count FROM chatlogs_room WHERE timestamp >= ${rangeStart} GROUP BY day_bucket`, prisma.$queryRaw<{ day_bucket: bigint; count: bigint }[]>` SELECT FLOOR(timestamp / 86400) AS day_bucket, COUNT(*) AS count FROM commandlogs WHERE timestamp >= ${rangeStart} GROUP BY day_bucket`, prisma.$queryRaw<{ day_bucket: bigint; count: bigint }[]>` SELECT FLOOR(timestamp / 86400) AS day_bucket, COUNT(*) AS count FROM bans WHERE timestamp >= ${rangeStart} GROUP BY day_bucket`, prisma.$queryRaw<{ day_bucket: bigint; count: bigint }[]>` SELECT FLOOR(account_created / 86400) AS day_bucket, COUNT(*) AS count FROM users WHERE account_created >= ${rangeStart} GROUP BY day_bucket`, ]); const toMap = (rows: { day_bucket: bigint; count: bigint }[]) => new Map(rows.map((r) => [Number(r.day_bucket), Number(r.count)])); const chatMap = toMap(chatRows); const cmdMap = toMap(cmdRows); const banMap = toMap(banRows); const regMap = toMap(regRows); const dailyData = []; for (let i = 6; i >= 0; i--) { const dayStart = todayStart - i * 86400; const bucket = Math.floor(dayStart / 86400); const date = new Date(dayStart * 1000); dailyData.push({ label: date.toLocaleDateString("en", { weekday: "short", month: "short", day: "numeric" }), chats: chatMap.get(bucket) ?? 0, commands: cmdMap.get(bucket) ?? 0, bans: banMap.get(bucket) ?? 0, registrations: regMap.get(bucket) ?? 0, }); } // Totals const [weekChatRows, weekCommandRows, weekBans, weekRegistrations] = await Promise.all([ prisma.$queryRaw< { count: bigint }[] >`SELECT COUNT(*) AS count FROM chatlogs_room WHERE timestamp >= ${weekAgo}`, prisma.$queryRaw< { count: bigint }[] >`SELECT COUNT(*) AS count FROM commandlogs WHERE timestamp >= ${weekAgo}`, prisma.ban.count({ where: { timestamp: { gte: weekAgo } } }), prisma.user.count({ where: { accountCreated: { gte: weekAgo } } }), ]); const weekChats = Number(weekChatRows[0]?.count ?? 0); const weekCommands = Number(weekCommandRows[0]?.count ?? 0); // Most active users (by chat messages) const topChatters = await prisma.$queryRaw<{ userId: number; messages: bigint }[]>` SELECT user_from_id AS userId, COUNT(*) AS messages FROM chatlogs_room WHERE timestamp >= ${weekAgo} GROUP BY user_from_id ORDER BY messages DESC LIMIT 10 `; const userIds = topChatters.map((c) => c.userId); const users = await prisma.user.findMany({ where: { id: { in: userIds } }, select: { id: true, username: true }, }); const userMap = new Map(users.map((u) => [u.id, u.username])); const topChattersWithNames = topChatters.map((c) => ({ userId: c.userId, username: userMap.get(c.userId) ?? `User #${c.userId}`, messages: Number(c.messages), })); return { dailyData, weekChats, weekCommands, weekBans, weekRegistrations, topChatters: topChattersWithNames, }; } export default async function ActivityPage() { const { session, permissions } = await getAdminContext(); if (!canAccess(permissions, PERMS.ANALYTICS_VIEW, session.user.rank)) { redirect("/admin"); } const data = await getActivityData(); return (
User activity breakdown and engagement metrics
| Day | Chats | Cmds | Bans | Regs |
|---|---|---|---|---|
| {day.label} | {day.chats.toLocaleString()} | {day.commands.toLocaleString()} | {day.bans} | {day.registrations} |
No chat activity
) : (