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