import { NextRequest, NextResponse } from 'next/server' import { createServerSupabaseClient } from '@/lib/supabase' export const runtime = 'nodejs' export const dynamic = 'force-dynamic' export async function GET(request: NextRequest) { try { const userId = request.nextUrl.searchParams.get('userId') const detailed = request.nextUrl.searchParams.get('detailed') === 'true' const year = parseInt(request.nextUrl.searchParams.get('year') || '') const month = parseInt(request.nextUrl.searchParams.get('month') || '') if (!userId) { return NextResponse.json({ error: 'User ID is required' }, { status: 400 }) } const supabase = createServerSupabaseClient() // Get user's current billing period using RPC const { data: periodData, error: periodError } = await supabase .rpc('get_current_usage_period', { user_uuid: userId }) if (periodError) { console.error('Error getting billing period:', periodError) return NextResponse.json({ error: 'Failed to get billing period' }, { status: 500 }) } const period = periodData?.[0] if (!period) { return NextResponse.json({ error: 'No billing period found' }, { status: 404 }) } // Use direct date filtering like monthly API let queryStartDate: string let queryEndDate: string if (year && month) { // Use specific year/month if provided const targetDate = new Date(year, month - 1, 1) // month - 1 because JS months are 0-indexed queryStartDate = targetDate.toISOString() queryEndDate = new Date(year, month, 1).toISOString() } else { // Use current month like monthly API does const currentDate = new Date() const expectedStartDate = new Date(currentDate.getFullYear(), currentDate.getMonth(), 1) const expectedEndDate = new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 1) queryStartDate = expectedStartDate.toISOString() queryEndDate = expectedEndDate.toISOString() } // Get detailed AI usage using direct date filtering const { data: usageData, error: usageError } = await supabase .from('ai_usage') .select(` model_name, model_tier, model_provider, request_type, prompt_tokens, completion_tokens, total_tokens, cost_usd, created_at, success `) .eq('user_id', userId) .gte('created_at', queryStartDate) .lt('created_at', queryEndDate) .eq('success', true) .order('created_at', { ascending: false }) if (usageError) { console.error('Error getting usage data:', usageError) return NextResponse.json({ error: 'Failed to get usage data' }, { status: 500 }) } // If detailed is requested, return raw usage data for daily model breakdown if (detailed) { const response = NextResponse.json({ period: { start: queryStartDate, end: queryEndDate }, rawUsage: usageData || [] }) response.headers.set('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate') response.headers.set('Pragma', 'no-cache') response.headers.set('Expires', '0') return response } // Aggregate the data for charts const modelStats = new Map() const dailyStats = new Map() const dailyModelStats = new Map() // New: daily model breakdown let totalPromptTokens = 0 let totalCompletionTokens = 0 let totalTokens = 0 let totalCost = 0 usageData?.forEach(usage => { // Model stats const modelKey = `${usage.model_name} (${usage.model_tier})` if (!modelStats.has(modelKey)) { modelStats.set(modelKey, { modelName: usage.model_name, modelTier: usage.model_tier, provider: usage.model_provider, promptTokens: 0, completionTokens: 0, totalTokens: 0, requests: 0, cost: 0 }) } const modelStat = modelStats.get(modelKey) modelStat.promptTokens += usage.prompt_tokens || 0 modelStat.completionTokens += usage.completion_tokens || 0 modelStat.totalTokens += usage.total_tokens || 0 modelStat.requests += 1 modelStat.cost += usage.cost_usd || 0 // Daily stats const date = new Date(usage.created_at).toISOString().split('T')[0] if (!dailyStats.has(date)) { dailyStats.set(date, { date, promptTokens: 0, completionTokens: 0, totalTokens: 0, requests: 0, cost: 0, models: new Map() }) } const dailyStat = dailyStats.get(date) dailyStat.promptTokens += usage.prompt_tokens || 0 dailyStat.completionTokens += usage.completion_tokens || 0 dailyStat.totalTokens += usage.total_tokens || 0 dailyStat.requests += 1 dailyStat.cost += usage.cost_usd || 0 // Daily model breakdown if (!dailyStat.models.has(modelKey)) { dailyStat.models.set(modelKey, { modelName: usage.model_name, modelTier: usage.model_tier, provider: usage.model_provider, totalTokens: 0, requests: 0 }) } const dailyModelStat = dailyStat.models.get(modelKey) dailyModelStat.totalTokens += usage.total_tokens || 0 dailyModelStat.requests += 1 // Totals totalPromptTokens += usage.prompt_tokens || 0 totalCompletionTokens += usage.completion_tokens || 0 totalTokens += usage.total_tokens || 0 totalCost += usage.cost_usd || 0 }) // Convert daily stats with model breakdown const dailyArray = Array.from(dailyStats.values()).map(day => ({ date: day.date, promptTokens: day.promptTokens, completionTokens: day.completionTokens, totalTokens: day.totalTokens, requests: day.requests, cost: day.cost, models: Array.from(day.models.values()).sort((a: any, b: any) => b.totalTokens - a.totalTokens) })).sort((a, b) => a.date.localeCompare(b.date)) const response = NextResponse.json({ period: { start: queryStartDate, end: queryEndDate }, summary: { totalPromptTokens, totalCompletionTokens, totalTokens, totalRequests: usageData?.length || 0, totalCost }, models: Array.from(modelStats.values()).sort((a, b) => b.totalTokens - a.totalTokens), daily: dailyArray }) // Add cache control headers to prevent any caching response.headers.set('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate') response.headers.set('Pragma', 'no-cache') response.headers.set('Expires', '0') return response } catch (error) { console.error('Error in billing usage API:', error) return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) } }