fix: add rpc-backed member removal migration
This commit is contained in:
parent
da9981d297
commit
febba238ec
2 changed files with 90 additions and 7 deletions
|
|
@ -1,7 +1,64 @@
|
||||||
-- Run this in Supabase SQL Editor to allow a read-only viewer role.
|
-- Run in Supabase SQL Editor.
|
||||||
|
-- 1) Allow viewer role
|
||||||
alter table public.collection_members
|
alter table public.collection_members
|
||||||
drop constraint if exists collection_members_role_check;
|
drop constraint if exists collection_members_role_check;
|
||||||
|
|
||||||
alter table public.collection_members
|
alter table public.collection_members
|
||||||
add constraint collection_members_role_check
|
add constraint collection_members_role_check
|
||||||
check (role in ('owner', 'member', 'viewer'));
|
check (role in ('owner', 'member', 'viewer'));
|
||||||
|
|
||||||
|
-- 2) Ensure owners can delete members from their collections
|
||||||
|
-- (and users can still remove themselves for leave flow).
|
||||||
|
drop policy if exists "Owner can remove members" on public.collection_members;
|
||||||
|
|
||||||
|
create policy "Owner can remove members"
|
||||||
|
on public.collection_members
|
||||||
|
for delete
|
||||||
|
to authenticated
|
||||||
|
using (
|
||||||
|
collection_id in (
|
||||||
|
select c.id
|
||||||
|
from public.collections c
|
||||||
|
where c.owner_id = auth.uid()
|
||||||
|
)
|
||||||
|
or user_id = auth.uid()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 3) Stable, policy-safe removal RPC for app use
|
||||||
|
create or replace function public.remove_collection_member(
|
||||||
|
p_collection_id uuid,
|
||||||
|
p_member_user_id uuid
|
||||||
|
)
|
||||||
|
returns void
|
||||||
|
language plpgsql
|
||||||
|
security definer
|
||||||
|
set search_path = public
|
||||||
|
as $$
|
||||||
|
declare
|
||||||
|
v_owner_id uuid;
|
||||||
|
begin
|
||||||
|
select c.owner_id
|
||||||
|
into v_owner_id
|
||||||
|
from public.collections c
|
||||||
|
where c.id = p_collection_id;
|
||||||
|
|
||||||
|
if v_owner_id is null then
|
||||||
|
raise exception 'Collection not found.';
|
||||||
|
end if;
|
||||||
|
|
||||||
|
if v_owner_id <> auth.uid() then
|
||||||
|
raise exception 'Only the collection owner can remove members.';
|
||||||
|
end if;
|
||||||
|
|
||||||
|
if p_member_user_id = v_owner_id then
|
||||||
|
raise exception 'Collection owner cannot be removed.';
|
||||||
|
end if;
|
||||||
|
|
||||||
|
delete from public.collection_members cm
|
||||||
|
where cm.collection_id = p_collection_id
|
||||||
|
and cm.user_id = p_member_user_id
|
||||||
|
and cm.role <> 'owner';
|
||||||
|
end;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
grant execute on function public.remove_collection_member(uuid, uuid) to authenticated;
|
||||||
|
|
|
||||||
|
|
@ -386,17 +386,43 @@ class CollectionService {
|
||||||
throw Exception('Collection owner cannot be removed.');
|
throw Exception('Collection owner cannot be removed.');
|
||||||
}
|
}
|
||||||
|
|
||||||
await supabase
|
var deleted = false;
|
||||||
.from('collection_members')
|
try {
|
||||||
.delete()
|
await supabase.rpc('remove_collection_member', params: {
|
||||||
.eq('collection_id', collectionId)
|
'p_collection_id': collectionId,
|
||||||
.eq('user_id', memberUserId);
|
'p_member_user_id': memberUserId,
|
||||||
|
});
|
||||||
|
deleted = true;
|
||||||
|
} catch (e) {
|
||||||
|
final message = e.toString();
|
||||||
|
final rpcUnavailable = message.contains('remove_collection_member') &&
|
||||||
|
(message.contains('not found') ||
|
||||||
|
message.contains('does not exist') ||
|
||||||
|
message.contains('PGRST202'));
|
||||||
|
|
||||||
|
if (!rpcUnavailable) {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
|
||||||
|
await supabase
|
||||||
|
.from('collection_members')
|
||||||
|
.delete()
|
||||||
|
.eq('collection_id', collectionId)
|
||||||
|
.eq('user_id', memberUserId);
|
||||||
|
deleted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!deleted) {
|
||||||
|
throw Exception('Member removal failed.');
|
||||||
|
}
|
||||||
|
|
||||||
final membersAfter = await getMembers(collectionId);
|
final membersAfter = await getMembers(collectionId);
|
||||||
final stillExists =
|
final stillExists =
|
||||||
membersAfter.any((member) => member.userId == memberUserId);
|
membersAfter.any((member) => member.userId == memberUserId);
|
||||||
if (stillExists) {
|
if (stillExists) {
|
||||||
throw Exception('Member removal did not persist. Please check RLS delete policy.');
|
throw Exception(
|
||||||
|
'Member removal did not persist. Run VIEWER_ROLE_MIGRATION.sql to update policies/functions.',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue