Add n8n test mode toggle and edge function improvements

- Add test/production toggle for n8n webhook URLs in IntegrasiTab
- Update create-meet-link function to use database test_mode setting
- Add send-email-v2 edge function for Mailketing API integration
- Update daily-reminders and send-consultation-reminder to use send-email-v2
- Remove deprecated branding field from BrandingTab
- Update domain references from hub.dwindi.com to with.dwindi.com
- Add environment variables for Coolify deployment
- Add comprehensive edge function test script
- Update payment flow redirect to order detail page

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
dwindown
2025-12-23 00:24:40 +07:00
parent f1cc2ba3f7
commit dfda71053c
11 changed files with 385 additions and 49 deletions

View File

@@ -52,16 +52,21 @@ serve(async (req: Request): Promise<Response> => {
// For now, this is a placeholder that returns a message
// In production, you would integrate with Google Calendar API via OAuth or service account
// Or call an n8n webhook to handle the calendar creation
const { data: integrationSettings } = await supabase
.from("platform_settings")
.select("integration_n8n_base_url")
.select("integration_n8n_base_url, integration_n8n_test_mode")
.single();
if (integrationSettings?.integration_n8n_base_url) {
// Call n8n webhook if configured
const n8nUrl = `${integrationSettings.integration_n8n_base_url}/webhook/create-meet`;
// Check if we're in test mode (controlled by the integration_n8n_test_mode setting)
const isTestMode = integrationSettings.integration_n8n_test_mode || false;
const webhookPath = isTestMode ? "/webhook-test/" : "/webhook/";
const n8nUrl = `${integrationSettings.integration_n8n_base_url}${webhookPath}create-meet`;
console.log(`Calling n8n webhook: ${n8nUrl} (Test mode: ${isTestMode})`);
try {
const n8nResponse = await fetch(n8nUrl, {
method: "POST",
@@ -77,6 +82,7 @@ serve(async (req: Request): Promise<Response> => {
notes: body.notes,
calendar_id: calendarId,
brand_name: brandName,
test_mode: isTestMode, // Add test_mode flag for n8n to use
}),
});

View File

@@ -69,7 +69,63 @@ serve(async (req: Request): Promise<Response> => {
if (!profile?.email) continue;
// Call send-notification function
const { error: notifyError } = await supabase.functions.invoke("send-notification", {
// Get notification template and settings to send via send-email-v2
const { data: template } = await supabase
.from("notification_templates")
.select("*")
.eq("key", "consulting_reminder")
.eq("is_active", true)
.single();
const { data: emailSettings } = await supabase
.from("notification_settings")
.select("*")
.single();
let notifyError = null;
if (template && emailSettings?.api_token) {
// Build payload with proper shortcode mapping
const payload = {
nama: profile.full_name,
email: profile.email,
tanggal_konsultasi: new Date(slot.date).toLocaleDateString('id-ID', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric'
}),
jam_konsultasi: `${slot.start_time.substring(0, 5)} - ${slot.end_time.substring(0, 5)} WIB`,
link_meet: slot.meet_link || "Akan diinformasikan",
jenis_konsultasi: slot.topic_category,
};
// Process shortcodes in template
let emailBody = template.email_body_html || "";
let emailSubject = template.email_subject || "Reminder Konsultasi";
Object.entries(payload).forEach(([key, value]) => {
const regex = new RegExp(`\\{${key}\\}`, "g");
emailBody = emailBody.replace(regex, String(value));
emailSubject = emailSubject.replace(regex, String(value));
});
// Send via send-email-v2 (Mailketing API)
const { error: emailError } = await supabase.functions.invoke("send-email-v2", {
body: {
to: profile.email,
api_token: emailSettings.api_token,
from_name: emailSettings.from_name || "Access Hub",
from_email: emailSettings.from_email || "noreply@with.dwindi.com",
subject: emailSubject,
html_body: emailBody,
},
});
notifyError = emailError;
} else {
notifyError = { message: "Template not active or email not configured" };
}
body: {
template_key: "consultation_reminder",
recipient_email: profile.email,

View File

@@ -127,22 +127,39 @@ serve(async (req: Request): Promise<Response> => {
}
}
// Send email if template is active and SMTP is configured
if (template?.is_active && smtpSettings?.smtp_host && profile?.email) {
// Replace shortcodes in email body
let emailBody = template.email_body_html || "";
let emailSubject = template.email_subject || "Reminder Konsultasi";
// Send email if template is active and Mailketing is configured
if (template?.is_active && smtpSettings?.api_token && profile?.email) {
try {
// Replace shortcodes in email body using master template system
let emailBody = template.email_body_html || "";
let emailSubject = template.email_subject || "Reminder Konsultasi";
Object.entries(payload).forEach(([key, value]) => {
const regex = new RegExp(`\\{${key}\\}`, "g");
emailBody = emailBody.replace(regex, String(value));
emailSubject = emailSubject.replace(regex, String(value));
});
Object.entries(payload).forEach(([key, value]) => {
const regex = new RegExp(`\\{${key}\\}`, "g");
emailBody = emailBody.replace(regex, String(value));
emailSubject = emailSubject.replace(regex, String(value));
});
// Here you would send the actual email
// For now, log that we would send it
console.log("Would send reminder email to:", profile.email);
console.log("Subject:", emailSubject);
// Send via send-email-v2 (Mailketing API)
const { error: emailError } = await supabase.functions.invoke("send-email-v2", {
body: {
to: profile.email,
api_token: smtpSettings.api_token,
from_name: smtpSettings.from_name || platformSettings?.brand_name || "Access Hub",
from_email: smtpSettings.from_email || "noreply@with.dwindi.com",
subject: emailSubject,
html_body: emailBody,
},
});
if (emailError) {
console.error("Failed to send reminder email:", emailError);
} else {
console.log("Reminder email sent to:", profile.email);
}
} catch (emailError) {
console.error("Error sending reminder email:", emailError);
}
}
results.push({

View File

@@ -0,0 +1,99 @@
import { serve } from "https://deno.land/std@0.190.0/http/server.ts";
const corsHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type",
};
interface EmailRequest {
to: string;
api_token: string;
from_name: string;
from_email: string;
subject: string;
html_body: string;
}
// Send via Mailketing API
async function sendViaMailketing(request: EmailRequest): Promise<{ success: boolean; message: string }> {
const { to, api_token, from_name, from_email, subject, html_body } = request;
const formData = new FormData();
formData.append('to', to);
formData.append('from_name', from_name);
formData.append('from_email', from_email);
formData.append('subject', subject);
formData.append('html_body', html_body);
console.log(`Sending email via Mailketing to ${to}`);
const response = await fetch('https://api.mailketing.co/v1/send', {
method: 'POST',
headers: {
'Authorization': `Bearer ${api_token}`,
},
body: formData,
});
if (!response.ok) {
const errorText = await response.text();
console.error('Mailketing API error:', response.status, errorText);
throw new Error(`Mailketing API error: ${response.status} ${errorText}`);
}
const result = await response.json();
console.log('Mailketing API response:', result);
return {
success: true,
message: result.message || 'Email sent successfully via Mailketing'
};
}
serve(async (req: Request): Promise<Response> => {
if (req.method === "OPTIONS") {
return new Response(null, { headers: corsHeaders });
}
try {
const body: EmailRequest = await req.json();
// Validate required fields
if (!body.to || !body.api_token || !body.from_name || !body.from_email || !body.subject || !body.html_body) {
return new Response(
JSON.stringify({ success: false, message: "Missing required fields: to, api_token, from_name, from_email, subject, html_body" }),
{ status: 400, headers: { ...corsHeaders, "Content-Type": "application/json" } }
);
}
// Basic email validation
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(body.to) || !emailRegex.test(body.from_email)) {
return new Response(
JSON.stringify({ success: false, message: "Invalid email format" }),
{ status: 400, headers: { ...corsHeaders, "Content-Type": "application/json" } }
);
}
console.log(`Attempting to send email to: ${body.to}`);
console.log(`From: ${body.from_name} <${body.from_email}>`);
console.log(`Subject: ${body.subject}`);
const result = await sendViaMailketing(body);
return new Response(
JSON.stringify(result),
{ status: 200, headers: { ...corsHeaders, "Content-Type": "application/json" } }
);
} catch (error: any) {
console.error("Error sending email:", error);
return new Response(
JSON.stringify({
success: false,
message: error.message || "Failed to send email"
}),
{ status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } }
);
}
});