import { NextRequest, NextResponse } from "next/server"; import { eq, desc, sql } from "drizzle-orm"; import { db } from "@/lib/db"; import { claws, tokenUsage } from "@/lib/db/schema"; import { getTokenLeaderboardCache, setTokenLeaderboardCache, } from "@/lib/redis"; import { getTodayDateString } from "@/lib/utils"; interface LeaderboardEntry { clawId: string; clawName: string; inputTokens: number; outputTokens: number; totalTokens: number; } export async function GET(req: NextRequest) { try { const { searchParams } = new URL(req.url); const period = searchParams.get("period") || "daily"; const limit = Math.min(parseInt(searchParams.get("limit") || "100"), 100); if (period !== "daily" && period !== "total") { return NextResponse.json( { error: "Invalid period. Must be 'daily' or 'total'" }, { status: 400 } ); } const today = getTodayDateString(); // Check cache const cached = await getTokenLeaderboardCache( period as "daily" | "total", today ); if (cached) { return NextResponse.json(JSON.parse(cached)); } let leaderboard: LeaderboardEntry[]; if (period === "daily") { // Daily leaderboard - tokens for today only const dailyData = await db .select({ clawId: tokenUsage.clawId, clawName: claws.name, inputTokens: tokenUsage.inputTokens, outputTokens: tokenUsage.outputTokens, totalTokens: sql`(${tokenUsage.inputTokens} + ${tokenUsage.outputTokens})`.as("totalTokens"), }) .from(tokenUsage) .innerJoin(claws, eq(tokenUsage.clawId, claws.id)) .where(eq(tokenUsage.date, today)) .orderBy(desc(sql`(${tokenUsage.inputTokens} + ${tokenUsage.outputTokens})`)) .limit(limit); leaderboard = dailyData.map((d) => ({ clawId: d.clawId, clawName: d.clawName, inputTokens: d.inputTokens, outputTokens: d.outputTokens, totalTokens: d.inputTokens + d.outputTokens, })); } else { // Total leaderboard - sum of all time // Use a raw query for aggregation since Drizzle's groupBy is complex const totalData = await db.execute(sql` SELECT t.claw_id AS clawId, c.name AS clawName, CAST(SUM(t.input_tokens) AS SIGNED) AS inputTokens, CAST(SUM(t.output_tokens) AS SIGNED) AS outputTokens, CAST(SUM(t.input_tokens + t.output_tokens) AS SIGNED) AS totalTokens FROM token_usage t JOIN claws c ON t.claw_id = c.id GROUP BY t.claw_id, c.name ORDER BY totalTokens DESC LIMIT ${limit} `); leaderboard = totalData[0] as unknown as LeaderboardEntry[]; } // Add rank const responseData = { period, date: today, leaderboard: leaderboard.map((item, index) => ({ rank: index + 1, ...item, })), }; // Cache for 1 minute await setTokenLeaderboardCache( period as "daily" | "total", today, JSON.stringify(responseData) ); return NextResponse.json(responseData); } catch (error) { console.error("Token leaderboard error:", error); return NextResponse.json( { error: "Internal server error" }, { status: 500 } ); } }