learning_ai_notes/backend/src/modules/saved-views/routes.ts

110 lines
3.5 KiB
TypeScript

import type { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify';
import { BadRequestError, NotFoundError } from '@bytelyst/errors';
import { extractAuth } from '../../lib/auth.js';
import { PRODUCT_ID } from '../../lib/product-config.js';
import * as repo from './repository.js';
import {
CreateSavedViewSchema,
ListSavedViewsQuerySchema,
UpdateSavedViewSchema,
type SavedViewDoc,
} from './types.js';
export async function savedViewRoutes(app: FastifyInstance) {
app.get('/saved-views', async (req: FastifyRequest) => {
const auth = await extractAuth(req);
const parsed = ListSavedViewsQuerySchema.safeParse(req.query);
if (!parsed.success) {
throw new BadRequestError(parsed.error.issues.map((issue: { message: string }) => issue.message).join('; '));
}
const result = await repo.listSavedViews(auth.sub, PRODUCT_ID, parsed.data);
return { ...result, limit: parsed.data.limit, offset: parsed.data.offset };
});
app.post('/saved-views', async (req: FastifyRequest, reply: FastifyReply) => {
const auth = await extractAuth(req);
const parsed = CreateSavedViewSchema.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: SavedViewDoc = {
id: parsed.data.id,
productId: PRODUCT_ID,
userId: auth.sub,
name: parsed.data.name,
scope: parsed.data.scope,
description: parsed.data.description,
query: parsed.data.query,
filters: parsed.data.filters,
sortOrder: parsed.data.sortOrder,
createdAt: now,
updatedAt: now,
createdBy: auth.sub,
updatedBy: auth.sub,
};
const created = await repo.createSavedView(doc);
reply.code(201);
return created;
});
app.get('/saved-views/:id', async (req: FastifyRequest) => {
const auth = await extractAuth(req);
const { id } = req.params as { id: string };
const view = await repo.getSavedView(id, auth.sub);
if (!view || view.productId !== PRODUCT_ID) {
throw new NotFoundError('Saved view not found');
}
return view;
});
app.patch('/saved-views/:id', async (req: FastifyRequest) => {
const auth = await extractAuth(req);
const { id } = req.params as { id: string };
const parsed = UpdateSavedViewSchema.safeParse(req.body);
if (!parsed.success) {
throw new BadRequestError(parsed.error.issues.map((issue: { message: string }) => issue.message).join('; '));
}
const existing = await repo.getSavedView(id, auth.sub);
if (!existing || existing.productId !== PRODUCT_ID) {
throw new NotFoundError('Saved view not found');
}
const updated = await repo.updateSavedView(id, auth.sub, {
...parsed.data,
updatedAt: new Date().toISOString(),
updatedBy: auth.sub,
});
if (!updated) {
throw new NotFoundError('Saved view not found');
}
return updated;
});
app.delete('/saved-views/:id', async (req: FastifyRequest, reply: FastifyReply) => {
const auth = await extractAuth(req);
const { id } = req.params as { id: string };
const existing = await repo.getSavedView(id, auth.sub);
if (!existing || existing.productId !== PRODUCT_ID) {
throw new NotFoundError('Saved view not found');
}
const deleted = await repo.deleteSavedView(id, auth.sub);
if (!deleted) {
throw new NotFoundError('Saved view not found');
}
reply.code(204).send();
});
}