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,107 @@
import { createClient } from "https://esm.sh/@supabase/supabase-js@2.99.1";
const corsHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type",
};
Deno.serve(async (req) => {
if (req.method === "OPTIONS") return new Response(null, { headers: corsHeaders });
try {
const authHeader = req.headers.get("authorization") || "";
if (!authHeader.startsWith("Bearer ")) {
return new Response(JSON.stringify({ error: "Unauthorized" }), { status: 401, headers: { ...corsHeaders, "Content-Type": "application/json" } });
}
const supabaseUrl = Deno.env.get("SUPABASE_URL")!;
const anonKey = Deno.env.get("SUPABASE_ANON_KEY")!;
const serviceRoleKey = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!;
const callerClient = createClient(supabaseUrl, anonKey, { global: { headers: { Authorization: authHeader } } });
const adminClient = createClient(supabaseUrl, serviceRoleKey);
const { data: { user }, error: userError } = await callerClient.auth.getUser();
if (userError || !user?.id || !user.email) {
return new Response(JSON.stringify({ error: "Unauthorized" }), { status: 401, headers: { ...corsHeaders, "Content-Type": "application/json" } });
}
const normalizedEmail = user.email.trim().toLowerCase();
const displayName = (user.user_metadata?.full_name as string | undefined) || normalizedEmail;
const { data: committees, error: committeeError } = await adminClient
.from("arc_committee_members")
.select("id, association_id, is_active")
.or(`user_id.eq.${user.id},email.ilike.${normalizedEmail}`);
if (committeeError) throw committeeError;
// Ensure caller has arc_member role if they're on any committee roster
if ((committees ?? []).some((c) => c.is_active)) {
const { data: existingRole } = await adminClient
.from("user_roles")
.select("role")
.eq("user_id", user.id)
.eq("role", "arc_member")
.maybeSingle();
if (!existingRole) {
await adminClient.from("user_roles").insert({ user_id: user.id, role: "arc_member" });
}
}
const { data: owners, error: ownersError } = await adminClient
.from("owners")
.select("association_id, first_name, last_name")
.eq("user_id", user.id)
.neq("status", "archived");
if (ownersError) throw ownersError;
const committeeAssociationIds = (committees ?? []).filter((c) => c.is_active).map((c) => c.association_id).filter(Boolean);
const allCommitteeAssociationIds = (committees ?? []).map((c) => c.association_id).filter(Boolean);
const ownerAssociationIds = (owners ?? []).map((o) => o.association_id).filter(Boolean);
const inactiveRosterIds = (committees ?? [])
.filter((c) => !c.is_active && ownerAssociationIds.includes(c.association_id))
.map((c) => c.id);
const missingRosterAssociationIds = ownerAssociationIds.filter((id) => !allCommitteeAssociationIds.includes(id));
if (inactiveRosterIds.length > 0) {
const { error: activateError } = await adminClient
.from("arc_committee_members")
.update({ is_active: true })
.in("id", inactiveRosterIds);
if (activateError) throw activateError;
}
if (missingRosterAssociationIds.length > 0) {
const ownerName = owners?.find((o) => o.first_name || o.last_name);
const name = ownerName ? `${ownerName.first_name || ""} ${ownerName.last_name || ""}`.trim() : displayName;
const rows = [...new Set(missingRosterAssociationIds)].map((association_id) => ({
association_id,
name,
email: normalizedEmail,
is_active: true,
role: "Member",
}));
const { error: insertError } = await adminClient.from("arc_committee_members").insert(rows);
if (insertError) throw insertError;
}
const associationIds = [...new Set([...committeeAssociationIds, ...ownerAssociationIds])];
if (!associationIds.length) {
return new Response(JSON.stringify({ applications: [], associationIds, noCommittee: true }), { headers: { ...corsHeaders, "Content-Type": "application/json" } });
}
const { data: applications, error: appError } = await adminClient
.from("arc_applications")
.select("*, associations(name), units(unit_number, address), owners(first_name, last_name, property_address)")
.in("association_id", associationIds)
.order("created_at", { ascending: false });
if (appError) throw appError;
return new Response(JSON.stringify({ applications: applications ?? [], associationIds, noCommittee: false }), {
headers: { ...corsHeaders, "Content-Type": "application/json" },
});
} catch (error) {
const message = error instanceof Error ? error.message : "Failed to load ARC reviews";
return new Response(JSON.stringify({ error: message }), { status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } });
}
});