Private
Public Access
1
0

Phase 5: Real-time updates via Socket.IO

Wire Socket.IO broadcast from all write API routes so board changes
propagate instantly to other connected clients. Add session-based auth
on socket connections, in-memory presence tracking, and a presence UI
in the board header showing who else is viewing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Catherine Renelle
2026-02-26 00:03:13 -05:00
parent 095d9756a8
commit e61c549ddb
23 changed files with 408 additions and 120 deletions
@@ -5,6 +5,7 @@ import { requireAuth } from '$lib/server/guards.js';
import { canViewBoard } from '$lib/server/permissions.js';
import { readFile, unlink } from 'fs/promises';
import { json } from '@sveltejs/kit';
import { broadcast } from '$lib/server/realtime.js';
export const GET: RequestHandler = async ({ params, locals }) => {
const tenant = locals.tenant;
@@ -48,12 +49,12 @@ export const GET: RequestHandler = async ({ params, locals }) => {
});
};
export const DELETE: RequestHandler = async ({ params, locals }) => {
export const DELETE: RequestHandler = async ({ params, request, locals }) => {
const tenant = locals.tenant;
if (!tenant) throw error(400, 'Unknown tenant');
requireAuth(locals.user);
await withTenant(tenant.id, async (tx) => {
const deleted = await withTenant(tenant.id, async (tx) => {
const att = await tx.attachment.findFirst({
where: { id: params.attachmentId },
include: {
@@ -61,7 +62,7 @@ export const DELETE: RequestHandler = async ({ params, locals }) => {
include: {
column: {
include: {
board: { select: { tenantId: true, members: true } }
board: { select: { id: true, tenantId: true, members: true } }
}
}
}
@@ -84,7 +85,12 @@ export const DELETE: RequestHandler = async ({ params, locals }) => {
} catch {
// File may already be gone
}
return { boardId: att.card.column.board.id, cardId: att.cardId, columnId: att.card.columnId };
});
const socketId = request.headers.get('X-Socket-ID');
broadcast(deleted.boardId, 'card:updated', { cardId: deleted.cardId, columnId: deleted.columnId, socketId });
return json({ ok: true });
};