import { serve } from "https://deno.land/std@0.190.0/http/server.ts"; import { createClient } from "https://esm.sh/@supabase/supabase-js@2"; import { EmailTemplateRenderer } from "../shared/email-template-renderer.ts"; const corsHeaders = { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type", }; interface SendOTPRequest { user_id: string; email: string; } // Generate 6-digit OTP code function generateOTP(): string { return Math.floor(100000 + Math.random() * 900000).toString(); } serve(async (req: Request) => { if (req.method === "OPTIONS") { return new Response(null, { headers: corsHeaders }); } try { const { user_id, email }: SendOTPRequest = await req.json(); // Validate required fields if (!user_id || !email) { return new Response( JSON.stringify({ success: false, message: "Missing required fields: user_id, email" }), { status: 400, headers: { ...corsHeaders, "Content-Type": "application/json" } } ); } // Basic email validation const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(email)) { return new Response( JSON.stringify({ success: false, message: "Invalid email format" }), { status: 400, headers: { ...corsHeaders, "Content-Type": "application/json" } } ); } // Initialize Supabase client with service role const supabaseUrl = Deno.env.get('SUPABASE_URL')!; const supabaseServiceKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!; const supabase = createClient(supabaseUrl, supabaseServiceKey, { auth: { autoRefreshToken: false, persistSession: false } }); // Generate OTP code const otpCode = generateOTP(); const expiresAt = new Date(Date.now() + 15 * 60 * 1000); // 15 minutes from now console.log(`Generating OTP for user ${user_id}, email ${email}`); // Store OTP in database const { error: otpError } = await supabase .from('auth_otps') .insert({ user_id, email, otp_code: otpCode, expires_at: expiresAt.toISOString(), }); if (otpError) { console.error('Error storing OTP:', otpError); throw new Error(`Failed to store OTP: ${otpError.message}`); } // Get notification settings const { data: settings, error: settingsError } = await supabase .from('notification_settings') .select('*') .single(); if (settingsError || !settings) { console.error('Error fetching notification settings:', settingsError); throw new Error('Notification settings not configured'); } // Get platform settings for brand_name const { data: platformSettings, error: platformError } = await supabase .from('platform_settings') .select('brand_name') .single(); if (platformError) { console.error('Error fetching platform settings:', platformError); // Continue with fallback if platform settings not found } const brandName = platformSettings?.brand_name || settings.platform_name || 'ACCESS HUB'; // Get email template console.log('Fetching email template with key: auth_email_verification'); const { data: template, error: templateError } = await supabase .from('notification_templates') .select('*') .eq('key', 'auth_email_verification') .single(); console.log('Template query result:', { template, templateError }); if (templateError || !template) { console.error('Error fetching email template:', templateError); throw new Error('Email template not found. Please create template with key: auth_email_verification'); } // Get user data from auth.users const { data: { user }, error: userError } = await supabase.auth.admin.getUserById(user_id); if (userError || !user) { console.error('Error fetching user:', userError); throw new Error('User not found'); } // Prepare template variables const templateVars = { platform_name: brandName, nama: user.user_metadata?.name || user.email || 'Pengguna', email: email, otp_code: otpCode, expiry_minutes: '15', confirmation_link: '', // Not used for OTP year: new Date().getFullYear().toString(), }; // Process shortcodes in subject let subject = template.email_subject; Object.entries(templateVars).forEach(([key, value]) => { subject = subject.replace(new RegExp(`{${key}}`, 'g'), value); }); // Process shortcodes in HTML body content let htmlContent = template.email_body_html; Object.entries(templateVars).forEach(([key, value]) => { htmlContent = htmlContent.replace(new RegExp(`{${key}}`, 'g'), value); }); // Wrap in master template const htmlBody = EmailTemplateRenderer.render({ subject: subject, content: htmlContent, brandName: brandName, }); // Send email via send-email-v2 console.log(`Sending OTP email to ${email}`); console.log('Settings:', { hasMailketingToken: !!settings.mailketing_api_token, hasApiToken: !!settings.api_token, hasFromName: !!settings.from_name, hasFromEmail: !!settings.from_email, platformName: settings.platform_name, }); // Use api_token (not mailketing_api_token) const apiToken = settings.api_token || settings.mailketing_api_token; if (!apiToken) { throw new Error('API token not found in notification_settings'); } // Log email details (truncate HTML body for readability) console.log('Email payload:', { recipient: email, from_name: settings.from_name || brandName, from_email: settings.from_email || 'noreply@example.com', subject: subject, content_length: htmlBody.length, content_preview: htmlBody.substring(0, 200), }); const emailResponse = await fetch(`${supabaseUrl}/functions/v1/send-email-v2`, { method: 'POST', headers: { 'Authorization': `Bearer ${supabaseServiceKey}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ recipient: email, api_token: apiToken, from_name: settings.from_name || brandName, from_email: settings.from_email || 'noreply@example.com', subject: subject, content: htmlBody, }), }); if (!emailResponse.ok) { const errorText = await emailResponse.text(); console.error('Email send error:', emailResponse.status, errorText); throw new Error(`Failed to send email: ${emailResponse.status} ${errorText}`); } const emailResult = await emailResponse.json(); console.log('Email sent successfully:', emailResult); // Note: notification_logs table doesn't exist, skipping logging return new Response( JSON.stringify({ success: true, message: 'OTP sent successfully' }), { status: 200, headers: { ...corsHeaders, "Content-Type": "application/json" } } ); } catch (error: any) { console.error("Error sending OTP:", error); // Note: notification_logs table doesn't exist, skipping error logging return new Response( JSON.stringify({ success: false, message: error.message || "Failed to send OTP" }), { status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } } ); } });