import { db } from '@/lib/db' import type { Prisma } from '@afis/db' /** * Ereignisse an alle offenen Ansichten schicken. * * Über `pg_notify`, nicht über einen prozessinternen Verteiler: dann stimmt es * auch, wenn später ein zweiter Web-Prozess dazukommt, und es braucht keinen * zusätzlichen Dienst. Siehe docs/06-chat-und-realtime.md. * * **In der Nutzlast steht nie ein Inhalt**, nur ein Thema, eine Art und eine * Kennung. Wer das Ereignis bekommt, entscheidet der Strom-Endpunkt, und was * er daraus zu sehen bekommt, entscheidet die Sichtbarkeitsprüfung. Ein Text * in der Nutzlast wäre an jedem Filter vorbei. */ export const STREAM_CHANNEL = 'afis_stream' export type EventKind = | 'chat.new' | 'chat.edit' | 'chat.withdrawn' | 'chat.reaction' | 'chat.adopted' | 'entry.new' | 'entry.updated' | 'case.update' | 'attachment.scanned' /** Wer das Ereignis angeht. */ export type Topic = `case:${string}` | `channel:${string}` | `user:${string}` export function caseTopic(caseId: string): Topic { return `case:${caseId}` } export function channelTopic(channelId: string): Topic { return `channel:${channelId}` } export function userTopic(userId: string): Topic { return `user:${userId}` } export type StreamEvent = { t: Topic; k: EventKind; i: string } export async function publish( topic: Topic, kind: EventKind, id: string, tx?: Prisma.TransactionClient, ): Promise { const payload = JSON.stringify({ t: topic, k: kind, i: id } satisfies StreamEvent) try { const client = tx ?? db await client.$executeRaw`SELECT pg_notify(${STREAM_CHANNEL}, ${payload})` } catch (error) { // Ein verlorenes Ereignis kostet eine Aktualisierung von Hand, mehr nicht. // Es ist kein Grund, die Änderung scheitern zu lassen, die es meldet. console.error('[realtime] Ereignis nicht zugestellt', error) } } /** Kurzform für die Prüfkette der Dateien, die nur ein Thema kennt. */ export async function notifyAttachmentScanned( attachmentId: string, topic: Topic, ): Promise { await publish(topic, 'attachment.scanned', attachmentId) }