learning_ai_notes/backend/src/modules/notes/routes.ts

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;
});
}