From 85adf2483291e6902d8934b522904a1a6a6f5042 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Tue, 21 Oct 2025 19:30:08 +0000 Subject: [PATCH] Implement user notifications --- .../notify-user-submission-status/index.ts | 128 ++++++++++++++++++ ...1_4df33904-1245-4943-ad7f-496ffadbfc43.sql | 62 +++++++++ 2 files changed, 190 insertions(+) create mode 100644 supabase/functions/notify-user-submission-status/index.ts create mode 100644 supabase/migrations/20251021192931_4df33904-1245-4943-ad7f-496ffadbfc43.sql diff --git a/supabase/functions/notify-user-submission-status/index.ts b/supabase/functions/notify-user-submission-status/index.ts new file mode 100644 index 00000000..beb14dc6 --- /dev/null +++ b/supabase/functions/notify-user-submission-status/index.ts @@ -0,0 +1,128 @@ +import { serve } from "https://deno.land/std@0.168.0/http/server.ts"; +import { createClient } from "https://esm.sh/@supabase/supabase-js@2.57.4"; +import { startRequest, endRequest } from "../_shared/logger.ts"; + +const corsHeaders = { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type, x-request-id', +}; + +interface RequestBody { + submission_id: string; + user_id: string; + submission_type: string; + status: 'approved' | 'rejected'; + rejection_reason?: string; +} + +serve(async (req) => { + if (req.method === 'OPTIONS') { + return new Response(null, { headers: corsHeaders }); + } + + const tracking = startRequest('notify-user-submission-status'); + + try { + const supabaseUrl = Deno.env.get('SUPABASE_URL')!; + const supabaseServiceKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!; + + const supabase = createClient(supabaseUrl, supabaseServiceKey); + + const { submission_id, user_id, submission_type, status, rejection_reason } = await req.json() as RequestBody; + + // Fetch submission items to get entity name + const { data: items, error: itemsError } = await supabase + .from('submission_items') + .select('item_data') + .eq('submission_id', submission_id) + .order('order_index', { ascending: true }) + .limit(1) + .maybeSingle(); + + if (itemsError) { + throw new Error(`Failed to fetch submission items: ${itemsError.message}`); + } + + // Extract entity name from item_data + const entityName = items?.item_data?.name || 'your submission'; + const entityType = submission_type.replace('_', ' '); + + // Determine workflow and build payload + const workflowId = status === 'approved' ? 'submission-approved' : 'submission-rejected'; + + const payload: Record = { + entityName, + entityType, + submissionId: submission_id, + }; + + if (status === 'rejected' && rejection_reason) { + payload.rejectionReason = rejection_reason; + } + + console.log('Sending notification to user:', { + userId: user_id, + workflowId, + entityName, + status, + requestId: tracking.requestId + }); + + // Call trigger-notification function + const { data: notificationResult, error: notificationError } = await supabase.functions.invoke( + 'trigger-notification', + { + body: { + workflowId, + subscriberId: user_id, + payload, + }, + } + ); + + if (notificationError) { + throw new Error(`Failed to trigger notification: ${notificationError.message}`); + } + + console.log('User notification sent successfully:', notificationResult); + + endRequest(tracking, 200); + + return new Response( + JSON.stringify({ + success: true, + transactionId: notificationResult?.transactionId, + requestId: tracking.requestId + }), + { + headers: { + ...corsHeaders, + 'Content-Type': 'application/json', + 'X-Request-ID': tracking.requestId + }, + status: 200, + } + ); + } catch (error: unknown) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'; + console.error('Error notifying user about submission status:', errorMessage); + + endRequest(tracking, 500, errorMessage); + + return new Response( + JSON.stringify({ + success: false, + error: errorMessage, + requestId: tracking.requestId + }), + { + headers: { + ...corsHeaders, + 'Content-Type': 'application/json', + 'X-Request-ID': tracking.requestId + }, + status: 500, + } + ); + } +}); diff --git a/supabase/migrations/20251021192931_4df33904-1245-4943-ad7f-496ffadbfc43.sql b/supabase/migrations/20251021192931_4df33904-1245-4943-ad7f-496ffadbfc43.sql new file mode 100644 index 00000000..65dc0d77 --- /dev/null +++ b/supabase/migrations/20251021192931_4df33904-1245-4943-ad7f-496ffadbfc43.sql @@ -0,0 +1,62 @@ +-- Add notification templates for user submission status notifications +INSERT INTO notification_templates (workflow_id, novu_workflow_id, name, description, category, is_active) +VALUES + ('submission-approved', 'submission-approved', 'Submission Approved', 'Notify users when their submission is approved', 'submission', true), + ('submission-rejected', 'submission-rejected', 'Submission Rejected', 'Notify users when their submission is rejected', 'submission', true) +ON CONFLICT (workflow_id) DO NOTHING; + +-- Function to notify users when submission status changes +CREATE OR REPLACE FUNCTION public.notify_user_on_submission_status_change() +RETURNS TRIGGER +LANGUAGE plpgsql +SECURITY DEFINER +SET search_path TO 'public' +AS $function$ +DECLARE + function_url text; + anon_key text; +BEGIN + -- Only notify on status changes to approved or rejected + IF OLD.status IS DISTINCT FROM NEW.status AND NEW.status IN ('approved', 'rejected') THEN + + -- Build the function URL + function_url := 'https://ydvtmnrszybqnbcqbdcy.supabase.co/functions/v1/notify-user-submission-status'; + + -- Use the public anon key + anon_key := 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InlkdnRtbnJzenlicW5iY3FiZGN5Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTgzMjYzNTYsImV4cCI6MjA3MzkwMjM1Nn0.DM3oyapd_omP5ZzIlrT0H9qBsiQBxBRgw2tYuqgXKX4'; + + -- Call edge function asynchronously + PERFORM net.http_post( + url := function_url, + headers := jsonb_build_object( + 'Content-Type', 'application/json', + 'Authorization', 'Bearer ' || anon_key, + 'apikey', anon_key + ), + body := jsonb_build_object( + 'submission_id', NEW.id, + 'user_id', NEW.user_id, + 'submission_type', NEW.submission_type, + 'status', NEW.status, + 'rejection_reason', NEW.reviewer_notes + ) + ); + + RAISE LOG 'Triggered user notification for submission % with status %', NEW.id, NEW.status; + END IF; + + RETURN NEW; +EXCEPTION + WHEN OTHERS THEN + -- Log error but don't fail the status update + RAISE WARNING 'Failed to notify user about submission status: %', SQLERRM; + RETURN NEW; +END; +$function$; + +-- Create trigger on content_submissions +DROP TRIGGER IF EXISTS notify_user_on_submission_status_change_trigger ON content_submissions; +CREATE TRIGGER notify_user_on_submission_status_change_trigger + AFTER UPDATE ON content_submissions + FOR EACH ROW + EXECUTE FUNCTION notify_user_on_submission_status_change(); \ No newline at end of file