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 GoogleOAuthConfig { client_id: string; client_secret: string; refresh_token: string; access_token?: string; expires_at?: number; } interface UpdateEventRequest { session_id: string; date: string; start_time: string; end_time: string; } // Function to get access token from refresh token (OAuth2) async function getGoogleAccessToken(oauthConfig: GoogleOAuthConfig): Promise<{ access_token: string; expires_in: number }> { try { console.log("Attempting to exchange refresh token for access token..."); const tokenRequest = { client_id: oauthConfig.client_id, client_secret: oauthConfig.client_secret, refresh_token: oauthConfig.refresh_token, grant_type: "refresh_token", }; const response = await fetch("https://oauth2.googleapis.com/token", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams(tokenRequest), }); if (!response.ok) { const errorText = await response.text(); console.error("Token response error:", errorText); throw new Error(`Token exchange failed: ${errorText}`); } const data = await response.json(); if (!data.access_token) { throw new Error("No access token in response"); } console.log("Successfully obtained access token"); return { access_token: data.access_token, expires_in: data.expires_in || 3600 }; } catch (error: any) { console.error("Error getting Google access token:", error); throw error; } } serve(async (req: Request): Promise => { if (req.method === "OPTIONS") { return new Response(null, { headers: corsHeaders }); } const logs: string[] = []; const log = (msg: string) => { console.log(msg); logs.push(msg); }; try { const supabaseUrl = Deno.env.get("SUPABASE_URL")!; const supabaseServiceKey = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!; const supabase = createClient(supabaseUrl, supabaseServiceKey); const body: UpdateEventRequest = await req.json(); const { session_id, date, start_time, end_time } = body; log(`Updating calendar event for session: ${session_id}`); log(`New time: ${date} ${start_time} - ${end_time}`); // Get session details including calendar_event_id const { data: session, error: sessionError } = await supabase .from("consulting_sessions") .select("id, calendar_event_id, topic_category, profiles(name, email), notes, meet_link") .eq("id", session_id) .single(); if (sessionError || !session) { log(`Session not found: ${sessionError?.message}`); return new Response( JSON.stringify({ success: false, message: "Session not found", logs: logs }), { status: 404, headers: { ...corsHeaders, "Content-Type": "application/json" } } ); } if (!session.calendar_event_id) { log("No calendar event ID found for this session"); return new Response( JSON.stringify({ success: false, message: "No calendar event linked to this session", logs: logs }), { status: 400, headers: { ...corsHeaders, "Content-Type": "application/json" } } ); } // Get platform settings log("Fetching platform settings..."); const { data: settings, error: settingsError } = await supabase .from("platform_settings") .select("integration_google_calendar_id, google_oauth_config") .single(); if (settingsError || !settings) { log(`Error fetching settings: ${settingsError?.message}`); return new Response( JSON.stringify({ success: false, message: "Error fetching settings", logs: logs }), { status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } } ); } const calendarId = settings.integration_google_calendar_id; if (!calendarId) { log("Calendar ID not configured"); return new Response( JSON.stringify({ success: false, message: "Google Calendar ID not configured", logs: logs }), { status: 400, headers: { ...corsHeaders, "Content-Type": "application/json" } } ); } // Get OAuth config const oauthConfigJson = settings.google_oauth_config; if (!oauthConfigJson) { log("OAuth config not found"); return new Response( JSON.stringify({ success: false, message: "Google OAuth Config not configured", logs: logs }), { status: 400, headers: { ...corsHeaders, "Content-Type": "application/json" } } ); } let oauthConfig: GoogleOAuthConfig; try { oauthConfig = JSON.parse(oauthConfigJson); } catch (error: any) { log(`Failed to parse OAuth config: ${error.message}`); return new Response( JSON.stringify({ success: false, message: "Invalid OAuth config format", logs: logs }), { status: 400, headers: { ...corsHeaders, "Content-Type": "application/json" } } ); } // Get access token let accessToken: string; const now = Math.floor(Date.now() / 1000); if (oauthConfig.access_token && oauthConfig.expires_at && oauthConfig.expires_at > now + 60) { log(`Using cached access_token`); accessToken = oauthConfig.access_token; } else { log("Refreshing access token..."); const tokenData = await getGoogleAccessToken(oauthConfig); accessToken = tokenData.access_token; const newExpiresAt = now + tokenData.expires_in; const updatedConfig = { ...oauthConfig, access_token: accessToken, expires_at: newExpiresAt }; await supabase .from("platform_settings") .update({ google_oauth_config: JSON.stringify(updatedConfig) }) .eq("id", settings.id); log("Updated cached access_token in database"); } // Build event data for update const startDate = new Date(`${date}T${start_time}+07:00`); const endDate = new Date(`${date}T${end_time}+07:00`); log(`Event time: ${startDate.toISOString()} to ${endDate.toISOString()}`); const eventData = { start: { dateTime: startDate.toISOString(), timeZone: "Asia/Jakarta", }, end: { dateTime: endDate.toISOString(), timeZone: "Asia/Jakarta", }, }; log(`Updating event ${session.calendar_event_id} in calendar ${calendarId}`); // Update event via Google Calendar API const calendarResponse = await fetch( `https://www.googleapis.com/calendar/v3/calendars/${encodeURIComponent(calendarId)}/events/${encodeURIComponent(session.calendar_event_id)}`, { method: "PATCH", headers: { "Authorization": `Bearer ${accessToken}`, "Content-Type": "application/json", }, body: JSON.stringify(eventData), } ); log(`Calendar API response status: ${calendarResponse.status}`); if (!calendarResponse.ok) { const errorText = await calendarResponse.text(); log(`Google Calendar API error: ${errorText}`); return new Response( JSON.stringify({ success: false, message: "Failed to update event in Google Calendar: " + errorText, logs: logs }), { status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } } ); } const eventDataResult = await calendarResponse.json(); log(`Event updated successfully: ${eventDataResult.id}`); return new Response( JSON.stringify({ success: true, event_id: eventDataResult.id, html_link: eventDataResult.htmlLink, logs: logs }), { headers: { ...corsHeaders, "Content-Type": "application/json" } } ); } catch (error: any) { log(`Error updating calendar event: ${error.message}`); log(`Stack: ${error.stack}`); return new Response( JSON.stringify({ success: false, message: error.message || "Internal server error", logs: logs }), { status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } } ); } });