import { NextRequest, NextResponse } from 'next/server'; import { redis } from '@/lib/redis'; import { prisma } from '@/lib/prisma'; import { parseRedisInt, getCurrentPeriodKey } from '@/lib/quota-utils'; function verifyCronAuth(req: NextRequest): boolean { const cronSecret = process.env.CRON_SECRET; if (!cronSecret) { console.error('[sync-usage] CRON_SECRET env var is required but not set'); return false; } const authHeader = req.headers.get('authorization'); return authHeader === `Bearer ${cronSecret}`; } async function scanKeys(pattern: string): Promise { const keys: string[] = []; let cursor = '0'; do { const [nextCursor, batch] = await redis.scan( cursor, 'MATCH', pattern, 'COUNT', 200, ); cursor = nextCursor; keys.push(...batch); } while (cursor !== '0'); return keys; } function parseUsageKey(key: string, period: string): { userId: string; feature: string } | null { const suffix = `:${period}`; if (!key.endsWith(suffix)) return null; if (key.endsWith(':tokens')) return null; const prefix = key.slice(0, key.length - suffix.length); const firstColon = prefix.indexOf(':'); if (firstColon === -1) return null; const afterPrefix = prefix.slice(firstColon + 1); const lastColon = afterPrefix.lastIndexOf(':'); if (lastColon === -1) return null; const userId = afterPrefix.slice(0, lastColon); const feature = afterPrefix.slice(lastColon + 1); if (!userId || !feature) return null; return { userId, feature }; } export async function POST(req: NextRequest) { if (!verifyCronAuth(req)) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } try { const period = getCurrentPeriodKey(); const pattern = `usage:*:${period}`; const keys = await scanKeys(pattern); let synced = 0; let errors = 0; for (const key of keys) { if (key.endsWith(':tokens')) continue; try { const parsed = parseUsageKey(key, period); if (!parsed) continue; const { userId, feature } = parsed; const [counter, tokens] = await Promise.all([ redis.get(key), redis.get(`${key}:tokens`), ]); const periodStart = new Date(`${period}-01`); const periodEnd = new Date( periodStart.getFullYear(), periodStart.getMonth() + 1, 1, ); await prisma.usageLog.upsert({ where: { userId_feature_periodStart: { userId, feature, periodStart, }, }, create: { userId, feature, periodStart, periodEnd, requestsCount: parseRedisInt(counter), tokensUsed: parseRedisInt(tokens), }, update: { requestsCount: parseRedisInt(counter), tokensUsed: parseRedisInt(tokens), syncedAt: new Date(), }, }); synced++; } catch (err) { errors++; console.error(`[sync-usage] Failed to sync key ${key}:`, err); } } return NextResponse.json({ synced, errors, total: keys.length }); } catch (error) { console.error('[sync-usage] Error:', error); return NextResponse.json({ error: 'Sync failed' }, { status: 500 }); } }