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,149 @@
-- RV/Boat Lot inventory
CREATE TABLE public.rv_boat_lots (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
association_id UUID NOT NULL REFERENCES public.associations(id) ON DELETE CASCADE,
lot_number TEXT NOT NULL,
lot_type TEXT NOT NULL DEFAULT 'rv', -- 'rv' | 'boat' | 'either'
size TEXT,
monthly_rate NUMERIC(10,2),
notes TEXT,
status TEXT NOT NULL DEFAULT 'available', -- 'available' | 'occupied' | 'maintenance'
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
UNIQUE (association_id, lot_number)
);
-- Active rentals (assignments to owners)
CREATE TABLE public.rv_boat_lot_rentals (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
association_id UUID NOT NULL REFERENCES public.associations(id) ON DELETE CASCADE,
lot_id UUID NOT NULL REFERENCES public.rv_boat_lots(id) ON DELETE CASCADE,
owner_id UUID REFERENCES public.owners(id) ON DELETE SET NULL,
unit_id UUID REFERENCES public.units(id) ON DELETE SET NULL,
renter_name TEXT NOT NULL,
renter_email TEXT,
renter_phone TEXT,
vehicle_description TEXT,
start_date DATE NOT NULL DEFAULT CURRENT_DATE,
end_date DATE,
monthly_rate NUMERIC(10,2),
status TEXT NOT NULL DEFAULT 'active', -- 'active' | 'ended'
notes TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- Waitlist
CREATE TABLE public.rv_boat_lot_waitlist (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
association_id UUID NOT NULL REFERENCES public.associations(id) ON DELETE CASCADE,
position INTEGER NOT NULL,
requester_name TEXT NOT NULL,
requester_email TEXT,
requester_phone TEXT,
unit_address TEXT,
owner_id UUID REFERENCES public.owners(id) ON DELETE SET NULL,
unit_id UUID REFERENCES public.units(id) ON DELETE SET NULL,
requested_lot_type TEXT DEFAULT 'either', -- 'rv' | 'boat' | 'either'
vehicle_description TEXT,
notes TEXT,
status TEXT NOT NULL DEFAULT 'waiting', -- 'waiting' | 'fulfilled' | 'removed'
source TEXT NOT NULL DEFAULT 'manual', -- 'manual' | 'public_form'
fulfilled_at TIMESTAMPTZ,
fulfilled_lot_number TEXT,
fulfilled_by UUID REFERENCES auth.users(id) ON DELETE SET NULL,
joined_at TIMESTAMPTZ NOT NULL DEFAULT now(),
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX rv_boat_lots_assoc_idx ON public.rv_boat_lots(association_id);
CREATE INDEX rv_boat_lot_rentals_assoc_idx ON public.rv_boat_lot_rentals(association_id);
CREATE INDEX rv_boat_lot_rentals_lot_idx ON public.rv_boat_lot_rentals(lot_id);
CREATE INDEX rv_boat_lot_waitlist_assoc_pos_idx ON public.rv_boat_lot_waitlist(association_id, position) WHERE status = 'waiting';
-- Updated_at triggers
CREATE TRIGGER trg_rv_boat_lots_updated BEFORE UPDATE ON public.rv_boat_lots
FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column();
CREATE TRIGGER trg_rv_boat_lot_rentals_updated BEFORE UPDATE ON public.rv_boat_lot_rentals
FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column();
CREATE TRIGGER trg_rv_boat_lot_waitlist_updated BEFORE UPDATE ON public.rv_boat_lot_waitlist
FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column();
-- Auto-assign next position on insert (for waiting entries)
CREATE OR REPLACE FUNCTION public.assign_rv_boat_waitlist_position()
RETURNS TRIGGER LANGUAGE plpgsql SECURITY DEFINER SET search_path = public AS $$
BEGIN
IF NEW.position IS NULL OR NEW.position = 0 THEN
SELECT COALESCE(MAX(position), 0) + 1 INTO NEW.position
FROM public.rv_boat_lot_waitlist
WHERE association_id = NEW.association_id;
END IF;
RETURN NEW;
END;
$$;
CREATE TRIGGER trg_rv_boat_waitlist_position
BEFORE INSERT ON public.rv_boat_lot_waitlist
FOR EACH ROW EXECUTE FUNCTION public.assign_rv_boat_waitlist_position();
-- When a rental is created, mark lot occupied; when ended, mark available
CREATE OR REPLACE FUNCTION public.sync_rv_boat_lot_status()
RETURNS TRIGGER LANGUAGE plpgsql SECURITY DEFINER SET search_path = public AS $$
BEGIN
IF TG_OP = 'INSERT' AND NEW.status = 'active' THEN
UPDATE public.rv_boat_lots SET status = 'occupied', updated_at = now() WHERE id = NEW.lot_id;
ELSIF TG_OP = 'UPDATE' AND OLD.status <> NEW.status THEN
IF NEW.status = 'active' THEN
UPDATE public.rv_boat_lots SET status = 'occupied', updated_at = now() WHERE id = NEW.lot_id;
ELSIF NEW.status = 'ended' THEN
UPDATE public.rv_boat_lots SET status = 'available', updated_at = now() WHERE id = NEW.lot_id;
END IF;
END IF;
RETURN NEW;
END;
$$;
CREATE TRIGGER trg_sync_rv_boat_lot_status
AFTER INSERT OR UPDATE ON public.rv_boat_lot_rentals
FOR EACH ROW EXECUTE FUNCTION public.sync_rv_boat_lot_status();
-- Enable RLS
ALTER TABLE public.rv_boat_lots ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.rv_boat_lot_rentals ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.rv_boat_lot_waitlist ENABLE ROW LEVEL SECURITY;
-- Lots policies
CREATE POLICY "Staff and members can view lots" ON public.rv_boat_lots
FOR SELECT USING (public.user_belongs_to_association(auth.uid(), association_id));
CREATE POLICY "Staff can manage lots" ON public.rv_boat_lots
FOR ALL USING (public.has_role(auth.uid(), 'admin'::public.app_role) OR public.has_role(auth.uid(), 'manager'::public.app_role))
WITH CHECK (public.has_role(auth.uid(), 'admin'::public.app_role) OR public.has_role(auth.uid(), 'manager'::public.app_role));
-- Rentals policies
CREATE POLICY "Staff and members can view rentals" ON public.rv_boat_lot_rentals
FOR SELECT USING (public.user_belongs_to_association(auth.uid(), association_id));
CREATE POLICY "Staff can manage rentals" ON public.rv_boat_lot_rentals
FOR ALL USING (public.has_role(auth.uid(), 'admin'::public.app_role) OR public.has_role(auth.uid(), 'manager'::public.app_role))
WITH CHECK (public.has_role(auth.uid(), 'admin'::public.app_role) OR public.has_role(auth.uid(), 'manager'::public.app_role));
-- Waitlist policies
CREATE POLICY "Staff and members can view waitlist" ON public.rv_boat_lot_waitlist
FOR SELECT USING (public.user_belongs_to_association(auth.uid(), association_id));
CREATE POLICY "Staff can manage waitlist" ON public.rv_boat_lot_waitlist
FOR ALL USING (public.has_role(auth.uid(), 'admin'::public.app_role) OR public.has_role(auth.uid(), 'manager'::public.app_role))
WITH CHECK (public.has_role(auth.uid(), 'admin'::public.app_role) OR public.has_role(auth.uid(), 'manager'::public.app_role));
-- Public can submit waitlist entries via published associations
CREATE POLICY "Public can submit waitlist entries" ON public.rv_boat_lot_waitlist
FOR INSERT TO anon, authenticated
WITH CHECK (
source = 'public_form'
AND status = 'waiting'
AND EXISTS (
SELECT 1 FROM public.association_public_pages app
WHERE app.association_id = rv_boat_lot_waitlist.association_id
AND app.is_published = true
)
);