fix(notes): export route order and lexical snippet ellipsis
Register GET /notes/export before GET /notes/:id so the path is not captured as an id. Compute search snippets from stripped plain text so the trailing ellipsis matches visible length, not raw HTML length. Made-with: Cursor
This commit is contained in:
parent
a697752d15
commit
5e3e374d3a
@ -40,14 +40,17 @@ const ChatBodySchema = z.object({
|
||||
});
|
||||
|
||||
function toLexicalHits(items: NoteDoc[]) {
|
||||
return items.map((n) => ({
|
||||
noteId: n.id,
|
||||
workspaceId: n.workspaceId,
|
||||
title: n.title,
|
||||
score: 1,
|
||||
matchKind: 'lexical' as const,
|
||||
snippet: n.body.replace(/<[^>]*>/g, ' ').replace(/\s+/g, ' ').trim().slice(0, 180) + (n.body.length > 180 ? '…' : ''),
|
||||
}));
|
||||
return items.map((n) => {
|
||||
const plain = n.body.replace(/<[^>]*>/g, ' ').replace(/\s+/g, ' ').trim();
|
||||
return {
|
||||
noteId: n.id,
|
||||
workspaceId: n.workspaceId,
|
||||
title: n.title,
|
||||
score: 1,
|
||||
matchKind: 'lexical' as const,
|
||||
snippet: plain.slice(0, 180) + (plain.length > 180 ? '…' : ''),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export async function noteRoutes(app: RouteApp) {
|
||||
@ -81,6 +84,33 @@ export async function noteRoutes(app: RouteApp) {
|
||||
return { ...result, limit: parsed.data.limit, offset: parsed.data.offset };
|
||||
});
|
||||
|
||||
// Must be registered before GET /notes/:id or "export" is captured as :id.
|
||||
app.get('/notes/export', async (req, reply) => {
|
||||
const auth = await extractAuth(req);
|
||||
const query = req.query as { format?: string; workspaceId?: string };
|
||||
const format = query.format ?? 'json';
|
||||
|
||||
if (format !== 'json' && format !== 'markdown') {
|
||||
throw new BadRequestError('format must be json or markdown');
|
||||
}
|
||||
|
||||
const listQuery = { workspaceId: query.workspaceId, limit: 100, offset: 0 };
|
||||
const result = await repo.listNotes(auth.sub, PRODUCT_ID, listQuery);
|
||||
|
||||
if (format === 'markdown') {
|
||||
const md = result.items
|
||||
.map((n: NoteDoc) => `# ${n.title}\n\n${n.body}\n\n---\n`)
|
||||
.join('\n');
|
||||
reply.header('Content-Type', 'text/markdown');
|
||||
reply.header('Content-Disposition', 'attachment; filename="notes-export.md"');
|
||||
return md;
|
||||
}
|
||||
|
||||
reply.header('Content-Type', 'application/json');
|
||||
reply.header('Content-Disposition', 'attachment; filename="notes-export.json"');
|
||||
return { exportedAt: new Date().toISOString(), notes: result.items };
|
||||
});
|
||||
|
||||
app.get('/notes/:id', async req => {
|
||||
const auth = await extractAuth(req);
|
||||
const { id } = req.params as { id: string };
|
||||
@ -470,32 +500,6 @@ export async function noteRoutes(app: RouteApp) {
|
||||
return { answer, citations };
|
||||
});
|
||||
|
||||
app.get('/notes/export', async (req, reply) => {
|
||||
const auth = await extractAuth(req);
|
||||
const query = req.query as { format?: string; workspaceId?: string };
|
||||
const format = query.format ?? 'json';
|
||||
|
||||
if (format !== 'json' && format !== 'markdown') {
|
||||
throw new BadRequestError('format must be json or markdown');
|
||||
}
|
||||
|
||||
const listQuery = { workspaceId: query.workspaceId, limit: 100, offset: 0 };
|
||||
const result = await repo.listNotes(auth.sub, PRODUCT_ID, listQuery);
|
||||
|
||||
if (format === 'markdown') {
|
||||
const md = result.items
|
||||
.map((n: NoteDoc) => `# ${n.title}\n\n${n.body}\n\n---\n`)
|
||||
.join('\n');
|
||||
reply.header('Content-Type', 'text/markdown');
|
||||
reply.header('Content-Disposition', 'attachment; filename="notes-export.md"');
|
||||
return md;
|
||||
}
|
||||
|
||||
reply.header('Content-Type', 'application/json');
|
||||
reply.header('Content-Disposition', 'attachment; filename="notes-export.json"');
|
||||
return { exportedAt: new Date().toISOString(), notes: result.items };
|
||||
});
|
||||
|
||||
app.delete('/notes/:id', async (req, reply) => {
|
||||
const auth = await requireWriter(req);
|
||||
const { id } = req.params as { id: string };
|
||||
|
||||
Loading…
Reference in New Issue
Block a user