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 = { 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); } });