Files
openclaw-market/app/api/v1/stats/route.ts

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 }
);
}
}