Add ACMCC app source, Supabase backend, and project config

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-01 20:19:26 -04:00
parent 313b51b412
commit 183fe0a93c
1422 changed files with 259271 additions and 0 deletions
@@ -0,0 +1,269 @@
import { createClient } from "https://esm.sh/@supabase/supabase-js@2.49.1";
const corsHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers":
"authorization, x-client-info, apikey, content-type, x-supabase-client-platform, x-supabase-client-platform-version, x-supabase-client-runtime, x-supabase-client-runtime-version",
};
function jsonResponse(payload: unknown, status = 200) {
return new Response(JSON.stringify(payload), {
status,
headers: { ...corsHeaders, "Content-Type": "application/json" },
});
}
function extractUserIdFromAuthHeader(authHeader: string | null) {
if (!authHeader?.startsWith("Bearer ")) return null;
try {
const token = authHeader.replace("Bearer ", "");
const payload = token.split(".")[1];
if (!payload) return null;
const normalized = payload.replace(/-/g, "+").replace(/_/g, "/");
const padded = normalized.padEnd(Math.ceil(normalized.length / 4) * 4, "=");
const decoded = JSON.parse(atob(padded));
return typeof decoded?.sub === "string" ? decoded.sub : null;
} catch (error) {
console.error("sync-google-calendar token decode failed", error);
return null;
}
}
function mapGoogleCalendarError(error: any) {
const reason = error?.details?.find?.((detail: any) => detail?.reason)?.reason
?? error?.errors?.[0]?.reason;
const message = error?.message || "Google Calendar request failed.";
if (reason === "ACCESS_TOKEN_SCOPE_INSUFFICIENT" || reason === "insufficientPermissions") {
return {
error: "Your Google connection needs to be refreshed for Calendar access. Disconnect Google and connect it again.",
code: "RECONNECT_REQUIRED",
reconnectRequired: true,
};
}
if (reason === "SERVICE_DISABLED" || /calendar api has not been used|calendar api.*disabled/i.test(message)) {
return {
error: "Google Calendar API is not enabled in the connected Google project.",
code: "API_DISABLED",
};
}
return {
error: message,
code: "GOOGLE_API_ERROR",
};
}
async function refreshAccessToken(serviceClient: any, userId: string, refreshToken: string, clientId: string, clientSecret: string) {
const res = await fetch("https://oauth2.googleapis.com/token", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
client_id: clientId,
client_secret: clientSecret,
refresh_token: refreshToken,
grant_type: "refresh_token",
}),
});
const data = await res.json();
if (data.error) throw new Error(data.error_description || data.error);
const expiresAt = new Date(Date.now() + data.expires_in * 1000).toISOString();
await serviceClient
.from("google_drive_tokens")
.update({
access_token: data.access_token,
token_expires_at: expiresAt,
})
.eq("user_id", userId);
return data.access_token;
}
Deno.serve(async (req) => {
if (req.method === "OPTIONS") {
return new Response(null, { headers: corsHeaders });
}
try {
const GOOGLE_CLIENT_ID = Deno.env.get("GOOGLE_CLIENT_ID");
const GOOGLE_CLIENT_SECRET = Deno.env.get("GOOGLE_CLIENT_SECRET");
const SUPABASE_URL = Deno.env.get("SUPABASE_URL")!;
const SERVICE_ROLE_KEY = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!;
if (!GOOGLE_CLIENT_ID || !GOOGLE_CLIENT_SECRET) {
return jsonResponse({
error: "Google Calendar integration is not configured on the server.",
code: "MISSING_CREDENTIALS",
});
}
const authHeader = req.headers.get("Authorization");
const userId = extractUserIdFromAuthHeader(authHeader);
if (!userId) {
return jsonResponse({ error: "Unauthorized" }, 401);
}
const serviceClient = createClient(SUPABASE_URL, SERVICE_ROLE_KEY);
const { data: roles } = await serviceClient
.from("user_roles")
.select("role")
.eq("user_id", userId);
const isAdmin = roles?.some((role: any) => role.role === "admin" || role.role === "manager");
if (!isAdmin) {
return jsonResponse({ error: "Only admins can sync Google Calendar" }, 403);
}
const { data: tokenRow } = await serviceClient
.from("google_drive_tokens")
.select("*")
.eq("user_id", userId)
.maybeSingle();
if (!tokenRow) {
return jsonResponse({
error: "Google account not connected. Connect Google first.",
code: "NOT_CONNECTED",
});
}
let accessToken = tokenRow.access_token;
const expiresAt = new Date(tokenRow.token_expires_at);
if (expiresAt <= new Date()) {
if (!tokenRow.refresh_token) {
return jsonResponse({
error: "Your Google connection expired. Disconnect Google and connect it again.",
code: "RECONNECT_REQUIRED",
reconnectRequired: true,
});
}
try {
accessToken = await refreshAccessToken(
serviceClient,
userId,
tokenRow.refresh_token,
GOOGLE_CLIENT_ID,
GOOGLE_CLIENT_SECRET,
);
} catch (error: any) {
return jsonResponse({
error: "Your Google connection could not be refreshed. Disconnect Google and connect it again.",
code: "RECONNECT_REQUIRED",
reconnectRequired: true,
details: error?.message,
});
}
}
const body = await req.json();
const { action, calendarIds } = body;
if (action === "list_calendars") {
const calRes = await fetch("https://www.googleapis.com/calendar/v3/users/me/calendarList", {
headers: { Authorization: `Bearer ${accessToken}` },
});
const calData = await calRes.json();
if (calData.error) {
return jsonResponse(mapGoogleCalendarError(calData.error));
}
return jsonResponse({ calendars: calData.items || [] });
}
if (action === "sync") {
const { data: assocs } = await serviceClient
.from("associations")
.select("id")
.eq("status", "active")
.limit(1);
if (!assocs?.length) {
return jsonResponse({ error: "No active association found" }, 400);
}
const defaultAssocId = assocs[0].id;
const calendarsToSync = calendarIds || ["primary"];
const timeMin = new Date();
timeMin.setMonth(timeMin.getMonth() - 1);
const timeMax = new Date();
timeMax.setMonth(timeMax.getMonth() + 6);
const allEvents: any[] = [];
for (const calId of calendarsToSync) {
const params = new URLSearchParams({
timeMin: timeMin.toISOString(),
timeMax: timeMax.toISOString(),
singleEvents: "true",
orderBy: "startTime",
maxResults: "250",
});
const evtRes = await fetch(
`https://www.googleapis.com/calendar/v3/calendars/${encodeURIComponent(calId)}/events?${params}`,
{ headers: { Authorization: `Bearer ${accessToken}` } },
);
const evtData = await evtRes.json();
if (evtData.error) {
console.error(`Error fetching calendar ${calId}:`, evtData.error);
return jsonResponse(mapGoogleCalendarError(evtData.error));
}
const events = (evtData.items || []).filter((event: any) => event.status !== "cancelled");
for (const event of events) {
const startDate = event.start?.dateTime || event.start?.date || null;
const endDate = event.end?.dateTime || event.end?.date || null;
if (!startDate) continue;
allEvents.push({
title: event.summary || "Untitled",
description: event.description || null,
start_date: startDate,
end_date: endDate || startDate,
location: event.location || null,
event_type: "event",
all_day: !event.start?.dateTime,
association_id: defaultAssocId,
created_by: userId,
visibility: ["admin"],
is_blocked: false,
});
}
}
if (allEvents.length > 0) {
await serviceClient
.from("calendar_events")
.delete()
.eq("created_by", userId)
.eq("event_type", "google_sync");
const rows = allEvents.map((event) => ({ ...event, event_type: "google_sync" }));
const { error: insertError } = await serviceClient.from("calendar_events").insert(rows);
if (insertError) {
console.error("Insert error:", insertError);
return jsonResponse({ error: `Failed to save events: ${insertError.message}` }, 500);
}
}
return jsonResponse({ count: allEvents.length });
}
return jsonResponse({ error: "Unknown action" }, 400);
} catch (err: any) {
console.error("sync-google-calendar error:", err);
return jsonResponse({ error: err.message }, 500);
}
});