From bb389d6f78f3257f4250de3f665bcf42e3ffadcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20M=C3=BCllner?= Date: Thu, 5 Mar 2026 10:56:20 +0100 Subject: [PATCH] fix: correct member counts and member removal flow --- VIEWER_ROLE_MIGRATION.sql | 7 +++ lib/services/collection_service.dart | 67 +++++++++++++++++----------- 2 files changed, 49 insertions(+), 25 deletions(-) create mode 100644 VIEWER_ROLE_MIGRATION.sql diff --git a/VIEWER_ROLE_MIGRATION.sql b/VIEWER_ROLE_MIGRATION.sql new file mode 100644 index 0000000..9d000e2 --- /dev/null +++ b/VIEWER_ROLE_MIGRATION.sql @@ -0,0 +1,7 @@ +-- Run this in Supabase SQL Editor to allow a read-only viewer role. +alter table public.collection_members + drop constraint if exists collection_members_role_check; + +alter table public.collection_members + add constraint collection_members_role_check + check (role in ('owner', 'member', 'viewer')); diff --git a/lib/services/collection_service.dart b/lib/services/collection_service.dart index 361aaf8..c237e8a 100644 --- a/lib/services/collection_service.dart +++ b/lib/services/collection_service.dart @@ -115,16 +115,32 @@ class CollectionService { itemCounts[collectionId] = (itemCounts[collectionId] ?? 0) + 1; } - // Fetch all members for these collections in a single query and count them in memory. - final members = await supabase - .from('collection_members') - .select('id, collection_id') - .inFilter('collection_id', collectionIdList); - final memberCounts = {}; - for (final member in members) { - final collectionId = member['collection_id'] as String; - memberCounts[collectionId] = (memberCounts[collectionId] ?? 0) + 1; + await Future.wait(collectionIdList.map((collectionId) async { + try { + final rows = await supabase.rpc('get_collection_members', params: { + 'p_collection_id': collectionId, + }); + memberCounts[collectionId] = (rows as List).length; + } catch (_) { + // Keep fallback below when RPC fails. + } + })); + + // Fallback member count for collections where RPC did not return data. + final unresolvedIds = collectionIdList + .where((id) => !memberCounts.containsKey(id)) + .toList(growable: false); + if (unresolvedIds.isNotEmpty) { + final members = await supabase + .from('collection_members') + .select('id, collection_id') + .inFilter('collection_id', unresolvedIds); + + for (final member in members) { + final collectionId = member['collection_id'] as String; + memberCounts[collectionId] = (memberCounts[collectionId] ?? 0) + 1; + } } final collections = []; @@ -316,11 +332,22 @@ class CollectionService { throw Exception('This user is already a member of this collection.'); } - await supabase.from('collection_members').insert({ - 'collection_id': collectionId, - 'user_id': userId, - 'role': normalizedRole, - }); + try { + await supabase.from('collection_members').insert({ + 'collection_id': collectionId, + 'user_id': userId, + 'role': normalizedRole, + }); + } catch (e) { + final message = e.toString(); + if (normalizedRole == 'viewer' && + message.contains('collection_members_role_check')) { + throw Exception( + 'Viewer role is not enabled in your database yet. Please apply the viewer-role migration first.', + ); + } + rethrow; + } } /// Remove a member from a collection. @@ -343,17 +370,7 @@ class CollectionService { throw Exception('Only the collection owner can remove members.'); } - final target = await supabase - .from('collection_members') - .select('role, user_id') - .eq('user_id', memberUserId) - .eq('collection_id', collectionId) - .maybeSingle(); - - if (target == null) { - throw Exception('Member not found.'); - } - if (target['role'] == 'owner') { + if (memberUserId == collection['owner_id']) { throw Exception('Collection owner cannot be removed.'); }