Files
veridian/server/api/topic/[topicId]/index.get.ts

61 lines
2.1 KiB
TypeScript

import { db } from '~~/server/lib/db';
import { topicEvents } from '~~/server/utils/events';
import { createTopicSnapshotStream } from '~~/server/utils/topic-snapshot-stream';
export default defineEventHandler(async (event) => {
await protectRoute(event);
const topicId = getRouterParam(event, 'topicId')!;
const userId = event.context.user!.id as string;
const isSSE = getHeader(event, 'accept')?.includes('text/event-stream') ?? false;
const readTopic = () => db.query.topics.findFirst({
where: { id: topicId, userId },
with: {
messages: {
orderBy: { createdAt: 'asc' },
with: {
parts: {
orderBy: { createdAt: 'asc' },
with: { toolCall: true },
},
attachments: { with: { file: true } },
generation: true,
},
},
},
});
if (!isSSE) {
const topic = await readTopic();
if (!topic) {
setResponseStatus(event, 404);
return { ok: false, error: 'Topic not found' };
}
topicEvents.applyLiveText(topicId, topic.messages);
return topic;
}
// Authorize before subscribing; no topic data is exposed before this check.
const ownedTopic = await db.query.topics.findFirst({
where: { id: topicId, userId },
columns: { id: true },
});
if (!ownedTopic) {
setResponseStatus(event, 404);
return { ok: false, error: 'Topic not found' };
}
const subscription = createTopicSnapshotStream(topicId, getQuery(event).snapshot);
event.node.res.once('close', subscription.close);
const stream = await subscription.open(readTopic);
if (!stream.ok) {
setResponseStatus(event, stream.error === 'read-failed' ? 500 : 404);
return { ok: false, error: stream.error };
}
setHeader(event, 'Content-Type', 'text/event-stream');
setHeader(event, 'Cache-Control', 'no-cache');
setHeader(event, 'X-Accel-Buffering', 'no');
return sendStream(event, stream.data);
});