import { Router } from 'express'; import { z } from 'zod'; import * as repo from '../../db/repos/refs.js'; import { validate } from '../validate.js'; import { parsePagination } from '../pagination.js'; import { NotFoundError, ValidationError, asyncWrap } from '../errors.js'; const KINDS = ['url', 'video', 'pdf', 'image', 'file']; const baseFields = { space_id: z.string().uuid(), kind: z.enum(KINDS), source_url: z.string().url().nullable().optional(), title: z.string().max(500).nullable().optional(), description: z.string().nullable().optional(), summary: z.string().nullable().optional(), body_text: z.string().nullable().optional(), blob_path: z.string().nullable().optional(), thumbnail: z.string().nullable().optional(), metadata: z.record(z.string(), z.any()).optional(), status: z.string().optional(), source_kind: z.string().nullable().optional(), external_id: z.string().nullable().optional(), captured_at: z.string().datetime().optional() }; const createSchema = z.object(baseFields); const upsertSchema = z.object({ ...baseFields, source_kind: z.string().min(1), external_id: z.string().min(1) }); const patchSchema = z.object(Object.fromEntries( Object.entries(baseFields).map(([k, v]) => [k, v.optional()]) )).partial(); const idParams = z.object({ id: z.string().uuid() }); export const router = Router(); router.get('/', validate({ query: z.object({ space_id: z.string().uuid().optional(), kind: z.enum(KINDS).optional(), limit: z.string().optional(), offset: z.string().optional() })}), asyncWrap(async (req, res) => { const { limit, offset } = parsePagination(req); res.json(await repo.list({ space_id: req.validatedQuery.space_id, kind: req.validatedQuery.kind, limit, offset })); }) ); router.post('/', validate({ body: createSchema }), asyncWrap(async (req, res) => { try { const row = await repo.create(req.body, req.actor); res.status(201).json(row); } catch (e) { if (e.code === '23503') throw new ValidationError('invalid space', { space_id: req.body.space_id }); throw e; } }) ); router.post('/upsert', validate({ body: upsertSchema }), asyncWrap(async (req, res) => { try { const row = await repo.upsertByExternal(req.body, req.actor); res.json(row); } catch (e) { if (e.code === '23503') throw new ValidationError('invalid space', { space_id: req.body.space_id }); throw e; } }) ); router.get('/:id', validate({ params: idParams }), asyncWrap(async (req, res) => { const row = await repo.getById(req.params.id); if (!row) throw new NotFoundError('ref not found'); res.json(row); }) ); router.patch('/:id', validate({ params: idParams, body: patchSchema }), asyncWrap(async (req, res) => { const existing = await repo.getById(req.params.id); if (!existing) throw new NotFoundError('ref not found'); const row = await repo.update(req.params.id, req.body, req.actor); res.json(row); }) ); router.delete('/:id', validate({ params: idParams }), asyncWrap(async (req, res) => { const existing = await repo.getById(req.params.id); if (!existing) throw new NotFoundError('ref not found'); await repo.del(req.params.id, req.actor); res.status(204).end(); }) );