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
) : (