import { supabase } from '../commons/supabase.js'; import { logger } from '../commons/logger.js'; import { FunctionRegistry } from '../commons/registry.js'; /** * Middleware to track API usage for billing and monitoring * Tracks request start and updates with completion status */ export async function usageTrackingMiddleware(c, next) { const startTime = Date.now(); // Extract user ID from context (set by auth middleware) const userId = c.get('userId'); // Skip tracking for unauthenticated requests if (!userId) { logger.trace('[UsageTracking] Skipping - No userId'); await next(); return; } // Determine product and action const path = c.req.path; const method = c.req.method; // Use Registry to find config const config = FunctionRegistry.findByRoute(path, method); const product = config?.productId; const action = config?.actionId; logger.trace(`[UsageTracking] Identified: product=${product}, action=${action}`); // Skip if not a tracked endpoint if (!product || !action || !config) { logger.info('[UsageTracking] Skipping - Not a tracked endpoint'); await next(); return; } // Generate a job ID for this request const jobId = `${product}_${action}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; // Create initial usage record with 'processing' status let usageId = null; try { const { data, error } = await supabase .from('api_usage') .insert({ user_id: userId, endpoint: path, method, product, action, status: 'processing', job_id: jobId, cancellable: config.cancellable || false, cost_units: config.costUnits, metadata: { query: c.req.query(), userAgent: c.req.header('user-agent'), ip: c.req.header('x-forwarded-for') || c.req.header('x-real-ip'), }, }) .select('id') .single(); if (error) { logger.error({ err: error }, '[UsageTracking] Error creating usage record'); } else if (data) { logger.trace(`[UsageTracking] Created usage record: ${data.id}`); usageId = data.id; // Store usage ID in context for potential use in handlers c.set('usageId', usageId); c.set('jobId', jobId); } else { logger.trace('[UsageTracking] No data returned from insert'); } } catch (err) { logger.error({ err }, 'Failed to create usage record'); } // Execute the request let requestError = null; try { await next(); } catch (err) { requestError = err; throw err; // Re-throw to let error handler deal with it } finally { // Update usage record with completion status const endTime = Date.now(); const responseTime = endTime - startTime; if (usageId) { // Check if handler requested to skip status update (e.g. for background jobs) const skipUpdate = c.get('skipUsageStatusUpdate'); if (!skipUpdate) { updateUsageRecord({ usageId, responseStatus: c.res.status, responseTimeMs: responseTime, error: requestError, }).catch(err => { logger.error({ err }, 'Failed to update usage record'); }); } } } } /** * Update usage record with completion status */ export async function updateUsageRecord(data) { const status = data.error ? 'failed' : (data.responseStatus >= 200 && data.responseStatus < 300) ? 'completed' : 'failed'; const updateData = { status, response_status: data.responseStatus, response_time_ms: data.responseTimeMs, }; if (data.error) { updateData.error_message = data.error.message; } const { error } = await supabase .from('api_usage') .update(updateData) .eq('id', data.usageId); if (error) { logger.error({ err: error }, 'Error updating usage record'); } } /** * Helper function to manually track usage (for non-middleware scenarios) */ export async function trackUsage(data) { try { const { data: record, error } = await supabase .from('api_usage') .insert({ user_id: data.userId, endpoint: data.endpoint, method: data.method, product: data.product, action: data.action, status: data.responseStatus ? 'completed' : 'processing', job_id: data.jobId, cancellable: data.cancellable, response_status: data.responseStatus, response_time_ms: data.responseTimeMs, cost_units: data.costUnits, metadata: data.metadata, api_key_id: data.apiKeyId, }) .select('id') .single(); if (error) { logger.error({ err: error }, 'Error tracking usage'); return null; } return record?.id || null; } catch (err) { logger.error({ err }, 'Failed to track usage'); return null; } } /** * Cancel a job by job ID */ export async function cancelJob(userId, jobId) { try { const { data, error } = await supabase .from('api_usage') .update({ status: 'cancelled', }) .eq('user_id', userId) .eq('job_id', jobId) .eq('cancellable', true) .in('status', ['pending', 'processing']) .select('id'); if (error) { logger.error({ err: error }, 'Error cancelling job'); return false; } return !!data && data.length > 0; } catch (err) { logger.error({ err }, 'Failed to cancel job'); return false; } } /** * Get active (cancellable) jobs for a user */ export async function getActiveJobs(userId) { try { const { data, error } = await supabase .from('api_usage') .select('id, job_id, product, action, status, created_at, metadata') .eq('user_id', userId) .eq('cancellable', true) .in('status', ['pending', 'processing']) .order('created_at', { ascending: false }); if (error) { logger.error({ err: error }, 'Error fetching active jobs'); return []; } return data || []; } catch (err) { logger.error({ err }, 'Failed to fetch active jobs'); return []; } } /** * Pause a job by job ID */ export async function pauseJob(userId, jobId) { try { const { data, error } = await supabase .from('api_usage') .update({ status: 'paused', }) .eq('user_id', userId) .eq('job_id', jobId) .eq('cancellable', true) .eq('status', 'processing') // Only processing jobs can be paused .select('id'); if (error) { logger.error({ err: error }, 'Error pausing job'); return false; } return !!data && data.length > 0; } catch (err) { logger.error({ err }, 'Failed to pause job'); return false; } } /** * Resume a paused job by job ID */ export async function resumeJob(userId, jobId) { try { const { data, error } = await supabase .from('api_usage') .update({ status: 'processing', }) .eq('user_id', userId) .eq('job_id', jobId) .eq('cancellable', true) .eq('status', 'paused') // Only paused jobs can be resumed .select('id'); if (error) { logger.error({ err: error }, 'Error resuming job'); return false; } return !!data && data.length > 0; } catch (err) { logger.error({ err }, 'Failed to resume job'); return false; } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXNhZ2VUcmFja2luZy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9taWRkbGV3YXJlL3VzYWdlVHJhY2tpbmcudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQ0EsT0FBTyxFQUFFLFFBQVEsRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBQ2xELE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSxzQkFBc0IsQ0FBQztBQUM5QyxPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSx3QkFBd0IsQ0FBQztBQWlCMUQ7OztHQUdHO0FBQ0gsTUFBTSxDQUFDLEtBQUssVUFBVSx1QkFBdUIsQ0FBQyxDQUFVLEVBQUUsSUFBVTtJQUNoRSxNQUFNLFNBQVMsR0FBRyxJQUFJLENBQUMsR0FBRyxFQUFFLENBQUM7SUFFN0Isd0RBQXdEO0lBQ3hELE1BQU0sTUFBTSxHQUFHLENBQUMsQ0FBQyxHQUFHLENBQUMsUUFBUSxDQUFDLENBQUM7SUFDL0IsNkNBQTZDO0lBQzdDLElBQUksQ0FBQyxNQUFNLEVBQUUsQ0FBQztRQUNWLE1BQU0sQ0FBQyxLQUFLLENBQUMsc0NBQXNDLENBQUMsQ0FBQztRQUNyRCxNQUFNLElBQUksRUFBRSxDQUFDO1FBQ2IsT0FBTztJQUNYLENBQUM7SUFFRCwrQkFBK0I7SUFDL0IsTUFBTSxJQUFJLEdBQUcsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUM7SUFDeEIsTUFBTSxNQUFNLEdBQUcsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxNQUFNLENBQUM7SUFFNUIsOEJBQThCO0lBQzlCLE1BQU0sTUFBTSxHQUFHLGdCQUFnQixDQUFDLFdBQVcsQ0FBQyxJQUFJLEVBQUUsTUFBTSxDQUFDLENBQUM7SUFDMUQsTUFBTSxPQUFPLEdBQUcsTUFBTSxFQUFFLFNBQVMsQ0FBQztJQUNsQyxNQUFNLE1BQU0sR0FBRyxNQUFNLEVBQUUsUUFBUSxDQUFDO0lBRWhDLE1BQU0sQ0FBQyxLQUFLLENBQUMsdUNBQXVDLE9BQU8sWUFBWSxNQUFNLEVB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