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,159 @@
|
||||
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",
|
||||
};
|
||||
|
||||
function json(body: unknown, status = 200) {
|
||||
return new Response(JSON.stringify(body), {
|
||||
status,
|
||||
headers: { ...corsHeaders, "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
|
||||
Deno.serve(async (req) => {
|
||||
if (req.method === "OPTIONS") return new Response("ok", { headers: corsHeaders });
|
||||
|
||||
try {
|
||||
const { code, email, password, full_name } = await req.json();
|
||||
if (!code || !email || !password) {
|
||||
return json({ error: "Missing required fields" }, 400);
|
||||
}
|
||||
if (String(password).length < 8) {
|
||||
return json({ error: "Password must be at least 8 characters" }, 400);
|
||||
}
|
||||
|
||||
const admin = createClient(
|
||||
Deno.env.get("SUPABASE_URL")!,
|
||||
Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!,
|
||||
{ auth: { autoRefreshToken: false, persistSession: false } }
|
||||
);
|
||||
|
||||
// Validate code
|
||||
const { data: codeRow, error: codeErr } = await admin
|
||||
.from("signup_codes")
|
||||
.select("*")
|
||||
.eq("code", String(code).trim())
|
||||
.maybeSingle();
|
||||
|
||||
if (codeErr || !codeRow) return json({ error: "Invalid sign-up code" }, 404);
|
||||
if (codeRow.redeemed_at) return json({ error: "This code has already been used" }, 410);
|
||||
if (codeRow.expires_at && new Date(codeRow.expires_at) < new Date()) {
|
||||
return json({ error: "This code has expired" }, 410);
|
||||
}
|
||||
|
||||
const normalizedEmail = String(email).trim().toLowerCase();
|
||||
|
||||
// Create the auth user, auto-confirmed. If the email already exists,
|
||||
// update the existing user's password and reuse their account so the
|
||||
// signup code can still link them to the owner/rental records.
|
||||
let userId: string | null = null;
|
||||
const { data: created, error: createErr } = await admin.auth.admin.createUser({
|
||||
email: normalizedEmail,
|
||||
password: String(password),
|
||||
email_confirm: true,
|
||||
user_metadata: { full_name: full_name || null },
|
||||
});
|
||||
|
||||
if (created?.user) {
|
||||
userId = created.user.id;
|
||||
} else {
|
||||
const msg = (createErr?.message || "").toLowerCase();
|
||||
const code = (createErr as any)?.code || (createErr as any)?.error_code;
|
||||
const isDuplicate =
|
||||
code === "email_exists" ||
|
||||
msg.includes("already been registered") ||
|
||||
msg.includes("already registered") ||
|
||||
msg.includes("already exists");
|
||||
|
||||
if (!isDuplicate) {
|
||||
return json({ error: createErr?.message || "Failed to create account" }, 400);
|
||||
}
|
||||
|
||||
// Find the existing user by email and reset their password so they can sign in
|
||||
let existingId: string | null = null;
|
||||
let page = 1;
|
||||
while (page <= 20 && !existingId) {
|
||||
const { data: list, error: listErr } = await admin.auth.admin.listUsers({ page, perPage: 200 });
|
||||
if (listErr) break;
|
||||
const match = list?.users?.find((u) => (u.email || "").toLowerCase() === normalizedEmail);
|
||||
if (match) existingId = match.id;
|
||||
if (!list?.users || list.users.length < 200) break;
|
||||
page++;
|
||||
}
|
||||
|
||||
if (!existingId) {
|
||||
return json({ error: "An account with this email already exists. Please sign in instead." }, 409);
|
||||
}
|
||||
|
||||
const { error: updErr } = await admin.auth.admin.updateUserById(existingId, {
|
||||
password: String(password),
|
||||
email_confirm: true,
|
||||
user_metadata: { full_name: full_name || null },
|
||||
});
|
||||
if (updErr) return json({ error: updErr.message }, 400);
|
||||
userId = existingId;
|
||||
}
|
||||
|
||||
if (!userId) {
|
||||
return json({ error: "Failed to resolve user account" }, 500);
|
||||
}
|
||||
|
||||
// Replace default 'homeowner' role from handle_new_user trigger if a different role was assigned
|
||||
if (codeRow.role && codeRow.role !== "homeowner") {
|
||||
await admin.from("user_roles").delete().eq("user_id", userId).eq("role", "homeowner");
|
||||
await admin.from("user_roles").insert({ user_id: userId, role: codeRow.role });
|
||||
}
|
||||
|
||||
// Optional owner pin: link the new auth user to the owner record
|
||||
if (codeRow.owner_id) {
|
||||
await admin
|
||||
.from("owners")
|
||||
.update({ user_id: userId })
|
||||
.eq("id", codeRow.owner_id);
|
||||
}
|
||||
|
||||
// Link RV/Boat lot rentals so the renter/owner portal can find them.
|
||||
// Strategy:
|
||||
// 1. If the code pinned a specific rental_id, link that one.
|
||||
// 2. Otherwise, backfill any active rentals that match the pinned owner_id
|
||||
// or whose renter_email matches the new account.
|
||||
const isRvRole = codeRow.role === "rv_boat_lot" || codeRow.role === "rv_renter" || codeRow.role === "rv_owner";
|
||||
if (isRvRole) {
|
||||
const rentalUpdate: Record<string, unknown> = { user_id: userId };
|
||||
if (codeRow.role === "rv_owner") rentalUpdate.is_owner = true;
|
||||
|
||||
if (codeRow.rental_id) {
|
||||
await admin
|
||||
.from("rv_boat_lot_rentals")
|
||||
.update(rentalUpdate)
|
||||
.eq("id", codeRow.rental_id);
|
||||
} else {
|
||||
// Match by owner_id (if pinned) OR renter_email
|
||||
const orParts: string[] = [];
|
||||
if (codeRow.owner_id) orParts.push(`owner_id.eq.${codeRow.owner_id}`);
|
||||
orParts.push(`renter_email.eq.${normalizedEmail}`);
|
||||
await admin
|
||||
.from("rv_boat_lot_rentals")
|
||||
.update(rentalUpdate)
|
||||
.eq("status", "active")
|
||||
.or(orParts.join(","));
|
||||
}
|
||||
}
|
||||
|
||||
// Mark code redeemed
|
||||
await admin
|
||||
.from("signup_codes")
|
||||
.update({
|
||||
redeemed_at: new Date().toISOString(),
|
||||
redeemed_by_user_id: userId,
|
||||
redeemed_email: normalizedEmail,
|
||||
})
|
||||
.eq("id", codeRow.id);
|
||||
|
||||
return json({ success: true, role: codeRow.role });
|
||||
} catch (e) {
|
||||
return json({ error: (e as Error).message || "Unexpected error" }, 500);
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user