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,198 @@
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",
};
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 new Response(JSON.stringify({ error: "Google OAuth credentials not configured" }), {
status: 500,
headers: { ...corsHeaders, "Content-Type": "application/json" },
});
}
const body = await req.json();
const { action, code, redirect_uri } = body;
const serviceClient = createClient(SUPABASE_URL, SERVICE_ROLE_KEY);
// Authenticate user
const authHeader = req.headers.get("Authorization");
if (!authHeader?.startsWith("Bearer ")) {
// For check_status, return not-connected instead of 401
if (action === "check_status") {
return new Response(JSON.stringify({ connected: false }), {
headers: { ...corsHeaders, "Content-Type": "application/json" },
});
}
return new Response(JSON.stringify({ error: "Unauthorized" }), {
status: 401,
headers: { ...corsHeaders, "Content-Type": "application/json" },
});
}
const token = authHeader.replace("Bearer ", "");
let userId: string | null = null;
try {
const payload = token.split(".")[1];
if (!payload) throw new Error("Missing JWT payload");
const normalized = payload.replace(/-/g, "+").replace(/_/g, "/");
const padded = normalized.padEnd(Math.ceil(normalized.length / 4) * 4, "=");
const decoded = JSON.parse(atob(padded));
userId = typeof decoded?.sub === "string" ? decoded.sub : null;
} catch (error) {
console.error("google-drive-auth token decode failed", error);
}
if (!userId) {
if (action === "check_status") {
return new Response(JSON.stringify({ connected: false }), {
headers: { ...corsHeaders, "Content-Type": "application/json" },
});
}
return new Response(JSON.stringify({ error: "Unauthorized" }), {
status: 401,
headers: { ...corsHeaders, "Content-Type": "application/json" },
});
}
// Check staff role (Google Drive is staff-only)
const { data: roles } = await serviceClient
.from("user_roles")
.select("role")
.eq("user_id", userId);
const STAFF_ROLES = ["admin", "manager", "employee", "staff"];
const isStaff = roles?.some((r: any) => STAFF_ROLES.includes(r.role));
if (!isStaff) {
if (action === "check_status") {
return new Response(JSON.stringify({ connected: false }), {
headers: { ...corsHeaders, "Content-Type": "application/json" },
});
}
return new Response(JSON.stringify({ error: "Only staff can connect Google Drive" }), {
status: 403,
headers: { ...corsHeaders, "Content-Type": "application/json" },
});
}
if (action === "get_auth_url") {
const scopes = [
"https://www.googleapis.com/auth/drive",
"https://www.googleapis.com/auth/calendar.readonly",
].join(" ");
const params = new URLSearchParams({
client_id: GOOGLE_CLIENT_ID,
redirect_uri: redirect_uri,
response_type: "code",
scope: scopes,
access_type: "offline",
prompt: "consent",
state: "drive_connect",
});
return new Response(JSON.stringify({ url: `https://accounts.google.com/o/oauth2/v2/auth?${params}` }), {
headers: { ...corsHeaders, "Content-Type": "application/json" },
});
}
if (action === "exchange_code") {
if (!code || !redirect_uri) {
return new Response(JSON.stringify({ error: "Missing code or redirect_uri" }), {
status: 400,
headers: { ...corsHeaders, "Content-Type": "application/json" },
});
}
const tokenRes = await fetch("https://oauth2.googleapis.com/token", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
client_id: GOOGLE_CLIENT_ID,
client_secret: GOOGLE_CLIENT_SECRET,
code,
grant_type: "authorization_code",
redirect_uri,
}),
});
const tokenData = await tokenRes.json();
if (tokenData.error) {
return new Response(JSON.stringify({ error: tokenData.error_description || tokenData.error }), {
status: 400,
headers: { ...corsHeaders, "Content-Type": "application/json" },
});
}
const expiresAt = new Date(Date.now() + tokenData.expires_in * 1000).toISOString();
const { error: upsertError } = await serviceClient
.from("google_drive_tokens")
.upsert({
user_id: userId,
access_token: tokenData.access_token,
refresh_token: tokenData.refresh_token,
token_expires_at: expiresAt,
}, { onConflict: "user_id" });
if (upsertError) {
return new Response(JSON.stringify({ error: "Failed to store tokens" }), {
status: 500,
headers: { ...corsHeaders, "Content-Type": "application/json" },
});
}
return new Response(JSON.stringify({ success: true }), {
headers: { ...corsHeaders, "Content-Type": "application/json" },
});
}
if (action === "check_status") {
const { data: tokenRow } = await serviceClient
.from("google_drive_tokens")
.select("id, token_expires_at")
.eq("user_id", userId)
.maybeSingle();
return new Response(JSON.stringify({ connected: !!tokenRow }), {
headers: { ...corsHeaders, "Content-Type": "application/json" },
});
}
if (action === "disconnect") {
await serviceClient
.from("google_drive_tokens")
.delete()
.eq("user_id", userId);
return new Response(JSON.stringify({ success: true }), {
headers: { ...corsHeaders, "Content-Type": "application/json" },
});
}
return new Response(JSON.stringify({ error: "Unknown action" }), {
status: 400,
headers: { ...corsHeaders, "Content-Type": "application/json" },
});
} catch (err) {
console.error("google-drive-auth error:", err);
return new Response(JSON.stringify({ error: err.message }), {
status: 500,
headers: { ...corsHeaders, "Content-Type": "application/json" },
});
}
});