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:
@@ -6,6 +6,10 @@ VITE_SUPABASE_EDGE_URL=your_supabase_url_here/functions/v1
|
|||||||
# Application Configuration
|
# Application Configuration
|
||||||
VITE_APP_NAME=Access Hub
|
VITE_APP_NAME=Access Hub
|
||||||
VITE_APP_ENV=production
|
VITE_APP_ENV=production
|
||||||
|
SITE_URL=https://with.dwindi.com/
|
||||||
|
|
||||||
|
# Google Integration
|
||||||
|
VITE_GOOGLE_CLIENT_ID=your_google_oauth_client_id_here
|
||||||
|
|
||||||
# Third-party Integrations
|
# Third-party Integrations
|
||||||
VITE_PAKASIR_API_KEY=your_pakasir_api_key_here
|
VITE_PAKASIR_API_KEY=your_pakasir_api_key_here
|
||||||
|
|||||||
@@ -32,9 +32,10 @@ deploy_function() {
|
|||||||
# Deploy all functions
|
# Deploy all functions
|
||||||
deploy_function "pakasir-webhook"
|
deploy_function "pakasir-webhook"
|
||||||
deploy_function "send-test-email"
|
deploy_function "send-test-email"
|
||||||
deploy_function "create-meet-link"
|
deploy_function "create-meet-link" # Includes n8n test mode toggle
|
||||||
deploy_function "send-consultation-reminder"
|
deploy_function "send-consultation-reminder"
|
||||||
deploy_function "send-notification"
|
deploy_function "send-notification"
|
||||||
|
deploy_function "send-email-v2"
|
||||||
deploy_function "daily-reminders"
|
deploy_function "daily-reminders"
|
||||||
|
|
||||||
echo "Deployment complete!"
|
echo "Deployment complete!"
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ interface PlatformSettings {
|
|||||||
brand_favicon_url: string;
|
brand_favicon_url: string;
|
||||||
brand_primary_color: string;
|
brand_primary_color: string;
|
||||||
brand_accent_color: string;
|
brand_accent_color: string;
|
||||||
brand_email_from_name: string;
|
|
||||||
homepage_headline: string;
|
homepage_headline: string;
|
||||||
homepage_description: string;
|
homepage_description: string;
|
||||||
homepage_features: HomepageFeature[];
|
homepage_features: HomepageFeature[];
|
||||||
@@ -41,7 +40,6 @@ const emptySettings: PlatformSettings = {
|
|||||||
brand_favicon_url: '',
|
brand_favicon_url: '',
|
||||||
brand_primary_color: '#111827',
|
brand_primary_color: '#111827',
|
||||||
brand_accent_color: '#0F766E',
|
brand_accent_color: '#0F766E',
|
||||||
brand_email_from_name: '',
|
|
||||||
homepage_headline: 'Learn. Grow. Succeed.',
|
homepage_headline: 'Learn. Grow. Succeed.',
|
||||||
homepage_description: 'Access premium consulting, live webinars, and intensive bootcamps to accelerate your career.',
|
homepage_description: 'Access premium consulting, live webinars, and intensive bootcamps to accelerate your career.',
|
||||||
homepage_features: defaultFeatures,
|
homepage_features: defaultFeatures,
|
||||||
@@ -84,7 +82,6 @@ export function BrandingTab() {
|
|||||||
brand_favicon_url: data.brand_favicon_url || '',
|
brand_favicon_url: data.brand_favicon_url || '',
|
||||||
brand_primary_color: data.brand_primary_color || '#111827',
|
brand_primary_color: data.brand_primary_color || '#111827',
|
||||||
brand_accent_color: data.brand_accent_color || '#0F766E',
|
brand_accent_color: data.brand_accent_color || '#0F766E',
|
||||||
brand_email_from_name: data.brand_email_from_name || '',
|
|
||||||
homepage_headline: data.homepage_headline || emptySettings.homepage_headline,
|
homepage_headline: data.homepage_headline || emptySettings.homepage_headline,
|
||||||
homepage_description: data.homepage_description || emptySettings.homepage_description,
|
homepage_description: data.homepage_description || emptySettings.homepage_description,
|
||||||
homepage_features: features,
|
homepage_features: features,
|
||||||
@@ -102,7 +99,6 @@ export function BrandingTab() {
|
|||||||
brand_favicon_url: settings.brand_favicon_url,
|
brand_favicon_url: settings.brand_favicon_url,
|
||||||
brand_primary_color: settings.brand_primary_color,
|
brand_primary_color: settings.brand_primary_color,
|
||||||
brand_accent_color: settings.brand_accent_color,
|
brand_accent_color: settings.brand_accent_color,
|
||||||
brand_email_from_name: settings.brand_email_from_name,
|
|
||||||
homepage_headline: settings.homepage_headline,
|
homepage_headline: settings.homepage_headline,
|
||||||
homepage_description: settings.homepage_description,
|
homepage_description: settings.homepage_description,
|
||||||
homepage_features: settings.homepage_features,
|
homepage_features: settings.homepage_features,
|
||||||
@@ -280,22 +276,6 @@ export function BrandingTab() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label className="flex items-center gap-2">
|
|
||||||
<Mail className="w-4 h-4" />
|
|
||||||
Nama Pengirim Default Email
|
|
||||||
</Label>
|
|
||||||
<Input
|
|
||||||
value={settings.brand_email_from_name}
|
|
||||||
onChange={(e) => setSettings({ ...settings, brand_email_from_name: e.target.value })}
|
|
||||||
placeholder="LearnHub Team"
|
|
||||||
className="border-2"
|
|
||||||
/>
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
Digunakan jika SMTP from_name kosong
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { Input } from '@/components/ui/input';
|
|||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||||
import { Alert, AlertDescription } from '@/components/ui/alert';
|
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||||
|
import { Switch } from '@/components/ui/switch';
|
||||||
import { toast } from '@/hooks/use-toast';
|
import { toast } from '@/hooks/use-toast';
|
||||||
import { Puzzle, Webhook, MessageSquare, Calendar, Mail, Link as LinkIcon, Key, Send, AlertTriangle } from 'lucide-react';
|
import { Puzzle, Webhook, MessageSquare, Calendar, Mail, Link as LinkIcon, Key, Send, AlertTriangle } from 'lucide-react';
|
||||||
|
|
||||||
@@ -19,6 +20,7 @@ interface IntegrationSettings {
|
|||||||
integration_email_api_base_url: string;
|
integration_email_api_base_url: string;
|
||||||
integration_privacy_url: string;
|
integration_privacy_url: string;
|
||||||
integration_terms_url: string;
|
integration_terms_url: string;
|
||||||
|
integration_n8n_test_mode: boolean;
|
||||||
// Mailketing specific settings
|
// Mailketing specific settings
|
||||||
provider: 'mailketing' | 'smtp';
|
provider: 'mailketing' | 'smtp';
|
||||||
api_token: string;
|
api_token: string;
|
||||||
@@ -35,6 +37,7 @@ const emptySettings: IntegrationSettings = {
|
|||||||
integration_email_api_base_url: '',
|
integration_email_api_base_url: '',
|
||||||
integration_privacy_url: '/privacy',
|
integration_privacy_url: '/privacy',
|
||||||
integration_terms_url: '/terms',
|
integration_terms_url: '/terms',
|
||||||
|
integration_n8n_test_mode: false,
|
||||||
provider: 'mailketing',
|
provider: 'mailketing',
|
||||||
api_token: '',
|
api_token: '',
|
||||||
from_name: '',
|
from_name: '',
|
||||||
@@ -75,6 +78,7 @@ export function IntegrasiTab() {
|
|||||||
integration_email_api_base_url: platformData.integration_email_api_base_url || '',
|
integration_email_api_base_url: platformData.integration_email_api_base_url || '',
|
||||||
integration_privacy_url: platformData.integration_privacy_url || '/privacy',
|
integration_privacy_url: platformData.integration_privacy_url || '/privacy',
|
||||||
integration_terms_url: platformData.integration_terms_url || '/terms',
|
integration_terms_url: platformData.integration_terms_url || '/terms',
|
||||||
|
integration_n8n_test_mode: platformData.integration_n8n_test_mode || false,
|
||||||
// Email settings from notification_settings
|
// Email settings from notification_settings
|
||||||
provider: emailData?.provider || 'mailketing',
|
provider: emailData?.provider || 'mailketing',
|
||||||
api_token: emailData?.api_token || '',
|
api_token: emailData?.api_token || '',
|
||||||
@@ -99,6 +103,7 @@ export function IntegrasiTab() {
|
|||||||
integration_email_api_base_url: settings.integration_email_api_base_url,
|
integration_email_api_base_url: settings.integration_email_api_base_url,
|
||||||
integration_privacy_url: settings.integration_privacy_url,
|
integration_privacy_url: settings.integration_privacy_url,
|
||||||
integration_terms_url: settings.integration_terms_url,
|
integration_terms_url: settings.integration_terms_url,
|
||||||
|
integration_n8n_test_mode: settings.integration_n8n_test_mode,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (settings.id) {
|
if (settings.id) {
|
||||||
@@ -215,6 +220,28 @@ export function IntegrasiTab() {
|
|||||||
Digunakan sebagai target default untuk webhook lanjutan. webhook_url per template tetap harus URL lengkap.
|
Digunakan sebagai target default untuk webhook lanjutan. webhook_url per template tetap harus URL lengkap.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between p-4 border rounded-lg space-y-0">
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<Label>Mode Test n8n</Label>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Aktifkan untuk menggunakan webhook path /webhook-test/ instead of /webhook/
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Switch
|
||||||
|
checked={settings.integration_n8n_test_mode}
|
||||||
|
onCheckedChange={(checked) => setSettings({ ...settings, integration_n8n_test_mode: checked })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{settings.integration_n8n_test_mode && (
|
||||||
|
<Alert>
|
||||||
|
<AlertTriangle className="w-4 h-4" />
|
||||||
|
<AlertDescription>
|
||||||
|
Mode test aktif: Webhook akan menggunakan path <code>/webhook-test/</code>
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
|||||||
@@ -357,7 +357,7 @@ export class ShortcodeProcessor {
|
|||||||
total: 'Rp 1.500.000',
|
total: 'Rp 1.500.000',
|
||||||
metode_pembayaran: 'Transfer Bank',
|
metode_pembayaran: 'Transfer Bank',
|
||||||
status_pesanan: 'Diproses',
|
status_pesanan: 'Diproses',
|
||||||
invoice_url: 'https://example.com/invoice/ORD-123456',
|
invoice_url: 'https://with.dwindi.com/orders/ORD-123456',
|
||||||
|
|
||||||
// Product information
|
// Product information
|
||||||
produk: 'Digital Marketing Masterclass',
|
produk: 'Digital Marketing Masterclass',
|
||||||
@@ -366,7 +366,7 @@ export class ShortcodeProcessor {
|
|||||||
deskripsi_produk: 'Kelas lengkap digital marketing dari pemula hingga mahir',
|
deskripsi_produk: 'Kelas lengkap digital marketing dari pemula hingga mahir',
|
||||||
|
|
||||||
// Access information
|
// Access information
|
||||||
link_akses: 'https://example.com/access',
|
link_akses: 'https://with.dwindi.com/access',
|
||||||
username_akses: 'john.doe',
|
username_akses: 'john.doe',
|
||||||
password_akses: 'Temp123!',
|
password_akses: 'Temp123!',
|
||||||
kadaluarsa_akses: '22 Desember 2026',
|
kadaluarsa_akses: '22 Desember 2026',
|
||||||
@@ -383,7 +383,7 @@ export class ShortcodeProcessor {
|
|||||||
judul_event: 'Workshop Digital Marketing',
|
judul_event: 'Workshop Digital Marketing',
|
||||||
tanggal_event: '25 Desember 2025',
|
tanggal_event: '25 Desember 2025',
|
||||||
jam_event: '19:00',
|
jam_event: '19:00',
|
||||||
link_event: 'https://event.example.com',
|
link_event: 'https://with.dwindi.com/events',
|
||||||
lokasi_event: 'Zoom Online',
|
lokasi_event: 'Zoom Online',
|
||||||
kapasitas_event: '100 peserta',
|
kapasitas_event: '100 peserta',
|
||||||
|
|
||||||
@@ -392,12 +392,12 @@ export class ShortcodeProcessor {
|
|||||||
progres_bootcamp: '75%',
|
progres_bootcamp: '75%',
|
||||||
modul_selesai: '15 dari 20 modul',
|
modul_selesai: '15 dari 20 modul',
|
||||||
modul_selanjutnya: 'Final Assessment',
|
modul_selanjutnya: 'Final Assessment',
|
||||||
link_progress: 'https://example.com/progress',
|
link_progress: 'https://with.dwindi.com/bootcamp/progress',
|
||||||
|
|
||||||
// Company information
|
// Company information
|
||||||
nama_perusahaan: 'ACCESS HUB',
|
nama_perusahaan: 'ACCESS HUB',
|
||||||
website_perusahaan: 'https://accesshub.example.com',
|
website_perusahaan: 'https://with.dwindi.com',
|
||||||
email_support: 'support@accesshub.example.com',
|
email_support: 'support@with.dwindi.com',
|
||||||
telepon_support: '+62 812-3456-7890',
|
telepon_support: '+62 812-3456-7890',
|
||||||
|
|
||||||
// Payment information
|
// Payment information
|
||||||
@@ -406,8 +406,8 @@ export class ShortcodeProcessor {
|
|||||||
atas_nama: 'PT Access Hub Indonesia',
|
atas_nama: 'PT Access Hub Indonesia',
|
||||||
jumlah_pembayaran: 'Rp 1.500.000',
|
jumlah_pembayaran: 'Rp 1.500.000',
|
||||||
batas_pembayaran: '22 Desember 2025 23:59',
|
batas_pembayaran: '22 Desember 2025 23:59',
|
||||||
payment_link: 'https://accesshub.example.com/checkout',
|
payment_link: 'https://with.dwindi.com/checkout',
|
||||||
thank_you_page: 'https://accesshub.example.com/orders/{order_id}'
|
thank_you_page: 'https://with.dwindi.com/orders/{order_id}'
|
||||||
};
|
};
|
||||||
|
|
||||||
static process(content: string, customData: Record<string, string> = {}): string {
|
static process(content: string, customData: Record<string, string> = {}): string {
|
||||||
|
|||||||
@@ -32,3 +32,6 @@ verify_jwt = false
|
|||||||
|
|
||||||
[functions.daily-reminders]
|
[functions.daily-reminders]
|
||||||
verify_jwt = false
|
verify_jwt = false
|
||||||
|
|
||||||
|
[functions.send-email-v2]
|
||||||
|
verify_jwt = false
|
||||||
|
|||||||
@@ -55,12 +55,17 @@ serve(async (req: Request): Promise<Response> => {
|
|||||||
|
|
||||||
const { data: integrationSettings } = await supabase
|
const { data: integrationSettings } = await supabase
|
||||||
.from("platform_settings")
|
.from("platform_settings")
|
||||||
.select("integration_n8n_base_url")
|
.select("integration_n8n_base_url, integration_n8n_test_mode")
|
||||||
.single();
|
.single();
|
||||||
|
|
||||||
if (integrationSettings?.integration_n8n_base_url) {
|
if (integrationSettings?.integration_n8n_base_url) {
|
||||||
// Call n8n webhook if configured
|
// Check if we're in test mode (controlled by the integration_n8n_test_mode setting)
|
||||||
const n8nUrl = `${integrationSettings.integration_n8n_base_url}/webhook/create-meet`;
|
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 {
|
try {
|
||||||
const n8nResponse = await fetch(n8nUrl, {
|
const n8nResponse = await fetch(n8nUrl, {
|
||||||
@@ -77,6 +82,7 @@ serve(async (req: Request): Promise<Response> => {
|
|||||||
notes: body.notes,
|
notes: body.notes,
|
||||||
calendar_id: calendarId,
|
calendar_id: calendarId,
|
||||||
brand_name: brandName,
|
brand_name: brandName,
|
||||||
|
test_mode: isTestMode, // Add test_mode flag for n8n to use
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -69,7 +69,63 @@ serve(async (req: Request): Promise<Response> => {
|
|||||||
if (!profile?.email) continue;
|
if (!profile?.email) continue;
|
||||||
|
|
||||||
// Call send-notification function
|
// 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: {
|
body: {
|
||||||
template_key: "consultation_reminder",
|
template_key: "consultation_reminder",
|
||||||
recipient_email: profile.email,
|
recipient_email: profile.email,
|
||||||
|
|||||||
@@ -127,22 +127,39 @@ serve(async (req: Request): Promise<Response> => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send email if template is active and SMTP is configured
|
// Send email if template is active and Mailketing is configured
|
||||||
if (template?.is_active && smtpSettings?.smtp_host && profile?.email) {
|
if (template?.is_active && smtpSettings?.api_token && profile?.email) {
|
||||||
// Replace shortcodes in email body
|
try {
|
||||||
let emailBody = template.email_body_html || "";
|
// Replace shortcodes in email body using master template system
|
||||||
let emailSubject = template.email_subject || "Reminder Konsultasi";
|
let emailBody = template.email_body_html || "";
|
||||||
|
let emailSubject = template.email_subject || "Reminder Konsultasi";
|
||||||
|
|
||||||
Object.entries(payload).forEach(([key, value]) => {
|
Object.entries(payload).forEach(([key, value]) => {
|
||||||
const regex = new RegExp(`\\{${key}\\}`, "g");
|
const regex = new RegExp(`\\{${key}\\}`, "g");
|
||||||
emailBody = emailBody.replace(regex, String(value));
|
emailBody = emailBody.replace(regex, String(value));
|
||||||
emailSubject = emailSubject.replace(regex, String(value));
|
emailSubject = emailSubject.replace(regex, String(value));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Here you would send the actual email
|
// Send via send-email-v2 (Mailketing API)
|
||||||
// For now, log that we would send it
|
const { error: emailError } = await supabase.functions.invoke("send-email-v2", {
|
||||||
console.log("Would send reminder email to:", profile.email);
|
body: {
|
||||||
console.log("Subject:", emailSubject);
|
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({
|
results.push({
|
||||||
|
|||||||
99
supabase/functions/send-email-v2/index.ts
Normal file
99
supabase/functions/send-email-v2/index.ts
Normal 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" } }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
143
test-edge-functions.sh
Executable file
143
test-edge-functions.sh
Executable file
@@ -0,0 +1,143 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Test script for Supabase Edge Functions
|
||||||
|
# Make sure to set: export SUPABASE_ACCESS_TOKEN=your_service_role_key
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
SUPABASE_URL="https://lovable.backoffice.biz.id"
|
||||||
|
SERVICE_ROLE_KEY="eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJzdXBhYmFzZSIsImlhdCI6MTc2NjAzNzEyMCwiZXhwIjo0OTIxNzEwNzIwLCJyb2xlIjoic2VydmljZV9yb2xlIn0.t6D9VwaukYGq4c_VbW1bkd3ZkKgldpCKRR13nN14XXc"
|
||||||
|
|
||||||
|
if [ -z "$SERVICE_ROLE_KEY" ]; then
|
||||||
|
echo "❌ Error: SUPABASE_ACCESS_TOKEN environment variable not set"
|
||||||
|
echo "Run: export SUPABASE_ACCESS_TOKEN=your_service_role_key"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "🚀 Testing Supabase Edge Functions"
|
||||||
|
echo "=================================="
|
||||||
|
echo "URL: $SUPABASE_URL"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Colors for output
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# Function to test API
|
||||||
|
test_function() {
|
||||||
|
local func_name=$1
|
||||||
|
local payload=$2
|
||||||
|
local expected_status=${3:-200}
|
||||||
|
|
||||||
|
echo -e "${YELLOW}Testing: $func_name${NC}"
|
||||||
|
echo "Payload: $payload"
|
||||||
|
|
||||||
|
response=$(curl -s -w "%{http_code}" \
|
||||||
|
-X POST "$SUPABASE_URL/rest/v1/functions" \
|
||||||
|
-H "Authorization: Bearer $SERVICE_ROLE_KEY" \
|
||||||
|
-H "apikey: $SERVICE_ROLE_KEY" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{
|
||||||
|
\"name\": \"$func_name\",
|
||||||
|
\"verify_jwt\": false
|
||||||
|
}")
|
||||||
|
|
||||||
|
# Extract status code (last 3 characters)
|
||||||
|
status_code="${response: -3}"
|
||||||
|
# Extract response body (everything except last 3 characters)
|
||||||
|
response_body="${response%???}"
|
||||||
|
|
||||||
|
if [ "$status_code" = "200" ] || [ "$status_code" = "201" ]; then
|
||||||
|
echo -e "${GREEN}✅ Success: $func_name${NC}"
|
||||||
|
echo "Response: $response_body"
|
||||||
|
else
|
||||||
|
echo -e "${RED}❌ Error: $func_name (Status: $status_code)${NC}"
|
||||||
|
echo "Response: $response_body"
|
||||||
|
fi
|
||||||
|
echo "----------------------------------------"
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
# Test function invocation
|
||||||
|
test_invoke_function() {
|
||||||
|
local func_name=$1
|
||||||
|
local payload=$2
|
||||||
|
|
||||||
|
echo -e "${YELLOW}Invoking: $func_name${NC}"
|
||||||
|
|
||||||
|
response=$(curl -s -w "%{http_code}" \
|
||||||
|
-X POST "$SUPABASE_URL/functions/v1/$func_name" \
|
||||||
|
-H "Authorization: Bearer $SERVICE_ROLE_KEY" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "$payload")
|
||||||
|
|
||||||
|
# Extract status code (last 3 characters)
|
||||||
|
status_code="${response: -3}"
|
||||||
|
# Extract response body (everything except last 3 characters)
|
||||||
|
response_body="${response%???}"
|
||||||
|
|
||||||
|
if [ "$status_code" = "200" ]; then
|
||||||
|
echo -e "${GREEN}✅ Function executed successfully${NC}"
|
||||||
|
echo "Response: $response_body"
|
||||||
|
else
|
||||||
|
echo -e "${RED}❌ Function invocation failed (Status: $status_code)${NC}"
|
||||||
|
echo "Response: $response_body"
|
||||||
|
fi
|
||||||
|
echo "----------------------------------------"
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
# 1. Test creating/updating functions
|
||||||
|
echo "📋 Testing Function Creation/Update"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
test_function "send-email-v2"
|
||||||
|
test_function "create-meet-link"
|
||||||
|
test_function "send-consultation-reminder"
|
||||||
|
test_function "daily-reminders"
|
||||||
|
test_function "pakasir-webhook"
|
||||||
|
|
||||||
|
# 2. Test function invocations
|
||||||
|
echo "🔧 Testing Function Invocations"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Test send-email-v2 (will fail without proper Mailketing token, but should reach the function)
|
||||||
|
test_invoke_function "send-email-v2" '{
|
||||||
|
"to": "dwinx.ramz@gmail.com",
|
||||||
|
"api_token": "b4d1beb302282f411d196146f6806eab",
|
||||||
|
"from_name": "Test Hub",
|
||||||
|
"from_email": "with@dwindi.com",
|
||||||
|
"subject": "Test Email",
|
||||||
|
"html_body": "<h1>Test Email</h1><p>This is a test from send-email-v2</p>"
|
||||||
|
}'
|
||||||
|
|
||||||
|
# Test create-meet-link (will fail without proper n8n webhook, but should reach the function)
|
||||||
|
# Note: Will use webhook path based on integration_n8n_test_mode setting
|
||||||
|
test_invoke_function "create-meet-link" '{
|
||||||
|
"slot_id": "test-slot-123",
|
||||||
|
"date": "2025-12-23",
|
||||||
|
"start_time": "14:00:00",
|
||||||
|
"end_time": "15:00:00",
|
||||||
|
"client_name": "Test Client",
|
||||||
|
"client_email": "dewe.pw@gmail.com",
|
||||||
|
"topic": "Test Topic",
|
||||||
|
"notes": "Test notes"
|
||||||
|
}'
|
||||||
|
|
||||||
|
# Test daily-reminders (should work with database queries)
|
||||||
|
test_invoke_function "daily-reminders" '{}'
|
||||||
|
|
||||||
|
echo "✨ Testing Complete!"
|
||||||
|
echo ""
|
||||||
|
echo "📝 Notes:"
|
||||||
|
echo "- send-email-v2: Needs valid Mailketing API token to actually send emails"
|
||||||
|
echo "- create-meet-link: Needs n8n webhook configured in platform_settings"
|
||||||
|
echo " - Will use /webhook-test/ if integration_n8n_test_mode is enabled"
|
||||||
|
echo " - Will use /webhook/ if integration_n8n_test_mode is disabled"
|
||||||
|
echo "- Functions are deployed and accessible via the functions/v1/ endpoint"
|
||||||
|
echo ""
|
||||||
|
echo "🌐 Function URLs:"
|
||||||
|
echo "- https://lovable.backoffice.biz.id/functions/v1/send-email-v2"
|
||||||
|
echo "- https://lovable.backoffice.biz.id/functions/v1/create-meet-link"
|
||||||
|
echo "- https://lovable.backoffice.biz.id/functions/v1/daily-reminders"
|
||||||
Reference in New Issue
Block a user