-- Helper function: returns all association_ids the current user belongs to -- via either the owners table or the board_members table CREATE OR REPLACE FUNCTION public.get_user_association_ids() RETURNS SETOF uuid LANGUAGE sql STABLE SECURITY DEFINER SET search_path = public AS $$ SELECT DISTINCT association_id FROM ( SELECT association_id FROM public.owners WHERE user_id = auth.uid() UNION SELECT association_id FROM public.board_members WHERE user_id = auth.uid() ) sub $$; -- ============================================================ -- ASSOCIATIONS: scope homeowners/board to their own HOAs -- ============================================================ DROP POLICY IF EXISTS "Authenticated users can view associations" ON public.associations; CREATE POLICY "Staff can view all associations" ON public.associations FOR SELECT TO authenticated USING ( has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'manager'::app_role) OR has_role(auth.uid(), 'employee'::app_role) ); CREATE POLICY "Members can view own associations" ON public.associations FOR SELECT TO authenticated USING ( id IN (SELECT get_user_association_ids()) ); -- ============================================================ -- OWNERS: homeowners see only their association's owners -- ============================================================ DROP POLICY IF EXISTS "Authenticated users can view owners" ON public.owners; CREATE POLICY "Staff can view all owners" ON public.owners FOR SELECT TO authenticated USING ( has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'manager'::app_role) OR has_role(auth.uid(), 'employee'::app_role) ); CREATE POLICY "Members can view own association owners" ON public.owners FOR SELECT TO authenticated USING ( association_id IN (SELECT get_user_association_ids()) ); -- ============================================================ -- UNITS: scope to association -- ============================================================ DROP POLICY IF EXISTS "Authenticated users can view units" ON public.units; CREATE POLICY "Staff can view all units" ON public.units FOR SELECT TO authenticated USING ( has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'manager'::app_role) OR has_role(auth.uid(), 'employee'::app_role) ); CREATE POLICY "Members can view own association units" ON public.units FOR SELECT TO authenticated USING ( association_id IN (SELECT get_user_association_ids()) ); -- ============================================================ -- VIOLATIONS: scope authenticated users to their association -- ============================================================ DROP POLICY IF EXISTS "Authenticated users can view violations" ON public.violations; CREATE POLICY "Staff can view all violations" ON public.violations FOR SELECT TO authenticated USING ( has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'manager'::app_role) OR has_role(auth.uid(), 'employee'::app_role) ); CREATE POLICY "Members can view own association violations" ON public.violations FOR SELECT TO authenticated USING ( association_id IN (SELECT get_user_association_ids()) ); -- ============================================================ -- BOARD_MEMBERS: scope to association -- ============================================================ DROP POLICY IF EXISTS "Authenticated users can view board_members" ON public.board_members; CREATE POLICY "Staff can view all board_members" ON public.board_members FOR SELECT TO authenticated USING ( has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'manager'::app_role) OR has_role(auth.uid(), 'employee'::app_role) ); CREATE POLICY "Members can view own association board_members" ON public.board_members FOR SELECT TO authenticated USING ( association_id IN (SELECT get_user_association_ids()) ); -- ============================================================ -- BOARD_VOTES: scope to association -- ============================================================ DROP POLICY IF EXISTS "Authenticated users can view board_votes" ON public.board_votes; CREATE POLICY "Members can view own association board_votes" ON public.board_votes FOR SELECT TO authenticated USING ( association_id IN (SELECT get_user_association_ids()) ); -- ============================================================ -- ASSOCIATION_FAQS: scope to association -- ============================================================ DROP POLICY IF EXISTS "Authenticated users can view association FAQs" ON public.association_faqs; DROP POLICY IF EXISTS "Authenticated users can update association FAQs" ON public.association_faqs; DROP POLICY IF EXISTS "Authenticated users can delete association FAQs" ON public.association_faqs; DROP POLICY IF EXISTS "Authenticated users can insert association FAQs" ON public.association_faqs; CREATE POLICY "Staff can manage association FAQs" ON public.association_faqs FOR ALL TO authenticated USING ( has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'manager'::app_role) OR has_role(auth.uid(), 'employee'::app_role) ) WITH CHECK ( has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'manager'::app_role) OR has_role(auth.uid(), 'employee'::app_role) ); CREATE POLICY "Members can view own association FAQs" ON public.association_faqs FOR SELECT TO authenticated USING ( association_id IN (SELECT get_user_association_ids()) ); -- ============================================================ -- CALENDAR_EVENTS: add read access for members (staff already has ALL) -- ============================================================ CREATE POLICY "Members can view own association calendar_events" ON public.calendar_events FOR SELECT TO authenticated USING ( association_id IN (SELECT get_user_association_ids()) ); -- ============================================================ -- ANNOUNCEMENTS: scope to association (announcements table has no -- association_id, so we keep the existing policy but note this) -- ============================================================ -- Announcements don't have association_id, keeping existing behavior -- ============================================================ -- Also tighten board_members DELETE/UPDATE/INSERT to staff only -- ============================================================ DROP POLICY IF EXISTS "Authenticated users can delete board_members" ON public.board_members; DROP POLICY IF EXISTS "Authenticated users can update board_members" ON public.board_members; DROP POLICY IF EXISTS "Authenticated users can insert board_members" ON public.board_members; CREATE POLICY "Staff can manage board_members" ON public.board_members FOR ALL TO authenticated USING ( has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'manager'::app_role) ) WITH CHECK ( has_role(auth.uid(), 'admin'::app_role) OR has_role(auth.uid(), 'manager'::app_role) );