import { FastifyInstance } from 'fastify'; import { z } from 'zod'; const createNodeBodySchema = z.object({ type: z.enum(['task', 'event', 'note', 'project', 'goal']), workspaceId: z.string().uuid(), title: z.string().min(1).max(500), content: z.record(z.unknown()).optional(), priority: z.number().int().min(1).max(5).optional(), startTime: z.string().datetime().optional(), endTime: z.string().datetime().optional(), parentId: z.string().uuid().optional(), }); export async function nodeRoutes(app: FastifyInstance) { app.addHook('preHandler', app.authenticate); app.get('/nodes', async (request) => { const { id: userId } = request.user as { id: string }; const prisma = app.prisma; const { type, status, workspaceId } = request.query as Record; const nodes = await prisma.node.findMany({ where: { ownerId: userId, ...(type ? { type: type as never } : {}), ...(status ? { status: status as never } : {}), ...(workspaceId ? { workspaceId } : {}), }, orderBy: { updatedAt: 'desc' }, take: 100, }); return { nodes }; }); app.post<{ Body: z.infer }>('/nodes', async (request, reply) => { const body = createNodeBodySchema.parse(request.body); const { id: userId } = request.user as { id: string }; const prisma = app.prisma; const node = await prisma.node.create({ data: { type: body.type, workspaceId: body.workspaceId, ownerId: userId, title: body.title, content: (body.content ?? {}) as never, priority: body.priority, startTime: body.startTime ? new Date(body.startTime) : null, endTime: body.endTime ? new Date(body.endTime) : null, parentId: body.parentId, }, }); return reply.status(201).send({ node }); }); app.get<{ Params: { id: string } }>('/nodes/:id', async (request, reply) => { const { id } = request.params; const { id: userId } = request.user as { id: string }; const prisma = app.prisma; const node = await prisma.node.findFirst({ where: { id, ownerId: userId }, include: { tags: { include: { tag: true } }, links: true, backlinks: true }, }); if (!node) return reply.status(404).send({ error: 'Node not found' }); return { node }; }); app.patch<{ Params: { id: string } }>('/nodes/:id', async (request, reply) => { const { id } = request.params; const { id: userId } = request.user as { id: string }; const prisma = app.prisma; const existing = await prisma.node.findFirst({ where: { id, ownerId: userId } }); if (!existing) return reply.status(404).send({ error: 'Node not found' }); const body = request.body as Record; const node = await prisma.node.update({ where: { id }, data: { ...(body.title ? { title: body.title as string } : {}), ...(body.content ? { content: body.content as never } : {}), ...(body.priority !== undefined ? { priority: body.priority as number } : {}), ...(body.status ? { status: body.status as never } : {}), version: { increment: 1 }, }, }); return { node }; }); app.delete<{ Params: { id: string } }>('/nodes/:id', async (request, reply) => { const { id } = request.params; const { id: userId } = request.user as { id: string }; const prisma = app.prisma; const existing = await prisma.node.findFirst({ where: { id, ownerId: userId } }); if (!existing) return reply.status(404).send({ error: 'Node not found' }); await prisma.node.update({ where: { id }, data: { status: 'deleted', archivedAt: new Date() }, }); return reply.status(204).send(); }); app.post<{ Params: { id: string } }>('/nodes/:id/restore', async (request, reply) => { const { id } = request.params; const { id: userId } = request.user as { id: string }; const prisma = app.prisma; const existing = await prisma.node.findFirst({ where: { id, ownerId: userId, status: 'deleted' } }); if (!existing) return reply.status(404).send({ error: 'Node not found' }); const node = await prisma.node.update({ where: { id }, data: { status: 'active', archivedAt: null }, }); return { node }; }); }