feat(P4B.T2): WhiteboardPayload + shared whiteboards CRUD wrappers + tests
Adds WhiteboardPayload wire-format variant to MessagePayload/ParsedMessagePayload, creates the whiteboards.ts CRUD wrapper (createWhiteboard, listWhiteboardStrokes, insertWhiteboardStroke, clearWhiteboardStrokes), writes 5 unit tests (38/38 pass), re-exports from chat/index.ts, and registers conversation_whiteboards + whiteboard_strokes in packages/db-types so typecheck passes cleanly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
import type { Json } from '@chat-app/db-types';
|
||||
|
||||
import type { AppSupabaseClient } from '../supabase/client';
|
||||
|
||||
export interface Whiteboard {
|
||||
id: string;
|
||||
conversationId: string;
|
||||
ownerUserId: string;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export interface WhiteboardStroke {
|
||||
id: string;
|
||||
whiteboardId: string;
|
||||
authorUserId: string;
|
||||
strokeJson: unknown;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export async function createWhiteboard(
|
||||
client: AppSupabaseClient,
|
||||
conversationId: string,
|
||||
): Promise<Whiteboard> {
|
||||
const { data: session } = await client.auth.getUser();
|
||||
if (!session.user) throw new Error('not authenticated');
|
||||
const { data, error } = await client
|
||||
.from('conversation_whiteboards')
|
||||
.insert({
|
||||
conversation_id: conversationId,
|
||||
owner_user_id: session.user.id,
|
||||
})
|
||||
.select('id, conversation_id, owner_user_id, created_at')
|
||||
.single();
|
||||
if (error) throw error;
|
||||
return {
|
||||
id: data.id,
|
||||
conversationId: data.conversation_id,
|
||||
ownerUserId: data.owner_user_id,
|
||||
createdAt: data.created_at,
|
||||
};
|
||||
}
|
||||
|
||||
export async function listWhiteboardStrokes(
|
||||
client: AppSupabaseClient,
|
||||
whiteboardId: string,
|
||||
): Promise<WhiteboardStroke[]> {
|
||||
const { data, error } = await client
|
||||
.from('whiteboard_strokes')
|
||||
.select('id, whiteboard_id, author_user_id, stroke_json, created_at')
|
||||
.eq('whiteboard_id', whiteboardId)
|
||||
.order('created_at', { ascending: true });
|
||||
if (error) throw error;
|
||||
return data.map((row) => ({
|
||||
id: row.id,
|
||||
whiteboardId: row.whiteboard_id,
|
||||
authorUserId: row.author_user_id,
|
||||
strokeJson: row.stroke_json,
|
||||
createdAt: row.created_at,
|
||||
}));
|
||||
}
|
||||
|
||||
export async function insertWhiteboardStroke(
|
||||
client: AppSupabaseClient,
|
||||
params: { whiteboardId: string; strokeJson: unknown },
|
||||
): Promise<WhiteboardStroke> {
|
||||
const { data: session } = await client.auth.getUser();
|
||||
if (!session.user) throw new Error('not authenticated');
|
||||
const { data, error } = await client
|
||||
.from('whiteboard_strokes')
|
||||
.insert({
|
||||
whiteboard_id: params.whiteboardId,
|
||||
author_user_id: session.user.id,
|
||||
stroke_json: params.strokeJson as unknown as Json,
|
||||
})
|
||||
.select('id, whiteboard_id, author_user_id, stroke_json, created_at')
|
||||
.single();
|
||||
if (error) throw error;
|
||||
return {
|
||||
id: data.id,
|
||||
whiteboardId: data.whiteboard_id,
|
||||
authorUserId: data.author_user_id,
|
||||
strokeJson: data.stroke_json,
|
||||
createdAt: data.created_at,
|
||||
};
|
||||
}
|
||||
|
||||
export async function clearWhiteboardStrokes(
|
||||
client: AppSupabaseClient,
|
||||
whiteboardId: string,
|
||||
): Promise<void> {
|
||||
const { error } = await client
|
||||
.from('whiteboard_strokes')
|
||||
.delete()
|
||||
.eq('whiteboard_id', whiteboardId);
|
||||
if (error) throw error;
|
||||
}
|
||||
Reference in New Issue
Block a user