fix(db): add atomic collection creation RPC path with migration script
This commit is contained in:
parent
38de6d8b86
commit
fb96d0bfb7
3 changed files with 11586 additions and 8 deletions
11498
DB_README.md
11498
DB_README.md
File diff suppressed because one or more lines are too long
48
db/atomic_create_collection_with_owner.sql
Normal file
48
db/atomic_create_collection_with_owner.sql
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
-- Atomic collection creation + owner membership insert
|
||||
-- Run this in Supabase SQL Editor.
|
||||
|
||||
create or replace function public.create_collection_with_owner(
|
||||
p_name text,
|
||||
p_description text default null
|
||||
)
|
||||
returns public.collections
|
||||
language plpgsql
|
||||
security definer
|
||||
set search_path = public
|
||||
as $$
|
||||
declare
|
||||
v_user_id uuid := auth.uid();
|
||||
v_collection public.collections;
|
||||
begin
|
||||
if v_user_id is null then
|
||||
raise exception 'Not authenticated';
|
||||
end if;
|
||||
|
||||
insert into public.collections (
|
||||
name,
|
||||
owner_id,
|
||||
description
|
||||
)
|
||||
values (
|
||||
p_name,
|
||||
v_user_id,
|
||||
nullif(trim(p_description), '')
|
||||
)
|
||||
returning * into v_collection;
|
||||
|
||||
insert into public.collection_members (
|
||||
collection_id,
|
||||
user_id,
|
||||
role
|
||||
)
|
||||
values (
|
||||
v_collection.id,
|
||||
v_user_id,
|
||||
'owner'
|
||||
);
|
||||
|
||||
return v_collection;
|
||||
end;
|
||||
$$;
|
||||
|
||||
grant execute on function public.create_collection_with_owner(text, text) to authenticated;
|
||||
|
|
@ -263,13 +263,57 @@ class CollectionService {
|
|||
}) async {
|
||||
final userId = _requireUserId();
|
||||
|
||||
final trimmedDescription = description?.trim();
|
||||
final normalizedDescription =
|
||||
(trimmedDescription != null && trimmedDescription.isNotEmpty)
|
||||
? trimmedDescription
|
||||
: null;
|
||||
|
||||
try {
|
||||
final rpcResult = await supabase.rpc(
|
||||
'create_collection_with_owner',
|
||||
params: {
|
||||
'p_name': name,
|
||||
'p_description': normalizedDescription,
|
||||
},
|
||||
);
|
||||
|
||||
final row = rpcResult is List
|
||||
? (rpcResult.isNotEmpty
|
||||
? rpcResult.first as Map<String, dynamic>
|
||||
: <String, dynamic>{})
|
||||
: rpcResult as Map<String, dynamic>;
|
||||
|
||||
if (row.isNotEmpty) {
|
||||
return Collection(
|
||||
id: row['id'] as String,
|
||||
name: row['name'] as String,
|
||||
description: row['description'] as String?,
|
||||
ownerId: row['owner_id'] as String,
|
||||
createdAt: DateTime.parse(row['created_at'] as String),
|
||||
role: 'owner',
|
||||
itemCount: 0,
|
||||
memberCount: 1,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
final message = e.toString();
|
||||
final rpcUnavailable = message.contains('create_collection_with_owner') &&
|
||||
(message.contains('not found') ||
|
||||
message.contains('does not exist') ||
|
||||
message.contains('PGRST202'));
|
||||
if (!rpcUnavailable) {
|
||||
rethrow;
|
||||
}
|
||||
debugPrint('CollectionService.create RPC unavailable, using fallback: $e');
|
||||
}
|
||||
|
||||
final row = await supabase
|
||||
.from('collections')
|
||||
.insert({
|
||||
'name': name,
|
||||
'owner_id': userId,
|
||||
if (description != null && description.isNotEmpty)
|
||||
'description': description,
|
||||
'description': normalizedDescription,
|
||||
})
|
||||
.select()
|
||||
.single();
|
||||
|
|
|
|||
Loading…
Reference in a new issue