This commit is contained in:
2026-04-18 23:11:35 +02:00
commit f7cfd2a86e
196 changed files with 35538 additions and 0 deletions
+96
View File
@@ -0,0 +1,96 @@
import type { AppSupabaseClient } from '../supabase/client.js';
import type { MemberRole } from '../supabase/types.js';
async function currentUserId(client: AppSupabaseClient): Promise<string> {
const { data, error } = await client.auth.getUser();
if (error) throw error;
if (!data.user) throw new Error('not authenticated');
return data.user.id;
}
export interface CreateGroupParams {
client: AppSupabaseClient;
name: string;
memberUserIds: string[];
}
// Client-side 3-step create: conversation → self as admin → peers as members.
// On any peer-insert failure the conversation still survives (partial group is
// still usable, admin can retry). On self-insert failure we delete the empty
// conversation to avoid orphans.
export async function createGroup(params: CreateGroupParams): Promise<string> {
const myId = await currentUserId(params.client);
const trimmed = params.name.trim();
if (trimmed.length === 0) throw new Error('group name required');
// Generate the uuid client-side so we don't need a post-insert SELECT on
// conversations — the SELECT policy requires membership, which only exists
// AFTER we insert the creator's member row in step 2.
const conversationId = crypto.randomUUID();
const { error: cErr } = await params.client
.from('conversations')
.insert({
id: conversationId,
type: 'group',
name: trimmed,
created_by: myId,
} as never);
if (cErr) throw cErr;
const { error: sErr } = await params.client.from('conversation_members').insert({
conversation_id: conversationId,
user_id: myId,
role: 'admin',
accepted: true,
} as never);
if (sErr) {
await params.client.from('conversations').delete().eq('id', conversationId);
throw sErr;
}
const peerIds = Array.from(new Set(params.memberUserIds)).filter((id) => id !== myId);
if (peerIds.length > 0) {
const rows = peerIds.map((user_id) => ({
conversation_id: conversationId,
user_id,
role: 'member' as MemberRole,
accepted: true,
}));
const { error: mErr } = await params.client
.from('conversation_members')
.insert(rows as never);
if (mErr) throw mErr;
}
return conversationId;
}
// Add an accepted friend to a group. RLS allows the insert only when the
// caller is admin/mod of the conversation AND is friends with `userId`.
export async function addGroupMember(
client: AppSupabaseClient,
conversationId: string,
userId: string,
): Promise<void> {
const { error } = await client.from('conversation_members').insert({
conversation_id: conversationId,
user_id: userId,
role: 'member',
accepted: true,
} as never);
if (error) throw error;
}
export async function leaveGroup(
client: AppSupabaseClient,
conversationId: string,
): Promise<void> {
const myId = await currentUserId(client);
const { error } = await client
.from('conversation_members')
.delete()
.eq('conversation_id', conversationId)
.eq('user_id', myId);
if (error) throw error;
}