61 lines
2.1 KiB
TypeScript
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);
|
|
});
|