150 lines
4.6 KiB
TypeScript
150 lines
4.6 KiB
TypeScript
import type { FastifyApp } from '@bytelyst/fastify-core';
|
|
import { BadRequestError, NotFoundError } from '../../lib/errors.js';
|
|
import { extractAuth } from '../../lib/auth.js';
|
|
import { PRODUCT_ID } from '../../lib/product-config.js';
|
|
import * as repo from './repository.js';
|
|
import { CreateNoteSchema, ListNotesQuerySchema, UpdateNoteSchema, type NoteDoc } from './types.js';
|
|
|
|
type RouteApp = Omit<FastifyApp, 'setReadyState' | 'isReadyState'>;
|
|
|
|
export async function noteRoutes(app: RouteApp) {
|
|
app.get('/notes/search', async req => {
|
|
const auth = await extractAuth(req);
|
|
const parsed = ListNotesQuerySchema.safeParse(req.query);
|
|
if (!parsed.success) {
|
|
throw new BadRequestError(parsed.error.issues.map((issue: { message: string }) => issue.message).join('; '));
|
|
}
|
|
|
|
const result = await repo.listNotes(auth.sub, PRODUCT_ID, parsed.data);
|
|
return {
|
|
query: parsed.data.search ?? null,
|
|
...result,
|
|
limit: parsed.data.limit,
|
|
offset: parsed.data.offset,
|
|
};
|
|
});
|
|
|
|
app.get('/notes', async req => {
|
|
const auth = await extractAuth(req);
|
|
const parsed = ListNotesQuerySchema.safeParse(req.query);
|
|
if (!parsed.success) {
|
|
throw new BadRequestError(parsed.error.issues.map((issue: { message: string }) => issue.message).join('; '));
|
|
}
|
|
|
|
const result = await repo.listNotes(auth.sub, PRODUCT_ID, parsed.data);
|
|
return { ...result, limit: parsed.data.limit, offset: parsed.data.offset };
|
|
});
|
|
|
|
app.get('/notes/:id', async req => {
|
|
const auth = await extractAuth(req);
|
|
const { id } = req.params as { id: string };
|
|
const workspaceId = (req.query as { workspaceId?: string }).workspaceId;
|
|
|
|
if (!workspaceId) {
|
|
throw new BadRequestError('workspaceId is required');
|
|
}
|
|
|
|
const note = await repo.getNote(id, workspaceId);
|
|
if (!note || note.userId !== auth.sub || note.productId !== PRODUCT_ID) {
|
|
throw new NotFoundError('Note not found');
|
|
}
|
|
|
|
return note;
|
|
});
|
|
|
|
app.post('/notes', async (req, reply) => {
|
|
const auth = await extractAuth(req);
|
|
const parsed = CreateNoteSchema.safeParse(req.body);
|
|
if (!parsed.success) {
|
|
throw new BadRequestError(
|
|
parsed.error.issues.map((issue: { message: string }) => issue.message).join('; ')
|
|
);
|
|
}
|
|
|
|
const now = new Date().toISOString();
|
|
const doc: NoteDoc = {
|
|
id: parsed.data.id,
|
|
productId: PRODUCT_ID,
|
|
workspaceId: parsed.data.workspaceId,
|
|
userId: auth.sub,
|
|
title: parsed.data.title,
|
|
body: parsed.data.body,
|
|
status: 'draft',
|
|
tags: parsed.data.tags,
|
|
links: parsed.data.links,
|
|
sourceType: parsed.data.sourceType,
|
|
sourceUri: parsed.data.sourceUri,
|
|
createdAt: now,
|
|
updatedAt: now,
|
|
createdBy: auth.sub,
|
|
updatedBy: auth.sub,
|
|
agentId: parsed.data.agentId,
|
|
};
|
|
|
|
const created = await repo.createNote(doc);
|
|
reply.code(201);
|
|
return created;
|
|
});
|
|
|
|
app.patch('/notes/:id', async req => {
|
|
const auth = await extractAuth(req);
|
|
const { id } = req.params as { id: string };
|
|
const workspaceId = (req.query as { workspaceId?: string }).workspaceId;
|
|
|
|
if (!workspaceId) {
|
|
throw new BadRequestError('workspaceId is required');
|
|
}
|
|
|
|
const parsed = UpdateNoteSchema.safeParse(req.body);
|
|
if (!parsed.success) {
|
|
throw new BadRequestError(
|
|
parsed.error.issues.map((issue: { message: string }) => issue.message).join('; ')
|
|
);
|
|
}
|
|
|
|
const existing = await repo.getNote(id, workspaceId);
|
|
if (!existing || existing.userId !== auth.sub || existing.productId !== PRODUCT_ID) {
|
|
throw new NotFoundError('Note not found');
|
|
}
|
|
|
|
const updated = await repo.updateNote(id, workspaceId, {
|
|
...parsed.data,
|
|
updatedAt: new Date().toISOString(),
|
|
updatedBy: auth.sub,
|
|
});
|
|
|
|
if (!updated) {
|
|
throw new NotFoundError('Note not found');
|
|
}
|
|
|
|
return updated;
|
|
});
|
|
|
|
app.post('/notes/:id/archive', async req => {
|
|
const auth = await extractAuth(req);
|
|
const { id } = req.params as { id: string };
|
|
const workspaceId = (req.body as { workspaceId?: string } | undefined)?.workspaceId;
|
|
|
|
if (!workspaceId) {
|
|
throw new BadRequestError('workspaceId is required');
|
|
}
|
|
|
|
const existing = await repo.getNote(id, workspaceId);
|
|
if (!existing || existing.userId !== auth.sub || existing.productId !== PRODUCT_ID) {
|
|
throw new NotFoundError('Note not found');
|
|
}
|
|
|
|
const updated = await repo.updateNote(id, workspaceId, {
|
|
status: 'archived',
|
|
updatedAt: new Date().toISOString(),
|
|
updatedBy: auth.sub,
|
|
});
|
|
|
|
if (!updated) {
|
|
throw new NotFoundError('Note not found');
|
|
}
|
|
|
|
return updated;
|
|
});
|
|
}
|