lilith-platform.live/codebase/@features/api/src/surfaces/admin/site-text.ts
2026-04-19 19:35:55 -07:00

100 lines
3.7 KiB
TypeScript

import { Hono } from 'hono';
import { z } from 'zod';
import {
SITE_TEXT_KINDS,
createSiteTextBlock,
deleteSiteTextBlock,
getSiteTextBlock,
listSiteTextBlocks,
updateSiteTextBlock,
upsertSiteTextBlock,
} from '@/entities/site-text';
import type { SiteTextBlockDraft, SiteTextBlockPatch } from '@/entities/site-text';
import { getDb } from '@/shared/db';
import { badRequest } from '@/shared/http/errors';
const idParam = z.coerce.number().int().positive();
const draftSchema = z.object({
kind: z.enum(SITE_TEXT_KINDS),
namespace: z.string().default(''),
key: z.string().min(1),
value: z.string().optional(),
listJson: z.string().nullable().optional(),
sortOrder: z.number().int().optional(),
providerSlug: z.string().optional(),
});
const patchSchema = z.object({
kind: z.enum(SITE_TEXT_KINDS).optional(),
namespace: z.string().optional(),
key: z.string().min(1).optional(),
value: z.string().optional(),
listJson: z.string().nullable().optional(),
sortOrder: z.number().int().optional(),
providerSlug: z.string().optional(),
});
export const siteTextRouter = new Hono()
.get('/', async (c) => {
const kind = c.req.query('kind');
const provider = c.req.query('provider');
const parsedKind = kind ? z.enum(SITE_TEXT_KINDS).safeParse(kind) : null;
if (parsedKind && !parsedKind.success) throw badRequest('bad_kind', 'invalid kind filter');
return c.json(
await listSiteTextBlocks(getDb(), {
...(parsedKind?.success ? { kind: parsedKind.data } : {}),
...(provider ? { providerSlug: provider } : {}),
}),
);
})
.get('/:id', async (c) => {
const id = idParam.parse(c.req.param('id'));
return c.json(await getSiteTextBlock(getDb(), id));
})
.post('/', async (c) => {
const raw = draftSchema.parse(await c.req.json());
const draft: SiteTextBlockDraft = {
kind: raw.kind,
namespace: raw.namespace,
key: raw.key,
...(raw.value !== undefined ? { value: raw.value } : {}),
...(raw.listJson !== undefined ? { listJson: raw.listJson } : {}),
...(raw.sortOrder !== undefined ? { sortOrder: raw.sortOrder } : {}),
...(raw.providerSlug !== undefined ? { providerSlug: raw.providerSlug } : {}),
};
return c.json(await createSiteTextBlock(getDb(), draft), 201);
})
.put('/upsert', async (c) => {
const raw = draftSchema.parse(await c.req.json());
const draft: SiteTextBlockDraft = {
kind: raw.kind,
namespace: raw.namespace,
key: raw.key,
...(raw.value !== undefined ? { value: raw.value } : {}),
...(raw.listJson !== undefined ? { listJson: raw.listJson } : {}),
...(raw.sortOrder !== undefined ? { sortOrder: raw.sortOrder } : {}),
...(raw.providerSlug !== undefined ? { providerSlug: raw.providerSlug } : {}),
};
return c.json(await upsertSiteTextBlock(getDb(), draft));
})
.put('/:id', async (c) => {
const id = idParam.parse(c.req.param('id'));
const raw = patchSchema.parse(await c.req.json());
const patch: SiteTextBlockPatch = {
...(raw.kind !== undefined ? { kind: raw.kind } : {}),
...(raw.namespace !== undefined ? { namespace: raw.namespace } : {}),
...(raw.key !== undefined ? { key: raw.key } : {}),
...(raw.value !== undefined ? { value: raw.value } : {}),
...(raw.listJson !== undefined ? { listJson: raw.listJson } : {}),
...(raw.sortOrder !== undefined ? { sortOrder: raw.sortOrder } : {}),
...(raw.providerSlug !== undefined ? { providerSlug: raw.providerSlug } : {}),
};
return c.json(await updateSiteTextBlock(getDb(), id, patch));
})
.delete('/:id', async (c) => {
const id = idParam.parse(c.req.param('id'));
await deleteSiteTextBlock(getDb(), id);
return c.body(null, 204);
});