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,205 @@
|
||||
-- Flag rentals as owner-occupied (owner using their own lot)
|
||||
ALTER TABLE public.rv_boat_lot_rentals
|
||||
ADD COLUMN IF NOT EXISTS is_owner BOOLEAN NOT NULL DEFAULT false;
|
||||
|
||||
-- Helpers for rv_owner role (owner_ids and association_ids the user has access to)
|
||||
CREATE OR REPLACE FUNCTION public.get_rv_owner_owner_ids(_user_id uuid DEFAULT auth.uid())
|
||||
RETURNS SETOF uuid
|
||||
LANGUAGE sql STABLE SECURITY DEFINER SET search_path = public
|
||||
AS $$
|
||||
SELECT DISTINCT r.owner_id
|
||||
FROM public.rv_boat_lot_rentals r
|
||||
WHERE r.user_id = _user_id
|
||||
AND r.is_owner = true
|
||||
AND r.owner_id IS NOT NULL
|
||||
AND r.status = 'active'
|
||||
$$;
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.get_rv_owner_association_ids(_user_id uuid DEFAULT auth.uid())
|
||||
RETURNS SETOF uuid
|
||||
LANGUAGE sql STABLE SECURITY DEFINER SET search_path = public
|
||||
AS $$
|
||||
SELECT DISTINCT r.association_id
|
||||
FROM public.rv_boat_lot_rentals r
|
||||
WHERE r.user_id = _user_id
|
||||
AND r.is_owner = true
|
||||
AND r.status = 'active'
|
||||
$$;
|
||||
|
||||
-- RV owners can read their own rental row (covers the is_owner = true rows)
|
||||
CREATE POLICY "RV owners can view their own rental"
|
||||
ON public.rv_boat_lot_rentals
|
||||
FOR SELECT
|
||||
TO authenticated
|
||||
USING (user_id = auth.uid() AND is_owner = true);
|
||||
|
||||
-- RV owners can read their lot
|
||||
CREATE POLICY "RV owners can view their lot"
|
||||
ON public.rv_boat_lots
|
||||
FOR SELECT
|
||||
TO authenticated
|
||||
USING (
|
||||
EXISTS (
|
||||
SELECT 1 FROM public.rv_boat_lot_rentals r
|
||||
WHERE r.lot_id = rv_boat_lots.id
|
||||
AND r.user_id = auth.uid()
|
||||
AND r.is_owner = true
|
||||
AND r.status = 'active'
|
||||
)
|
||||
);
|
||||
|
||||
-- RV owners can read their association
|
||||
CREATE POLICY "RV owners can view their association"
|
||||
ON public.associations
|
||||
FOR SELECT
|
||||
TO authenticated
|
||||
USING (id IN (SELECT public.get_rv_owner_association_ids(auth.uid())));
|
||||
|
||||
-- RV owners can read the linked owner ledger
|
||||
CREATE POLICY "RV owners can view linked owner ledger"
|
||||
ON public.owner_ledger_entries
|
||||
FOR SELECT
|
||||
TO authenticated
|
||||
USING (owner_id IN (SELECT public.get_rv_owner_owner_ids(auth.uid())));
|
||||
|
||||
-- RV owners can read the linked owner record
|
||||
CREATE POLICY "RV owners can view linked owner"
|
||||
ON public.owners
|
||||
FOR SELECT
|
||||
TO authenticated
|
||||
USING (id IN (SELECT public.get_rv_owner_owner_ids(auth.uid())));
|
||||
|
||||
-- Intent to Vacate
|
||||
CREATE TABLE IF NOT EXISTS public.rv_intent_to_vacate (
|
||||
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
rental_id UUID NOT NULL REFERENCES public.rv_boat_lot_rentals(id) ON DELETE CASCADE,
|
||||
association_id UUID NOT NULL REFERENCES public.associations(id) ON DELETE CASCADE,
|
||||
lot_id UUID REFERENCES public.rv_boat_lots(id) ON DELETE SET NULL,
|
||||
owner_id UUID REFERENCES public.owners(id) ON DELETE SET NULL,
|
||||
submitted_by_user_id UUID REFERENCES auth.users(id) ON DELETE SET NULL,
|
||||
vacate_date DATE NOT NULL,
|
||||
notes TEXT,
|
||||
status TEXT NOT NULL DEFAULT 'pending',
|
||||
acknowledged_at TIMESTAMP WITH TIME ZONE,
|
||||
acknowledged_by_user_id UUID REFERENCES auth.users(id) ON DELETE SET NULL,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS rv_itv_rental_idx ON public.rv_intent_to_vacate(rental_id);
|
||||
CREATE INDEX IF NOT EXISTS rv_itv_assoc_idx ON public.rv_intent_to_vacate(association_id);
|
||||
CREATE INDEX IF NOT EXISTS rv_itv_user_idx ON public.rv_intent_to_vacate(submitted_by_user_id);
|
||||
|
||||
ALTER TABLE public.rv_intent_to_vacate ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE TRIGGER update_rv_intent_to_vacate_updated_at
|
||||
BEFORE UPDATE ON public.rv_intent_to_vacate
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION public.update_updated_at_column();
|
||||
|
||||
-- RLS: RV owners manage their own intents
|
||||
CREATE POLICY "RV owners can view their intents"
|
||||
ON public.rv_intent_to_vacate
|
||||
FOR SELECT
|
||||
TO authenticated
|
||||
USING (submitted_by_user_id = auth.uid());
|
||||
|
||||
CREATE POLICY "RV owners can submit intents for their rental"
|
||||
ON public.rv_intent_to_vacate
|
||||
FOR INSERT
|
||||
TO authenticated
|
||||
WITH CHECK (
|
||||
submitted_by_user_id = auth.uid()
|
||||
AND EXISTS (
|
||||
SELECT 1 FROM public.rv_boat_lot_rentals r
|
||||
WHERE r.id = rv_intent_to_vacate.rental_id
|
||||
AND r.user_id = auth.uid()
|
||||
AND r.is_owner = true
|
||||
AND r.status = 'active'
|
||||
)
|
||||
);
|
||||
|
||||
CREATE POLICY "RV owners can cancel their pending intents"
|
||||
ON public.rv_intent_to_vacate
|
||||
FOR UPDATE
|
||||
TO authenticated
|
||||
USING (submitted_by_user_id = auth.uid() AND status = 'pending')
|
||||
WITH CHECK (submitted_by_user_id = auth.uid());
|
||||
|
||||
-- Staff can manage all
|
||||
CREATE POLICY "Staff can view all intents"
|
||||
ON public.rv_intent_to_vacate
|
||||
FOR SELECT
|
||||
TO authenticated
|
||||
USING (
|
||||
public.has_role(auth.uid(), 'admin'::public.app_role)
|
||||
OR public.has_role(auth.uid(), 'manager'::public.app_role)
|
||||
);
|
||||
|
||||
CREATE POLICY "Staff can update all intents"
|
||||
ON public.rv_intent_to_vacate
|
||||
FOR UPDATE
|
||||
TO authenticated
|
||||
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)
|
||||
);
|
||||
|
||||
CREATE POLICY "Staff can delete intents"
|
||||
ON public.rv_intent_to_vacate
|
||||
FOR DELETE
|
||||
TO authenticated
|
||||
USING (
|
||||
public.has_role(auth.uid(), 'admin'::public.app_role)
|
||||
OR public.has_role(auth.uid(), 'manager'::public.app_role)
|
||||
);
|
||||
|
||||
-- Notify staff when an Intent to Vacate is submitted
|
||||
CREATE OR REPLACE FUNCTION public.notify_staff_on_rv_intent_to_vacate()
|
||||
RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
SET search_path = public
|
||||
AS $$
|
||||
DECLARE
|
||||
staff_user record;
|
||||
v_renter text;
|
||||
v_message text;
|
||||
BEGIN
|
||||
SELECT r.renter_name INTO v_renter
|
||||
FROM public.rv_boat_lot_rentals r WHERE r.id = NEW.rental_id;
|
||||
|
||||
v_message := COALESCE(v_renter, 'An RV/Boat lot owner')
|
||||
|| ' submitted an Intent to Vacate effective '
|
||||
|| to_char(NEW.vacate_date, 'Mon DD, YYYY');
|
||||
|
||||
FOR staff_user IN
|
||||
SELECT DISTINCT user_id FROM public.user_roles
|
||||
WHERE role IN ('admin'::public.app_role, 'manager'::public.app_role)
|
||||
AND user_id IS NOT NULL
|
||||
LOOP
|
||||
INSERT INTO public.in_app_notifications
|
||||
(user_id, type, title, message, related_item_id, related_item_type, link)
|
||||
VALUES (
|
||||
staff_user.user_id,
|
||||
'rv_intent_to_vacate',
|
||||
'RV/Boat Lot — Intent to Vacate',
|
||||
v_message,
|
||||
NEW.id,
|
||||
'rv_intent_to_vacate',
|
||||
'/dashboard/rv-boat-lots'
|
||||
);
|
||||
END LOOP;
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE TRIGGER trg_notify_staff_on_rv_intent_to_vacate
|
||||
AFTER INSERT ON public.rv_intent_to_vacate
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION public.notify_staff_on_rv_intent_to_vacate();
|
||||
Reference in New Issue
Block a user