mirror of
https://github.com/renee-png/acmcc.git
synced 2026-06-21 09:50:01 +00:00
Add ACMCC app source, Supabase backend, and project config
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -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" } });
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user