diff --git a/supabase/functions/collect-metrics/index.ts b/supabase/functions/collect-metrics/index.ts index d66322e5..8dc12288 100644 --- a/supabase/functions/collect-metrics/index.ts +++ b/supabase/functions/collect-metrics/index.ts @@ -1,6 +1,7 @@ -import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.57.4'; -import { createEdgeFunction } from '../_shared/edgeFunctionWrapper.ts'; -import { edgeLogger } from '../_shared/logger.ts'; +import { serve } from 'https://deno.land/std@0.190.0/http/server.ts'; +import { createEdgeFunction, type EdgeFunctionContext } from '../_shared/edgeFunctionWrapper.ts'; +import { addSpanEvent } from '../_shared/logger.ts'; +import { corsHeaders } from '../_shared/cors.ts'; interface MetricRecord { metric_name: string; @@ -9,13 +10,8 @@ interface MetricRecord { timestamp: string; } -export default createEdgeFunction( - { - name: 'collect-metrics', - requireAuth: false, - }, - async (req, context, supabase) => { - edgeLogger.info('Starting metrics collection', { requestId: context.requestId }); +const handler = async (req: Request, { supabase, span, requestId }: EdgeFunctionContext) => { + addSpanEvent(span, 'starting_metrics_collection', { requestId }); const metrics: MetricRecord[] = []; const timestamp = new Date().toISOString(); @@ -151,27 +147,24 @@ export default createEdgeFunction( .from('metric_time_series') .insert(metrics); - if (insertError) { - edgeLogger.error('Error inserting metrics', { - error: insertError, - requestId: context.requestId - }); - throw insertError; - } - - edgeLogger.info('Successfully recorded metrics', { - count: metrics.length, - requestId: context.requestId - }); + if (insertError) { + addSpanEvent(span, 'error_inserting_metrics', { error: insertError.message }); + throw insertError; } - return new Response( - JSON.stringify({ - success: true, - metrics_collected: metrics.length, - metrics: metrics.map(m => ({ name: m.metric_name, value: m.metric_value })), - }), - { headers: { 'Content-Type': 'application/json' } } - ); + addSpanEvent(span, 'metrics_recorded', { count: metrics.length }); } -); + + return { + success: true, + metrics_collected: metrics.length, + metrics: metrics.map(m => ({ name: m.metric_name, value: m.metric_value })), + }; +}; + +serve(createEdgeFunction({ + name: 'collect-metrics', + requireAuth: false, + corsHeaders, + enableTracing: true, +}, handler)); diff --git a/supabase/functions/detect-anomalies/index.ts b/supabase/functions/detect-anomalies/index.ts index 57388bed..17ccaf59 100644 --- a/supabase/functions/detect-anomalies/index.ts +++ b/supabase/functions/detect-anomalies/index.ts @@ -1,6 +1,7 @@ -import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.57.4'; -import { createEdgeFunction } from '../_shared/edgeFunctionWrapper.ts'; -import { edgeLogger } from '../_shared/logger.ts'; +import { serve } from 'https://deno.land/std@0.190.0/http/server.ts'; +import { createEdgeFunction, type EdgeFunctionContext } from '../_shared/edgeFunctionWrapper.ts'; +import { addSpanEvent } from '../_shared/logger.ts'; +import { corsHeaders } from '../_shared/cors.ts'; interface MetricData { timestamp: string; @@ -288,13 +289,8 @@ class AnomalyDetector { } } -export default createEdgeFunction( - { - name: 'detect-anomalies', - requireAuth: false, - }, - async (req, context, supabase) => { - edgeLogger.info('Starting anomaly detection run', { requestId: context.requestId }); +const handler = async (req: Request, { supabase, span, requestId }: EdgeFunctionContext) => { + addSpanEvent(span, 'starting_anomaly_detection', { requestId }); // Get all enabled anomaly detection configurations const { data: configs, error: configError } = await supabase @@ -303,14 +299,11 @@ export default createEdgeFunction( .eq('enabled', true); if (configError) { - console.error('Error fetching configs:', configError); + addSpanEvent(span, 'error_fetching_configs', { error: configError.message }); throw configError; } - edgeLogger.info('Processing metric configurations', { - count: configs?.length || 0, - requestId: context.requestId - }); + addSpanEvent(span, 'processing_metric_configs', { count: configs?.length || 0 }); const anomaliesDetected: any[] = []; @@ -327,17 +320,19 @@ export default createEdgeFunction( .order('timestamp', { ascending: true }); if (metricError) { - console.error(`Error fetching metric data for ${config.metric_name}:`, metricError); + addSpanEvent(span, 'error_fetching_metric_data', { + metric: config.metric_name, + error: metricError.message + }); continue; } const data = metricData as MetricData[]; if (!data || data.length < config.min_data_points) { - edgeLogger.info('Insufficient data for metric', { + addSpanEvent(span, 'insufficient_data', { metric: config.metric_name, - points: data?.length || 0, - requestId: context.requestId + points: data?.length || 0 }); continue; } @@ -421,7 +416,10 @@ export default createEdgeFunction( .single(); if (anomalyError) { - console.error(`Error inserting anomaly for ${config.metric_name}:`, anomalyError); + addSpanEvent(span, 'error_inserting_anomaly', { + metric: config.metric_name, + error: anomalyError.message + }); continue; } @@ -453,29 +451,39 @@ export default createEdgeFunction( .update({ alert_created: true, alert_id: alert.id }) .eq('id', anomaly.id); - console.log(`Created alert for anomaly in ${config.metric_name}`); + addSpanEvent(span, 'alert_created', { + metric: config.metric_name, + alertId: alert.id + }); } } - console.log(`Anomaly detected: ${config.metric_name} - ${bestResult.anomalyType} (${bestResult.deviationScore.toFixed(2)}σ)`); + addSpanEvent(span, 'anomaly_detected', { + metric: config.metric_name, + type: bestResult.anomalyType, + deviation: bestResult.deviationScore.toFixed(2) + }); } } catch (error) { - console.error(`Error processing metric ${config.metric_name}:`, error); + addSpanEvent(span, 'error_processing_metric', { + metric: config.metric_name, + error: error instanceof Error ? error.message : String(error) + }); } } - edgeLogger.info('Anomaly detection complete', { - detected: anomaliesDetected.length, - requestId: context.requestId - }); + addSpanEvent(span, 'anomaly_detection_complete', { detected: anomaliesDetected.length }); - return new Response( - JSON.stringify({ - success: true, - anomalies_detected: anomaliesDetected.length, - anomalies: anomaliesDetected, - }), - { headers: { 'Content-Type': 'application/json' } } - ); - } -); + return { + success: true, + anomalies_detected: anomaliesDetected.length, + anomalies: anomaliesDetected, + }; +}; + +serve(createEdgeFunction({ + name: 'detect-anomalies', + requireAuth: false, + corsHeaders, + enableTracing: true, +}, handler)); diff --git a/supabase/functions/detect-location/index.ts b/supabase/functions/detect-location/index.ts index b109c089..9f15d617 100644 --- a/supabase/functions/detect-location/index.ts +++ b/supabase/functions/detect-location/index.ts @@ -1,7 +1,7 @@ -import { serve } from "https://deno.land/std@0.168.0/http/server.ts"; -import { corsHeadersWithTracing as corsHeaders } from '../_shared/cors.ts'; -import { edgeLogger, startRequest, endRequest, logSpanToDatabase, startSpan, endSpan } from "../_shared/logger.ts"; -import { formatEdgeError } from "../_shared/errorFormatter.ts"; +import { serve } from "https://deno.land/std@0.190.0/http/server.ts"; +import { createEdgeFunction, type EdgeFunctionContext } from '../_shared/edgeFunctionWrapper.ts'; +import { corsHeaders } from '../_shared/cors.ts'; +import { addSpanEvent } from '../_shared/logger.ts'; interface IPLocationResponse { country: string; @@ -11,86 +11,47 @@ interface IPLocationResponse { // Simple in-memory rate limiter const rateLimitMap = new Map(); -const RATE_LIMIT_WINDOW = 60000; // 1 minute in milliseconds +const RATE_LIMIT_WINDOW = 60000; // 1 minute const MAX_REQUESTS = 10; // 10 requests per minute per IP -const MAX_MAP_SIZE = 10000; // Maximum number of IPs to track +const MAX_MAP_SIZE = 10000; -// Cleanup failure tracking to prevent silent failures let cleanupFailureCount = 0; -const MAX_CLEANUP_FAILURES = 5; // Threshold before forcing drastic cleanup -const CLEANUP_FAILURE_RESET_INTERVAL = 300000; // Reset failure count every 5 minutes +const MAX_CLEANUP_FAILURES = 5; +const CLEANUP_FAILURE_RESET_INTERVAL = 300000; // 5 minutes function cleanupExpiredEntries() { try { const now = Date.now(); - let deletedCount = 0; - const mapSizeBefore = rateLimitMap.size; - for (const [ip, data] of rateLimitMap.entries()) { if (now > data.resetAt) { rateLimitMap.delete(ip); - deletedCount++; } } - - // Cleanup runs silently unless there are issues if (cleanupFailureCount > 0) { cleanupFailureCount = 0; } - } catch (error: unknown) { - // CRITICAL: Increment failure counter and log detailed error information cleanupFailureCount++; - const errorMessage = formatEdgeError(error); - - edgeLogger.error('Cleanup error', { - attempt: cleanupFailureCount, - maxAttempts: MAX_CLEANUP_FAILURES, - error: errorMessage, - mapSize: rateLimitMap.size - }); - - // FALLBACK MECHANISM: If cleanup fails repeatedly, force clear to prevent memory leak if (cleanupFailureCount >= MAX_CLEANUP_FAILURES) { - edgeLogger.error('Cleanup critical - forcing emergency cleanup', { - consecutiveFailures: cleanupFailureCount, - mapSize: rateLimitMap.size - }); - try { - // Emergency: Clear oldest 50% of entries to prevent unbounded growth const entriesToClear = Math.floor(rateLimitMap.size * 0.5); const sortedEntries = Array.from(rateLimitMap.entries()) .sort((a, b) => a[1].resetAt - b[1].resetAt); - let clearedCount = 0; for (let i = 0; i < entriesToClear && i < sortedEntries.length; i++) { rateLimitMap.delete(sortedEntries[i][0]); - clearedCount++; } - edgeLogger.warn('Emergency cleanup completed', { clearedCount, newMapSize: rateLimitMap.size }); - - // Reset failure count after emergency cleanup cleanupFailureCount = 0; - - } catch (emergencyError) { - // Last resort: If even emergency cleanup fails, clear everything - const originalSize = rateLimitMap.size; + } catch { rateLimitMap.clear(); - - edgeLogger.error('Emergency cleanup failed - cleared entire map', { - originalSize, - error: emergencyError instanceof Error ? emergencyError.message : String(emergencyError) - }); cleanupFailureCount = 0; } } } } -// Reset cleanup failure count periodically to avoid permanent emergency state setInterval(() => { if (cleanupFailureCount > 0) { cleanupFailureCount = 0; @@ -101,7 +62,6 @@ function checkRateLimit(ip: string): { allowed: boolean; retryAfter?: number } { const now = Date.now(); const existing = rateLimitMap.get(ip); - // Handle existing entries (most common case - early return for performance) if (existing && now <= existing.resetAt) { if (existing.count >= MAX_REQUESTS) { const retryAfter = Math.ceil((existing.resetAt - now) / 1000); @@ -111,213 +71,108 @@ function checkRateLimit(ip: string): { allowed: boolean; retryAfter?: number } { return { allowed: true }; } - // Need to add new entry or reset expired one - // Only perform cleanup if we're at capacity AND adding a new IP if (!existing && rateLimitMap.size >= MAX_MAP_SIZE) { - // First try cleaning expired entries cleanupExpiredEntries(); - // If still at capacity after cleanup, remove oldest entries (LRU eviction) if (rateLimitMap.size >= MAX_MAP_SIZE) { try { - const toDelete = Math.floor(MAX_MAP_SIZE * 0.3); // Remove 30% of entries + const toDelete = Math.floor(MAX_MAP_SIZE * 0.3); const sortedEntries = Array.from(rateLimitMap.entries()) .sort((a, b) => a[1].resetAt - b[1].resetAt); - let deletedCount = 0; for (let i = 0; i < toDelete && i < sortedEntries.length; i++) { rateLimitMap.delete(sortedEntries[i][0]); - deletedCount++; - } - - edgeLogger.warn('Rate limit map at capacity - evicted entries', { - maxSize: MAX_MAP_SIZE, - deletedCount, - newSize: rateLimitMap.size - }); - } catch (evictionError) { - // CRITICAL: LRU eviction failed - log error and attempt emergency clear - edgeLogger.error('LRU eviction failed', { - error: evictionError instanceof Error ? evictionError.message : String(evictionError), - mapSize: rateLimitMap.size - }); - - try { - // Emergency: Clear first 30% of entries without sorting - const targetSize = Math.floor(MAX_MAP_SIZE * 0.7); - const keysToDelete: string[] = []; - - for (const [key] of rateLimitMap.entries()) { - if (rateLimitMap.size <= targetSize) break; - keysToDelete.push(key); - } - - keysToDelete.forEach(key => rateLimitMap.delete(key)); - - edgeLogger.warn('Emergency eviction completed', { - clearedCount: keysToDelete.length, - newSize: rateLimitMap.size - }); - } catch (emergencyError) { - edgeLogger.error('Emergency eviction failed - clearing entire map', { - error: emergencyError instanceof Error ? emergencyError.message : String(emergencyError) - }); - rateLimitMap.clear(); } + } catch { + rateLimitMap.clear(); } } } - // Create new entry or reset expired entry rateLimitMap.set(ip, { count: 1, resetAt: now + RATE_LIMIT_WINDOW }); return { allowed: true }; } -// Clean up old entries periodically to prevent memory leak -// Run cleanup more frequently to catch expired entries sooner -setInterval(cleanupExpiredEntries, Math.min(RATE_LIMIT_WINDOW / 2, 30000)); // Every 30 seconds or half the window - -serve(async (req) => { - // Handle CORS preflight requests - if (req.method === 'OPTIONS') { - return new Response(null, { headers: corsHeaders }); - } - - const tracking = startRequest('detect-location'); - - try { - // Get the client's IP address - const forwarded = req.headers.get('x-forwarded-for'); - const realIP = req.headers.get('x-real-ip'); - const clientIP = forwarded?.split(',')[0] || realIP || '8.8.8.8'; // fallback to Google DNS for testing - - // Check rate limit - const rateLimit = checkRateLimit(clientIP); - if (!rateLimit.allowed) { - return new Response( - JSON.stringify({ - error: 'Rate limit exceeded', - message: 'Too many requests. Please try again later.', - retryAfter: rateLimit.retryAfter - }), - { - status: 429, - headers: { - ...corsHeaders, - 'Content-Type': 'application/json', - 'Retry-After': String(rateLimit.retryAfter || 60) - } - } - ); - } - - // PII Note: Do not log full IP addresses in production - edgeLogger.info('Detecting location for request', { requestId: tracking.requestId }); - - // Use configurable geolocation service with proper error handling - // Defaults to ip-api.com if not configured - const geoApiUrl = Deno.env.get('GEOLOCATION_API_URL') || 'http://ip-api.com/json'; - const geoApiFields = Deno.env.get('GEOLOCATION_API_FIELDS') || 'status,country,countryCode'; - - let geoResponse; - try { - geoResponse = await fetch(`${geoApiUrl}/${clientIP}?fields=${geoApiFields}`); - } catch (fetchError) { - edgeLogger.error('Network error fetching location data', { - error: fetchError instanceof Error ? fetchError.message : String(fetchError), - requestId: tracking.requestId - }); - throw new Error('Network error: Unable to reach geolocation service'); - } - - if (!geoResponse.ok) { - throw new Error(`Geolocation service returned ${geoResponse.status}: ${geoResponse.statusText}`); - } - - let geoData; - try { - geoData = await geoResponse.json(); - } catch (parseError) { - edgeLogger.error('Failed to parse geolocation response', { - error: parseError instanceof Error ? parseError.message : String(parseError), - requestId: tracking.requestId - }); - throw new Error('Invalid response format from geolocation service'); - } - - if (geoData.status !== 'success') { - throw new Error(`Geolocation failed: ${geoData.message || 'Invalid location data'}`); - } - - // Countries that primarily use imperial system - const imperialCountries = ['US', 'LR', 'MM']; // USA, Liberia, Myanmar - const measurementSystem = imperialCountries.includes(geoData.countryCode) ? 'imperial' : 'metric'; - - const result: IPLocationResponse = { - country: geoData.country, - countryCode: geoData.countryCode, - measurementSystem - }; - - edgeLogger.info('Location detected', { - country: result.country, - countryCode: result.countryCode, - measurementSystem: result.measurementSystem, - requestId: tracking.requestId - }); - - endRequest(tracking); - - return new Response( - JSON.stringify({ ...result, requestId: tracking.requestId }), - { - headers: { - ...corsHeaders, - 'Content-Type': 'application/json', - 'X-Request-ID': tracking.requestId - } - } - ); - - } catch (error: unknown) { - // Enhanced error logging for better visibility and debugging - const errorMessage = formatEdgeError(error); - - edgeLogger.error('Location detection error', { - error: errorMessage, - requestId: tracking.requestId - }); - - // Persist error to database for monitoring - const errorSpan = startSpan('detect-location-error', 'SERVER'); - endSpan(errorSpan, 'error', error); - logSpanToDatabase(errorSpan, tracking.requestId); - - endRequest(tracking); - - // Return default (metric) with 500 status to indicate error occurred - // This allows proper error monitoring while still providing fallback data - const defaultResult: IPLocationResponse = { - country: 'Unknown', - countryCode: 'XX', - measurementSystem: 'metric' - }; +setInterval(cleanupExpiredEntries, Math.min(RATE_LIMIT_WINDOW / 2, 30000)); +const handler = async (req: Request, { span, requestId }: EdgeFunctionContext) => { + // Get client IP + const forwarded = req.headers.get('x-forwarded-for'); + const realIP = req.headers.get('x-real-ip'); + const clientIP = forwarded?.split(',')[0] || realIP || '8.8.8.8'; + + // Check rate limit + const rateLimit = checkRateLimit(clientIP); + if (!rateLimit.allowed) { + addSpanEvent(span, 'rate_limit_exceeded', { clientIP: clientIP.substring(0, 8) + '...' }); return new Response( JSON.stringify({ - ...defaultResult, - error: errorMessage, - fallback: true, - requestId: tracking.requestId + error: 'Rate limit exceeded', + message: 'Too many requests. Please try again later.', + retryAfter: rateLimit.retryAfter }), - { - headers: { - ...corsHeaders, - 'Content-Type': 'application/json', - 'X-Request-ID': tracking.requestId - }, - status: 500 + { + status: 429, + headers: { + 'Retry-After': String(rateLimit.retryAfter || 60) + } } ); } -}); \ No newline at end of file + + addSpanEvent(span, 'detecting_location', { requestId }); + + // Use configurable geolocation service + const geoApiUrl = Deno.env.get('GEOLOCATION_API_URL') || 'http://ip-api.com/json'; + const geoApiFields = Deno.env.get('GEOLOCATION_API_FIELDS') || 'status,country,countryCode'; + + let geoResponse; + try { + geoResponse = await fetch(`${geoApiUrl}/${clientIP}?fields=${geoApiFields}`); + } catch (fetchError) { + addSpanEvent(span, 'network_error', { error: fetchError instanceof Error ? fetchError.message : String(fetchError) }); + throw new Error('Network error: Unable to reach geolocation service'); + } + + if (!geoResponse.ok) { + throw new Error(`Geolocation service returned ${geoResponse.status}: ${geoResponse.statusText}`); + } + + let geoData; + try { + geoData = await geoResponse.json(); + } catch (parseError) { + addSpanEvent(span, 'parse_error', { error: parseError instanceof Error ? parseError.message : String(parseError) }); + throw new Error('Invalid response format from geolocation service'); + } + + if (geoData.status !== 'success') { + throw new Error(`Geolocation failed: ${geoData.message || 'Invalid location data'}`); + } + + // Countries that primarily use imperial system + const imperialCountries = ['US', 'LR', 'MM']; + const measurementSystem = imperialCountries.includes(geoData.countryCode) ? 'imperial' : 'metric'; + + const result: IPLocationResponse = { + country: geoData.country, + countryCode: geoData.countryCode, + measurementSystem + }; + + addSpanEvent(span, 'location_detected', { + country: result.country, + countryCode: result.countryCode, + measurementSystem: result.measurementSystem + }); + + return result; +}; + +serve(createEdgeFunction({ + name: 'detect-location', + requireAuth: false, + corsHeaders, + enableTracing: true, + logRequests: true, +}, handler)); diff --git a/supabase/functions/process-expired-bans/index.ts b/supabase/functions/process-expired-bans/index.ts index cfd2b027..c7ef2955 100644 --- a/supabase/functions/process-expired-bans/index.ts +++ b/supabase/functions/process-expired-bans/index.ts @@ -1,16 +1,10 @@ -import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.57.4'; -import { createEdgeFunction } from '../_shared/edgeFunctionWrapper.ts'; -import { edgeLogger } from '../_shared/logger.ts'; +import { serve } from 'https://deno.land/std@0.190.0/http/server.ts'; +import { createEdgeFunction, type EdgeFunctionContext } from '../_shared/edgeFunctionWrapper.ts'; +import { addSpanEvent } from '../_shared/logger.ts'; +import { corsHeaders } from '../_shared/cors.ts'; -export default createEdgeFunction( - { - name: 'process-expired-bans', - requireAuth: false, - }, - async (req, context, supabase) => { - edgeLogger.info('Processing expired bans', { - requestId: context.requestId - }); +const handler = async (req: Request, { supabase, span, requestId }: EdgeFunctionContext) => { + addSpanEvent(span, 'processing_expired_bans', { requestId }); const now = new Date().toISOString(); @@ -23,25 +17,18 @@ export default createEdgeFunction( .lte('ban_expires_at', now); if (fetchError) { - edgeLogger.error('Error fetching expired bans', { - error: fetchError, - requestId: context.requestId - }); + addSpanEvent(span, 'error_fetching_expired_bans', { error: fetchError.message }); throw fetchError; } - edgeLogger.info('Found expired bans to process', { - count: expiredBans?.length || 0, - requestId: context.requestId - }); + addSpanEvent(span, 'found_expired_bans', { count: expiredBans?.length || 0 }); // Unban users with expired bans const unbannedUsers: string[] = []; for (const profile of expiredBans || []) { - edgeLogger.info('Unbanning user', { + addSpanEvent(span, 'unbanning_user', { username: profile.username, - userId: profile.user_id, - requestId: context.requestId + userId: profile.user_id }); const { error: unbanError } = await supabase @@ -54,10 +41,9 @@ export default createEdgeFunction( .eq('user_id', profile.user_id); if (unbanError) { - edgeLogger.error('Failed to unban user', { + addSpanEvent(span, 'failed_to_unban_user', { username: profile.username, - error: unbanError, - requestId: context.requestId + error: unbanError.message }); continue; } @@ -76,29 +62,28 @@ export default createEdgeFunction( }); if (logError) { - edgeLogger.error('Failed to log auto-unban', { + addSpanEvent(span, 'failed_to_log_auto_unban', { username: profile.username, - error: logError, - requestId: context.requestId + error: logError.message }); } unbannedUsers.push(profile.username); } - edgeLogger.info('Successfully unbanned users', { - count: unbannedUsers.length, - requestId: context.requestId - }); + addSpanEvent(span, 'successfully_unbanned_users', { count: unbannedUsers.length }); - return new Response( - JSON.stringify({ - success: true, - unbanned_count: unbannedUsers.length, - unbanned_users: unbannedUsers, - processed_at: now - }), - { headers: { 'Content-Type': 'application/json' } } - ); - } -); + return { + success: true, + unbanned_count: unbannedUsers.length, + unbanned_users: unbannedUsers, + processed_at: now + }; +}; + +serve(createEdgeFunction({ + name: 'process-expired-bans', + requireAuth: false, + corsHeaders, + enableTracing: true, +}, handler)); diff --git a/supabase/functions/rate-limit-metrics/index.ts b/supabase/functions/rate-limit-metrics/index.ts index 590e7a05..e251925f 100644 --- a/supabase/functions/rate-limit-metrics/index.ts +++ b/supabase/functions/rate-limit-metrics/index.ts @@ -1,11 +1,6 @@ -/** - * Rate Limit Metrics API - * - * Exposes rate limiting metrics for monitoring and analysis. - * Requires admin/moderator authentication. - */ - -import { createEdgeFunction } from '../_shared/edgeFunctionWrapper.ts'; +import { serve } from 'https://deno.land/std@0.190.0/http/server.ts'; +import { createEdgeFunction, type EdgeFunctionContext } from '../_shared/edgeFunctionWrapper.ts'; +import { corsHeaders } from '../_shared/cors.ts'; import { getRecentMetrics, getMetricsStats, @@ -15,18 +10,7 @@ import { clearMetrics, } from '../_shared/rateLimitMetrics.ts'; -interface QueryParams { - action?: string; - limit?: string; - timeWindow?: string; - functionName?: string; - userId?: string; - clientIP?: string; -} - -const handler = async (req: Request, context: { supabase: any; user: any; span: any; requestId: string }) => { - const { supabase, user } = context; - +const handler = async (req: Request, { supabase, user, span, requestId }: EdgeFunctionContext) => { // Check if user has admin or moderator role const { data: roles } = await supabase .from('user_roles') @@ -116,14 +100,10 @@ const handler = async (req: Request, context: { supabase: any; user: any; span: return responseData; }; -// Create edge function with automatic error handling, CORS, auth, and logging -createEdgeFunction( - { - name: 'rate-limit-metrics', - requireAuth: true, - corsEnabled: true, - enableTracing: false, - rateLimitTier: 'lenient' - }, - handler -); +serve(createEdgeFunction({ + name: 'rate-limit-metrics', + requireAuth: true, + corsHeaders, + enableTracing: true, + rateLimitTier: 'lenient', +}, handler));