-- ============================================================ -- Migration: Add external role and project_members table -- Run this in Supabase → SQL Editor → Run -- ============================================================ -- 1. Update profiles.role check constraint to include 'external' do $$ declare cname text; begin select constraint_name into cname from information_schema.table_constraints where table_schema = 'public' and table_name = 'profiles' and constraint_type = 'CHECK' and constraint_name ilike '%role%'; if cname is not null then execute 'alter table public.profiles drop constraint ' || quote_ident(cname); end if; end; $$; alter table public.profiles add constraint profiles_role_check check (role in ('team', 'client', 'external')); -- 2. project_members table create table public.project_members ( id uuid default gen_random_uuid() primary key, project_id uuid references public.projects(id) on delete cascade not null, profile_id uuid references public.profiles(id) on delete cascade not null, created_at timestamptz default now() not null, unique(project_id, profile_id) ); alter table public.project_members enable row level security; -- 3. Helper function create or replace function public.is_external() returns boolean as $$ select get_my_role() = 'external'; $$ language sql security definer stable; -- 4. RLS: project_members create policy "Team all project_members" on public.project_members for all using (get_my_role() = 'team'); create policy "External reads own memberships" on public.project_members for select using (profile_id = auth.uid()); -- 5. RLS: projects (external reads assigned only) create policy "External reads assigned projects" on public.projects for select using ( get_my_role() = 'external' and id in (select project_id from public.project_members where profile_id = auth.uid()) ); -- 6. RLS: tasks (external reads + updates assigned projects) create policy "External reads assigned tasks" on public.tasks for select using ( get_my_role() = 'external' and project_id in (select project_id from public.project_members where profile_id = auth.uid()) ); create policy "External updates assigned tasks" on public.tasks for update using ( get_my_role() = 'external' and project_id in (select project_id from public.project_members where profile_id = auth.uid()) ); -- 7. RLS: submissions create policy "External reads assigned submissions" on public.submissions for select using ( get_my_role() = 'external' and task_id in ( select t.id from public.tasks t join public.project_members pm on pm.project_id = t.project_id where pm.profile_id = auth.uid() ) ); create policy "External inserts submissions" on public.submissions for insert with check ( get_my_role() = 'external' and submitted_by = auth.uid() ); -- 8. RLS: submission_files create policy "External reads assigned submission_files" on public.submission_files for select using ( get_my_role() = 'external' and submission_id in ( select s.id from public.submissions s join public.tasks t on t.id = s.task_id join public.project_members pm on pm.project_id = t.project_id where pm.profile_id = auth.uid() ) ); create policy "External inserts submission_files" on public.submission_files for insert with check (get_my_role() = 'external'); -- 9. RLS: deliveries (read only) create policy "External reads assigned deliveries" on public.deliveries for select using ( get_my_role() = 'external' and submission_id in ( select s.id from public.submissions s join public.tasks t on t.id = s.task_id join public.project_members pm on pm.project_id = t.project_id where pm.profile_id = auth.uid() ) ); -- 10. RLS: delivery_files (read only) create policy "External reads assigned delivery_files" on public.delivery_files for select using ( get_my_role() = 'external' and delivery_id in ( select d.id from public.deliveries d join public.submissions s on s.id = d.submission_id join public.tasks t on t.id = s.task_id join public.project_members pm on pm.project_id = t.project_id where pm.profile_id = auth.uid() ) ); -- 11. RLS: profiles (external reads own profile only — already covered by existing policy) -- "Own profile select" policy already handles this with: id = auth.uid() -- No additional policy needed.