77 lines
2.1 KiB
TypeScript
77 lines
2.1 KiB
TypeScript
import { NextResponse } from "next/server";
|
|
import { gte, sql } from "drizzle-orm";
|
|
import { db } from "@/lib/db";
|
|
import { claws, tasks } from "@/lib/db/schema";
|
|
import {
|
|
redis,
|
|
getGlobalStats,
|
|
getRegionStats,
|
|
getHourlyActivity,
|
|
} from "@/lib/redis";
|
|
|
|
export async function GET() {
|
|
try {
|
|
const globalStats = await getGlobalStats();
|
|
const regionStats = await getRegionStats();
|
|
const hourlyRaw = await getHourlyActivity();
|
|
|
|
// Convert hourly data to array format
|
|
const hourlyActivity = Object.entries(hourlyRaw).map(([hour, count]) => ({
|
|
hour: hour.split("T")[1] + ":00",
|
|
count,
|
|
}));
|
|
|
|
const now = Date.now();
|
|
const fiveMinutesAgo = now - 300_000;
|
|
const activeClaws = await redis.zcount(
|
|
"active:claws",
|
|
fiveMinutesAgo,
|
|
"+inf"
|
|
);
|
|
|
|
const totalClawsResult = await db
|
|
.select({ count: sql<number>`count(*)` })
|
|
.from(claws);
|
|
const totalClaws = totalClawsResult[0]?.count ?? 0;
|
|
|
|
const todayStart = new Date();
|
|
todayStart.setHours(0, 0, 0, 0);
|
|
|
|
const tasksTodayResult = await db
|
|
.select({ count: sql<number>`count(*)` })
|
|
.from(tasks)
|
|
.where(gte(tasks.timestamp, todayStart));
|
|
const tasksToday = tasksTodayResult[0]?.count ?? 0;
|
|
|
|
const avgDurationResult = await db
|
|
.select({ avg: sql<number>`AVG(${tasks.durationMs})` })
|
|
.from(tasks)
|
|
.where(gte(tasks.timestamp, todayStart));
|
|
const avgTaskDuration = avgDurationResult[0]?.avg
|
|
? Math.round(avgDurationResult[0].avg)
|
|
: 0;
|
|
|
|
// Convert region stats from string values to numbers
|
|
const regionBreakdown: Record<string, number> = {};
|
|
for (const [key, val] of Object.entries(regionStats)) {
|
|
regionBreakdown[key] = parseInt(val, 10) || 0;
|
|
}
|
|
|
|
return NextResponse.json({
|
|
totalClaws,
|
|
activeClaws,
|
|
tasksToday,
|
|
tasksTotal: parseInt(globalStats.total_tasks ?? "0", 10),
|
|
avgTaskDuration,
|
|
regionBreakdown,
|
|
hourlyActivity,
|
|
});
|
|
} catch (error) {
|
|
console.error("Stats error:", error);
|
|
return NextResponse.json(
|
|
{ error: "Internal server error" },
|
|
{ status: 500 }
|
|
);
|
|
}
|
|
}
|