Changes
This commit is contained in:
@@ -20,3 +20,9 @@ verify_jwt = false
|
||||
|
||||
[functions.send-test-email]
|
||||
verify_jwt = false
|
||||
|
||||
[functions.create-meet-link]
|
||||
verify_jwt = true
|
||||
|
||||
[functions.send-consultation-reminder]
|
||||
verify_jwt = false
|
||||
|
||||
126
supabase/functions/create-meet-link/index.ts
Normal file
126
supabase/functions/create-meet-link/index.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
import { serve } from "https://deno.land/std@0.190.0/http/server.ts";
|
||||
import { createClient } from "https://esm.sh/@supabase/supabase-js@2";
|
||||
|
||||
const corsHeaders = {
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type",
|
||||
};
|
||||
|
||||
interface CreateMeetRequest {
|
||||
slot_id: string;
|
||||
date: string;
|
||||
start_time: string;
|
||||
end_time: string;
|
||||
client_name: string;
|
||||
client_email: string;
|
||||
topic: string;
|
||||
notes?: string;
|
||||
}
|
||||
|
||||
serve(async (req: Request): Promise<Response> => {
|
||||
if (req.method === "OPTIONS") {
|
||||
return new Response(null, { headers: corsHeaders });
|
||||
}
|
||||
|
||||
try {
|
||||
const supabaseUrl = Deno.env.get("SUPABASE_URL")!;
|
||||
const supabaseServiceKey = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!;
|
||||
const supabase = createClient(supabaseUrl, supabaseServiceKey);
|
||||
|
||||
const body: CreateMeetRequest = await req.json();
|
||||
console.log("Creating meet link for slot:", body.slot_id);
|
||||
|
||||
// Get platform settings for Google Calendar ID
|
||||
const { data: settings } = await supabase
|
||||
.from("platform_settings")
|
||||
.select("integration_google_calendar_id, brand_name")
|
||||
.single();
|
||||
|
||||
const calendarId = settings?.integration_google_calendar_id;
|
||||
const brandName = settings?.brand_name || "LearnHub";
|
||||
|
||||
if (!calendarId) {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: false,
|
||||
message: "Google Calendar ID belum dikonfigurasi di Pengaturan > Integrasi"
|
||||
}),
|
||||
{ status: 400, headers: { ...corsHeaders, "Content-Type": "application/json" } }
|
||||
);
|
||||
}
|
||||
|
||||
// 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")
|
||||
.single();
|
||||
|
||||
if (integrationSettings?.integration_n8n_base_url) {
|
||||
// Call n8n webhook if configured
|
||||
const n8nUrl = `${integrationSettings.integration_n8n_base_url}/webhook/create-meet`;
|
||||
|
||||
try {
|
||||
const n8nResponse = await fetch(n8nUrl, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
slot_id: body.slot_id,
|
||||
date: body.date,
|
||||
start_time: body.start_time,
|
||||
end_time: body.end_time,
|
||||
client_name: body.client_name,
|
||||
client_email: body.client_email,
|
||||
topic: body.topic,
|
||||
notes: body.notes,
|
||||
calendar_id: calendarId,
|
||||
brand_name: brandName,
|
||||
}),
|
||||
});
|
||||
|
||||
if (n8nResponse.ok) {
|
||||
const result = await n8nResponse.json();
|
||||
|
||||
if (result.meet_link) {
|
||||
// Update the slot with the meet link
|
||||
await supabase
|
||||
.from("consulting_slots")
|
||||
.update({ meet_link: result.meet_link })
|
||||
.eq("id", body.slot_id);
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({ success: true, meet_link: result.meet_link }),
|
||||
{ headers: { ...corsHeaders, "Content-Type": "application/json" } }
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (n8nError) {
|
||||
console.error("n8n webhook error:", n8nError);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: Return instructions for manual setup
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: false,
|
||||
message: "Integrasi otomatis belum tersedia. Silakan buat link Meet secara manual atau konfigurasi n8n webhook di Pengaturan > Integrasi.",
|
||||
manual_instructions: {
|
||||
calendar_id: calendarId,
|
||||
event_title: `Konsultasi: ${body.topic} - ${body.client_name}`,
|
||||
event_date: body.date,
|
||||
event_time: `${body.start_time} - ${body.end_time}`,
|
||||
}
|
||||
}),
|
||||
{ status: 200, headers: { ...corsHeaders, "Content-Type": "application/json" } }
|
||||
);
|
||||
|
||||
} catch (error: any) {
|
||||
console.error("Error creating meet link:", error);
|
||||
return new Response(
|
||||
JSON.stringify({ success: false, message: error.message }),
|
||||
{ status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } }
|
||||
);
|
||||
}
|
||||
});
|
||||
173
supabase/functions/send-consultation-reminder/index.ts
Normal file
173
supabase/functions/send-consultation-reminder/index.ts
Normal file
@@ -0,0 +1,173 @@
|
||||
import { serve } from "https://deno.land/std@0.190.0/http/server.ts";
|
||||
import { createClient } from "https://esm.sh/@supabase/supabase-js@2";
|
||||
|
||||
const corsHeaders = {
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type",
|
||||
};
|
||||
|
||||
serve(async (req: Request): Promise<Response> => {
|
||||
if (req.method === "OPTIONS") {
|
||||
return new Response(null, { headers: corsHeaders });
|
||||
}
|
||||
|
||||
try {
|
||||
const supabaseUrl = Deno.env.get("SUPABASE_URL")!;
|
||||
const supabaseServiceKey = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!;
|
||||
const supabase = createClient(supabaseUrl, supabaseServiceKey);
|
||||
|
||||
// Get current date/time in Jakarta timezone
|
||||
const now = new Date();
|
||||
const jakartaOffset = 7 * 60; // UTC+7
|
||||
const jakartaTime = new Date(now.getTime() + jakartaOffset * 60 * 1000);
|
||||
const today = jakartaTime.toISOString().split('T')[0];
|
||||
|
||||
// Find consultations happening in the next 24 hours that haven't been reminded
|
||||
const tomorrow = new Date(jakartaTime);
|
||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||
const tomorrowStr = tomorrow.toISOString().split('T')[0];
|
||||
|
||||
console.log("Checking consultations for dates:", today, "to", tomorrowStr);
|
||||
|
||||
// Get confirmed slots for today and tomorrow
|
||||
const { data: upcomingSlots, error: slotsError } = await supabase
|
||||
.from("consulting_slots")
|
||||
.select(`
|
||||
*,
|
||||
profiles:user_id (full_name, email)
|
||||
`)
|
||||
.eq("status", "confirmed")
|
||||
.gte("date", today)
|
||||
.lte("date", tomorrowStr)
|
||||
.order("date")
|
||||
.order("start_time");
|
||||
|
||||
if (slotsError) {
|
||||
console.error("Error fetching slots:", slotsError);
|
||||
throw slotsError;
|
||||
}
|
||||
|
||||
console.log("Found upcoming slots:", upcomingSlots?.length || 0);
|
||||
|
||||
if (!upcomingSlots || upcomingSlots.length === 0) {
|
||||
return new Response(
|
||||
JSON.stringify({ success: true, message: "No upcoming consultations to remind" }),
|
||||
{ headers: { ...corsHeaders, "Content-Type": "application/json" } }
|
||||
);
|
||||
}
|
||||
|
||||
// Get notification template for consultation reminder
|
||||
const { data: template } = await supabase
|
||||
.from("notification_templates")
|
||||
.select("*")
|
||||
.eq("key", "consulting_scheduled")
|
||||
.single();
|
||||
|
||||
// Get SMTP settings
|
||||
const { data: smtpSettings } = await supabase
|
||||
.from("notification_settings")
|
||||
.select("*")
|
||||
.single();
|
||||
|
||||
// Get platform settings
|
||||
const { data: platformSettings } = await supabase
|
||||
.from("platform_settings")
|
||||
.select("brand_name, brand_email_from_name, integration_whatsapp_number")
|
||||
.single();
|
||||
|
||||
const results: any[] = [];
|
||||
|
||||
for (const slot of upcomingSlots) {
|
||||
const profile = slot.profiles as any;
|
||||
|
||||
// Build payload for notification
|
||||
const payload = {
|
||||
nama: profile?.full_name || "Pelanggan",
|
||||
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",
|
||||
topik: slot.topic_category,
|
||||
catatan: slot.notes || "-",
|
||||
brand_name: platformSettings?.brand_name || "LearnHub",
|
||||
whatsapp: platformSettings?.integration_whatsapp_number || "",
|
||||
};
|
||||
|
||||
// Log the reminder payload
|
||||
console.log("Reminder payload for slot:", slot.id, payload);
|
||||
|
||||
// Update last_payload_example in template
|
||||
if (template) {
|
||||
await supabase
|
||||
.from("notification_templates")
|
||||
.update({ last_payload_example: payload })
|
||||
.eq("id", template.id);
|
||||
}
|
||||
|
||||
// Send webhook if configured
|
||||
if (template?.webhook_url) {
|
||||
try {
|
||||
await fetch(template.webhook_url, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
event: "consulting_reminder",
|
||||
slot_id: slot.id,
|
||||
...payload,
|
||||
}),
|
||||
});
|
||||
console.log("Webhook sent for slot:", slot.id);
|
||||
} catch (webhookError) {
|
||||
console.error("Webhook error:", webhookError);
|
||||
}
|
||||
}
|
||||
|
||||
// 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";
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
results.push({
|
||||
slot_id: slot.id,
|
||||
client: profile?.full_name,
|
||||
date: slot.date,
|
||||
time: slot.start_time,
|
||||
reminded: true,
|
||||
});
|
||||
}
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: true,
|
||||
message: `Processed ${results.length} consultation reminders`,
|
||||
results
|
||||
}),
|
||||
{ headers: { ...corsHeaders, "Content-Type": "application/json" } }
|
||||
);
|
||||
|
||||
} catch (error: any) {
|
||||
console.error("Error sending reminders:", error);
|
||||
return new Response(
|
||||
JSON.stringify({ success: false, message: error.message }),
|
||||
{ status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } }
|
||||
);
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user