bookwiz.io / app / api / usage / billing / route.ts
route.ts
Raw
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 })
  }
}