-- 75-cabinet-memberships-rls-definer.sql
-- Fix cabinet_memberships/file_cabinets/files recursive RLS.
--
-- The original cabinet membership policies joined file_cabinets directly, while
-- the file_cabinets/files membership policies joined cabinet_memberships
-- directly. Under an authenticated as-user SELECT this creates an RLS cycle and
-- PostgreSQL raises 42P17 (infinite recursion detected in policy for relation
-- "cabinet_memberships").
--
-- SECURITY DEFINER helpers evaluate the ownership/membership checks as the
-- function owner (bypassing the inner RLS checks) while still keying the result
-- to auth.uid(), matching the class-management helper pattern in 71.
--
-- The existing public tables are owned by supabase_admin; Supabase migrations
-- run as that table owner on dev/prod so the SECURITY DEFINER helpers are owned
-- by a role that bypasses the inner RLS checks.

create or replace function public.is_cabinet_owner(p_cabinet uuid)
returns boolean
language sql
stable
security definer
set search_path = public
as $$
  select exists (
    select 1
    from public.file_cabinets c
    where c.id = p_cabinet
      and c.user_id = auth.uid()
  )
$$;

create or replace function public.is_cabinet_member(p_cabinet uuid)
returns boolean
language sql
stable
security definer
set search_path = public
as $$
  select exists (
    select 1
    from public.cabinet_memberships m
    where m.cabinet_id = p_cabinet
      and m.profile_id = auth.uid()
  )
$$;

-- Keep function execution available to the roles used by RLS policies. The
-- functions disclose only a boolean about the caller's own auth.uid() state.
revoke all on function public.is_cabinet_owner(uuid) from public, anon;
revoke all on function public.is_cabinet_member(uuid) from public, anon;
grant execute on function public.is_cabinet_owner(uuid) to authenticated, service_role;
grant execute on function public.is_cabinet_member(uuid) to authenticated, service_role;

-- Re-declare cabinet_memberships policies without direct file_cabinets subqueries.
drop policy if exists cm_read_self_or_owner on public.cabinet_memberships;
create policy cm_read_self_or_owner on public.cabinet_memberships
  for select to authenticated
  using (profile_id = auth.uid() or public.is_cabinet_owner(cabinet_id));

drop policy if exists cm_insert_by_owner on public.cabinet_memberships;
create policy cm_insert_by_owner on public.cabinet_memberships
  for insert to authenticated
  with check (public.is_cabinet_owner(cabinet_id));

drop policy if exists cm_update_by_owner on public.cabinet_memberships;
create policy cm_update_by_owner on public.cabinet_memberships
  for update to authenticated
  using (public.is_cabinet_owner(cabinet_id))
  with check (public.is_cabinet_owner(cabinet_id));

drop policy if exists cm_delete_by_owner on public.cabinet_memberships;
create policy cm_delete_by_owner on public.cabinet_memberships
  for delete to authenticated
  using (public.is_cabinet_owner(cabinet_id));

-- Re-declare membership-based cabinet/file read policies without direct
-- cabinet_memberships subqueries, so selecting cabinets/files does not recurse
-- back through cabinet_memberships RLS.
drop policy if exists "User can access cabinets via membership" on public.file_cabinets;
create policy "User can access cabinets via membership" on public.file_cabinets
  for select to authenticated
  using (public.is_cabinet_member(id));

drop policy if exists "User can access files via membership" on public.files;
create policy "User can access files via membership" on public.files
  for select to authenticated
  using (public.is_cabinet_member(cabinet_id));
