Files
gpt-engineer-app[bot] 96b7594738 Migrate Phase 2 admin edges
Migrate five admin/moderator edge functions (merge-contact-tickets, send-escalation-notification, notify-moderators-report, notify-moderators-submission, send-password-added-email) to use createEdgeFunction wrapper. Remove manual CORS, auth, service-client setup, logging, and error handling. Implement handler with EdgeFunctionContext, apply appropriate wrapper config (requireAuth, requiredRoles/useServiceRole, corsEnabled, enableTracing, rateLimitTier). Replace edgeLogger with span events, maintain core business logic and retry/email integration patterns.
2025-11-11 20:39:10 +00:00

181 lines
5.3 KiB
TypeScript

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';
import { withEdgeRetry } from '../_shared/retryHelper.ts';
interface NotificationPayload {
reportId: string;
reportType: string;
reportedEntityType: string;
reportedEntityId: string;
reporterName: string;
reason: string;
reportedAt: string;
entityPreview: string;
}
const handler = async (req: Request, { supabase, span, requestId }: EdgeFunctionContext) => {
const payload: NotificationPayload = await req.json();
addSpanEvent(span, 'processing_report_notification', {
reportId: payload.reportId,
reportType: payload.reportType,
reportedEntityType: payload.reportedEntityType
});
// Calculate relative time
const reportedAt = new Date(payload.reportedAt);
const now = new Date();
const diffMs = now.getTime() - reportedAt.getTime();
const diffMins = Math.floor(diffMs / 60000);
let relativeTime: string;
if (diffMins < 1) {
relativeTime = 'just now';
} else if (diffMins < 60) {
relativeTime = `${diffMins} minute${diffMins === 1 ? '' : 's'} ago`;
} else {
const diffHours = Math.floor(diffMins / 60);
relativeTime = `${diffHours} hour${diffHours === 1 ? '' : 's'} ago`;
}
// Determine priority based on report type and age
let priority: string;
const criticalTypes = ['harassment', 'offensive'];
const isUrgent = diffMins < 5;
if (criticalTypes.includes(payload.reportType) || isUrgent) {
priority = 'high';
} else if (diffMins < 30) {
priority = 'medium';
} else {
priority = 'low';
}
// Fetch the workflow ID for report alerts
const { data: template, error: templateError } = await supabase
.from('notification_templates')
.select('workflow_id')
.eq('workflow_id', 'report-alert')
.eq('is_active', true)
.maybeSingle();
if (templateError) {
addSpanEvent(span, 'workflow_fetch_failed', { error: templateError.message });
throw new Error(`Failed to fetch workflow: ${templateError.message}`);
}
if (!template) {
addSpanEvent(span, 'no_active_workflow', {});
return new Response(
JSON.stringify({
success: false,
error: 'No active report-alert workflow configured',
}),
{ status: 400 }
);
}
// Fetch reported entity name
let reportedEntityName = 'Unknown';
try {
if (payload.reportedEntityType === 'review') {
const { data: review } = await supabase
.from('reviews')
.select('ride:rides(name), park:parks(name)')
.eq('id', payload.reportedEntityId)
.maybeSingle();
reportedEntityName = review?.ride?.name || review?.park?.name || 'Review';
} else if (payload.reportedEntityType === 'profile') {
const { data: profile } = await supabase
.from('profiles')
.select('display_name, username')
.eq('user_id', payload.reportedEntityId)
.maybeSingle();
reportedEntityName = profile?.display_name || profile?.username || 'User Profile';
} else if (payload.reportedEntityType === 'content_submission') {
const { data: metadata } = await supabase
.from('submission_metadata')
.select('metadata_value')
.eq('submission_id', payload.reportedEntityId)
.eq('metadata_key', 'name')
.maybeSingle();
reportedEntityName = metadata?.metadata_value || 'Submission';
}
} catch (error) {
addSpanEvent(span, 'entity_name_fetch_failed', {
error: error instanceof Error ? error.message : String(error)
});
}
// Build enhanced notification payload
const notificationPayload = {
baseUrl: 'https://www.thrillwiki.com',
reportId: payload.reportId,
reportType: payload.reportType,
reportedEntityType: payload.reportedEntityType,
reportedEntityId: payload.reportedEntityId,
reporterName: payload.reporterName,
reason: payload.reason,
entityPreview: payload.entityPreview,
reportedEntityName,
reportedAt: payload.reportedAt,
relativeTime,
priority,
};
addSpanEvent(span, 'triggering_notification', {
workflowId: template.workflow_id,
priority
});
// Invoke the trigger-notification function with retry
const result = await withEdgeRetry(
async () => {
const { data, error } = await supabase.functions.invoke(
'trigger-notification',
{
body: {
workflowId: template.workflow_id,
topicKey: 'moderation-reports',
payload: notificationPayload,
},
}
);
if (error) {
const enhancedError = new Error(error.message || 'Notification trigger failed');
(enhancedError as any).status = error.status;
throw enhancedError;
}
return data;
},
{ maxAttempts: 3, baseDelay: 1000 },
requestId,
'trigger-report-notification'
);
addSpanEvent(span, 'notification_sent', { transactionId: result?.transactionId });
return {
success: true,
transactionId: result?.transactionId,
payload: notificationPayload,
};
};
serve(createEdgeFunction({
name: 'notify-moderators-report',
requireAuth: false,
useServiceRole: true,
corsHeaders,
enableTracing: true,
logRequests: true,
}, handler));