@bobbykim/manguito-cms-api 0.4.3 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/relations.ts","../src/field-keys.ts","../src/graphql/scalars.ts","../src/graphql/type-mapping.ts","../src/graphql/naming.ts","../src/graphql/filters.ts","../src/graphql/resolvers.ts","../src/graphql/schema.ts","../src/graphql/dataloaders.ts","../src/graphql/security.ts","../src/graphql/handler.ts","../src/index.ts","../src/paths.ts","../src/app.ts","../src/middleware/cors.ts","../src/middleware/security-headers.ts","../src/middleware/error.ts","../src/middleware/auth.ts","../src/auth/jwt.ts","../src/middleware/must-change-password.ts","../src/middleware/permission.ts","../src/middleware/hierarchy.ts","../src/middleware/rate-limit.ts","../src/auth/registry.ts","../src/programmatic/resolve.ts","../src/projector.ts","../src/routes/query-params.ts","../src/routes/content.ts","../src/routes/media.ts","../src/routes/admin/content.ts","../src/media-references.ts","../src/routes/admin/media.ts","../src/routes/admin/auth.ts","../src/auth/password.ts","../src/routes/admin/users.ts","../src/routes/admin/config.ts","../src/routes/admin/schema.ts","../src/repositories/content.ts","../src/repositories/media.ts","../src/server/node.ts"],"sourcesContent":["import crypto from 'node:crypto'\nimport { sql } from 'drizzle-orm'\nimport type { ParsedField, ParsedParagraphType, SchemaRegistry } from '@bobbykim/manguito-cms-core'\nimport type { DrizzlePostgresInstance } from '@bobbykim/manguito-cms-db'\nimport type { MediaDelta } from './media-references.js'\n\n// ─── Relation persistence ─────────────────────────────────────────────────────\n//\n// Owns paragraph/junction/reference/media relation table SQL — both the write\n// side (persist/delete below) and the read side (descriptors + resolvers further\n// down). Each persist/delete returns the MediaDelta of media ids it gained and\n// lost (see media-references.ts), which the caller merges and reconciles once.\n// The repository's resolveRows() drives the read resolvers.\n\nfunction quoteIdent(name: string): string {\n if (!/^[a-z][a-z0-9_-]*$/.test(name)) throw new Error(`Unsafe identifier: ${name}`)\n return `\"${name}\"`\n}\n\n// Paragraph rows live on a child table with their own image/video/file columns\n// (e.g. a \"photo card\" block embeds an image) — these need the same reference-count\n// bookkeeping as top-level content fields, one level down.\nfunction paragraphMediaFieldNames(pType: ParsedParagraphType): Set<string> {\n return new Set(\n pType.fields\n .filter((f) => f.field_type === 'image' || f.field_type === 'video' || f.field_type === 'file')\n .map((f) => f.name)\n )\n}\n\nasync function fetchParagraphMediaIds(\n db: DrizzlePostgresInstance,\n pType: ParsedParagraphType,\n itemId: string,\n fieldName: string\n): Promise<string[]> {\n const mediaColumns = pType.fields\n .filter((f) => f.field_type === 'image' || f.field_type === 'video' || f.field_type === 'file')\n .map((f) => f.db_column!.column_name)\n if (mediaColumns.length === 0) return []\n\n const tbl = sql.raw(quoteIdent(pType.db.table_name))\n const cols = sql.join(mediaColumns.map((c) => sql.raw(quoteIdent(c))), sql`, `)\n const result = await db.execute(\n sql`SELECT ${cols} FROM ${tbl} WHERE parent_id = ${itemId} AND parent_field = ${fieldName}`\n )\n\n const ids: string[] = []\n for (const row of result.rows as Record<string, unknown>[]) {\n for (const col of mediaColumns) {\n const v = row[col]\n if (typeof v === 'string' && v !== '') ids.push(v)\n }\n }\n return ids\n}\n\n// A paragraph type's own paragraph fields — the one level of nesting ADR\n// core/0005 permits. Empty without a registry, which is how callers that have no\n// nesting to persist opt out.\nfunction nestedParagraphFields(\n pType: ParsedParagraphType,\n registry?: SchemaRegistry\n): Array<{ fieldName: string; nType: ParsedParagraphType }> {\n if (!registry) return []\n const out: Array<{ fieldName: string; nType: ParsedParagraphType }> = []\n for (const f of pType.fields) {\n if (f.db_column !== null) continue\n const comp = f.ui_component as { component: string; ref?: string }\n if (comp.component !== 'paragraph-embed' || !comp.ref) continue\n const nType = registry.paragraph_types[comp.ref]\n if (nType) out.push({ fieldName: f.name, nType })\n }\n return out\n}\n\n// Ids of the paragraph rows currently stored for one field of one parent.\nasync function fetchParagraphRowIds(\n db: DrizzlePostgresInstance,\n pType: ParsedParagraphType,\n itemId: string,\n fieldName: string\n): Promise<string[]> {\n const tbl = sql.raw(quoteIdent(pType.db.table_name))\n const result = await db.execute(\n sql`SELECT id FROM ${tbl} WHERE parent_id = ${itemId} AND parent_field = ${fieldName}`\n )\n return (result.rows as Record<string, unknown>[])\n .map((r) => r['id'])\n .filter((id): id is string => typeof id === 'string')\n}\n\n// Replaces every paragraph row for one field of one parent (delete + reinsert),\n// returning the media ids removed (old rows) and added (new rows).\n//\n// `registry` enables one level of paragraph nesting (ADR core/0005): a nested\n// paragraph field has no column on its parent's table, so without this its items\n// were silently dropped on write. Nested rows hang off the *paragraph* row's id,\n// and are torn down with it so the delete+reinsert cannot orphan them.\nexport async function persistParagraphField(\n db: DrizzlePostgresInstance,\n itemId: string,\n parentType: string,\n fieldName: string,\n pType: ParsedParagraphType,\n items: unknown[],\n registry?: SchemaRegistry\n): Promise<MediaDelta> {\n const removed = await fetchParagraphMediaIds(db, pType, itemId, fieldName)\n const nested = nestedParagraphFields(pType, registry)\n\n // Tear down nested rows before their parents disappear, collecting the media\n // they referenced so reference counts stay correct.\n if (nested.length > 0) {\n const staleIds = await fetchParagraphRowIds(db, pType, itemId, fieldName)\n for (const rowId of staleIds) {\n for (const n of nested) {\n const d = await deleteParagraphField(db, rowId, n.fieldName, n.nType, registry)\n removed.push(...d.removed)\n }\n }\n }\n\n const tbl = sql.raw(quoteIdent(pType.db.table_name))\n await db.execute(sql`DELETE FROM ${tbl} WHERE parent_id = ${itemId} AND parent_field = ${fieldName}`)\n\n const mediaFieldNames = paragraphMediaFieldNames(pType)\n const added: string[] = []\n\n for (let i = 0; i < items.length; i++) {\n const pItem = items[i] as Record<string, unknown>\n const rowId = crypto.randomUUID()\n const data: Record<string, unknown> = {\n id: rowId,\n parent_id: itemId,\n parent_type: parentType,\n parent_field: fieldName,\n order: i,\n created_at: new Date(),\n updated_at: new Date(),\n }\n for (const pf of pType.fields) {\n if (!pf.db_column || pf.db_column.junction) continue\n data[pf.db_column.column_name] = pItem[pf.name] ?? null\n }\n const cols = sql.join(Object.keys(data).map((k) => sql.raw(quoteIdent(k))), sql`, `)\n const vals = sql.join(Object.values(data).map((v) => sql`${v}`), sql`, `)\n await db.execute(sql`INSERT INTO ${tbl} (${cols}) VALUES (${vals})`)\n\n for (const name of mediaFieldNames) {\n const val = pItem[name]\n if (typeof val === 'string' && val !== '') added.push(val)\n }\n\n // Persist this row's nested paragraph items against the row just inserted.\n for (const n of nested) {\n const nestedItems = Array.isArray(pItem[n.fieldName]) ? (pItem[n.fieldName] as unknown[]) : []\n const d = await persistParagraphField(\n db,\n rowId,\n pType.db.table_name,\n n.fieldName,\n n.nType,\n nestedItems,\n registry\n )\n added.push(...d.added)\n removed.push(...d.removed)\n }\n }\n\n return { added, removed }\n}\n\n// Deletes all paragraph rows for one field of one parent (used when the parent is\n// being deleted — paragraph rows have no FK/cascade back to their parent, so they'd\n// leak otherwise). Returns the media those rows referenced, all removed.\nexport async function deleteParagraphField(\n db: DrizzlePostgresInstance,\n itemId: string,\n fieldName: string,\n pType: ParsedParagraphType,\n registry?: SchemaRegistry\n): Promise<MediaDelta> {\n const removed = await fetchParagraphMediaIds(db, pType, itemId, fieldName)\n\n // Cascade to nested paragraph rows, which hang off these rows' ids.\n const nested = nestedParagraphFields(pType, registry)\n if (nested.length > 0) {\n const rowIds = await fetchParagraphRowIds(db, pType, itemId, fieldName)\n for (const rowId of rowIds) {\n for (const n of nested) {\n const d = await deleteParagraphField(db, rowId, n.fieldName, n.nType, registry)\n removed.push(...d.removed)\n }\n }\n }\n\n const tbl = sql.raw(quoteIdent(pType.db.table_name))\n await db.execute(sql`DELETE FROM ${tbl} WHERE parent_id = ${itemId} AND parent_field = ${fieldName}`)\n return { added: [], removed }\n}\n\n// Replaces every junction row for one many-to-many field of one parent.\nexport async function persistJunctionField(\n db: DrizzlePostgresInstance,\n itemId: string,\n junction: { table_name: string; left_column: string; right_column: string },\n relatedIds: string[]\n): Promise<void> {\n const tbl = sql.raw(quoteIdent(junction.table_name))\n const left = sql.raw(quoteIdent(junction.left_column))\n const right = sql.raw(quoteIdent(junction.right_column))\n await db.execute(sql`DELETE FROM ${tbl} WHERE ${left} = ${itemId}`)\n for (const rid of relatedIds) {\n await db.execute(sql`INSERT INTO ${tbl} (${left}, ${right}) VALUES (${itemId}, ${rid})`)\n }\n}\n\n// ─── Relation descriptors and resolution (read side) ──────────────────────────\n//\n// Relation read resolution. These were lifted out of the repository so that all\n// paragraph/junction/reference/media table SQL lives in one module alongside the\n// write persistence above. The repository's resolveRows() calls into here.\n\nexport type ParagraphRelationDef = {\n type: 'paragraph'\n table: string\n}\n\nexport type ReferenceRelationDef = {\n type: 'reference'\n table: string\n fk_column: string\n}\n\nexport type JunctionRelationDef = {\n type: 'junction'\n table: string\n junction_table: string\n left_column: string\n right_column: string\n // Only ordered junctions have an \"order\" column (db codegen emits it only when\n // order_column is true) — the resolver must not ORDER BY it otherwise.\n order_column: boolean\n}\n\nexport type MediaRelationDef = {\n type: 'media'\n fk_column: string\n}\n\nexport type RelationDef =\n | ParagraphRelationDef\n | ReferenceRelationDef\n | JunctionRelationDef\n | MediaRelationDef\n\n// Derives a content type's relations map from its parsed fields, so paragraph/\n// reference/media fields are actually resolved instead of silently dropped.\n// Junction and foreign_key info is already on each field's db_column; paragraph\n// fields store their target table on the paragraph type, hence the registry.\nexport function buildRelationsMap(\n fields: ParsedField[],\n registry: SchemaRegistry\n): Record<string, RelationDef> {\n const relations: Record<string, RelationDef> = {}\n\n for (const field of fields) {\n if (field.field_type === 'image' || field.field_type === 'video' || field.field_type === 'file') {\n if (!field.db_column) continue\n relations[field.name] = { type: 'media', fk_column: field.db_column.column_name }\n } else if (field.field_type === 'paragraph') {\n const ref = (field.ui_component as { ref?: string }).ref\n const pType = ref ? registry.paragraph_types[ref] : undefined\n if (!pType) continue\n relations[field.name] = { type: 'paragraph', table: pType.db.table_name }\n } else if (field.field_type === 'reference') {\n if (!field.db_column) continue\n if (field.db_column.junction) {\n const j = field.db_column.junction\n relations[field.name] = {\n type: 'junction',\n table: j.right_table,\n junction_table: j.table_name,\n left_column: j.left_column,\n right_column: j.right_column,\n order_column: j.order_column,\n }\n } else if (field.db_column.foreign_key) {\n relations[field.name] = {\n type: 'reference',\n table: field.db_column.foreign_key.table,\n fk_column: field.db_column.column_name,\n }\n }\n }\n }\n\n return relations\n}\n\nfunction groupBy<T extends Record<string, unknown>>(\n items: T[],\n key: string\n): Record<string, T[]> {\n const result: Record<string, T[]> = {}\n for (const item of items) {\n const k = String(item[key])\n if (!result[k]) result[k] = []\n result[k]!.push(item)\n }\n return result\n}\n\n// A reference/media pass is DESTRUCTIVE: the resolved object replaces the raw FK\n// value, and when the field's label differs from its FK column (a renamed field)\n// the raw FK key is deleted outright — otherwise the response would carry both\n// the column name and the label.\n//\n// That makes a second pass over the same row object dangerous: the FK the first\n// pass consumed is gone, so the row would be nulled out. And the same object\n// really does recur — reference/junction targets are cached by `table:id`, so two\n// parents receive ONE shared object, and the GraphQL dataloaders deliberately do\n// not memoize by parent identity (\"rows are mutated and may recur across nesting\n// levels\", dataloaders.ts). So a row that already holds a resolved value is\n// skipped, which makes the pass idempotent.\nfunction needsFkResolution(\n row: Record<string, unknown>,\n fkColumn: string,\n fieldName: string\n): boolean {\n // Label diverges from the column: a missing FK key means it was already dropped.\n if (fkColumn !== fieldName && !(fkColumn in row)) return false\n // Label equals the column: the resolved object sits where the raw id was, so an\n // object (never a bare id) means this row is already done.\n const current = row[fieldName]\n return !(typeof current === 'object' && current !== null)\n}\n\n// Fully resolves a relation field on a batch of rows: paragraph → ordered rows,\n// reference → the target row, junction → target rows, media → the media row.\n// The cache (keyed table:id) dedupes target lookups within a request.\n//\n// `publishedOnly` (default false, preserving prior behavior for admin callers)\n// adds `AND published = true` to the reference/junction target-row SELECTs.\n// Reference and junction targets are always content or taxonomy tables, which\n// always carry a `published` boolean system field — see CONTENT_SYSTEM_FIELDS /\n// TAXONOMY_SYSTEM_FIELDS in packages/core/src/parser/parseSchema.ts. Paragraph\n// and media targets are deliberately excluded: paragraph rows are owned by\n// their parent (no independent publish state) and media rows have no\n// `published` column at all.\nexport async function resolveRelationField(\n db: DrizzlePostgresInstance,\n rows: Record<string, unknown>[],\n fieldName: string,\n rel: RelationDef,\n cache: Map<string, unknown>,\n publishedOnly = false\n): Promise<void> {\n if (rows.length === 0) return\n\n // Deduped by object identity: one batch can hold the same row twice (see\n // needsFkResolution above), and the destructive branches would then read an FK\n // the first visit already consumed. The caller's array is left untouched — the\n // GraphQL dataloader maps over it to build its results.\n const batch = [...new Set(rows)]\n\n if (rel.type === 'paragraph') {\n const parentIds = batch.map((r) => r['id'] as string)\n const inList = sql.join(parentIds.map((id) => sql`${id}`), sql`, `)\n // Scoped by parent_field as well as parent_id: two paragraph fields of the\n // same paragraph type share one table, so filtering on the parent alone\n // hands each field the other's rows.\n const result = await db.execute(\n sql`SELECT * FROM ${sql.raw(quoteIdent(rel.table))} WHERE parent_id IN (${inList}) AND parent_field = ${fieldName} ORDER BY \"order\" ASC`\n )\n const byParent = groupBy(result.rows as Record<string, unknown>[], 'parent_id')\n for (const row of batch) {\n row[fieldName] = byParent[row['id'] as string] ?? []\n }\n } else if (rel.type === 'reference') {\n const dropFk = rel.fk_column !== fieldName\n const pending = batch.filter((r) => needsFkResolution(r, rel.fk_column, fieldName))\n if (pending.length === 0) return\n\n const fkValues = pending.map((r) => r[rel.fk_column] as string).filter(Boolean)\n if (fkValues.length === 0) {\n for (const row of pending) {\n row[fieldName] = null\n if (dropFk) delete row[rel.fk_column]\n }\n return\n }\n const unique = [...new Set(fkValues)]\n const uncached = unique.filter((id) => !cache.has(`${rel.table}:${id}`))\n if (uncached.length > 0) {\n const inList = sql.join(uncached.map((id) => sql`${id}`), sql`, `)\n const publishedCond = publishedOnly ? sql` AND published = true` : sql``\n const result = await db.execute(\n sql`SELECT * FROM ${sql.raw(quoteIdent(rel.table))} WHERE id IN (${inList})${publishedCond}`\n )\n for (const item of result.rows as Record<string, unknown>[]) {\n cache.set(`${rel.table}:${item['id']}`, item)\n }\n }\n for (const row of pending) {\n const fkVal = row[rel.fk_column] as string\n row[fieldName] = fkVal ? (cache.get(`${rel.table}:${fkVal}`) ?? null) : null\n if (dropFk) delete row[rel.fk_column]\n }\n } else if (rel.type === 'junction') {\n const parentIds = batch.map((r) => r['id'] as string)\n const inList = sql.join(parentIds.map((id) => sql`${id}`), sql`, `)\n const orderBy = rel.order_column ? sql` ORDER BY \"order\" ASC` : sql``\n const junctionResult = await db.execute(\n sql`SELECT * FROM ${sql.raw(quoteIdent(rel.junction_table))} WHERE ${sql.raw(quoteIdent(rel.left_column))} IN (${inList})${orderBy}`\n )\n const jRows = junctionResult.rows as Record<string, unknown>[]\n const rightIds = [...new Set(jRows.map((r) => r[rel.right_column] as string))]\n const uncached = rightIds.filter((id) => !cache.has(`${rel.table}:${id}`))\n if (uncached.length > 0) {\n const rightInList = sql.join(uncached.map((id) => sql`${id}`), sql`, `)\n const publishedCond = publishedOnly ? sql` AND published = true` : sql``\n const entitiesResult = await db.execute(\n sql`SELECT * FROM ${sql.raw(quoteIdent(rel.table))} WHERE id IN (${rightInList})${publishedCond}`\n )\n for (const item of entitiesResult.rows as Record<string, unknown>[]) {\n cache.set(`${rel.table}:${item['id']}`, item)\n }\n }\n const byLeft = groupBy(jRows, rel.left_column)\n for (const row of batch) {\n const jEntries = byLeft[row['id'] as string] ?? []\n row[fieldName] = jEntries\n .map((jr) => cache.get(`${rel.table}:${jr[rel.right_column] as string}`))\n .filter(Boolean)\n }\n } else if (rel.type === 'media') {\n const dropFk = rel.fk_column !== fieldName\n const pending = batch.filter((r) => needsFkResolution(r, rel.fk_column, fieldName))\n if (pending.length === 0) return\n\n const fkValues = pending.map((r) => r[rel.fk_column] as string).filter(Boolean)\n if (fkValues.length === 0) {\n for (const row of pending) {\n row[fieldName] = null\n if (dropFk) delete row[rel.fk_column]\n }\n return\n }\n const unique = [...new Set(fkValues)]\n const uncached = unique.filter((id) => !cache.has(`media:${id}`))\n if (uncached.length > 0) {\n const inList = sql.join(uncached.map((id) => sql`${id}`), sql`, `)\n const result = await db.execute(sql`SELECT * FROM \"media\" WHERE id IN (${inList})`)\n for (const item of result.rows as Record<string, unknown>[]) {\n cache.set(`media:${item['id']}`, item)\n }\n }\n for (const row of pending) {\n const fkVal = row[rel.fk_column] as string\n row[fieldName] = fkVal ? (cache.get(`media:${fkVal}`) ?? null) : null\n if (dropFk) delete row[rel.fk_column]\n }\n }\n}\n\n// Populates paragraph/junction fields with bare ID arrays when they are not\n// explicitly included — they have no column on the owning table, so without this\n// they'd be silently absent. Reference fields already carry their bare ID as a\n// real column from `SELECT *`, so they need no extra query.\nexport async function resolveRelationBareIds(\n db: DrizzlePostgresInstance,\n rows: Record<string, unknown>[],\n fieldName: string,\n rel: RelationDef\n): Promise<void> {\n if (rows.length === 0) return\n\n if (rel.type === 'paragraph') {\n const parentIds = rows.map((r) => r['id'] as string)\n const inList = sql.join(parentIds.map((id) => sql`${id}`), sql`, `)\n const result = await db.execute(\n sql`SELECT id, parent_id FROM ${sql.raw(quoteIdent(rel.table))} WHERE parent_id IN (${inList}) AND parent_field = ${fieldName} ORDER BY \"order\" ASC`\n )\n const byParent = groupBy(result.rows as Record<string, unknown>[], 'parent_id')\n for (const row of rows) {\n row[fieldName] = (byParent[row['id'] as string] ?? []).map((r) => r['id'])\n }\n } else if (rel.type === 'junction') {\n const parentIds = rows.map((r) => r['id'] as string)\n const inList = sql.join(parentIds.map((id) => sql`${id}`), sql`, `)\n const result = await db.execute(\n sql`SELECT ${sql.raw(quoteIdent(rel.left_column))} AS left_id, ${sql.raw(quoteIdent(rel.right_column))} AS right_id FROM ${sql.raw(quoteIdent(rel.junction_table))} WHERE ${sql.raw(quoteIdent(rel.left_column))} IN (${inList})`\n )\n const byLeft = groupBy(result.rows as Record<string, unknown>[], 'left_id')\n for (const row of rows) {\n row[fieldName] = (byLeft[row['id'] as string] ?? []).map((r) => r['right_id'])\n }\n }\n}\n","import type { DbColumn, ParsedField } from '@bobbykim/manguito-cms-core'\n\n// ─── Label ↔ storage key mapping ──────────────────────────────────────────────\n//\n// A field has two names: its public LABEL (`field.name`, what API consumers and\n// the admin panel see) and its STORAGE key (`db_column.column_name`, the actual\n// Postgres column). They are identical today, but schema versioning makes a\n// rename change only the label — the column keeps its original name for the life\n// of the data. Every place that reads or writes a DB row must therefore use the\n// storage key, and every place that reads a request body or writes a response\n// must use the label.\n//\n// Only column-backed fields participate. Paragraph fields have no column\n// (their association lives on the paragraph table via parent_field) and\n// many-to-many references have no column either (the junction table owns the\n// association), so both keep the field name as their identity and are excluded.\n\n// A type predicate, not a plain boolean: narrowing `db_column` to non-null lets\n// the compiler enforce the invariant at every call site instead of each one\n// re-asserting it with `db_column!`.\nexport function isColumnBacked(field: ParsedField): field is ParsedField & { db_column: DbColumn } {\n const col = field.db_column\n if (col === null) return false\n if (col.column_name === '') return false\n if (col.junction) return false\n return true\n}\n\nexport type FieldKeyMap = {\n /** Request body (label-keyed) → storage-keyed. Unknown keys pass through. */\n toStorage(input: Record<string, unknown>): Record<string, unknown>\n /** DB row (storage-keyed) → label-keyed. Unknown keys pass through. */\n toLabels(row: Record<string, unknown>): Record<string, unknown>\n columnFor(label: string): string | undefined\n labelFor(column: string): string | undefined\n /** Labels of column-backed fields — the valid filter/sort surface. */\n labels: string[]\n /** False when every label equals its column, letting callers skip the copy. */\n diverges: boolean\n}\n\n/**\n * Built ONCE per content type at startup, not per request. Throws on a key\n * collision, matching how createCmsApp refuses to boot on a broken roles\n * registry: an ambiguous mapping would silently corrupt responses.\n *\n * Tombstones (`field.removed === true`) are column-backed — the parser keeps\n * their column alive for older live versions — but the CURRENT version must\n * never serve or accept them. They are excluded from `labelToColumn` and the\n * public `columnToLabel` below, and additionally dropped by key in `remap`:\n * without that second step, an unmapped retained column would pass through\n * `remap` unchanged (see the comment on `remap`) and reach the response under\n * its raw column name.\n */\nexport function createFieldKeyMap(fields: ParsedField[]): FieldKeyMap {\n const labelToColumn = new Map<string, string>()\n // Includes tombstoned columns until the collision check below has run —\n // see the trap note there before touching this map.\n const columnToLabel = new Map<string, string>()\n // Both the name and the column of every tombstone, so remap can drop a\n // tombstone under either key. They differ when a field was renamed and\n // THEN removed: it carries both `column` (the original, still-live column)\n // and `removed`.\n const droppedKeys = new Set<string>()\n\n for (const f of fields) {\n if (!isColumnBacked(f)) continue\n columnToLabel.set(f.db_column.column_name, f.name)\n if (f.removed === true) {\n droppedKeys.add(f.name)\n droppedKeys.add(f.db_column.column_name)\n continue\n }\n labelToColumn.set(f.name, f.db_column.column_name)\n }\n\n // A label that is also some OTHER field's column name would make toLabels\n // ambiguous: two source keys would map onto one destination key.\n //\n // Checked against EVERY field's label, not just the column-backed subset.\n // Paragraph, many-to-many and programmatic fields have no column of their\n // own, but their labels are written into the same key space as storage\n // columns before toLabels runs (routes/admin/content.ts, relations.ts,\n // programmatic/resolve.ts). A paragraph field named after another field's\n // column would overwrite that column's value on the row and then be renamed\n // onto the other field's label — serving the wrong value under the wrong key.\n //\n // Deliberately run against `columnToLabel` BEFORE tombstoned columns are\n // stripped from it below: a live field's label may collide with a\n // TOMBSTONE's column (the column still physically exists on the row), and\n // that must keep throwing. Stripping tombstones first would silently pass\n // this configuration, and the exclusion step afterward would then delete\n // the live field's column from every response instead of the tombstone's.\n //\n // Not detected: two fields declaring the SAME column name. A field can now\n // declare its own `column`, so this is no longer structurally impossible —\n // but core rejects it with `DUPLICATE_COLUMN` at parse time, so it is still\n // unreachable here.\n for (const f of fields) {\n const columnOwner = columnToLabel.get(f.name)\n if (columnOwner !== undefined && columnOwner !== f.name) {\n throw new Error(\n `Fatal: field key map failed to build — field label \"${f.name}\" collides with the ` +\n `storage column of field \"${columnOwner}\". A field label may not reuse another ` +\n `field's column name. Rename the field, then run \\`manguito validate\\` to check your schema.`\n )\n }\n }\n\n // Only now strip tombstoned columns — the collision check above has already\n // run against the full map.\n for (const f of fields) {\n if (isColumnBacked(f) && f.removed === true) {\n columnToLabel.delete(f.db_column.column_name)\n }\n }\n\n const diverges = [...labelToColumn].some(([label, column]) => label !== column)\n\n function remap(\n input: Record<string, unknown>,\n lookup: Map<string, string>\n ): Record<string, unknown> {\n // Always returns a NEW object, even when nothing diverges. Returning `input`\n // unchanged would make aliasing depend on the schema, so a bug where a\n // caller mutates a row after mapping would reproduce only on renamed\n // fields. A shallow copy per row is not worth that class of bug.\n //\n // Unknown keys pass through unchanged — which is exactly why a tombstone\n // key must be checked and skipped explicitly here, rather than relying on\n // its absence from `lookup`: absence alone would let it pass through under\n // its raw key instead of being dropped.\n const out: Record<string, unknown> = {}\n for (const key of Object.keys(input)) {\n if (droppedKeys.has(key)) continue\n out[lookup.get(key) ?? key] = input[key]\n }\n return out\n }\n\n return {\n toStorage: (input) => remap(input, labelToColumn),\n toLabels: (row) => remap(row, columnToLabel),\n columnFor: (label) => labelToColumn.get(label),\n labelFor: (column) => columnToLabel.get(column),\n labels: [...labelToColumn.keys()],\n diverges,\n }\n}\n","import { GraphQLScalarType, Kind, valueFromASTUntyped } from 'graphql'\n\nexport const DateTimeScalar = new GraphQLScalarType({\n name: 'DateTime',\n description: 'ISO-8601 date-time string',\n serialize(value) {\n if (value instanceof Date) return value.toISOString()\n if (typeof value === 'string') return value\n if (typeof value === 'number') return new Date(value).toISOString()\n throw new TypeError(`DateTime cannot serialize value: ${String(value)}`)\n },\n parseValue(value) {\n if (typeof value !== 'string') throw new TypeError('DateTime must be a string')\n return value\n },\n parseLiteral(ast) {\n if (ast.kind !== Kind.STRING) throw new TypeError('DateTime must be a string')\n return ast.value\n },\n})\n\nexport const JSONScalar = new GraphQLScalarType({\n name: 'JSON',\n description: 'Arbitrary JSON value',\n serialize: (value) => value,\n parseValue: (value) => value,\n parseLiteral: (ast, variables) => valueFromASTUntyped(ast, variables ?? undefined),\n})\n","import {\n GraphQLString,\n GraphQLInt,\n GraphQLFloat,\n GraphQLBoolean,\n type GraphQLScalarType,\n} from 'graphql'\nimport type { FieldType } from '@bobbykim/manguito-cms-core'\nimport { DateTimeScalar, JSONScalar } from './scalars.js'\n\n// Scalar output type for a field. Relation/enum fields return null — schema.ts\n// resolves those against its type caches.\nexport function scalarOutputType(fieldType: FieldType): GraphQLScalarType | null {\n switch (fieldType) {\n case 'text/plain':\n case 'text/rich':\n return GraphQLString\n case 'integer':\n return GraphQLInt\n case 'float':\n return GraphQLFloat\n case 'boolean':\n return GraphQLBoolean\n case 'date':\n return DateTimeScalar\n case 'programmatic':\n return JSONScalar\n default:\n return null\n }\n}\n","// \"content--blog_post\" → \"blog_post\"; \"category\" → \"category\"\nexport function schemaSegment(machineName: string): string {\n const idx = machineName.indexOf('--')\n return idx === -1 ? machineName : machineName.slice(idx + 2)\n}\n\nfunction words(input: string): string[] {\n return input.split(/[_\\s-]+/).filter(Boolean)\n}\n\nexport function toPascalCase(input: string): string {\n return words(input)\n .map((w) => w.charAt(0).toUpperCase() + w.slice(1))\n .join('')\n}\n\nexport function toCamelCase(input: string): string {\n const pascal = toPascalCase(input)\n return pascal.charAt(0).toLowerCase() + pascal.slice(1)\n}\n\n// Deterministic English pluralization for the common cases; irregular plurals\n// (person→people) are a known limitation — an optional per-type override can be\n// added later if one is wrong.\nexport function pluralize(word: string): string {\n if (/[^aeiou]y$/.test(word)) return word.slice(0, -1) + 'ies'\n if (/(s|x|z|ch|sh)$/.test(word)) return word + 'es'\n return word + 's'\n}\n\nconst GRAPHQL_NAME = /^[_A-Za-z][_0-9A-Za-z]*$/\nexport function isValidGraphQLName(name: string): boolean {\n return GRAPHQL_NAME.test(name)\n}\n\nexport function graphqlTypeName(machineName: string): string {\n return toPascalCase(schemaSegment(machineName))\n}\n\nexport function singleQueryName(machineName: string): string {\n return toCamelCase(schemaSegment(machineName))\n}\n\nexport function collectionQueryName(machineName: string): string {\n return pluralize(singleQueryName(machineName))\n}\n\nexport function buildFieldNameMap(schemaNames: string[]): {\n toGraphql(schemaName: string): string\n toSchema(graphqlName: string): string\n} {\n const toG = new Map<string, string>()\n const toS = new Map<string, string>()\n for (const name of schemaNames) {\n const g = toCamelCase(name)\n toG.set(name, g)\n toS.set(g, name)\n }\n return {\n toGraphql: (schemaName) => toG.get(schemaName) ?? toCamelCase(schemaName),\n toSchema: (graphqlName) => toS.get(graphqlName) ?? graphqlName,\n }\n}\n","import {\n GraphQLEnumType,\n GraphQLInputObjectType,\n GraphQLList,\n GraphQLNonNull,\n GraphQLString,\n GraphQLBoolean,\n GraphQLInt,\n GraphQLFloat,\n type GraphQLInputType,\n} from 'graphql'\nimport type {\n ParsedContentType,\n ParsedTaxonomyType,\n FilterValue,\n FilterOperator,\n FieldType,\n} from '@bobbykim/manguito-cms-core'\nimport { graphqlTypeName, buildFieldNameMap } from './naming.js'\nimport { scalarOutputType } from './type-mapping.js'\nimport { DateTimeScalar } from './scalars.js'\n\nexport const SortOrderEnum = new GraphQLEnumType({\n name: 'SortOrder',\n values: { ASC: { value: 'asc' }, DESC: { value: 'desc' } },\n})\n\n// Only these fields are sortable (mirrors the REST SORTABLE_FIELDS).\n//\n// `value` is the enum's internal value, and it is NOT uniformly a storage\n// column: `created_at` / `updated_at` are the snake_case forms of real system\n// columns, but `title` is a schema field's LABEL — the same value REST accepts\n// in `?sort_by=`. `collectionResolver` maps it to a column through the type's\n// FieldKeyMap before the repository builds its ORDER BY.\nconst SORTABLE: Array<{ gql: string; value: string }> = [\n { gql: 'title', value: 'title' },\n { gql: 'createdAt', value: 'created_at' },\n { gql: 'updatedAt', value: 'updated_at' },\n]\n\nexport function buildSortFieldEnum(typeName: string): GraphQLEnumType {\n return new GraphQLEnumType({\n name: `${typeName}SortField`,\n values: Object.fromEntries(SORTABLE.map((s) => [s.gql, { value: s.value }])),\n })\n}\n\n// A comparable filter input (eq/in/gt/gte/lt/lte) for a given scalar input type.\nfunction comparableFilterInput(name: string, scalar: GraphQLInputType): GraphQLInputObjectType {\n return new GraphQLInputObjectType({\n name,\n fields: {\n eq: { type: scalar },\n in: { type: new GraphQLList(new GraphQLNonNull(scalar)) },\n gt: { type: scalar },\n gte: { type: scalar },\n lt: { type: scalar },\n lte: { type: scalar },\n },\n })\n}\n\nfunction equalityFilterInput(name: string, scalar: GraphQLInputType): GraphQLInputObjectType {\n return new GraphQLInputObjectType({\n name,\n fields: {\n eq: { type: scalar },\n in: { type: new GraphQLList(new GraphQLNonNull(scalar)) },\n },\n })\n}\n\n// Shared per-scalar filter inputs (built once).\nconst StringFilter = equalityFilterInput('StringFilter', GraphQLString)\nconst BooleanFilter = new GraphQLInputObjectType({\n name: 'BooleanFilter',\n fields: { eq: { type: GraphQLBoolean } },\n})\nconst DateTimeFilter = comparableFilterInput('DateTimeFilter', DateTimeScalar)\nconst IDFilter = equalityFilterInput('IDFilter', GraphQLString)\nconst IntFilter = comparableFilterInput('IntFilter', GraphQLInt)\nconst FloatFilter = comparableFilterInput('FloatFilter', GraphQLFloat)\n\n// Choose a filter input for a field based on its scalar output type.\nfunction filterInputForField(fieldType: FieldType): GraphQLInputObjectType | null {\n const scalar = scalarOutputType(fieldType)\n if (fieldType === 'boolean') return BooleanFilter\n if (fieldType === 'date') return DateTimeFilter\n if (fieldType === 'integer') return IntFilter\n if (fieldType === 'float') return FloatFilter\n if (fieldType === 'reference') return IDFilter\n if (scalar === null) return null // only 'enum' reaches here — not filterable via a scalar input\n return StringFilter\n}\n\n// Build the <Type>Filter input. Programmatic fields are excluded (no column).\nexport function buildFilterInputType(\n type: ParsedContentType | ParsedTaxonomyType\n): GraphQLInputObjectType | null {\n const fields: Record<string, { type: GraphQLInputObjectType }> = {}\n\n // System fields that are filterable columns.\n fields['id'] = { type: IDFilter }\n fields['published'] = { type: BooleanFilter }\n fields['createdAt'] = { type: DateTimeFilter }\n fields['updatedAt'] = { type: DateTimeFilter }\n if (type.schema_type === 'content-type') fields['slug'] = { type: StringFilter }\n\n for (const f of type.fields) {\n if (f.field_type === 'programmatic' || f.field_type === 'paragraph') continue\n if (f.field_type === 'image' || f.field_type === 'video' || f.field_type === 'file') continue\n const input = filterInputForField(f.field_type)\n if (input) fields[buildFieldNameMap([f.name]).toGraphql(f.name)] = { type: input }\n }\n\n if (Object.keys(fields).length === 0) return null\n return new GraphQLInputObjectType({ name: `${graphqlTypeName(type.name)}Filter`, fields })\n}\n\n// Convert a GraphQL filter argument into the repository's filters map, keyed by\n// snake_case column. eq → scalar, in → array (OR/IN), gt/gte/lt/lte → operator.\n//\n// `nameMap.toSchema` only ever recovers the schema field's LABEL (`field.name`)\n// from the GraphQL name — that mapping is unaware of storage columns. `columnFor`\n// (typically `FieldKeyMap.columnFor`) takes that label the rest of the way to the\n// storage column the repository actually filters on. It's optional so callers\n// that haven't threaded a FieldKeyMap through yet keep working unchanged: with no\n// mapper, or when it returns nothing for a given label, the label itself is used —\n// correct as long as it equals its column.\nexport function translateFilters(\n input: Record<string, unknown> | undefined,\n nameMap: { toSchema(g: string): string },\n columnFor?: (label: string) => string | undefined\n): Record<string, FilterValue> {\n const out: Record<string, FilterValue> = {}\n if (!input) return out\n\n for (const [gqlField, raw] of Object.entries(input)) {\n if (raw === null || typeof raw !== 'object') continue\n const label = nameMap.toSchema(gqlField)\n const column = columnFor?.(label) ?? label\n const spec = raw as Record<string, unknown>\n\n // eq/in take priority over range operators (gt/gte/lt/lte) when a client sends\n // both in one field filter — the range operators are silently dropped.\n if (spec['eq'] !== undefined) {\n out[column] = spec['eq'] as FilterValue\n } else if (Array.isArray(spec['in'])) {\n out[column] = spec['in'] as FilterValue\n } else {\n const op: FilterOperator = {}\n for (const k of ['gt', 'gte', 'lt', 'lte'] as const) {\n if (spec[k] !== undefined) op[k] = spec[k] as number | string\n }\n if (Object.keys(op).length > 0) out[column] = op\n }\n }\n return out\n}\n","import { GraphQLError } from 'graphql'\nimport type { ParsedField } from '@bobbykim/manguito-cms-core'\nimport type { GraphQLContext } from './context.js'\nimport { translateFilters } from './filters.js'\nimport { isColumnBacked, type FieldKeyMap } from '../field-keys.js'\n\ntype Row = Record<string, unknown>\n\nfunction codeError(code: string, message: string): GraphQLError {\n return new GraphQLError(message, { extensions: { code } })\n}\n\nfunction isPublished(item: Row | null): boolean {\n return !!item && item['published'] === true\n}\n\n/**\n * A GraphQL field's value on a content row. The GraphQL field NAME comes from\n * the field's label (see naming.ts), but the value lives under its storage\n * column, which differs once a field has been renamed.\n */\nexport function resolveFieldValue(field: ParsedField, row: Record<string, unknown>): unknown {\n const key = isColumnBacked(field) ? field.db_column.column_name : field.name\n return row[key] ?? null\n}\n\nexport function scalarFieldResolver(field: ParsedField) {\n return (parent: Row): unknown => resolveFieldValue(field, parent)\n}\n\nexport function relationFieldResolver(typeName: string, schemaFieldName: string) {\n return (parent: Row, _args: unknown, ctx: GraphQLContext): Promise<unknown> =>\n ctx.loaders.load(typeName, schemaFieldName, parent)\n}\n\nexport function programmaticFieldResolver(\n typeName: string,\n schemaFieldName: string,\n mediaFieldNames: readonly string[] = [],\n fieldKeys?: FieldKeyMap\n) {\n return async (parent: Row, _args: unknown, ctx: GraphQLContext): Promise<unknown> => {\n let p = ctx.programmaticMemo.get(parent)\n if (!p) {\n p = resolveProgrammaticRow(typeName, parent, ctx, mediaFieldNames, fieldKeys)\n ctx.programmaticMemo.set(parent, p)\n }\n return (await p)[schemaFieldName]\n }\n}\n\n// A programmatic resolver reads its record through `ctx.get()`, so that record\n// must look the same on both public surfaces. REST resolves media fields to full\n// objects before running resolvers; GraphQL reads through repositories that\n// resolve nothing, so without this `ctx.get('hero').url` would work over REST and\n// silently fall back to null over GraphQL.\n//\n// Media is resolved into a COPY, never the row itself: the dataloaders write\n// their results back into the row they are handed, and the real row must keep the\n// raw id so a query that also selects the media field still resolves. Handing an\n// already-resolved object back to the loader is exactly what broke media here in\n// the first place. Copies still batch — one media query per request, not per row.\n//\n// `ctx.get(fieldName)` takes the schema field name (the LABEL), so the record is\n// projected to labels before the resolver runs — the same order REST uses (see\n// \"Response projection order\" in routes/content.ts). GraphQL has no route-level\n// projection to double up with: every other field resolves per field by column\n// (resolveFieldValue), so this copy is the only mapped row, and it is mapped\n// strictly after the media loaders have run.\nasync function resolveProgrammaticRow(\n typeName: string,\n parent: Row,\n ctx: GraphQLContext,\n mediaFieldNames: readonly string[],\n fieldKeys?: FieldKeyMap\n): Promise<Record<string, unknown>> {\n const toLabels = (row: Row): Row => (fieldKeys ? fieldKeys.toLabels(row) : row)\n\n if (mediaFieldNames.length === 0) return ctx.resolver.resolveItem(typeName, toLabels(parent))\n\n const enriched: Row = { ...parent }\n await Promise.all(mediaFieldNames.map((name) => ctx.loaders.load(typeName, name, enriched)))\n return ctx.resolver.resolveItem(typeName, toLabels(enriched))\n}\n\ntype CollectionArgs = {\n page?: number\n perPage?: number\n /**\n * The sort enum's internal value (filters.ts `SORTABLE`), which mixes two key\n * spaces: `created_at` / `updated_at` are system COLUMNS, but `title` is a\n * schema field's LABEL. It must be mapped before it reaches the repository.\n */\n sortBy?: string\n sortOrder?: 'asc' | 'desc'\n filter?: Record<string, unknown>\n}\n\nexport function collectionResolver(\n typeName: string,\n nameMap: { toSchema(g: string): string },\n fieldKeys?: FieldKeyMap\n) {\n return async (_root: unknown, args: CollectionArgs, ctx: GraphQLContext) => {\n const page = args.page ?? 1\n const perPage = args.perPage ?? 10\n if (!Number.isInteger(page) || page < 1) {\n throw codeError('INVALID_PAGINATION', 'page must be ≥ 1')\n }\n if (!Number.isInteger(perPage) || perPage < 1 || perPage > 100) {\n throw codeError('INVALID_PAGINATION', 'perPage must be between 1 and 100')\n }\n const repo = ctx.repos[typeName]!\n // `ORDER BY` takes a storage column, never a label. `columnFor` resolves a\n // schema field's label to its column; a system column (`created_at`,\n // `updated_at`) is not a label at all, so it has no entry and falls\n // through unchanged. Identity today, since every label equals its column.\n const sortBy = args.sortBy ?? 'created_at'\n const sortColumn = fieldKeys?.columnFor(sortBy) ?? sortBy\n const result = await repo.findMany({\n published_only: true,\n page,\n per_page: perPage,\n sort_by: sortColumn as 'title' | 'created_at' | 'updated_at',\n sort_order: args.sortOrder ?? 'asc',\n filters: translateFilters(args.filter, nameMap, fieldKeys?.columnFor),\n })\n return { data: result.data as Row[], meta: result.meta }\n }\n}\n\nexport function singleBySlugResolver(typeName: string) {\n return async (_root: unknown, args: { slug: string }, ctx: GraphQLContext): Promise<Row | null> => {\n const repo = ctx.repos[typeName]!\n const item = (await repo.findBySlug(args.slug)) as Row | null\n return isPublished(item) ? item : null\n }\n}\n\nexport function singletonResolver(typeName: string) {\n return async (_root: unknown, _args: unknown, ctx: GraphQLContext): Promise<Row | null> => {\n const repo = ctx.repos[typeName]!\n const result = await repo.findMany({ published_only: true, page: 1, per_page: 1 })\n return (result.data[0] as Row | undefined) ?? null\n }\n}\n\nexport function taxonomySingleResolver(typeName: string) {\n return async (_root: unknown, args: { id: string }, ctx: GraphQLContext): Promise<Row | null> => {\n const repo = ctx.repos[typeName]!\n const item = (await repo.findOne(args.id)) as Row | null\n return isPublished(item) ? item : null\n }\n}\n","import {\n GraphQLSchema,\n GraphQLObjectType,\n GraphQLEnumType,\n GraphQLNonNull,\n GraphQLList,\n GraphQLID,\n GraphQLString,\n GraphQLInt,\n GraphQLBoolean,\n type GraphQLFieldConfig,\n type GraphQLOutputType,\n type GraphQLFieldConfigMap,\n} from 'graphql'\nimport type {\n SchemaRegistry,\n ParsedContentType,\n ParsedTaxonomyType,\n ParsedParagraphType,\n ParsedField,\n ParsedEnumType,\n} from '@bobbykim/manguito-cms-core'\nimport type { GraphQLContext } from './context.js'\nimport type { FieldKeyMap } from '../field-keys.js'\nimport { DateTimeScalar } from './scalars.js'\nimport { scalarOutputType } from './type-mapping.js'\nimport {\n graphqlTypeName,\n singleQueryName,\n collectionQueryName,\n toCamelCase,\n isValidGraphQLName,\n buildFieldNameMap,\n} from './naming.js'\nimport { SortOrderEnum, buildSortFieldEnum, buildFilterInputType } from './filters.js'\nimport {\n scalarFieldResolver,\n relationFieldResolver,\n programmaticFieldResolver,\n collectionResolver,\n singleBySlugResolver,\n singletonResolver,\n taxonomySingleResolver,\n} from './resolvers.js'\n\nconst PAGE_META = new GraphQLObjectType({\n name: 'PageMeta',\n fields: {\n total: { type: new GraphQLNonNull(GraphQLInt), resolve: (m) => m.total },\n page: { type: new GraphQLNonNull(GraphQLInt), resolve: (m) => m.page },\n perPage: { type: new GraphQLNonNull(GraphQLInt), resolve: (m) => m.per_page },\n totalPages: { type: new GraphQLNonNull(GraphQLInt), resolve: (m) => m.total_pages },\n hasNext: { type: new GraphQLNonNull(GraphQLBoolean), resolve: (m) => m.has_next },\n hasPrev: { type: new GraphQLNonNull(GraphQLBoolean), resolve: (m) => m.has_prev },\n },\n})\n\nconst MEDIA = new GraphQLObjectType({\n name: 'Media',\n fields: {\n id: { type: new GraphQLNonNull(GraphQLID), resolve: (m) => m.id },\n type: { type: new GraphQLNonNull(GraphQLString), resolve: (m) => m.type },\n url: { type: new GraphQLNonNull(GraphQLString), resolve: (m) => m.url },\n mimeType: { type: new GraphQLNonNull(GraphQLString), resolve: (m) => m.mime_type },\n alt: { type: GraphQLString, resolve: (m) => m.alt },\n fileSize: { type: GraphQLInt, resolve: (m) => m.file_size },\n width: { type: GraphQLInt, resolve: (m) => m.width },\n height: { type: GraphQLInt, resolve: (m) => m.height },\n duration: { type: GraphQLInt, resolve: (m) => m.duration },\n },\n})\n\nexport function buildGraphQLSchema(\n registry: SchemaRegistry,\n fieldKeyMaps: Record<string, FieldKeyMap> = {}\n): GraphQLSchema {\n const objectTypes = new Map<string, GraphQLObjectType>() // machineName → type\n const enumTypes = new Map<string, GraphQLEnumType>() // enum machineName → type (only when valid)\n\n // Enum types (only when every value is a valid GraphQL identifier).\n for (const [name, enumType] of Object.entries(registry.enum_types) as [string, ParsedEnumType][]) {\n if (enumType.values.every(isValidGraphQLName)) {\n enumTypes.set(\n name,\n new GraphQLEnumType({\n name: graphqlTypeName(name),\n values: Object.fromEntries(enumType.values.map((v) => [v, { value: v }])),\n })\n )\n } else {\n process.stderr.write(\n `⚠ enum '${name}' has values that aren't valid GraphQL identifiers; exposing as String\\n`\n )\n }\n }\n\n // Output type for one field (scalar, enum, relation, media, programmatic).\n function outputTypeForField(field: ParsedField): GraphQLOutputType {\n const scalar = scalarOutputType(field.field_type)\n if (scalar) return field.required ? new GraphQLNonNull(scalar) : scalar\n\n if (field.field_type === 'enum') {\n const ref = field.ui_component.component === 'select' ? field.ui_component.enum_ref : undefined\n const et = ref ? enumTypes.get(ref) : undefined\n const t = et ?? GraphQLString\n return field.required ? new GraphQLNonNull(t) : t\n }\n\n if (field.field_type === 'image' || field.field_type === 'video' || field.field_type === 'file') {\n return MEDIA\n }\n\n if (field.field_type === 'paragraph') {\n const ref = field.ui_component.component === 'paragraph-embed' ? field.ui_component.ref : undefined\n const target = ref ? objectTypes.get(ref) : undefined\n if (ref && !target) {\n process.stderr.write(\n `⚠ paragraph field '${field.name}' targets unknown paragraph type '${ref}'; exposing as Media\\n`\n )\n }\n return new GraphQLList(new GraphQLNonNull(target ?? MEDIA))\n }\n\n if (field.field_type === 'reference') {\n const ref = field.ui_component.component === 'typeahead-select' ? field.ui_component.ref : undefined\n const target = ref ? objectTypes.get(ref) : undefined\n if (ref && !target) {\n process.stderr.write(`⚠ reference field '${field.name}' targets unknown type '${ref}'; exposing as Media\\n`)\n }\n const rel = field.ui_component.component === 'typeahead-select' ? field.ui_component.rel : undefined\n const isMany = rel === 'many-to-many' || rel === 'one-to-many'\n const t = (target ?? MEDIA) as GraphQLObjectType\n return isMany ? new GraphQLList(new GraphQLNonNull(t)) : t\n }\n\n return GraphQLString\n }\n\n // Build the object type for a content/taxonomy/paragraph type. Fields are a\n // thunk so relations can reference types created later (circular graphs).\n function buildObjectType(\n machineName: string,\n type: ParsedContentType | ParsedTaxonomyType | ParsedParagraphType\n ): GraphQLObjectType {\n // Handed to the programmatic resolvers so they can present the same record\n // shape REST does, where media fields are resolved objects (see resolvers.ts).\n const mediaFieldNames = type.fields\n .filter((f) => f.field_type === 'image' || f.field_type === 'video' || f.field_type === 'file')\n .map((f) => f.name)\n\n return new GraphQLObjectType({\n name: graphqlTypeName(machineName),\n fields: () => {\n const fields: GraphQLFieldConfigMap<Record<string, unknown>, GraphQLContext> = {}\n // System fields.\n fields['id'] = { type: new GraphQLNonNull(GraphQLID), resolve: (p) => p['id'] }\n if (type.schema_type !== 'paragraph-type') {\n fields['published'] = { type: new GraphQLNonNull(GraphQLBoolean), resolve: (p) => p['published'] }\n }\n if (type.schema_type === 'content-type') {\n fields['slug'] = { type: new GraphQLNonNull(GraphQLString), resolve: (p) => p['slug'] }\n }\n fields['createdAt'] = { type: new GraphQLNonNull(DateTimeScalar), resolve: (p) => p['created_at'] }\n fields['updatedAt'] = { type: new GraphQLNonNull(DateTimeScalar), resolve: (p) => p['updated_at'] }\n\n for (const field of type.fields) {\n const gqlName = toCamelCase(field.name)\n const outType = outputTypeForField(field)\n let resolve: GraphQLFieldConfig<Record<string, unknown>, GraphQLContext>['resolve']\n if (field.field_type === 'programmatic') {\n resolve = programmaticFieldResolver(\n machineName,\n field.name,\n mediaFieldNames,\n fieldKeyMaps[machineName]\n )\n } else if (\n field.field_type === 'reference' ||\n field.field_type === 'paragraph' ||\n field.field_type === 'image' ||\n field.field_type === 'video' ||\n field.field_type === 'file'\n ) {\n resolve = relationFieldResolver(machineName, field.name)\n } else {\n resolve = scalarFieldResolver(field)\n }\n fields[gqlName] = { type: outType, resolve }\n }\n return fields\n },\n })\n }\n\n // Pass 1: create every object type (empty-safe thunks resolve later).\n // Track GraphQL type name → machine name so two schemas that collapse to the\n // same GraphQL type name (e.g. content--author / taxonomy--author both → \"Author\")\n // fail loud here instead of surfacing as a cryptic GraphQLSchema build error.\n const typeNameOwners = new Map<string, string>() // graphqlTypeName → machineName\n function registerTypeName(machineName: string): void {\n const gqlName = graphqlTypeName(machineName)\n const owner = typeNameOwners.get(gqlName)\n if (owner !== undefined && owner !== machineName) {\n throw new Error(\n `GraphQL type name collision: '${gqlName}' is produced by both '${owner}' and '${machineName}'. ` +\n `Rename one schema's machine name segment so their GraphQL type names differ.`\n )\n }\n typeNameOwners.set(gqlName, machineName)\n }\n\n for (const [name, ct] of Object.entries(registry.content_types)) {\n registerTypeName(name)\n objectTypes.set(name, buildObjectType(name, ct))\n }\n for (const [name, tt] of Object.entries(registry.taxonomy_types)) {\n registerTypeName(name)\n objectTypes.set(name, buildObjectType(name, tt))\n }\n for (const [name, pt] of Object.entries(registry.paragraph_types)) {\n registerTypeName(name)\n objectTypes.set(name, buildObjectType(name, pt))\n }\n\n // Pass 2: build the root Query type.\n const queryFields: GraphQLFieldConfigMap<unknown, GraphQLContext> = {}\n\n for (const [name, ct] of Object.entries(registry.content_types) as [string, ParsedContentType][]) {\n const objType = objectTypes.get(name)!\n const nameMap = buildFieldNameMap(ct.fields.map((f) => f.name))\n\n if (ct.only_one) {\n queryFields[singleQueryName(name)] = { type: objType, resolve: singletonResolver(name) }\n continue\n }\n\n const listType = new GraphQLObjectType({\n name: `${graphqlTypeName(name)}List`,\n fields: {\n data: { type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(objType))), resolve: (r) => r.data },\n meta: { type: new GraphQLNonNull(PAGE_META), resolve: (r) => r.meta },\n },\n })\n const filterType = buildFilterInputType(ct)\n\n queryFields[collectionQueryName(name)] = {\n type: new GraphQLNonNull(listType),\n args: {\n page: { type: GraphQLInt },\n perPage: { type: GraphQLInt },\n sortBy: { type: buildSortFieldEnum(graphqlTypeName(name)) },\n sortOrder: { type: SortOrderEnum },\n ...(filterType ? { filter: { type: filterType } } : {}),\n },\n resolve: collectionResolver(name, nameMap, fieldKeyMaps[name]),\n }\n queryFields[singleQueryName(name)] = {\n type: objType,\n args: { slug: { type: new GraphQLNonNull(GraphQLString) } },\n resolve: singleBySlugResolver(name),\n }\n }\n\n for (const [name, tt] of Object.entries(registry.taxonomy_types) as [string, ParsedTaxonomyType][]) {\n const objType = objectTypes.get(name)!\n const listType = new GraphQLObjectType({\n name: `${graphqlTypeName(name)}List`,\n fields: {\n data: { type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(objType))), resolve: (r) => r.data },\n meta: { type: new GraphQLNonNull(PAGE_META), resolve: (r) => r.meta },\n },\n })\n queryFields[collectionQueryName(name)] = {\n type: new GraphQLNonNull(listType),\n args: { page: { type: GraphQLInt }, perPage: { type: GraphQLInt } },\n resolve: collectionResolver(name, buildFieldNameMap(tt.fields.map((f) => f.name)), fieldKeyMaps[name]),\n }\n queryFields[singleQueryName(name)] = {\n type: objType,\n args: { id: { type: new GraphQLNonNull(GraphQLID) } },\n resolve: taxonomySingleResolver(name),\n }\n }\n\n return new GraphQLSchema({\n query: new GraphQLObjectType({ name: 'Query', fields: queryFields }),\n })\n}\n","import DataLoader from 'dataloader'\nimport type { DrizzlePostgresInstance } from '@bobbykim/manguito-cms-db'\nimport type {\n SchemaRegistry,\n ParsedContentType,\n ParsedTaxonomyType,\n ParsedParagraphType,\n} from '@bobbykim/manguito-cms-core'\nimport { buildRelationsMap, resolveRelationField, type RelationDef } from '../relations.js'\n\nexport interface RelationLoaders {\n load(typeName: string, fieldName: string, parent: Record<string, unknown>): Promise<unknown>\n}\n\ntype ParentRow = Record<string, unknown>\n\n// One RelationLoaders instance per request. Loaders batch sibling parents per\n// (type, field); the shared cache dedupes target lookups across all loaders in\n// the request, mirroring the repository's resolveRows cache.\nexport function createRelationLoaders(\n db: DrizzlePostgresInstance,\n registry: SchemaRegistry\n): RelationLoaders {\n const relMaps = new Map<string, Record<string, RelationDef>>()\n const loaders = new Map<string, DataLoader<ParentRow, unknown>>()\n const cache = new Map<string, unknown>()\n\n function relationsFor(typeName: string): Record<string, RelationDef> {\n let m = relMaps.get(typeName)\n if (!m) {\n const type = (registry.content_types[typeName] ??\n registry.taxonomy_types[typeName] ??\n registry.paragraph_types[typeName]) as\n | ParsedContentType\n | ParsedTaxonomyType\n | ParsedParagraphType\n | undefined\n m = type ? buildRelationsMap(type.fields, registry) : {}\n relMaps.set(typeName, m)\n }\n return m\n }\n\n function loaderFor(typeName: string, fieldName: string, rel: RelationDef): DataLoader<ParentRow, unknown> {\n const key = `${typeName}:${fieldName}`\n let dl = loaders.get(key)\n if (!dl) {\n dl = new DataLoader<ParentRow, unknown>(\n async (parents) => {\n const rows = parents as ParentRow[]\n // GraphQL is public-only (see app.ts) — always filter relation targets\n // to published rows, mirroring the REST public repos' publishedRelations.\n await resolveRelationField(db, rows, fieldName, rel, cache, true)\n return rows.map((r) => r[fieldName])\n },\n // Batch within a tick; do not memoize by parent identity (rows are mutated\n // and may recur across nesting levels).\n { cache: false }\n )\n loaders.set(key, dl)\n }\n return dl\n }\n\n return {\n async load(typeName, fieldName, parent) {\n const rel = relationsFor(typeName)[fieldName]\n if (!rel) return null\n return loaderFor(typeName, fieldName, rel).load(parent)\n },\n }\n}\n","import { EnvelopArmor } from '@escape.tech/graphql-armor'\nimport { NoSchemaIntrospectionCustomRule } from 'graphql'\n\n// Depth + cost + alias + directive limits via GraphQL Armor. Returns { plugins } to\n// spread into Yoga's `plugins` array.\n//\n// Note: the installed `@escape.tech/graphql-armor` (3.2.0) exports `EnvelopArmor`, a\n// class whose `.protect()` returns `{ plugins }` — the standalone `EnvelopArmorPlugin`\n// export is a function that returns a single plugin, not a class with `.protect()`.\nexport function buildArmorPlugin(options: { maxDepth: number; maxComplexity: number }): {\n plugins: unknown[]\n} {\n const armor = new EnvelopArmor({\n maxDepth: { n: options.maxDepth },\n costLimit: { maxCost: options.maxComplexity },\n maxAliases: { n: 15 },\n maxDirectives: { n: 50 },\n blockFieldSuggestion: { enabled: true },\n // maxTokens is intentionally left at Armor's secure default (n=1000) to bound\n // GraphQL document token count at parse time, a layer the depth/cost/alias/directive\n // limits do not cover.\n })\n return armor.protect()\n}\n\n// Disables introspection (and the __schema/__type meta-fields) in production by\n// adding graphql's built-in validation rule when `enabled` is false.\nexport function introspectionPlugin(enabled: boolean): {\n onValidate(payload: { addValidationRule: (rule: unknown) => void }): void\n} {\n return {\n onValidate({ addValidationRule }) {\n if (!enabled) addValidationRule(NoSchemaIntrospectionCustomRule)\n },\n }\n}\n","import { createYoga, maskError } from 'graphql-yoga'\nimport type { Handler } from 'hono'\nimport type { SchemaRegistry, ContentRepository } from '@bobbykim/manguito-cms-core'\nimport type { DrizzlePostgresInstance } from '@bobbykim/manguito-cms-db'\nimport type { ProgrammaticResolver } from '../programmatic/resolve.js'\nimport type { FieldKeyMap } from '../field-keys.js'\nimport type { GraphQLContext } from './context.js'\nimport { buildGraphQLSchema } from './schema.js'\nimport { createRelationLoaders } from './dataloaders.js'\nimport { buildArmorPlugin, introspectionPlugin } from './security.js'\n\nexport type ResolvedGraphQLOptions = {\n enabled: boolean\n maxDepth: number\n maxComplexity: number\n graphiql: boolean\n introspection: boolean\n}\n\n// Yoga decides whether an error is safe to show a client with\n// `error instanceof GraphQLError`. That check is realm-sensitive: GraphQL Armor\n// is CJS and throws a GraphQLError built from graphql's CJS entry, while Yoga is\n// ESM and compares against the class from graphql's ESM entry. Same version, two\n// classes — so `instanceof` fails and a depth/complexity rejection, which is the\n// client's own query being too large, was masked as\n// `INTERNAL_SERVER_ERROR: Unexpected error.` and logged server-side as a fault.\n//\n// Identify by name instead (what envelop's own masking does), walking the\n// originalError chain the same way. A GraphQLError with no originalError was\n// constructed deliberately by a validation rule or by us and carries no internals;\n// anything else — a resolver throwing, a driver error — still goes to Yoga's\n// masking untouched, dev-mode originalError detail included.\nfunction isClientSafeGraphQLError(error: unknown): boolean {\n let current = error as { name?: string; originalError?: unknown } | null | undefined\n while (current != null && typeof current === 'object' && current.name === 'GraphQLError') {\n if (current.originalError == null) return true\n current = current.originalError as typeof current\n }\n return false\n}\n\nexport function createGraphQLHandler(\n registry: SchemaRegistry,\n repos: Record<string, ContentRepository<unknown>>,\n fieldKeyMaps: Record<string, FieldKeyMap>,\n resolver: ProgrammaticResolver,\n db: DrizzlePostgresInstance,\n options: ResolvedGraphQLOptions\n): Handler {\n const schema = buildGraphQLSchema(registry, fieldKeyMaps)\n const { plugins } = buildArmorPlugin({\n maxDepth: options.maxDepth,\n maxComplexity: options.maxComplexity,\n })\n\n const yoga = createYoga<Record<string, never>, GraphQLContext>({\n schema,\n graphqlEndpoint: '/graphql',\n graphiql: options.graphiql,\n landingPage: false,\n maskedErrors: {\n maskError: (error, message, isDev) =>\n isClientSafeGraphQLError(error) ? (error as Error) : maskError(error, message, isDev),\n },\n // buildArmorPlugin's return type is `unknown[]` (see security.ts) since Armor's\n // plugin shapes aren't part of its public typings; Yoga's `plugins` option only\n // accepts its own `Plugin` type, so the cast bridges the two.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n plugins: [...(plugins as any[]), introspectionPlugin(options.introspection)],\n context: (): GraphQLContext => ({\n db,\n registry,\n repos,\n resolver,\n loaders: createRelationLoaders(db, registry),\n programmaticMemo: new WeakMap(),\n }),\n })\n\n return (c) => yoga.fetch(c.req.raw, {})\n}\n","import type {\n APIAdapter,\n ResolvedRateLimitConfig,\n GraphQLModuleConfig,\n ResolvedGraphQLConfig,\n} from '@bobbykim/manguito-cms-core'\nimport { normalizePrefix } from './paths.js'\n\nexport { createCmsApp } from './app.js'\nexport type { CreateCmsAppOptions } from './app.js'\n\nexport { createServer } from './server/node.js'\nexport type { NodeServerOptions } from './server/node.js'\n\nexport { createProgrammaticResolver, validateResolverBindings, resolverKey } from './programmatic/resolve.js'\nexport type { ResolverMap, ProgrammaticResolver } from './programmatic/resolve.js'\n\n// ─── User-facing config factory ───────────────────────────────────────────────\n\nexport type APIAdapterOptions = {\n prefix?: string\n media?: {\n max_file_size?: number\n }\n rateLimit?: ResolvedRateLimitConfig\n graphql?: GraphQLModuleConfig\n}\n\nfunction resolveGraphQL(cfg: GraphQLModuleConfig): ResolvedGraphQLConfig {\n const devDefault = process.env['NODE_ENV'] !== 'production'\n return {\n enabled: cfg.enabled ?? false,\n maxDepth: cfg.maxDepth ?? 8,\n maxComplexity: cfg.maxComplexity ?? 1000,\n graphiql: cfg.graphiql ?? devDefault,\n introspection: cfg.introspection ?? cfg.graphiql ?? devDefault,\n }\n}\n\nexport function createAPIAdapter(options: APIAdapterOptions = {}): APIAdapter {\n const prefix = normalizePrefix(options.prefix)\n const media = options.media?.max_file_size !== undefined\n ? { max_file_size: options.media.max_file_size }\n : undefined\n\n return {\n prefix,\n ...(media !== undefined && { media }),\n ...(options.rateLimit !== undefined && { rateLimit: options.rateLimit }),\n ...(options.graphql !== undefined && { graphql: resolveGraphQL(options.graphql) }),\n }\n}\n","// ─── Public route path construction ───────────────────────────────────────────\n//\n// The single place public route paths are built. Registrators must not hardcode\n// '/api/...': the prefix is configurable (api.prefix) and Stage 2 inserts a\n// version segment here.\n\nexport function normalizePrefix(prefix: string | undefined): string {\n if (prefix === undefined) return '/api'\n const withSlash = prefix.startsWith('/') ? prefix : `/${prefix}`\n const trimmed = withSlash.replace(/\\/+$/, '')\n return trimmed === '' ? '/api' : trimmed\n}\n\nexport type PublicPaths = {\n collection(basePath: string): string\n item(basePath: string): string\n taxonomyCollection(typeName: string): string\n taxonomyItem(typeName: string): string\n mediaCollection(): string\n mediaItem(): string\n openapi(): string\n}\n\nexport function createPublicPaths(prefix: string): PublicPaths {\n return {\n collection: (basePath) => `${prefix}/${basePath}`,\n item: (basePath) => `${prefix}/${basePath}/:slug`,\n taxonomyCollection: (typeName) => `${prefix}/taxonomy/${typeName}`,\n taxonomyItem: (typeName) => `${prefix}/taxonomy/${typeName}/:id`,\n mediaCollection: () => `${prefix}/media`,\n mediaItem: () => `${prefix}/media/:id`,\n openapi: () => `${prefix}/openapi.json`,\n }\n}\n","import { Hono } from 'hono'\nimport type { Handler } from 'hono'\nimport { sql } from 'drizzle-orm'\nimport type { StorageAdapter, SchemaRegistry, ParsedContentType, ParsedTaxonomyType, ParsedParagraphType, ResolvedRateLimitConfig, CorsConfig, ResolvedGraphQLConfig } from '@bobbykim/manguito-cms-core'\nimport type { DrizzlePostgresInstance } from '@bobbykim/manguito-cms-db'\nimport { createCorsMiddleware } from './middleware/cors.js'\nimport { createSecurityHeadersMiddleware } from './middleware/security-headers.js'\nimport { errorHandler } from './middleware/error.js'\nimport { createAuthMiddleware } from './middleware/auth.js'\nimport { mustChangePasswordCheck } from './middleware/must-change-password.js'\nimport { createPermissionMiddleware } from './middleware/permission.js'\nimport { createHierarchyMiddleware } from './middleware/hierarchy.js'\nimport { resolveListRateLimit } from './middleware/rate-limit.js'\nimport { buildRolesRegistry } from './auth/registry.js'\nimport { createProgrammaticResolver, validateResolverBindings, type ResolverMap } from './programmatic/resolve.js'\nimport { registerPublicContentRoutes } from './routes/content.js'\nimport { registerPublicMediaRoutes } from './routes/media.js'\nimport { registerAdminContentRoutes } from './routes/admin/content.js'\nimport { registerAdminMediaRoutes } from './routes/admin/media.js'\nimport { registerAuthRoutes } from './routes/admin/auth.js'\nimport { registerUserRoutes } from './routes/admin/users.js'\nimport { registerConfigRoute } from './routes/admin/config.js'\nimport { registerSchemaRoute } from './routes/admin/schema.js'\nimport { createDrizzleContentRepository } from './repositories/content.js'\nimport { buildRelationsMap } from './relations.js'\nimport { createMediaRepository } from './repositories/media.js'\nimport { createFieldKeyMap, type FieldKeyMap } from './field-keys.js'\nimport { SORTABLE_FIELDS } from './routes/query-params.js'\nimport { buildProjectors, type Projectors } from './projector.js'\nimport { normalizePrefix, createPublicPaths } from './paths.js'\n\nexport type CreateCmsAppOptions = {\n /** CMS display name shown in GET /admin/api/config. Defaults to 'Manguito CMS'. */\n name?: string\n prefix?: string\n storage: StorageAdapter\n registry: SchemaRegistry\n db: DrizzlePostgresInstance\n media?: {\n /** Optional upload size cap in bytes. Returned by GET /admin/api/config for client-side UX validation. */\n max_file_size?: number\n }\n rateLimit?: ResolvedRateLimitConfig\n cors?: CorsConfig\n /** Programmatic field resolvers, keyed `${schema}::${field}`. */\n resolvers?: ResolverMap\n /** GraphQL module config (resolved). When enabled, mounts POST /graphql. */\n graphql?: ResolvedGraphQLConfig\n}\n\nexport interface ManguitoCmsAPIAdapter {\n readonly prefix: string\n readonly app: Hono\n}\n\nconst MISSING_STORAGE_ERROR = `✗ api.storage is required but not configured.\n Add a storage adapter to your manguito.config.ts:\n\n api: createAPIAdapter({\n storage: createLocalAdapter(), // dev\n // storage: createS3Adapter({ bucket: '...', region: '...' }) // production\n })\n\nExiting.`\n\nexport function createCmsApp(options: CreateCmsAppOptions): ManguitoCmsAPIAdapter {\n if (!options.storage) {\n throw new Error(MISSING_STORAGE_ERROR)\n }\n\n const prefix = normalizePrefix(options.prefix)\n const publicPaths = createPublicPaths(prefix)\n const { storage, registry, db, rateLimit, media, cors } = options\n const cmsName = options.name ?? 'Manguito CMS'\n const maxFileSize = media?.max_file_size\n\n // Build roles registry — throws immediately if roles are missing or invalid.\n // The server must not start with a broken registry.\n const rolesRegistry = buildRolesRegistry(registry.roles.roles)\n\n // Validate programmatic-field bindings at startup (throws on mismatch, like\n // the storage/roles checks above). Undefined resolvers ⇒ empty map.\n const resolverMap = options.resolvers ?? new Map()\n validateResolverBindings(registry, resolverMap)\n const programmaticResolver = createProgrammaticResolver(resolverMap)\n\n const app = new Hono()\n\n // Security headers for all routes — registered before CORS so normal and\n // CORS-handled responses carry the conservative defaults. Responses produced\n // by app.onError (thrown errors) bypass this middleware's post-next step;\n // those are JSON error envelopes with no clickjacking/XSS surface. CSP\n // connect-src is derived from the storage adapter: presigned uploads go\n // browser→storage directly, so that host must be allowlisted.\n // The GraphiQL explorer (dev-only by default) boots from a CDN bundle plus\n // inline scripts, which the strict script-src blocks — so when it is enabled\n // the /graphql path alone gets a relaxed CSP (ADR api/0010).\n const uploadOrigins = storage.getUploadOrigins?.() ?? []\n const graphiqlEnabled = options.graphql?.enabled === true && options.graphql.graphiql === true\n app.use(\n '*',\n createSecurityHeadersMiddleware({\n connectSrc: uploadOrigins,\n ...(graphiqlEnabled && { graphiqlPath: '/graphql' }),\n }),\n )\n\n // CORS for all routes\n app.use('*', createCorsMiddleware(cors ?? { origin: '*', enabled: true }))\n app.onError(errorHandler)\n\n // Rate limiter for public list endpoints — threaded into route registrators,\n // applied only to paginated collection routes (not single-item lookups).\n // `undefined` when disabled via rateLimit.findAll === '*'.\n const listRateLimit = resolveListRateLimit(rateLimit)\n\n // ── Middleware factories — all close over rolesRegistry built once at startup ──\n\n const authMiddleware = createAuthMiddleware(db)\n const requirePermission = createPermissionMiddleware(rolesRegistry)\n\n const getUserRole = async (userId: string): Promise<string | null> => {\n const result = await db.execute(\n sql`SELECT r.name AS role\n FROM users u\n JOIN roles r ON r.id = u.role_id\n WHERE u.id = ${userId}\n LIMIT 1`\n )\n return (result.rows[0] as { role: string } | undefined)?.role ?? null\n }\n\n const requireHierarchy = createHierarchyMiddleware(rolesRegistry, getUserRole)\n\n // ── Field key maps ──────────────────────────────────────────────────────────\n //\n // One per content, taxonomy AND paragraph type, built once at startup, keyed\n // by machine name. Throws on a label / column collision — the server must not\n // boot with an ambiguous mapping.\n //\n // Paragraph types belong in the same object as the other two, not a sidecar:\n // both consumers index it by machine name over a key space that includes\n // them. `buildProjectors` walks `registry.paragraph_types` to project\n // paragraph children, and `graphql/schema.ts`'s `buildObjectType` runs over\n // paragraph types too — so a programmatic field on a paragraph type needs its\n // map here to be handed a label-keyed record, exactly like one on a content\n // type. Every other GraphQL field resolves per field by column and is\n // indifferent to this map.\n const fieldKeyMaps: Record<string, FieldKeyMap> = Object.fromEntries([\n ...Object.entries(registry.content_types).map(([typeName, ct]) => [\n typeName,\n createFieldKeyMap((ct as ParsedContentType).fields),\n ]),\n ...Object.entries(registry.taxonomy_types).map(([typeName, tt]) => [\n typeName,\n createFieldKeyMap((tt as ParsedTaxonomyType).fields),\n ]),\n ...Object.entries(registry.paragraph_types).map(([typeName, pt]) => [\n typeName,\n createFieldKeyMap((pt as ParsedParagraphType).fields),\n ]),\n ])\n\n // Recursive outbound projection. Every read response is projected through\n // this rather than through a bare toLabels.\n const projectors: Projectors = buildProjectors(registry, fieldKeyMaps)\n\n // Maps SORTABLE_FIELDS' labels to this type's storage columns, so the\n // repository can validate sort_by against columns once the route has mapped\n // it. Identity today for every schema the parser produces — diverges only\n // once schema versioning lands and a label's column_name differs from it.\n const sortableColumnsFor = (typeName: string): Set<string> =>\n new Set(\n [...SORTABLE_FIELDS].map((label) => fieldKeyMaps[typeName]!.columnFor(label) ?? label)\n )\n\n // ── Repositories ──────────────────────────────────────────────────────────────\n\n const contentRepos = Object.fromEntries(\n Object.entries(registry.content_types).map(([typeName, ct]) => [\n typeName,\n createDrizzleContentRepository(db, (ct as ParsedContentType).db.table_name, {\n sortableColumns: sortableColumnsFor(typeName),\n }),\n ])\n )\n const taxonomyRepos = Object.fromEntries(\n Object.entries(registry.taxonomy_types).map(([typeName, tt]) => [\n typeName,\n createDrizzleContentRepository(db, (tt as ParsedTaxonomyType).db.table_name, {\n sortableColumns: sortableColumnsFor(typeName),\n }),\n ])\n )\n const repos = { ...contentRepos, ...taxonomyRepos }\n const mediaRepo = createMediaRepository(db)\n\n // ── Public-only repos ───────────────────────────────────────────────────────\n //\n // Relation resolution (?include=, always-resolved media) is wired only for\n // the public read API. Admin routes reuse `repos` above and must keep\n // receiving raw foreign-key IDs back from findOne/update — the edit forms\n // (e.g. MediaUpload.vue) expect a plain ID string, not a resolved object.\n const publicContentRepos = Object.fromEntries(\n Object.entries(registry.content_types).map(([typeName, ct]) => [\n typeName,\n createDrizzleContentRepository(db, (ct as ParsedContentType).db.table_name, {\n relations: buildRelationsMap((ct as ParsedContentType).fields, registry),\n publishedRelations: true,\n sortableColumns: sortableColumnsFor(typeName),\n }),\n ])\n )\n const publicTaxonomyRepos = Object.fromEntries(\n Object.entries(registry.taxonomy_types).map(([typeName, tt]) => [\n typeName,\n createDrizzleContentRepository(db, (tt as ParsedTaxonomyType).db.table_name, {\n relations: buildRelationsMap((tt as ParsedTaxonomyType).fields, registry),\n publishedRelations: true,\n sortableColumns: sortableColumnsFor(typeName),\n }),\n ])\n )\n const publicRepos = { ...publicContentRepos, ...publicTaxonomyRepos }\n\n // ── GraphQL repos ───────────────────────────────────────────────────────────\n //\n // GraphQL resolves relations lazily, per selected field, through its own\n // request-scoped dataloaders — so its repos must NOT resolve relations\n // eagerly. Reusing `publicRepos` here is actively wrong: their eager pass\n // overwrites a media field's column (the FK column and the field share a\n // name) with the resolved media object, and the dataloader then reads that\n // object back where a UUID is expected. It also pays for resolving every\n // relation on every query, including the ones the client never selected.\n //\n // These are still public reads: every GraphQL resolver passes\n // `published_only: true`, and the dataloaders filter relation targets by\n // published (ADR api/0002). Built separately from the admin `repos` so a\n // future change there can never silently widen the public surface.\n //\n // They do take `sortableColumns`, like every other repo here: the GraphQL\n // sort enum's `title` value is a schema field's LABEL, which\n // `collectionResolver` maps to a column before calling findMany.\n const graphqlRepos = Object.fromEntries([\n ...Object.entries(registry.content_types).map(([typeName, ct]) => [\n typeName,\n createDrizzleContentRepository(db, (ct as ParsedContentType).db.table_name, {\n sortableColumns: sortableColumnsFor(typeName),\n }),\n ]),\n ...Object.entries(registry.taxonomy_types).map(([typeName, tt]) => [\n typeName,\n createDrizzleContentRepository(db, (tt as ParsedTaxonomyType).db.table_name, {\n sortableColumns: sortableColumnsFor(typeName),\n }),\n ]),\n ])\n\n // ── Auth routes registered directly on app BEFORE the blanket use() calls ───────\n //\n // registerAuthRoutes uses full paths (/admin/api/auth/login etc.). Mounting via\n // app.route('/admin/api/auth', subRouter) would strip that prefix and make the\n // routes unreachable, so we register directly on app.\n //\n // In Hono, handlers registered before a use() call are reached first. A route\n // handler that returns a Response does not call next(), so the blanket\n // authMiddleware registered below never runs for matched auth paths.\n registerAuthRoutes(app, db)\n\n // ── Blanket middleware for all /admin/api/* (registered after auth routes) ─────\n app.use('/admin/api/*', authMiddleware)\n app.use('/admin/api/*', mustChangePasswordCheck)\n\n // ── OpenAPI spec endpoints ────────────────────────────────────────────────────\n\n app.get(publicPaths.openapi(), (c) => {\n const paths: Record<string, unknown> = {}\n\n for (const ct of Object.values(registry.content_types) as ParsedContentType[]) {\n const base = ct.default_base_path\n if (ct.only_one) {\n paths[publicPaths.collection(base)] = { get: { summary: `Get ${ct.label}`, tags: [ct.label] } }\n } else {\n paths[publicPaths.collection(base)] = { get: { summary: `List published ${ct.label}`, tags: [ct.label] } }\n paths[publicPaths.item(base).replace(':slug', '{slug}')] = {\n get: { summary: `Get ${ct.label} by slug`, tags: [ct.label] },\n }\n }\n }\n\n for (const tt of Object.values(registry.taxonomy_types) as ParsedTaxonomyType[]) {\n paths[publicPaths.taxonomyCollection(tt.name)] = { get: { summary: `List published ${tt.label}`, tags: [tt.label] } }\n paths[publicPaths.taxonomyItem(tt.name).replace(':id', '{id}')] = {\n get: { summary: `Get ${tt.label} by id`, tags: [tt.label] },\n }\n }\n\n paths[publicPaths.mediaCollection()] = { get: { summary: 'List media items', tags: ['Media'] } }\n paths[publicPaths.mediaItem().replace(':id', '{id}')] = { get: { summary: 'Get media item by id', tags: ['Media'] } }\n\n return c.json({\n openapi: '3.0.3',\n info: { title: 'Manguito CMS Public API', version: '1.0.0' },\n paths,\n })\n })\n // Admin spec — auth covered by the blanket use() above\n app.get('/admin/api/openapi.json', (c) =>\n c.json({\n openapi: '3.0.3',\n info: { title: 'Manguito CMS Admin API', version: '1.0.0' },\n paths: {},\n })\n )\n\n // ── Public routes ─────────────────────────────────────────────────────────────\n\n registerPublicContentRoutes(app, registry, publicRepos, projectors, publicPaths, listRateLimit, programmaticResolver)\n registerPublicMediaRoutes(app, mediaRepo, publicPaths, listRateLimit)\n\n // ── GraphQL (opt-in) ──────────────────────────────────────────────────────────\n //\n // Loaded via a DYNAMIC import so `graphql`/`graphql-yoga` never load unless a\n // consumer opts in — the `.` entry (this file) must stay free of a static\n // dependency on the graphql/ subpath (ADR api/0006). The import kicks off\n // immediately (schema-building and Armor-plugin setup are synchronous once the\n // module is loaded; only the `import()` itself is async), and any request that\n // lands on /graphql before it resolves awaits the same in-flight promise —\n // there's a single shared `ready` promise, never a re-import per request.\n // Unauthenticated by design: it sits alongside /api/*, not behind the\n // /admin/api/* auth middleware registered above, and only ever reads through\n // `publicRepos` (published-only, same as the REST public routes).\n //\n // `buildGraphQLSchema` can throw synchronously (e.g. a GraphQL type-name\n // collision between a content type and a taxonomy type) — since that throw\n // happens inside a `.then()` callback, it rejects `ready`. A `.catch` is\n // attached below so that rejection is HANDLED: an unhandled rejection here\n // would otherwise crash the entire Node process at startup, taking down all\n // REST routes with it, regardless of whether any client ever hits /graphql.\n // Init failure is instead contained to a 500 on /graphql itself.\n if (options.graphql?.enabled) {\n const gqlOptions = options.graphql\n let gqlHandler: Handler | null = null\n let initError: unknown = null\n const ready = import('./graphql/handler.js')\n .then(({ createGraphQLHandler }) => {\n gqlHandler = createGraphQLHandler(registry, graphqlRepos, fieldKeyMaps, programmaticResolver, db, gqlOptions)\n })\n .catch((err: unknown) => {\n initError = err\n const message = err instanceof Error ? err.message : String(err)\n process.stderr.write(`✗ GraphQL schema failed to initialize; /graphql will return 500. ${message}\\n`)\n })\n const invokeGraphQL: Handler = async (c) => {\n if (!gqlHandler && !initError) await ready\n if (!gqlHandler) {\n return c.json(\n { ok: false, error: { code: 'GRAPHQL_INIT_FAILED', message: 'GraphQL schema failed to initialize' } },\n 500\n )\n }\n return gqlHandler(c, async () => {})\n }\n if (listRateLimit) {\n app.all('/graphql', listRateLimit, invokeGraphQL)\n } else {\n app.all('/graphql', invokeGraphQL)\n }\n }\n\n // ── Admin routes — all registered AFTER the blanket use() so auth middleware\n // runs before every handler. registerConfigRoute is called first so its\n // GET /admin/api/config handler wins over the stale stub in content.ts.\n registerConfigRoute(\n app,\n {\n name: cmsName,\n ...(maxFileSize !== undefined && { maxFileSize }),\n // Cloud storage (s3/cloudinary) issues real presigned URLs, so the admin\n // uploads straight to it — never routing the file through the server\n // (which breaks on serverless). Local storage has no presigned receiver,\n // so it uses the direct upload endpoint.\n presignedUploads: storage.type !== 'local',\n },\n rolesRegistry,\n db,\n )\n registerSchemaRoute(app, registry, db)\n registerUserRoutes(app, db, requirePermission, requireHierarchy)\n\n registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo, requirePermission, db)\n registerAdminMediaRoutes(app, mediaRepo, storage, requirePermission, maxFileSize)\n\n return { prefix, app }\n}\n","import type { MiddlewareHandler } from 'hono'\nimport type { CorsConfig } from '@bobbykim/manguito-cms-core'\n\nexport function createCorsMiddleware(corsConfig: CorsConfig): MiddlewareHandler {\n const allowList = Array.isArray(corsConfig.origin)\n ? corsConfig.origin\n : [corsConfig.origin]\n const wildcard = allowList.includes('*')\n const methods = corsConfig.methods?.join(',') ?? 'GET,POST,PATCH,DELETE,OPTIONS'\n\n return async function corsMiddleware(c, next) {\n if (corsConfig.enabled === false) {\n return next()\n }\n\n const requestOrigin = c.req.header('origin')\n\n if (wildcard) {\n // Wildcard cannot be combined with credentials per the CORS spec.\n c.res.headers.set('Access-Control-Allow-Origin', '*')\n } else if (requestOrigin && allowList.includes(requestOrigin)) {\n c.res.headers.set('Access-Control-Allow-Origin', requestOrigin)\n c.res.headers.set('Vary', 'Origin')\n if (corsConfig.credentials === true) {\n c.res.headers.set('Access-Control-Allow-Credentials', 'true')\n }\n }\n // Non-matching origins: emit no Allow-Origin (browser blocks the read).\n\n c.res.headers.set('Access-Control-Allow-Methods', methods)\n c.res.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization')\n\n if (c.req.method === 'OPTIONS') {\n return c.newResponse(null, 204)\n }\n return next()\n }\n}\n","import type { MiddlewareHandler } from 'hono'\n\nexport type SecurityHeadersOptions = {\n /** Extra origins allowed for connect-src (e.g. the storage upload host). */\n connectSrc?: string[]\n /**\n * Path of the GraphiQL explorer (e.g. `/graphql`), set ONLY when GraphiQL is\n * enabled. That one path gets a relaxed CSP so the explorer can boot; every\n * other route keeps the strict policy. Leave undefined to disable entirely.\n */\n graphiqlPath?: string\n}\n\n/** CDN origin Yoga's GraphiQL loads its UI bundle and stylesheet from. */\nconst GRAPHIQL_CDN = 'https://unpkg.com'\n\n/**\n * Conservative security headers. CSP allows same-origin scripts/styles/fonts\n * for the admin SPA (served same-origin) and blocks framing. connect-src is\n * 'self' plus any storage upload origins passed in — presigned uploads go\n * browser→storage directly (ADR api/0004), so that host must be allowlisted.\n *\n * The GraphiQL explorer is the one documented exception: its UI is a CDN bundle\n * booted by inline scripts, which the strict policy blocks. When `graphiqlPath`\n * is set (i.e. GraphiQL is enabled — off in production by default), requests to\n * exactly that path get `'unsafe-inline'` + the CDN origin for script/style.\n * The relaxation is scoped to that single path and never applies to the admin\n * SPA, the REST API, or GraphQL POST queries from other origins.\n */\nexport function createSecurityHeadersMiddleware(\n options: SecurityHeadersOptions = {},\n): MiddlewareHandler {\n const connectSrc = [\"'self'\", ...(options.connectSrc ?? [])].join(' ')\n\n type CspParts = {\n scriptSrc: string\n styleSrc: string\n connectSrc: string\n /** Emitted only for GraphiQL, which runs Monaco editor workers off blob: URLs. */\n workerSrc?: string\n }\n\n function buildCsp(parts: CspParts): string {\n return [\n \"default-src 'self'\",\n \"img-src 'self' data: https:\",\n \"media-src 'self' https:\",\n `style-src ${parts.styleSrc}`,\n `script-src ${parts.scriptSrc}`,\n \"font-src 'self' data:\",\n `connect-src ${parts.connectSrc}`,\n ...(parts.workerSrc ? [`worker-src ${parts.workerSrc}`] : []),\n \"frame-ancestors 'none'\",\n \"base-uri 'self'\",\n \"form-action 'self'\",\n ].join('; ')\n }\n\n const csp = buildCsp({\n scriptSrc: \"'self'\",\n styleSrc: \"'self' 'unsafe-inline'\",\n connectSrc,\n })\n\n // GraphiQL needs four exceptions, all confined to its own path: the CDN for\n // the UI bundle + stylesheet, 'unsafe-inline' for its bootstrap scripts,\n // connect-src for the Monaco worker bundles it fetches from that CDN, and\n // worker-src blob: because it spawns those workers from Blob URLs.\n const graphiqlCsp = buildCsp({\n scriptSrc: `'self' 'unsafe-inline' ${GRAPHIQL_CDN}`,\n styleSrc: `'self' 'unsafe-inline' ${GRAPHIQL_CDN}`,\n connectSrc: `${connectSrc} ${GRAPHIQL_CDN}`,\n workerSrc: \"'self' blob:\",\n })\n const graphiqlPath = options.graphiqlPath\n\n return async function securityHeaders(c, next) {\n await next()\n c.res.headers.set('X-Content-Type-Options', 'nosniff')\n c.res.headers.set('X-Frame-Options', 'DENY')\n c.res.headers.set('Referrer-Policy', 'no-referrer')\n const isGraphiql = graphiqlPath !== undefined && c.req.path === graphiqlPath\n c.res.headers.set('Content-Security-Policy', isGraphiql ? graphiqlCsp : csp)\n }\n}\n","import type { ErrorHandler } from 'hono'\nimport type { ErrorCode } from '@bobbykim/manguito-cms-core'\n\nconst ERROR_STATUS_MAP: Partial<Record<ErrorCode, number>> = {\n NOT_FOUND: 404,\n SLUG_NOT_FOUND: 404,\n METHOD_NOT_ALLOWED: 405,\n VALIDATION_ERROR: 422,\n INVALID_SLUG_FORMAT: 422,\n SLUG_CONFLICT: 409,\n PUBLISH_VALIDATION_ERROR: 422,\n SINGLETON_ALREADY_EXISTS: 409,\n UNAUTHORIZED: 401,\n TOKEN_EXPIRED: 401,\n TOKEN_INVALID: 401,\n INSUFFICIENT_PERMISSION: 403,\n INSUFFICIENT_PRIVILEGE: 403,\n INVALID_FILTER_FIELD: 400,\n INVALID_FILTER_OPERATOR: 400,\n INVALID_SORT_FIELD: 400,\n INVALID_PAGINATION: 400,\n INVALID_INCLUDE_FIELD: 400,\n UNSUPPORTED_MIME_TYPE: 415,\n STORAGE_ERROR: 502,\n MEDIA_IN_USE: 409,\n PRESIGNED_URL_EXPIRED: 410,\n RATE_LIMITED: 429,\n INTERNAL_ERROR: 500,\n}\n\ntype ApiError = Error & { code?: ErrorCode }\n\nexport const errorHandler: ErrorHandler = (err, c) => {\n console.error(err.stack ?? err.message)\n\n const apiErr = err as ApiError\n const code: ErrorCode = apiErr.code ?? 'INTERNAL_ERROR'\n const status = ERROR_STATUS_MAP[code] ?? 500\n\n return c.json({ ok: false, error: { code, message: err.message } }, status as Parameters<typeof c.json>[1])\n}\n","import type { MiddlewareHandler } from 'hono'\nimport { getCookie } from 'hono/cookie'\nimport { sql } from 'drizzle-orm'\nimport type { DrizzlePostgresInstance } from '@bobbykim/manguito-cms-db'\nimport {\n verifyToken,\n signToken,\n setAuthCookie,\n AUTH_TOKEN_TTL,\n PROACTIVE_REFRESH_THRESHOLD,\n} from '../auth/jwt.js'\n\ntype UserRow = {\n token_version: number\n must_change_password: boolean\n}\n\nexport function createAuthMiddleware(db: DrizzlePostgresInstance): MiddlewareHandler {\n return async (c, next) => {\n const token = getCookie(c, 'auth_token')\n if (!token) {\n return c.json(\n { ok: false, error: { code: 'UNAUTHORIZED', message: 'Authentication required' } },\n 401,\n )\n }\n\n let payload\n try {\n payload = await verifyToken(token)\n } catch (err) {\n const code = (err as { code?: string }).code\n if (code === 'TOKEN_EXPIRED') {\n return c.json(\n { ok: false, error: { code: 'TOKEN_EXPIRED', message: 'Token has expired' } },\n 401,\n )\n }\n return c.json(\n { ok: false, error: { code: 'TOKEN_INVALID', message: 'Token signature is invalid' } },\n 401,\n )\n }\n\n const result = await db.execute(\n sql`SELECT token_version, must_change_password FROM users WHERE id = ${payload.user_id} LIMIT 1`,\n )\n const row = result.rows[0] as UserRow | undefined\n\n if (!row) {\n return c.json(\n { ok: false, error: { code: 'TOKEN_INVALID', message: 'Token signature is invalid' } },\n 401,\n )\n }\n\n if (payload.token_version !== row.token_version) {\n return c.json(\n { ok: false, error: { code: 'TOKEN_INVALID', message: 'Token has been invalidated' } },\n 401,\n )\n }\n\n c.set('user', {\n id: payload.user_id,\n role: payload.role,\n must_change_password: row.must_change_password,\n })\n\n const now = Math.floor(Date.now() / 1000)\n if (payload.expires_at < now + PROACTIVE_REFRESH_THRESHOLD) {\n const newToken = await signToken(\n {\n user_id: payload.user_id,\n role: payload.role,\n token_version: payload.token_version,\n },\n AUTH_TOKEN_TTL,\n )\n setAuthCookie(c, 'auth_token', newToken, {})\n }\n\n await next()\n }\n}\n","import type { Context } from 'hono'\nimport { sign, verify as jwtVerify } from 'hono/jwt'\nimport { setCookie, deleteCookie } from 'hono/cookie'\nimport type { JWTPayload, ErrorCode } from '@bobbykim/manguito-cms-core'\n\ntype CookieOptions = NonNullable<Parameters<typeof setCookie>[3]>\n\n// ─── Token lifetimes ──────────────────────────────────────────────────────────\n\nexport const AUTH_TOKEN_TTL = 2 * 60 * 60\nexport const REFRESH_TOKEN_TTL = 7 * 24 * 60 * 60\nexport const PROACTIVE_REFRESH_THRESHOLD = 30 * 60\n\n// ─── JWT helpers ──────────────────────────────────────────────────────────────\n\nexport async function signToken(\n payload: Omit<JWTPayload, 'expires_at'>,\n expiresInSeconds: number,\n): Promise<string> {\n const secret = process.env['AUTH_SECRET']\n if (!secret) throw new Error('AUTH_SECRET environment variable is not set')\n const fullPayload: JWTPayload = {\n ...payload,\n expires_at: Math.floor(Date.now() / 1000) + expiresInSeconds,\n }\n return sign(fullPayload as Record<string, unknown>, secret)\n}\n\nexport async function verifyToken(token: string): Promise<JWTPayload> {\n const secret = process.env['AUTH_SECRET']\n if (!secret) throw new Error('AUTH_SECRET environment variable is not set')\n let raw: Record<string, unknown>\n try {\n raw = (await jwtVerify(token, secret, 'HS256')) as Record<string, unknown>\n } catch {\n throw Object.assign(new Error('Token signature is invalid'), {\n code: 'TOKEN_INVALID' as ErrorCode,\n })\n }\n const payload = raw as JWTPayload\n if (payload.expires_at < Math.floor(Date.now() / 1000)) {\n throw Object.assign(new Error('Token has expired'), {\n code: 'TOKEN_EXPIRED' as ErrorCode,\n })\n }\n return payload\n}\n\n// ─── Cookie helpers ───────────────────────────────────────────────────────────\n\nexport function setAuthCookie(\n c: Context,\n name: string,\n token: string,\n options: CookieOptions,\n): void {\n setCookie(c, name, token, {\n ...options,\n httpOnly: true,\n secure: true,\n sameSite: 'Strict',\n })\n}\n\nexport function clearAuthCookie(c: Context, name: string, path?: string): void {\n deleteCookie(c, name, path !== undefined ? { path } : undefined)\n}\n","import type { MiddlewareHandler } from 'hono'\n\nexport const mustChangePasswordCheck: MiddlewareHandler = async (c, next) => {\n const user = c.get('user') as { must_change_password: boolean } | undefined\n\n if (\n user?.must_change_password === true &&\n c.req.path !== '/admin/api/users/change-password'\n ) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'PASSWORD_CHANGE_REQUIRED',\n message: 'You must change your password before continuing.',\n },\n },\n 403,\n )\n }\n\n await next()\n}\n","import type { MiddlewareHandler } from 'hono'\nimport type { Permission } from '@bobbykim/manguito-cms-core'\nimport type { RolesRegistry } from '../auth/registry.js'\n\ntype AuthUser = { role: string }\n\nexport function createPermissionMiddleware(registry: RolesRegistry) {\n return function requirePermission(permission: Permission): MiddlewareHandler {\n return async (c, next) => {\n const user = c.get('user') as AuthUser | undefined\n const role = user ? registry[user.role] : undefined\n\n if (!role || !role.permissions.includes(permission)) {\n return c.json(\n {\n ok: false,\n error: { code: 'INSUFFICIENT_PERMISSION', message: 'Insufficient permission' },\n },\n 403,\n )\n }\n\n await next()\n }\n }\n}\n","import type { MiddlewareHandler } from 'hono'\nimport type { RolesRegistry } from '../auth/registry.js'\n\ntype AuthUser = { role: string }\n\nexport function createHierarchyMiddleware(\n registry: RolesRegistry,\n getUserRole: (id: string) => Promise<string | null>,\n) {\n return function requireHierarchy(): MiddlewareHandler {\n return async (c, next) => {\n const user = c.get('user') as AuthUser | undefined\n const actingRole = user ? registry[user.role] : undefined\n\n if (!actingRole) {\n return c.json(\n {\n ok: false,\n error: { code: 'INSUFFICIENT_PRIVILEGE', message: 'Insufficient privilege' },\n },\n 403,\n )\n }\n\n const method = c.req.method\n const targetId = c.req.param('id') as string | undefined\n\n let targetRoleName: string | null = null\n\n if (method === 'POST' && !targetId) {\n // POST /admin/api/users — create; role must be in request body\n let body: { role?: unknown } = {}\n try {\n body = await c.req.json<{ role?: unknown }>()\n } catch {\n // malformed body — role is absent, treated as INVALID_ROLE below\n }\n targetRoleName = typeof body.role === 'string' ? body.role : null\n if (!targetRoleName) {\n return c.json(\n {\n ok: false,\n error: { code: 'INVALID_ROLE', message: 'Request body must include a valid role' },\n },\n 400,\n )\n }\n } else if (method === 'PATCH' && targetId) {\n // PATCH /admin/api/users/:id — update; role from body if present, else from DB\n let bodyRole: string | undefined\n try {\n const body = await c.req.json<{ role?: unknown }>()\n if (typeof body.role === 'string') bodyRole = body.role\n } catch {\n // no JSON body — fall through to DB lookup\n }\n targetRoleName = bodyRole ?? (await getUserRole(targetId))\n } else if (targetId) {\n // DELETE /admin/api/users/:id or POST /admin/api/users/:id/reset-password\n targetRoleName = await getUserRole(targetId)\n }\n\n // Target user not found — let the route handler return 404\n if (targetRoleName === null) {\n return next()\n }\n\n const targetRole = registry[targetRoleName]\n if (!targetRole) {\n return c.json(\n {\n ok: false,\n error: { code: 'INVALID_ROLE', message: `Role \"${targetRoleName}\" is not valid` },\n },\n 400,\n )\n }\n\n if (actingRole.hierarchy_level >= targetRole.hierarchy_level) {\n return c.json(\n {\n ok: false,\n error: { code: 'INSUFFICIENT_PRIVILEGE', message: 'Insufficient privilege' },\n },\n 403,\n )\n }\n\n await next()\n }\n }\n}\n","import type { MiddlewareHandler } from 'hono'\nimport type { ResolvedRateLimitConfig } from '@bobbykim/manguito-cms-core'\n\n// Defaults for the public list-endpoint limiter. Single source of truth —\n// createCmsApp resolves its limiter through resolveListRateLimit below.\nconst DEFAULT_WINDOW_MS = 60_000\nconst DEFAULT_MAX_PER_IP = 30\nconst DEFAULT_MAX_GLOBAL = 500\n\nexport type RateLimitOptions = {\n windowMs: number\n maxPerIp: number\n maxGlobal: number\n}\n\nexport function createRateLimitMiddleware(options: RateLimitOptions): MiddlewareHandler {\n const { windowMs, maxPerIp, maxGlobal } = options\n const ipRequests = new Map<string, number[]>()\n const globalRequests: number[] = []\n\n return async function rateLimitMiddleware(c, next) {\n const authToken = c.req.raw.headers.get('cookie')\n ?.split(';')\n .map((s) => s.trim())\n .find((s) => s.startsWith('auth_token='))\n\n if (authToken) {\n return next()\n }\n\n const now = Date.now()\n const cutoff = now - windowMs\n\n // purge stale global entries\n let gi = 0\n while (gi < globalRequests.length && globalRequests[gi]! < cutoff) gi++\n globalRequests.splice(0, gi)\n\n // get client IP\n const forwarded = c.req.header('x-forwarded-for')\n const ip = forwarded ? forwarded.split(',')[0]!.trim() : (c.env?.incoming?.socket?.remoteAddress ?? 'unknown')\n\n // purge stale IP entries\n const ipTimes = ipRequests.get(ip) ?? []\n let ii = 0\n while (ii < ipTimes.length && ipTimes[ii]! < cutoff) ii++\n ipTimes.splice(0, ii)\n ipRequests.set(ip, ipTimes)\n\n const retryAfterFromGlobal =\n globalRequests.length > 0\n ? Math.ceil((globalRequests[0]! + windowMs - now) / 1000)\n : 0\n const retryAfterFromIp =\n ipTimes.length > 0 ? Math.ceil((ipTimes[0]! + windowMs - now) / 1000) : 0\n\n if (globalRequests.length >= maxGlobal) {\n const retryAfter = Math.max(retryAfterFromGlobal, 1)\n const reset = Math.floor((now + retryAfter * 1000) / 1000)\n return c.json(\n { ok: false, error: { code: 'RATE_LIMITED', message: `Too many requests. Please retry after ${retryAfter} seconds.` } },\n 429,\n {\n 'Retry-After': String(retryAfter),\n 'X-RateLimit-Limit': String(maxGlobal),\n 'X-RateLimit-Remaining': '0',\n 'X-RateLimit-Reset': String(reset),\n }\n )\n }\n\n if (ipTimes.length >= maxPerIp) {\n const retryAfter = Math.max(retryAfterFromIp, 1)\n const reset = Math.floor((now + retryAfter * 1000) / 1000)\n return c.json(\n { ok: false, error: { code: 'RATE_LIMITED', message: `Too many requests. Please retry after ${retryAfter} seconds.` } },\n 429,\n {\n 'Retry-After': String(retryAfter),\n 'X-RateLimit-Limit': String(maxPerIp),\n 'X-RateLimit-Remaining': '0',\n 'X-RateLimit-Reset': String(reset),\n }\n )\n }\n\n globalRequests.push(now)\n ipTimes.push(now)\n\n return next()\n }\n}\n\n/**\n * Resolves the public list-endpoint config into a middleware, or `undefined`\n * when the limiter is disabled via the `findAll: '*'` wildcard. Route\n * registrators skip registration when this is `undefined`, so a disabled\n * limiter has zero request-path overhead.\n */\nexport function resolveListRateLimit(\n rateLimit?: ResolvedRateLimitConfig,\n): MiddlewareHandler | undefined {\n const findAll = rateLimit?.findAll\n if (findAll === '*') {\n return undefined\n }\n return createRateLimitMiddleware({\n windowMs: findAll?.windowMs ?? DEFAULT_WINDOW_MS,\n maxPerIp: findAll?.maxPerIp ?? DEFAULT_MAX_PER_IP,\n maxGlobal: findAll?.maxGlobal ?? DEFAULT_MAX_GLOBAL,\n })\n}\n","import type { ParsedRole } from '@bobbykim/manguito-cms-core'\n\nexport type RolesRegistry = Record<string, ParsedRole>\n\nconst SYSTEM_ROLES = ['admin', 'manager', 'editor', 'writer', 'viewer'] as const\n\nfunction validateRoles(roles: ParsedRole[]): void {\n if (roles.length === 0) {\n throw new Error(\n 'Fatal: roles registry failed to build — roles array is empty. Run `manguito validate` to check your roles schema.',\n )\n }\n\n const nameSet = new Set(roles.map((r) => r.name))\n for (const systemRole of SYSTEM_ROLES) {\n if (!nameSet.has(systemRole)) {\n throw new Error(\n `Fatal: roles registry failed to build — missing system role \"${systemRole}\". Run \\`manguito validate\\` to check your roles schema.`,\n )\n }\n }\n\n const levelsSeen = new Map<number, string>()\n for (const role of roles) {\n const existing = levelsSeen.get(role.hierarchy_level)\n if (existing !== undefined) {\n throw new Error(\n `Fatal: roles registry failed to build — duplicate hierarchy_level ${role.hierarchy_level} on roles \"${existing}\" and \"${role.name}\". Run \\`manguito validate\\` to check your roles schema.`,\n )\n }\n levelsSeen.set(role.hierarchy_level, role.name)\n }\n\n const namesSeen = new Set<string>()\n for (const role of roles) {\n if (namesSeen.has(role.name)) {\n throw new Error(\n `Fatal: roles registry failed to build — duplicate role name \"${role.name}\". Run \\`manguito validate\\` to check your roles schema.`,\n )\n }\n namesSeen.add(role.name)\n }\n}\n\nexport function buildRolesRegistry(roles: ParsedRole[]): RolesRegistry {\n validateRoles(roles)\n return Object.fromEntries(roles.map((r) => [r.name, r]))\n}\n","import type {\n ProgrammaticFieldDefinition,\n ResolverContext,\n SchemaRegistry,\n ParsedContentType,\n ParsedTaxonomyType,\n} from '@bobbykim/manguito-cms-core'\n\nexport type ResolverMap = Map<string, ProgrammaticFieldDefinition>\n\nconst DEFAULT_TIMEOUT_MS = 5000\nconst LIST_CONCURRENCY = 10\n\nexport function resolverKey(schema: string, field: string): string {\n return `${schema}::${field}`\n}\n\n// ─── Boot-time binding validation ─────────────────────────────────────────────\n\n// Startup guard (throws, like createCmsApp's storage/roles checks): every\n// programmatic field must have exactly one resolver, and every resolver must\n// target an existing programmatic field.\nexport function validateResolverBindings(\n registry: SchemaRegistry,\n resolvers: ResolverMap,\n): void {\n const declared = new Set<string>()\n const schemas: Array<ParsedContentType | ParsedTaxonomyType> = [\n ...(Object.values(registry.content_types) as ParsedContentType[]),\n ...(Object.values(registry.taxonomy_types) as ParsedTaxonomyType[]),\n ]\n for (const schema of schemas) {\n for (const field of schema.fields) {\n if (field.field_type === 'programmatic') {\n declared.add(resolverKey(schema.name, field.name))\n }\n }\n }\n\n const missing: string[] = []\n for (const key of declared) if (!resolvers.has(key)) missing.push(key)\n\n const orphans: string[] = []\n for (const key of resolvers.keys()) if (!declared.has(key)) orphans.push(key)\n\n if (missing.length === 0 && orphans.length === 0) return\n\n const lines: string[] = ['✗ Programmatic field resolver bindings are invalid.']\n if (missing.length > 0) {\n lines.push('', ' Declared as `type: \"programmatic\"` but no resolver found:')\n for (const k of missing) lines.push(` - ${k} (add a programmaticField in src/programmatic)`)\n }\n if (orphans.length > 0) {\n lines.push('', ' Resolver has no matching programmatic field in the schema:')\n for (const k of orphans) lines.push(` - ${k}`)\n }\n lines.push('', 'Exiting.')\n throw new Error(lines.join('\\n'))\n}\n\n// ─── Runtime resolution ───────────────────────────────────────────────────────\n\nfunction buildContext(row: Record<string, unknown>): ResolverContext {\n return {\n get: (name) => row[name],\n record: row,\n }\n}\n\nasync function withTimeout<T>(p: Promise<T>, ms: number): Promise<T> {\n let timer: ReturnType<typeof setTimeout>\n const timeout = new Promise<never>((_, reject) => {\n timer = setTimeout(() => reject(new Error('resolver timeout')), ms)\n })\n try {\n return await Promise.race([p, timeout])\n } finally {\n clearTimeout(timer!)\n }\n}\n\n// Runs one resolver against one row; never throws. Failure/timeout → fallback.\nasync function runOne(\n def: ProgrammaticFieldDefinition,\n row: Record<string, unknown>,\n onError: (message: string) => void,\n): Promise<{ value: unknown; failed: boolean }> {\n const fallback = def.fallback ?? null\n try {\n const result = await withTimeout(\n Promise.resolve(def.resolve(buildContext(row))),\n def.timeout ?? DEFAULT_TIMEOUT_MS,\n )\n return { value: result === undefined ? null : result, failed: false }\n } catch (err) {\n onError(\n `⚠ programmatic field ${resolverKey(def.schema, def.field)} failed: ${err instanceof Error ? err.message : String(err)}`,\n )\n return { value: fallback, failed: true }\n }\n}\n\n// Fixed-size worker pool.\nasync function runPool<T>(items: T[], limit: number, fn: (item: T) => Promise<void>): Promise<void> {\n let i = 0\n async function worker(): Promise<void> {\n while (i < items.length) {\n const idx = i++\n await fn(items[idx]!)\n }\n }\n const workers = Array.from({ length: Math.min(limit, items.length) }, worker)\n await Promise.all(workers)\n}\n\nexport function createProgrammaticResolver(\n resolvers: ResolverMap,\n options?: { onError?: (message: string) => void },\n) {\n const onError =\n options?.onError ??\n ((message: string) => {\n process.stderr.write(message + '\\n')\n })\n\n // Group definitions by schema once.\n const bySchema = new Map<string, ProgrammaticFieldDefinition[]>()\n for (const def of resolvers.values()) {\n const arr = bySchema.get(def.schema) ?? []\n arr.push(def)\n bySchema.set(def.schema, arr)\n }\n\n // Per-process cache. Key = schema::field::itemId.\n const cache = new Map<string, { value: unknown; expires: number }>()\n\n async function resolveField(\n def: ProgrammaticFieldDefinition,\n row: Record<string, unknown>,\n ): Promise<unknown> {\n const itemId = row['id'] !== undefined ? String(row['id']) : ''\n if (def.cache && itemId) {\n const key = `${resolverKey(def.schema, def.field)}::${itemId}`\n const now = Date.now()\n const hit = cache.get(key)\n if (hit && hit.expires > now) return hit.value\n const { value, failed } = await runOne(def, row, onError)\n if (!failed) cache.set(key, { value, expires: now + def.cache.ttl * 1000 })\n return value\n }\n const { value } = await runOne(def, row, onError)\n return value\n }\n\n async function resolveItem(\n schema: string,\n row: Record<string, unknown>,\n ): Promise<Record<string, unknown>> {\n const defs = bySchema.get(schema)\n if (!defs || defs.length === 0) return { ...row }\n const out: Record<string, unknown> = { ...row }\n await Promise.all(\n defs.map(async (def) => {\n out[def.field] = await resolveField(def, row)\n }),\n )\n return out\n }\n\n async function resolveList(\n schema: string,\n rows: Record<string, unknown>[],\n ): Promise<Record<string, unknown>[]> {\n const defs = (bySchema.get(schema) ?? []).filter((d) => d.on_list === true)\n if (defs.length === 0) return rows.map((r) => ({ ...r }))\n const out = rows.map((r) => ({ ...r }))\n const tasks: Array<{ rowIndex: number; def: ProgrammaticFieldDefinition }> = []\n for (let i = 0; i < rows.length; i++) {\n for (const def of defs) tasks.push({ rowIndex: i, def })\n }\n await runPool(tasks, LIST_CONCURRENCY, async ({ rowIndex, def }) => {\n out[rowIndex]![def.field] = await resolveField(def, rows[rowIndex]!)\n })\n return out\n }\n\n function hasSchema(schema: string): boolean {\n return bySchema.has(schema)\n }\n\n return { resolveItem, resolveList, hasSchema }\n}\n\nexport type ProgrammaticResolver = ReturnType<typeof createProgrammaticResolver>\n","import type { ParsedField, SchemaRegistry } from '@bobbykim/manguito-cms-core'\nimport type { FieldKeyMap } from './field-keys.js'\n\n// ─── Recursive outbound projection ────────────────────────────────────────────\n//\n// Stage 1 mapped a response row's TOP-LEVEL keys from storage columns to public\n// labels. Nested rows — paragraph children, resolved reference and junction\n// targets — were attached straight from SELECT * and never mapped, so they\n// served column names. This walks them.\n//\n// Applied at exactly the points that called FieldKeyMap.toLabels before, which\n// is why it fixes both the public and admin paths without touching either\n// resolver: both already map at the right moment, after relation resolution.\n//\n// GraphQL does not use this. It resolves each field individually by column\n// through resolveFieldValue, so it is already correct at every depth.\n\n/**\n * Is this a resolved row worth recursing into? A relation that was not\n * `?include=`d holds a bare uuid string (or an array of them), and timestamps\n * arrive from the driver as Date instances — neither is a row.\n */\nexport function isPlainRow(v: unknown): v is Record<string, unknown> {\n return typeof v === 'object' && v !== null && !Array.isArray(v) && !(v instanceof Date)\n}\n\nexport type TypeProjector = {\n /** This type's own label↔column map. */\n map: FieldKeyMap\n /** Relation fields worth recursing into: the field's LABEL and its target type. */\n nested: Array<{ label: string; target: string }>\n}\n\nexport type Projectors = Record<string, TypeProjector>\n\n// Media relations resolve to rows of the `media` system table — fixed columns,\n// no schema fields — so there is nothing to project and they stay out of `nested`.\nconst MEDIA_FIELD_TYPES = new Set(['image', 'video', 'file'])\n\nfunction nestedTargets(fields: ParsedField[]): Array<{ label: string; target: string }> {\n const out: Array<{ label: string; target: string }> = []\n for (const f of fields) {\n if (MEDIA_FIELD_TYPES.has(f.field_type)) continue\n if (f.field_type !== 'paragraph' && f.field_type !== 'reference') continue\n // Both kinds name their target the same way.\n const ref = (f.ui_component as { ref?: string }).ref\n if (!ref) continue\n out.push({ label: f.name, target: ref })\n }\n return out\n}\n\n/**\n * Built ONCE at startup, not per request. Covers content, taxonomy and\n * paragraph types, keyed by machine name — the same key space `fieldKeyMaps`\n * uses and the same one a field's `ui_component.ref` points into.\n */\nexport function buildProjectors(\n registry: SchemaRegistry,\n fieldKeyMaps: Record<string, FieldKeyMap>\n): Projectors {\n const projectors: Projectors = {}\n const sources: Array<Record<string, { fields: ParsedField[] }>> = [\n registry.content_types,\n registry.taxonomy_types,\n registry.paragraph_types,\n ]\n\n for (const source of sources) {\n for (const [typeName, type] of Object.entries(source)) {\n const map = fieldKeyMaps[typeName]\n if (!map) continue\n projectors[typeName] = { map, nested: nestedTargets(type.fields) }\n }\n }\n\n return projectors\n}\n\n/**\n * Storage-keyed row → label-keyed, recursively. Never mutates its input: the\n * relation cache hands the SAME nested object to several parents, so editing in\n * place would project a shared row twice and lose its values on the second pass.\n */\nexport function projectRow(\n row: Record<string, unknown>,\n typeName: string,\n projectors: Projectors\n): Record<string, unknown> {\n const p = projectors[typeName]\n if (!p) return row\n\n // Top level first: `nested` is keyed by label, so the keys must be labels\n // before the loop below reads them. toLabels returns a fresh object.\n const out = p.map.toLabels(row)\n\n for (const { label, target } of p.nested) {\n const v = out[label]\n if (v === null || v === undefined) continue\n if (Array.isArray(v)) {\n out[label] = v.map((item) => (isPlainRow(item) ? projectRow(item, target, projectors) : item))\n } else if (isPlainRow(v)) {\n out[label] = projectRow(v, target, projectors)\n }\n }\n\n return out\n}\n","import type { FilterValue, FilterOperator } from '@bobbykim/manguito-cms-core'\n\n// Query-param parsing shared by the public and admin content/taxonomy list routes.\n\n// The sortable set. NOT \"indexed system fields\", despite what this comment used\n// to say: db codegen generates no indexes at all, and `title` is an ordinary\n// schema field, not a system field — nothing guarantees a content type has one.\n// Sorting by a label a type lacks still reaches Postgres and 500s; widening this\n// set to any column-backed field is a separate design (see the Stage 1.5 spec).\nexport const SORTABLE_FIELDS = new Set<string>(['title', 'created_at', 'updated_at'])\n\n// Field types whose values are relations rather than plain columns.\nexport const RELATION_FIELD_TYPES = new Set([\n 'paragraph',\n 'reference',\n 'image',\n 'video',\n 'file',\n])\n\nexport function parsePagination(\n pageStr: string | undefined,\n perPageStr: string | undefined\n): { ok: true; page: number; per_page: number } | { ok: false } {\n const page = pageStr !== undefined ? Number(pageStr) : 1\n const per_page = perPageStr !== undefined ? Number(perPageStr) : 10\n\n if (!Number.isInteger(page) || page < 1) return { ok: false }\n if (!Number.isInteger(per_page) || per_page < 1 || per_page > 100) return { ok: false }\n return { ok: true, page, per_page }\n}\n\nexport function parseInclude(includeParam: string | undefined): string[] {\n if (!includeParam) return []\n return includeParam\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean)\n}\n\nexport function parseFilters(\n url: string,\n validFields: Set<string>,\n columnFor?: (label: string) => string | undefined\n): { ok: true; filters: Record<string, FilterValue> } | { ok: false; invalidField: string } {\n const { searchParams } = new URL(url)\n const filters: Record<string, FilterValue> = {}\n\n // Filters are validated against LABELS (what the client sends) and emitted as\n // storage keys (what the repository queries). Without a mapper the two are\n // the same, which is the pre-versioning behavior.\n const toKey = (label: string): string => columnFor?.(label) ?? label\n\n for (const [key, value] of searchParams.entries()) {\n const simpleMatch = /^filter\\[([^\\]]+)\\]$/.exec(key)\n const opMatch = /^filter\\[([^\\]]+)\\]\\[([^\\]]+)\\]$/.exec(key)\n\n if (simpleMatch) {\n const field = simpleMatch[1]!\n if (!validFields.has(field)) return { ok: false, invalidField: field }\n const storageKey = toKey(field)\n const existing = filters[storageKey]\n if (existing !== undefined) {\n filters[storageKey] = Array.isArray(existing)\n ? [...existing, value]\n : [existing as string | number | boolean, value]\n } else {\n filters[storageKey] = value\n }\n } else if (opMatch) {\n const field = opMatch[1]!\n const operator = opMatch[2]!\n if (!validFields.has(field)) return { ok: false, invalidField: field }\n if (!['gt', 'gte', 'lt', 'lte'].includes(operator)) continue\n const storageKey = toKey(field)\n const existing = (filters[storageKey] as FilterOperator | undefined) ?? {}\n filters[storageKey] = { ...(existing as FilterOperator), [operator]: value }\n }\n }\n\n return { ok: true, filters }\n}\n","import type { Hono, Handler, MiddlewareHandler } from 'hono'\nimport type {\n SchemaRegistry,\n ContentRepository,\n} from '@bobbykim/manguito-cms-core'\nimport type { ProgrammaticResolver } from '../programmatic/resolve.js'\nimport type { Projectors } from '../projector.js'\nimport { projectRow } from '../projector.js'\nimport type { PublicPaths } from '../paths.js'\nimport {\n SORTABLE_FIELDS,\n RELATION_FIELD_TYPES,\n parsePagination,\n parseInclude,\n parseFilters,\n} from './query-params.js'\n\nexport type ContentRepos = Record<string, ContentRepository<unknown>>\n\n// ─── Response projection order ────────────────────────────────────────────────\n//\n// Every read response maps storage keys → labels exactly ONCE, and that mapping\n// runs BEFORE the programmatic resolver. Two reasons it sits there rather than\n// after:\n// - a programmatic resolver reads its record through `ctx.get(fieldName)`,\n// documented (docs/programmatic-fields.md) as the schema field name — the\n// LABEL — so the row handed to it must already speak labels;\n// - programmatic fields are not column-backed, so their output keys are labels\n// already; mapping afterwards would be a no-op on them.\n// Relations are resolved inside the repository, upstream of both, so the mapping\n// still lands strictly after relation resolution.\n\nfunction isPublished(item: unknown): boolean {\n return (item as Record<string, unknown>)['published'] === true\n}\n\nexport function registerPublicContentRoutes(\n app: Hono,\n registry: SchemaRegistry,\n repos: ContentRepos,\n projectors: Projectors,\n paths: PublicPaths,\n listRateLimit?: MiddlewareHandler,\n resolver?: ProgrammaticResolver\n): void {\n // ── Meta-endpoints: list available schema types ───────────────────────────\n // Registered before the dynamic per-type routes to avoid path conflicts.\n\n function registerListRoute(path: string, handler: Handler): void {\n if (listRateLimit) {\n app.get(path, listRateLimit, handler)\n } else {\n app.get(path, handler)\n }\n }\n\n registerListRoute(paths.collection('content'), (c) => {\n const data = Object.values(registry.content_types).map((ct) => ({\n name: ct.name,\n label: ct.label,\n only_one: ct.only_one,\n }))\n return c.json({ ok: true, data })\n })\n\n registerListRoute(paths.collection('taxonomy'), (c) => {\n const data = Object.values(registry.taxonomy_types).map((tt) => ({\n name: tt.name,\n label: tt.label,\n }))\n return c.json({ ok: true, data })\n })\n\n // ── Per-type content routes ───────────────────────────────────────────────\n\n for (const [typeName, contentType] of Object.entries(registry.content_types)) {\n const basePath = contentType.default_base_path\n const repo = repos[typeName]\n if (!repo) continue\n\n const relationFieldNames = new Set<string>(\n contentType.fields\n .filter((f) => RELATION_FIELD_TYPES.has(f.field_type))\n .map((f) => f.name)\n )\n\n // Programmatic fields have no db column — filtering one would be a SQL\n // error, so they're excluded from the filterable set.\n const filterableFieldNames = new Set<string>([\n ...contentType.fields.filter((f) => f.field_type !== 'programmatic').map((f) => f.name),\n ...contentType.system_fields.map((f) => f.name),\n ])\n\n if (contentType.only_one) {\n app.get(paths.collection(basePath), async (c) => {\n const result = await repo.findMany({ published_only: true, page: 1, per_page: 1 })\n if (result.data.length === 0) {\n return c.json(\n { ok: false, error: { code: 'NOT_FOUND', message: 'Not found' } },\n 404\n )\n }\n // Outbound boundary (see \"Response projection order\" above).\n let data = projectRow(result.data[0] as Record<string, unknown>, typeName, projectors)\n if (resolver?.hasSchema(typeName)) data = await resolver.resolveItem(typeName, data)\n return c.json({ ok: true, data })\n })\n } else {\n registerListRoute(paths.collection(basePath), async (c) => {\n // Inbound boundary: query params speak labels; filters query storage keys.\n const projector = projectors[typeName]!\n const fieldKeys = projector.map\n const pagination = parsePagination(c.req.query('page'), c.req.query('per_page'))\n if (!pagination.ok) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'INVALID_PAGINATION',\n message: 'page must be ≥ 1 and per_page must be between 1 and 100',\n },\n },\n 400\n )\n }\n\n const sortBy = c.req.query('sort_by') ?? 'created_at'\n if (!SORTABLE_FIELDS.has(sortBy)) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'INVALID_SORT_FIELD',\n message: `'${sortBy}' is not sortable. Allowed: title, created_at, updated_at`,\n },\n },\n 400\n )\n }\n\n const sortOrder = c.req.query('sort_order') ?? 'asc'\n if (sortOrder !== 'asc' && sortOrder !== 'desc') {\n return c.json(\n {\n ok: false,\n error: {\n code: 'INVALID_SORT_FIELD',\n message: `sort_order must be 'asc' or 'desc'`,\n },\n },\n 400\n )\n }\n\n const filtersResult = parseFilters(c.req.url, filterableFieldNames, fieldKeys.columnFor)\n if (!filtersResult.ok) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'INVALID_FILTER_FIELD',\n message: `Filter field '${filtersResult.invalidField}' does not exist on this content type`,\n },\n },\n 400\n )\n }\n\n const include = parseInclude(c.req.query('include'))\n for (const field of include) {\n if (!relationFieldNames.has(field)) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'INVALID_INCLUDE_FIELD',\n message: `'${field}' is not a valid relation field`,\n },\n },\n 400\n )\n }\n }\n\n // sortBy is a validated label (checked above); map it to its storage\n // column before it reaches the repository. The cast is a narrow lie —\n // core types sort_by as the label union, but the repository\n // immediately re-validates the mapped value against sortableColumns.\n const sortColumn = fieldKeys.columnFor(sortBy) ?? sortBy\n\n const result = await repo.findMany({\n published_only: true,\n page: pagination.page,\n per_page: pagination.per_page,\n sort_by: sortColumn as 'title' | 'created_at' | 'updated_at',\n sort_order: sortOrder as 'asc' | 'desc',\n filters: filtersResult.filters,\n include,\n })\n\n // Outbound boundary (see \"Response projection order\" above).\n const labeled = (result.data as Record<string, unknown>[]).map((row) =>\n projectRow(row, typeName, projectors)\n )\n const data = resolver?.hasSchema(typeName)\n ? await resolver.resolveList(typeName, labeled)\n : labeled\n return c.json({ ...result, data })\n })\n\n app.get(paths.item(basePath), async (c) => {\n // paths.item() always appends a literal ':slug' segment, but its return\n // type is the widened `string` from PublicPaths — not a template literal\n // type — so Hono can no longer statically prove the param is present.\n const slug = c.req.param('slug')!\n\n const include = parseInclude(c.req.query('include'))\n for (const field of include) {\n if (!relationFieldNames.has(field)) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'INVALID_INCLUDE_FIELD',\n message: `'${field}' is not a valid relation field`,\n },\n },\n 400\n )\n }\n }\n\n const item = await repo.findBySlug(slug, include)\n\n if (!item || !isPublished(item)) {\n return c.json(\n {\n ok: false,\n error: { code: 'SLUG_NOT_FOUND', message: `No item found with slug '${slug}'` },\n },\n 404\n )\n }\n\n // Outbound boundary (see \"Response projection order\" above).\n let data = projectRow(item as Record<string, unknown>, typeName, projectors)\n if (resolver?.hasSchema(typeName)) data = await resolver.resolveItem(typeName, data)\n return c.json({ ok: true, data })\n })\n }\n }\n\n for (const [typeName] of Object.entries(registry.taxonomy_types)) {\n const repo = repos[typeName]\n if (!repo) continue\n\n registerListRoute(paths.taxonomyCollection(typeName), async (c) => {\n const pagination = parsePagination(c.req.query('page'), c.req.query('per_page'))\n if (!pagination.ok) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'INVALID_PAGINATION',\n message: 'page must be ≥ 1 and per_page must be between 1 and 100',\n },\n },\n 400\n )\n }\n\n const result = await repo.findMany({\n published_only: true,\n page: pagination.page,\n per_page: pagination.per_page,\n })\n\n // Outbound boundary (see \"Response projection order\" above).\n const labeled = (result.data as Record<string, unknown>[]).map((row) =>\n projectRow(row, typeName, projectors)\n )\n const data = resolver?.hasSchema(typeName)\n ? await resolver.resolveList(typeName, labeled)\n : labeled\n return c.json({ ...result, data })\n })\n\n app.get(paths.taxonomyItem(typeName), async (c) => {\n // See the ':slug' comment above — same widened-string-return caveat applies.\n const id = c.req.param('id')!\n const item = await repo.findOne(id)\n\n if (!item || !isPublished(item)) {\n return c.json(\n {\n ok: false,\n error: { code: 'NOT_FOUND', message: 'Taxonomy term not found' },\n },\n 404\n )\n }\n\n // Outbound boundary (see \"Response projection order\" above).\n let data = projectRow(item as Record<string, unknown>, typeName, projectors)\n if (resolver?.hasSchema(typeName)) data = await resolver.resolveItem(typeName, data)\n return c.json({ ok: true, data })\n })\n }\n}\n","import type { Hono, Handler, MiddlewareHandler } from 'hono'\nimport type { MediaRepository } from '@bobbykim/manguito-cms-core'\nimport type { PublicPaths } from '../paths.js'\n\nconst VALID_MEDIA_TYPES = new Set(['image', 'video', 'file'])\n\nfunction parsePagination(\n pageStr: string | undefined,\n perPageStr: string | undefined\n): { ok: true; page: number; per_page: number } | { ok: false } {\n const page = pageStr !== undefined ? Number(pageStr) : 1\n const per_page = perPageStr !== undefined ? Number(perPageStr) : 10\n\n if (!Number.isInteger(page) || page < 1) return { ok: false }\n if (!Number.isInteger(per_page) || per_page < 1 || per_page > 100) return { ok: false }\n return { ok: true, page, per_page }\n}\n\nexport function registerPublicMediaRoutes(\n app: Hono,\n mediaRepo: MediaRepository,\n paths: PublicPaths,\n listRateLimit?: MiddlewareHandler\n): void {\n const mediaListHandler: Handler = async (c) => {\n const pagination = parsePagination(c.req.query('page'), c.req.query('per_page'))\n if (!pagination.ok) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'INVALID_PAGINATION',\n message: 'page must be ≥ 1 and per_page must be between 1 and 100',\n },\n },\n 400\n )\n }\n\n const typeParam = c.req.query('type')\n if (typeParam !== undefined && !VALID_MEDIA_TYPES.has(typeParam)) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'VALIDATION_ERROR',\n message: `type must be one of: image, video, file`,\n },\n },\n 422\n )\n }\n\n const opts: Parameters<typeof mediaRepo.findMany>[0] = {\n page: pagination.page,\n per_page: pagination.per_page,\n }\n if (typeParam !== undefined) {\n opts.type = typeParam as 'image' | 'video' | 'file'\n }\n\n const result = await mediaRepo.findMany(opts)\n return c.json(result)\n }\n\n if (listRateLimit) {\n app.get(paths.mediaCollection(), listRateLimit, mediaListHandler)\n } else {\n app.get(paths.mediaCollection(), mediaListHandler)\n }\n\n app.get(paths.mediaItem(), async (c) => {\n // paths.mediaItem() always appends a literal ':id' segment, but its return\n // type is the widened `string` from PublicPaths, so Hono can't statically\n // prove the param is present.\n const id = c.req.param('id')!\n const item = await mediaRepo.findOne(id)\n\n if (!item) {\n return c.json(\n { ok: false, error: { code: 'NOT_FOUND', message: 'Media item not found' } },\n 404\n )\n }\n\n return c.json({ ok: true, data: item })\n })\n}\n","import { sql } from 'drizzle-orm'\nimport type { Context, Hono } from 'hono'\nimport type {\n SchemaRegistry,\n MediaRepository,\n FilterValue,\n ParsedField,\n ParsedParagraphType,\n} from '@bobbykim/manguito-cms-core'\nimport {\n SORTABLE_FIELDS,\n RELATION_FIELD_TYPES,\n parsePagination,\n parseInclude,\n parseFilters,\n} from '../query-params.js'\nimport type { DrizzlePostgresInstance } from '@bobbykim/manguito-cms-db'\nimport {\n topLevelMediaDelta,\n mergeMediaDeltas,\n applyMediaReferenceDelta,\n type MediaDelta,\n} from '../../media-references.js'\nimport {\n persistParagraphField,\n deleteParagraphField,\n persistJunctionField,\n} from '../../relations.js'\nimport type { createPermissionMiddleware } from '../../middleware/permission.js'\nimport type { ContentRepos } from '../content.js'\nimport { isColumnBacked } from '../../field-keys.js'\nimport { projectRow, type Projectors } from '../../projector.js'\n\n// ─── SQL helpers ─────────────────────────────────────────────────────────────\n\nfunction quoteIdent(name: string): string {\n if (!/^[a-z][a-z0-9_-]*$/.test(name)) throw new Error(`Unsafe identifier: ${name}`)\n return `\"${name}\"`\n}\n\n// Loads the paragraph rows for one field of one parent, descending into the one\n// level of nested paragraph fields ADR core/0005 permits. Scoped by parent_id AND\n// parent_field so two paragraph fields sharing a paragraph type never mix — the\n// reason this read is not delegated to resolveRelationField.\nasync function loadParagraphRows(\n db: DrizzlePostgresInstance,\n registry: SchemaRegistry,\n pType: ParsedParagraphType,\n parentId: string,\n fieldName: string\n): Promise<Record<string, unknown>[]> {\n const result = await db.execute(\n sql`SELECT * FROM ${sql.raw(quoteIdent(pType.db.table_name))} WHERE parent_id = ${parentId} AND parent_field = ${fieldName} ORDER BY \"order\" ASC`\n )\n const rows = result.rows as Record<string, unknown>[]\n\n for (const nf of pType.fields) {\n if (nf.db_column !== null) continue\n const comp = nf.ui_component as { component: string; ref?: string }\n if (comp.component !== 'paragraph-embed' || !comp.ref) continue\n const nType = registry.paragraph_types[comp.ref]\n if (!nType) continue\n for (const row of rows) {\n row[nf.name] = await loadParagraphRows(db, registry, nType, row['id'] as string, nf.name)\n }\n }\n\n return rows\n}\n\nasync function lookupBasePathId(db: DrizzlePostgresInstance, pathOrName: string): Promise<string | null> {\n const r = await db.execute(\n sql`SELECT id FROM base_paths WHERE path = ${pathOrName} OR name = ${pathOrName} LIMIT 1`\n )\n return (r.rows[0] as { id: string } | undefined)?.id ?? null\n}\n\n// ─── Shared query-param helpers ───────────────────────────────────────────────\n\nconst SLUG_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/\n\n// ─── Validation helpers ───────────────────────────────────────────────────────\n\nfunction isEmpty(value: unknown): boolean {\n if (value === null || value === undefined) return true\n if (typeof value === 'string' && value.trim() === '') return true\n if (Array.isArray(value) && value.length === 0) return true\n return false\n}\n\nfunction checkRequiredFields(\n fields: ParsedField[],\n data: Record<string, unknown>\n): { field: string; message: string }[] {\n return fields\n .filter((f) => f.required && isEmpty(data[f.name]))\n .map((f) => ({ field: f.name, message: `${f.label} is required` }))\n}\n\n// Extract IDs from already-resolved media field values ({ id: string } objects).\n// Admin repos are created without relation resolution (see app.ts) so edit forms get\n// plain foreign-key id strings back — media fields here are raw ids, not resolved\n// { id, url, ... } objects.\n// ─── List query helpers (shared between content and taxonomy list routes) ─────\n\ntype ListQueryResult =\n | {\n ok: true\n pagination: { page: number; per_page: number }\n sortBy: string\n sortOrder: string\n filters: Record<string, FilterValue>\n include: string[]\n search: string\n }\n | { ok: false; response: { code: string; message: string }; status: 400 }\n\n// `columnFor` maps a validated label to its storage column. Filters are\n// validated against labels (what the client sends) and emitted as columns (what\n// the repository queries) — without it a renamed field would be filtered on a\n// column that does not exist.\nfunction parseListQuery(\n url: string,\n schemaFieldNames: Set<string>,\n relationFieldNames: Set<string>,\n columnFor: (label: string) => string | undefined\n): ListQueryResult {\n const searchParams = new URL(url).searchParams\n\n const pagination = parsePagination(\n searchParams.get('page') ?? undefined,\n searchParams.get('per_page') ?? undefined\n )\n if (!pagination.ok) {\n return {\n ok: false,\n response: {\n code: 'INVALID_PAGINATION',\n message: 'page must be ≥ 1 and per_page must be between 1 and 100',\n },\n status: 400,\n }\n }\n\n const sortBy = searchParams.get('sort_by') ?? 'created_at'\n if (!SORTABLE_FIELDS.has(sortBy)) {\n return {\n ok: false,\n response: {\n code: 'INVALID_SORT_FIELD',\n message: `'${sortBy}' is not sortable. Allowed: title, created_at, updated_at`,\n },\n status: 400,\n }\n }\n\n const sortOrder = searchParams.get('sort_order') ?? 'asc'\n if (sortOrder !== 'asc' && sortOrder !== 'desc') {\n return {\n ok: false,\n response: { code: 'INVALID_SORT_FIELD', message: `sort_order must be 'asc' or 'desc'` },\n status: 400,\n }\n }\n\n const filtersResult = parseFilters(url, schemaFieldNames, columnFor)\n if (!filtersResult.ok) {\n return {\n ok: false,\n response: {\n code: 'INVALID_FILTER_FIELD',\n message: `Filter field '${filtersResult.invalidField}' does not exist on this schema`,\n },\n status: 400,\n }\n }\n\n const include = parseInclude(searchParams.get('include') ?? undefined)\n for (const field of include) {\n if (!relationFieldNames.has(field)) {\n return {\n ok: false,\n response: { code: 'INVALID_INCLUDE_FIELD', message: `'${field}' is not a valid relation field` },\n status: 400,\n }\n }\n }\n\n const search = (searchParams.get('search') ?? '').trim()\n\n return {\n ok: true,\n pagination,\n sortBy,\n sortOrder,\n filters: filtersResult.filters,\n include,\n search,\n }\n}\n\n// ─── Register ─────────────────────────────────────────────────────────────────\n\nexport function registerAdminContentRoutes(\n app: Hono,\n registry: SchemaRegistry,\n repos: ContentRepos,\n projectors: Projectors,\n mediaRepo: MediaRepository,\n requirePermission: ReturnType<typeof createPermissionMiddleware>,\n db?: DrizzlePostgresInstance,\n): void {\n // ── Content type routes ───────────────────────────────────────────────────\n\n for (const [typeName, contentType] of Object.entries(registry.content_types)) {\n const basePath = `content/${typeName}`\n const repo = repos[typeName]\n if (!repo) continue\n\n const schemaFieldNames = new Set<string>([\n ...contentType.fields.map((f) => f.name),\n ...contentType.system_fields.map((f) => f.name),\n ])\n\n const relationFieldNames = new Set<string>(\n contentType.fields\n .filter((f) => RELATION_FIELD_TYPES.has(f.field_type))\n .map((f) => f.name)\n )\n\n const projector = projectors[typeName]!\n const fieldKeys = projector.map\n\n const requiredFields = contentType.fields.filter((f) => f.required)\n\n const mediaFields = contentType.fields.filter(\n (f) => f.field_type === 'image' || f.field_type === 'video' || f.field_type === 'file'\n )\n\n const paragraphFieldDefs = contentType.fields.filter((f) => f.db_column === null)\n\n // Free-text search target — text/plain fields (mirrors the \"first text field is\n // the title\" convention the admin frontend already uses) plus slug, when present.\n // Singleton (only_one) content types have no slug column at all.\n const searchableColumns = [\n ...contentType.fields\n .filter((f) => f.field_type === 'text/plain' && f.db_column !== null)\n .map((f) => f.db_column!.column_name),\n ...(contentType.system_fields.some((f) => f.name === 'slug') ? ['slug'] : []),\n ]\n\n // GET /admin/api/{base_path}\n app.get(\n `/admin/api/${basePath}`,\n requirePermission('content:read'),\n async (c) => {\n const parsed = parseListQuery(c.req.url, schemaFieldNames, relationFieldNames, fieldKeys.columnFor)\n if (!parsed.ok) {\n return c.json({ ok: false, error: parsed.response }, parsed.status)\n }\n\n const publishedParam = c.req.query('published')\n const extraFilters: Record<string, FilterValue> = {}\n if (publishedParam === 'false') extraFilters['published'] = false\n\n // parsed.sortBy is a validated label; map it to its storage column\n // before it reaches the repository. The cast is a narrow lie — core\n // types sort_by as the label union, but the repository immediately\n // re-validates the mapped value against sortableColumns.\n const sortColumn = fieldKeys.columnFor(parsed.sortBy) ?? parsed.sortBy\n\n const findOpts: Parameters<typeof repo.findMany>[0] = {\n page: parsed.pagination.page,\n per_page: parsed.pagination.per_page,\n sort_by: sortColumn as 'title' | 'created_at' | 'updated_at',\n sort_order: parsed.sortOrder as 'asc' | 'desc',\n filters: { ...parsed.filters, ...extraFilters },\n include: parsed.include,\n }\n if (publishedParam === 'true') findOpts.published_only = true\n if (parsed.search !== '' && searchableColumns.length > 0) {\n findOpts.search = { term: parsed.search, columns: searchableColumns }\n }\n\n const result = await repo.findMany(findOpts)\n\n // Outbound boundary: rows are storage-keyed; responses speak labels.\n const data = result.data.map((row) =>\n projectRow(row as Record<string, unknown>, typeName, projectors)\n )\n\n return c.json({ ...result, data })\n }\n )\n\n // GET /admin/api/{base_path}/:id\n app.get(\n `/admin/api/${basePath}/:id`,\n requirePermission('content:read'),\n async (c) => {\n const id = c.req.param('id')\n const item = await repo.findOne(id)\n\n if (!item) {\n return c.json(\n { ok: false, error: { code: 'NOT_FOUND', message: 'Not found' } },\n 404\n )\n }\n\n // Resolve paragraph and junction fields so the edit form loads real data.\n // This deliberately does NOT reuse the relation module's resolvers: the\n // edit read scopes paragraph rows by parent_id AND parent_field, whereas\n // resolveRelationField scopes by parent_id only. Two paragraph fields of\n // the same paragraph type would be mixed by the resolver — so the more\n // precise per-field read stays here on purpose.\n if (db) {\n const row = item as Record<string, unknown>\n for (const f of contentType.fields) {\n if (f.db_column === null) {\n const comp = f.ui_component as { component: string; ref?: string }\n if (comp.component !== 'paragraph-embed' || !comp.ref) continue\n const pType = registry.paragraph_types[comp.ref]\n if (!pType) continue\n row[f.name] = await loadParagraphRows(db, registry, pType, id, f.name)\n } else if (f.db_column.junction) {\n const j = f.db_column.junction\n const r = await db.execute(\n sql`SELECT ${sql.raw(quoteIdent(j.right_column))} FROM ${sql.raw(quoteIdent(j.table_name))} WHERE ${sql.raw(quoteIdent(j.left_column))} = ${id}`\n )\n row[f.name] = r.rows.map(\n (row) => (row as Record<string, unknown>)[j.right_column] as string\n )\n }\n }\n }\n\n // Outbound boundary: rows are storage-keyed; responses speak labels.\n // Mapped after the paragraph/junction population above so those\n // label-keyed additions survive.\n const data = projectRow(item as Record<string, unknown>, typeName, projectors)\n\n return c.json({ ok: true, data })\n }\n )\n\n // POST /admin/api/{base_path}\n app.post(\n `/admin/api/${basePath}`,\n requirePermission('content:create'),\n async (c) => {\n const body = (await c.req.json()) as Record<string, unknown>\n\n if (contentType.only_one) {\n const existing = await repo.findMany({ page: 1, per_page: 1 })\n if (existing.meta.total > 0) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'SINGLETON_ALREADY_EXISTS',\n message: `Only one instance of '${contentType.label}' is allowed`,\n },\n },\n 409\n )\n }\n } else {\n const slug = body['slug']\n if (typeof slug !== 'string' || slug.trim() === '') {\n return c.json(\n {\n ok: false,\n error: {\n code: 'VALIDATION_ERROR',\n message: 'Required fields are missing',\n details: [{ field: 'slug', message: 'Slug is required' }],\n },\n },\n 422\n )\n }\n\n if (!SLUG_PATTERN.test(slug)) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'INVALID_SLUG_FORMAT',\n message:\n 'Slug must be lowercase alphanumeric with hyphens only — no leading or trailing hyphens',\n },\n },\n 422\n )\n }\n\n const conflict = await repo.findBySlug(slug)\n if (conflict) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'SLUG_CONFLICT',\n message: `Slug '${slug}' already exists for this content type`,\n },\n },\n 409\n )\n }\n }\n\n return writeNewItem(c, body)\n }\n )\n\n // Shared write paths. POST, PATCH and the singleton PUT all funnel through\n // these two functions so the verbs cannot drift apart — the verb-specific\n // pre-checks (slug rules, singleton existence, 404s) stay in the routes.\n\n const writeNewItem = async (c: Context, body: Record<string, unknown>) => {\n // Inbound boundary: the request body arrives label-keyed; everything\n // downstream (insert data, media delta) works in storage keys.\n const storageBody = fieldKeys.toStorage(body)\n\n const fieldErrors = checkRequiredFields(requiredFields, body)\n if (fieldErrors.length > 0) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'VALIDATION_ERROR',\n message: 'Required fields are missing',\n details: fieldErrors,\n },\n },\n 422\n )\n }\n\n if (body['published'] === true) {\n const publishDeny = await requirePermission('content:edit')(c, async () => {})\n if (publishDeny) return publishDeny\n }\n\n // Classify fields\n const paragraphFields = contentType.fields.filter((f) => f.db_column === null)\n const junctionFields = contentType.fields.filter(\n (f) => f.db_column !== null && f.db_column.junction !== undefined\n )\n\n // Storage-keyed payload. `storageBody` was normalized at the inbound\n // boundary, so each value is found under its column name.\n const columnBackedFields = contentType.fields.filter(isColumnBacked)\n const columnData: Record<string, unknown> = {\n published: body['published'] ?? false,\n }\n if (contentType.only_one) {\n columnData['slug'] = typeName\n } else {\n columnData['slug'] = body['slug']\n }\n for (const f of columnBackedFields) {\n const key = f.db_column.column_name\n if (!(key in storageBody)) continue\n columnData[key] = storageBody[key]\n }\n\n // Resolve base_path_id (seeded from routes.json at startup)\n if (db) {\n const basePathId = await lookupBasePathId(db, contentType.default_base_path)\n if (!basePathId) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'BASE_PATH_NOT_FOUND',\n message: `Base path '${contentType.default_base_path}' not found — run manguito migrate`,\n },\n },\n 500\n )\n }\n columnData['base_path_id'] = basePathId\n }\n\n const item = await repo.create(columnData as Parameters<typeof repo.create>[0])\n const itemId = (item as Record<string, unknown>)['id'] as string\n\n // Reconcile media reference counts across top-level fields and paragraphs.\n const mediaDeltas: MediaDelta[] = [topLevelMediaDelta(mediaFields, null, storageBody)]\n\n // Save paragraph and junction rows\n if (db) {\n for (const f of paragraphFields) {\n const comp = f.ui_component as { component: string; ref?: string }\n if (comp.component !== 'paragraph-embed' || !comp.ref) continue\n const pType = registry.paragraph_types[comp.ref]\n if (!pType) continue\n const items = Array.isArray(body[f.name]) ? (body[f.name] as unknown[]) : []\n mediaDeltas.push(await persistParagraphField(db, itemId, contentType.db.table_name, f.name, pType, items, registry))\n }\n for (const f of junctionFields) {\n const junction = f.db_column!.junction!\n const relatedIds = Array.isArray(body[f.name])\n ? (body[f.name] as unknown[]).filter((v): v is string => typeof v === 'string')\n : []\n await persistJunctionField(db, itemId, junction, relatedIds)\n }\n }\n\n await applyMediaReferenceDelta(mergeMediaDeltas(...mediaDeltas), mediaRepo)\n\n // Outbound boundary: rows are storage-keyed; responses speak labels.\n const data = projectRow(item as Record<string, unknown>, typeName, projectors)\n\n return c.json({ ok: true, data }, 201)\n }\n\n // PATCH /admin/api/{base_path}/:id\n app.patch(\n `/admin/api/${basePath}/:id`,\n requirePermission('content:edit'),\n async (c) => {\n const id = c.req.param('id')\n const body = (await c.req.json()) as Record<string, unknown>\n\n const existing = await repo.findOne(id)\n if (!existing) {\n return c.json(\n { ok: false, error: { code: 'NOT_FOUND', message: 'Not found' } },\n 404\n )\n }\n\n if (!contentType.only_one && 'slug' in body) {\n const slug = body['slug']\n if (typeof slug !== 'string' || slug.trim() === '') {\n return c.json(\n {\n ok: false,\n error: {\n code: 'VALIDATION_ERROR',\n message: 'Required fields are missing',\n details: [{ field: 'slug', message: 'Slug cannot be empty' }],\n },\n },\n 422\n )\n }\n\n if (!SLUG_PATTERN.test(slug)) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'INVALID_SLUG_FORMAT',\n message:\n 'Slug must be lowercase alphanumeric with hyphens only — no leading or trailing hyphens',\n },\n },\n 422\n )\n }\n\n const conflict = await repo.findBySlug(slug)\n if (conflict && (conflict as Record<string, unknown>)['id'] !== id) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'SLUG_CONFLICT',\n message: `Slug '${slug}' already exists for this content type`,\n },\n },\n 409\n )\n }\n }\n\n return writeExistingItem(c, id, existing as Record<string, unknown>, body)\n }\n )\n\n const writeExistingItem = async (\n c: Context,\n id: string,\n existing: Record<string, unknown>,\n body: Record<string, unknown>,\n ) => {\n // Inbound boundary: the request body arrives label-keyed; everything\n // downstream (insert data, media delta) works in storage keys.\n const storageBody = fieldKeys.toStorage(body)\n\n if (body['published'] === true) {\n const publishDeny = await requirePermission('content:edit')(c, async () => {})\n if (publishDeny) return publishDeny\n\n // `existing` is a raw SELECT * row (storage-keyed) but\n // checkRequiredFields reads labels, as does `body` — project the row\n // to labels first so a renamed field's stored value is actually seen.\n const merged = { ...fieldKeys.toLabels(existing as Record<string, unknown>), ...body }\n const fieldErrors = checkRequiredFields(requiredFields, merged)\n if (fieldErrors.length > 0) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'PUBLISH_VALIDATION_ERROR',\n message: 'Cannot publish — required fields are missing',\n details: fieldErrors,\n },\n },\n 422\n )\n }\n }\n\n // Classify fields\n const patchParagraphFields = contentType.fields.filter((f) => f.db_column === null)\n const patchJunctionFields = contentType.fields.filter(\n (f) => f.db_column !== null && f.db_column.junction !== undefined\n )\n\n // Storage-keyed payload. `storageBody` was normalized at the inbound\n // boundary, so each value is found under its column name.\n const patchColumnBackedFields = contentType.fields.filter(isColumnBacked)\n const patchData: Record<string, unknown> = {}\n if (!contentType.only_one && 'slug' in body) patchData['slug'] = body['slug']\n if ('published' in body) patchData['published'] = body['published']\n for (const f of patchColumnBackedFields) {\n const key = f.db_column.column_name\n if (!(key in storageBody)) continue\n patchData[key] = storageBody[key]\n }\n\n const updated = await repo.update(id, patchData as Parameters<typeof repo.update>[1])\n if (!updated) {\n return c.json(\n { ok: false, error: { code: 'NOT_FOUND', message: 'Not found' } },\n 404\n )\n }\n\n // Reconcile media reference counts across top-level fields and paragraphs.\n const mediaDeltas: MediaDelta[] = [\n topLevelMediaDelta(mediaFields, existing as Record<string, unknown>, storageBody),\n ]\n\n // Delete+reinsert paragraph and junction rows\n if (db) {\n for (const f of patchParagraphFields) {\n const comp = f.ui_component as { component: string; ref?: string }\n if (comp.component !== 'paragraph-embed' || !comp.ref) continue\n const pType = registry.paragraph_types[comp.ref]\n if (!pType) continue\n const items = Array.isArray(body[f.name]) ? (body[f.name] as unknown[]) : []\n mediaDeltas.push(await persistParagraphField(db, id, contentType.db.table_name, f.name, pType, items, registry))\n }\n for (const f of patchJunctionFields) {\n const junction = f.db_column!.junction!\n const relatedIds = Array.isArray(body[f.name])\n ? (body[f.name] as unknown[]).filter((v): v is string => typeof v === 'string')\n : []\n await persistJunctionField(db, id, junction, relatedIds)\n }\n }\n\n await applyMediaReferenceDelta(mergeMediaDeltas(...mediaDeltas), mediaRepo)\n\n // Outbound boundary: rows are storage-keyed; responses speak labels.\n const data = projectRow(updated as Record<string, unknown>, typeName, projectors)\n\n return c.json({ ok: true, data })\n }\n\n // PUT /admin/api/{base_path} — singleton upsert.\n //\n // A singleton has no id in its admin route, so it cannot be addressed by the\n // `/:id` PATCH. The parser declares `http_methods: ['GET','PUT','PATCH']` for\n // `only_one` types and the admin form posts here, so this is the verb that\n // completes that contract: create the row on first save, update it after.\n // Registered only for singletons — collections keep POST/PATCH.\n if (contentType.only_one) {\n app.put(\n `/admin/api/${basePath}`,\n requirePermission('content:edit'),\n async (c) => {\n const body = (await c.req.json()) as Record<string, unknown>\n\n const existingResult = await repo.findMany({ page: 1, per_page: 1 })\n const existing = existingResult.data[0] as Record<string, unknown> | undefined\n\n if (!existing) {\n // First save materialises the row, which is a create — gate it on\n // content:create in addition to the route's content:edit.\n const createDeny = await requirePermission('content:create')(c, async () => {})\n if (createDeny) return createDeny\n return writeNewItem(c, body)\n }\n\n return writeExistingItem(c, existing['id'] as string, existing, body)\n }\n )\n }\n\n // DELETE /admin/api/{base_path}/:id\n app.delete(\n `/admin/api/${basePath}/:id`,\n requirePermission('content:delete'),\n async (c) => {\n const id = c.req.param('id')\n const item = await repo.findOne(id)\n\n if (!item) {\n return c.json(\n { ok: false, error: { code: 'NOT_FOUND', message: 'Not found' } },\n 404\n )\n }\n\n // A delete removes every media reference the item held (top-level + paragraphs).\n const mediaDeltas: MediaDelta[] = [\n topLevelMediaDelta(mediaFields, item as Record<string, unknown>, null),\n ]\n\n // Paragraph rows have no FK/cascade back to their parent — clean them up\n // explicitly so they don't leak, and collect their media for decrementing.\n if (db) {\n for (const f of paragraphFieldDefs) {\n const comp = f.ui_component as { component: string; ref?: string }\n if (comp.component !== 'paragraph-embed' || !comp.ref) continue\n const pType = registry.paragraph_types[comp.ref]\n if (!pType) continue\n mediaDeltas.push(await deleteParagraphField(db, id, f.name, pType, registry))\n }\n }\n\n await applyMediaReferenceDelta(mergeMediaDeltas(...mediaDeltas), mediaRepo)\n\n await repo.delete(id)\n return c.json({ ok: true })\n }\n )\n }\n\n // ── Taxonomy type routes ──────────────────────────────────────────────────\n\n for (const [typeName, taxonomyType] of Object.entries(registry.taxonomy_types)) {\n const repo = repos[typeName]\n if (!repo) continue\n\n const schemaFieldNames = new Set<string>([\n ...taxonomyType.fields.map((f) => f.name),\n ...taxonomyType.system_fields.map((f) => f.name),\n ])\n\n const relationFieldNames = new Set<string>(\n taxonomyType.fields\n .filter((f) => RELATION_FIELD_TYPES.has(f.field_type))\n .map((f) => f.name)\n )\n\n const projector = projectors[typeName]!\n const fieldKeys = projector.map\n\n const requiredFields = taxonomyType.fields.filter((f) => f.required)\n\n const mediaFields = taxonomyType.fields.filter(\n (f) => f.field_type === 'image' || f.field_type === 'video' || f.field_type === 'file'\n )\n\n const paragraphFieldDefs = taxonomyType.fields.filter((f) => f.db_column === null)\n\n const searchableColumns = [\n ...taxonomyType.fields\n .filter((f) => f.field_type === 'text/plain' && f.db_column !== null)\n .map((f) => f.db_column!.column_name),\n ...(taxonomyType.system_fields.some((f) => f.name === 'slug') ? ['slug'] : []),\n ]\n\n // GET /admin/api/taxonomy/{type}\n app.get(\n `/admin/api/taxonomy/${typeName}`,\n requirePermission('content:read'),\n async (c) => {\n const parsed = parseListQuery(c.req.url, schemaFieldNames, relationFieldNames, fieldKeys.columnFor)\n if (!parsed.ok) {\n return c.json({ ok: false, error: parsed.response }, parsed.status)\n }\n\n const publishedParam = c.req.query('published')\n const extraFilters: Record<string, FilterValue> = {}\n if (publishedParam === 'false') extraFilters['published'] = false\n\n // parsed.sortBy is a validated label; map it to its storage column\n // before it reaches the repository. The cast is a narrow lie — core\n // types sort_by as the label union, but the repository immediately\n // re-validates the mapped value against sortableColumns.\n const sortColumn = fieldKeys.columnFor(parsed.sortBy) ?? parsed.sortBy\n\n const findOpts: Parameters<typeof repo.findMany>[0] = {\n page: parsed.pagination.page,\n per_page: parsed.pagination.per_page,\n sort_by: sortColumn as 'title' | 'created_at' | 'updated_at',\n sort_order: parsed.sortOrder as 'asc' | 'desc',\n filters: { ...parsed.filters, ...extraFilters },\n include: parsed.include,\n }\n if (publishedParam === 'true') findOpts.published_only = true\n if (parsed.search !== '' && searchableColumns.length > 0) {\n findOpts.search = { term: parsed.search, columns: searchableColumns }\n }\n\n const result = await repo.findMany(findOpts)\n\n // Outbound boundary: rows are storage-keyed; responses speak labels.\n const data = result.data.map((row) =>\n projectRow(row as Record<string, unknown>, typeName, projectors)\n )\n\n return c.json({ ...result, data })\n }\n )\n\n // GET /admin/api/taxonomy/{type}/:id\n app.get(\n `/admin/api/taxonomy/${typeName}/:id`,\n requirePermission('content:read'),\n async (c) => {\n const id = c.req.param('id')\n const item = await repo.findOne(id)\n\n if (!item) {\n return c.json(\n { ok: false, error: { code: 'NOT_FOUND', message: 'Taxonomy term not found' } },\n 404\n )\n }\n\n // Outbound boundary: rows are storage-keyed; responses speak labels.\n const data = projectRow(item as Record<string, unknown>, typeName, projectors)\n\n return c.json({ ok: true, data })\n }\n )\n\n // POST /admin/api/taxonomy/{type}\n app.post(\n `/admin/api/taxonomy/${typeName}`,\n requirePermission('content:create'),\n async (c) => {\n const body = (await c.req.json()) as Record<string, unknown>\n\n // Inbound boundary: the request body arrives label-keyed; everything\n // downstream (insert data, media delta) works in storage keys.\n const storageBody = fieldKeys.toStorage(body)\n\n const fieldErrors = checkRequiredFields(requiredFields, body)\n if (fieldErrors.length > 0) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'VALIDATION_ERROR',\n message: 'Required fields are missing',\n details: fieldErrors,\n },\n },\n 422\n )\n }\n\n if (body['published'] === true) {\n const publishDeny = await requirePermission('content:edit')(c, async () => {})\n if (publishDeny) return publishDeny\n }\n\n // Filter to column-only fields\n const taxParagraphFields = taxonomyType.fields.filter((f) => f.db_column === null)\n\n // Storage-keyed payload. `storageBody` was normalized at the inbound\n // boundary, so each value is found under its column name.\n const taxColumnBackedFields = taxonomyType.fields.filter(isColumnBacked)\n const taxColumnData: Record<string, unknown> = {\n published: body['published'] ?? false,\n }\n for (const f of taxColumnBackedFields) {\n const key = f.db_column.column_name\n if (!(key in storageBody)) continue\n taxColumnData[key] = storageBody[key]\n }\n\n const item = await repo.create(taxColumnData as Parameters<typeof repo.create>[0])\n const taxItemId = (item as Record<string, unknown>)['id'] as string\n\n // Reconcile media reference counts across top-level fields and paragraphs.\n const mediaDeltas: MediaDelta[] = [topLevelMediaDelta(mediaFields, null, storageBody)]\n\n if (db) {\n for (const f of taxParagraphFields) {\n const comp = f.ui_component as { component: string; ref?: string }\n if (comp.component !== 'paragraph-embed' || !comp.ref) continue\n const pType = registry.paragraph_types[comp.ref]\n if (!pType) continue\n const items = Array.isArray(body[f.name]) ? (body[f.name] as unknown[]) : []\n mediaDeltas.push(await persistParagraphField(db, taxItemId, taxonomyType.db.table_name, f.name, pType, items, registry))\n }\n }\n\n await applyMediaReferenceDelta(mergeMediaDeltas(...mediaDeltas), mediaRepo)\n\n // Outbound boundary: rows are storage-keyed; responses speak labels.\n const data = projectRow(item as Record<string, unknown>, typeName, projectors)\n\n return c.json({ ok: true, data }, 201)\n }\n )\n\n // PATCH /admin/api/taxonomy/{type}/:id\n app.patch(\n `/admin/api/taxonomy/${typeName}/:id`,\n requirePermission('content:edit'),\n async (c) => {\n const id = c.req.param('id')\n const body = (await c.req.json()) as Record<string, unknown>\n\n // Inbound boundary: the request body arrives label-keyed; everything\n // downstream (insert data, media delta) works in storage keys.\n const storageBody = fieldKeys.toStorage(body)\n\n const existing = await repo.findOne(id)\n if (!existing) {\n return c.json(\n { ok: false, error: { code: 'NOT_FOUND', message: 'Taxonomy term not found' } },\n 404\n )\n }\n\n if (body['published'] === true) {\n const publishDeny = await requirePermission('content:edit')(c, async () => {})\n if (publishDeny) return publishDeny\n\n // `existing` is a raw SELECT * row (storage-keyed) but\n // checkRequiredFields reads labels, as does `body` — project the row\n // to labels first so a renamed field's stored value is actually seen.\n const merged = { ...fieldKeys.toLabels(existing as Record<string, unknown>), ...body }\n const fieldErrors = checkRequiredFields(requiredFields, merged)\n if (fieldErrors.length > 0) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'PUBLISH_VALIDATION_ERROR',\n message: 'Cannot publish — required fields are missing',\n details: fieldErrors,\n },\n },\n 422\n )\n }\n }\n\n // Filter to column-only fields\n const taxPatchParagraphFields = taxonomyType.fields.filter((f) => f.db_column === null)\n\n // Storage-keyed payload. `storageBody` was normalized at the inbound\n // boundary, so each value is found under its column name.\n const taxPatchColumnBackedFields = taxonomyType.fields.filter(isColumnBacked)\n const taxPatchData: Record<string, unknown> = {}\n if ('published' in body) taxPatchData['published'] = body['published']\n for (const f of taxPatchColumnBackedFields) {\n const key = f.db_column.column_name\n if (!(key in storageBody)) continue\n taxPatchData[key] = storageBody[key]\n }\n\n const updated = await repo.update(id, taxPatchData as Parameters<typeof repo.update>[1])\n if (!updated) {\n return c.json(\n { ok: false, error: { code: 'NOT_FOUND', message: 'Taxonomy term not found' } },\n 404\n )\n }\n\n // Reconcile media reference counts across top-level fields and paragraphs.\n const mediaDeltas: MediaDelta[] = [\n topLevelMediaDelta(mediaFields, existing as Record<string, unknown>, storageBody),\n ]\n\n if (db) {\n for (const f of taxPatchParagraphFields) {\n const comp = f.ui_component as { component: string; ref?: string }\n if (comp.component !== 'paragraph-embed' || !comp.ref) continue\n const pType = registry.paragraph_types[comp.ref]\n if (!pType) continue\n const items = Array.isArray(body[f.name]) ? (body[f.name] as unknown[]) : []\n mediaDeltas.push(await persistParagraphField(db, id, taxonomyType.db.table_name, f.name, pType, items, registry))\n }\n }\n\n await applyMediaReferenceDelta(mergeMediaDeltas(...mediaDeltas), mediaRepo)\n\n // Outbound boundary: rows are storage-keyed; responses speak labels.\n const data = projectRow(updated as Record<string, unknown>, typeName, projectors)\n\n return c.json({ ok: true, data })\n }\n )\n\n // DELETE /admin/api/taxonomy/{type}/:id\n app.delete(\n `/admin/api/taxonomy/${typeName}/:id`,\n requirePermission('content:delete'),\n async (c) => {\n const id = c.req.param('id')\n const item = await repo.findOne(id)\n\n if (!item) {\n return c.json(\n { ok: false, error: { code: 'NOT_FOUND', message: 'Taxonomy term not found' } },\n 404\n )\n }\n\n // A delete removes every media reference the item held (top-level + paragraphs).\n const mediaDeltas: MediaDelta[] = [\n topLevelMediaDelta(mediaFields, item as Record<string, unknown>, null),\n ]\n\n if (db) {\n for (const f of paragraphFieldDefs) {\n const comp = f.ui_component as { component: string; ref?: string }\n if (comp.component !== 'paragraph-embed' || !comp.ref) continue\n const pType = registry.paragraph_types[comp.ref]\n if (!pType) continue\n mediaDeltas.push(await deleteParagraphField(db, id, f.name, pType, registry))\n }\n }\n\n await applyMediaReferenceDelta(mergeMediaDeltas(...mediaDeltas), mediaRepo)\n\n await repo.delete(id)\n return c.json({ ok: true })\n }\n )\n }\n\n // ── Config endpoint ───────────────────────────────────────────────────────\n\n app.get('/admin/api/config', (c) => {\n return c.json({\n ok: true,\n data: {\n content_types: Object.values(registry.content_types).map((ct) => ({\n name: ct.name,\n label: ct.label,\n only_one: ct.only_one,\n default_base_path: ct.default_base_path,\n fields: ct.fields,\n system_fields: ct.system_fields,\n })),\n taxonomy_types: Object.values(registry.taxonomy_types).map((tt) => ({\n name: tt.name,\n label: tt.label,\n fields: tt.fields,\n system_fields: tt.system_fields,\n })),\n enum_types: Object.values(registry.enum_types).map((et) => ({\n name: et.name,\n label: et.label,\n values: et.values,\n })),\n },\n })\n })\n}\n","import type { MediaRepository, ParsedField } from '@bobbykim/manguito-cms-core'\n\n// ─── Media reference tracking ─────────────────────────────────────────────────\n//\n// Reconciles media.reference_count on a content write. reference_count counts\n// *content items* that reference a media row (see CONTEXT.md \"Orphaned media\"\n// and docs/adr/api/0007), not reference slots — so a media id used by several\n// fields/paragraphs of one item counts once, and an id moved between slots in\n// one write is a no-op.\n//\n// A write produces a MediaDelta of the ids it gains and loses. Top-level field\n// deltas are computed here (pure); paragraph deltas are produced by the paragraph\n// persistence path and merged in. The merged delta is applied once.\n\nexport type MediaDelta = {\n added: string[]\n removed: string[]\n}\n\n// A media field holds a raw id string (not a resolved { id, url } object) on the\n// content/paragraph row. Empty string and non-strings mean \"no media\".\nfunction extractMediaId(value: unknown): string | null {\n return typeof value === 'string' && value !== '' ? value : null\n}\n\n// Media ids gained and lost between two content states for the top-level media\n// fields. BOTH `before` and `after` are STORAGE-KEYED (column names), never\n// label-keyed — callers normalize a request body with FieldKeyMap.toStorage\n// before calling. Unifies all three writes:\n// create — before = null (no prior ids; set fields are added)\n// update — before/after are rows (a field absent from `after` is untouched)\n// delete — after = null (every current id is removed)\nexport function topLevelMediaDelta(\n mediaFields: ParsedField[],\n before: Record<string, unknown> | null,\n after: Record<string, unknown> | null\n): MediaDelta {\n const added: string[] = []\n const removed: string[] = []\n\n for (const f of mediaFields) {\n // Media fields are always column-backed, so the storage key always exists.\n const key = f.db_column?.column_name\n if (key === undefined || key === '') continue\n\n // On a partial update, a media field not present in the patch is untouched.\n // (after === null is a delete — every field is in scope.)\n if (after !== null && !(key in after)) continue\n\n const oldId = before ? extractMediaId(before[key]) : null\n const newId = after ? extractMediaId(after[key]) : null\n if (newId === oldId) continue\n if (oldId) removed.push(oldId)\n if (newId) added.push(newId)\n }\n\n return { added, removed }\n}\n\n// Combine the top-level delta with each paragraph field's delta into one delta.\nexport function mergeMediaDeltas(...deltas: MediaDelta[]): MediaDelta {\n return {\n added: deltas.flatMap((d) => d.added),\n removed: deltas.flatMap((d) => d.removed),\n }\n}\n\n// Apply a delta to reference counts, per content item:\n// - dedup each side (an id referenced by many slots counts once)\n// - cancel ids that are both added and removed (moved between slots → no-op)\n// - skip the repo call when a side is empty\nexport async function applyMediaReferenceDelta(\n delta: MediaDelta,\n mediaRepo: MediaRepository\n): Promise<void> {\n const added = new Set(delta.added)\n const removed = new Set(delta.removed)\n\n const netAdd = [...added].filter((id) => !removed.has(id))\n const netRemove = [...removed].filter((id) => !added.has(id))\n\n if (netAdd.length > 0) await mediaRepo.incrementReferenceCount(netAdd)\n if (netRemove.length > 0) await mediaRepo.decrementReferenceCount(netRemove)\n}\n","// Admin media routes — upload, presigned URL, update, delete\nimport type { Context, Hono } from 'hono'\nimport type { MediaRepository, StorageAdapter } from '@bobbykim/manguito-cms-core'\nimport { sign, verify } from 'hono/jwt'\nimport type { createPermissionMiddleware } from '../../middleware/permission.js'\n\n// ─── Accepted MIME types ──────────────────────────────────────────────────────\n\nconst IMAGE_MIME_TYPES = new Set([\n 'image/jpeg',\n 'image/png',\n 'image/webp',\n 'image/gif',\n])\n\nconst VIDEO_MIME_TYPES = new Set(['video/mp4', 'video/webm', 'video/quicktime'])\n\nconst FILE_MIME_TYPES = new Set(['application/pdf'])\n\n// ─── Pending upload token ─────────────────────────────────────────────────────\n// The presigned-url request and the confirm request can land on different\n// serverless instances, so pending-upload state cannot live in memory. Encode it\n// in a short-lived token signed with AUTH_SECRET — stateless and tamper-proof —\n// and hand it to the client as media_id; confirm verifies it.\n\ntype PendingUpload = {\n key: string\n folder: 'image' | 'video' | 'file'\n mime_type: string\n}\n\nfunction authSecret(): string {\n const secret = process.env['AUTH_SECRET']\n if (!secret) throw new Error('AUTH_SECRET environment variable is not set')\n return secret\n}\n\nasync function signPendingUpload(data: PendingUpload, expiresAt: number): Promise<string> {\n return sign({ ...data, exp: expiresAt }, authSecret())\n}\n\nasync function verifyPendingUpload(token: string): Promise<PendingUpload | null> {\n try {\n const p = await verify(token, authSecret(), 'HS256') as {\n key: string\n folder: 'image' | 'video' | 'file'\n mime_type: string\n }\n return { key: p.key, folder: p.folder, mime_type: p.mime_type }\n } catch {\n return null\n }\n}\n\n\nasync function handleDirectUpload(\n folder: 'image' | 'video' | 'file',\n acceptedTypes: Set<string>,\n mediaRepo: MediaRepository,\n storage: StorageAdapter,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n c: Context<any, any, any>,\n altRequired: boolean,\n maxFileSize: number | undefined\n): Promise<Response> {\n // Direct uploads buffer the whole file in the CMS server's memory (unlike the\n // presigned flow, which uploads straight to storage). Reject oversized bodies\n // up front via Content-Length so we never buffer them — and keep max_file_size\n // under the platform payload limit on serverless (Lambda ~6 MB).\n if (maxFileSize !== undefined) {\n const contentLength = Number(c.req.header('content-length'))\n if (Number.isFinite(contentLength) && contentLength > maxFileSize) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'FILE_TOO_LARGE',\n message: `Upload exceeds the maximum size of ${maxFileSize} bytes`,\n },\n },\n 413\n )\n }\n }\n\n let formData: FormData\n try {\n formData = await c.req.formData()\n } catch {\n return c.json(\n { ok: false, error: { code: 'VALIDATION_ERROR', message: 'Expected multipart/form-data' } },\n 422\n )\n }\n\n const fileField = formData.get('file')\n if (!(fileField instanceof File)) {\n return c.json(\n { ok: false, error: { code: 'VALIDATION_ERROR', message: 'file field is required' } },\n 422\n )\n }\n\n // Defensive second check: Content-Length may be absent (chunked) or count\n // multipart overhead, so also reject on the actual decoded file size.\n if (maxFileSize !== undefined && fileField.size > maxFileSize) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'FILE_TOO_LARGE',\n message: `File exceeds the maximum size of ${maxFileSize} bytes`,\n },\n },\n 413\n )\n }\n\n const mimeType = fileField.type\n if (!acceptedTypes.has(mimeType)) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'UNSUPPORTED_MIME_TYPE',\n message: `MIME type '${mimeType}' is not accepted by this endpoint`,\n },\n },\n 415\n )\n }\n\n const altValue = formData.get('alt')\n const alt = typeof altValue === 'string' && altValue.trim() !== '' ? altValue.trim() : undefined\n\n if (altRequired && !alt) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'VALIDATION_ERROR',\n message: 'alt is required for this media type',\n details: [{ field: 'alt', message: 'alt is required' }],\n },\n },\n 422\n )\n }\n\n let presigned: Awaited<ReturnType<StorageAdapter['getPresignedUploadUrl']>>\n try {\n presigned = await storage.getPresignedUploadUrl({\n folder,\n filename: fileField.name,\n mime_type: mimeType,\n })\n } catch {\n return c.json(\n { ok: false, error: { code: 'STORAGE_ERROR', message: 'Failed to generate upload URL' } },\n 502\n )\n }\n\n if (storage.upload) {\n try {\n const bytes = new Uint8Array(await fileField.arrayBuffer())\n await storage.upload(presigned.key, bytes, mimeType)\n } catch (err) {\n console.error('[media] storage.upload error:', err)\n return c.json(\n { ok: false, error: { code: 'STORAGE_ERROR', message: 'Storage upload failed' } },\n 502\n )\n }\n }\n\n const url = storage.getUrl(presigned.key)\n const fileSize = fileField.size\n\n const item = await mediaRepo.create({\n type: folder,\n url,\n mime_type: mimeType,\n ...(alt !== undefined && { alt }),\n file_size: fileSize,\n })\n\n return c.json({ ok: true, data: item }, 201)\n}\n\n// ─── Register ─────────────────────────────────────────────────────────────────\n\nexport function registerAdminMediaRoutes(\n app: Hono,\n mediaRepo: MediaRepository,\n storage: StorageAdapter,\n requirePermission: ReturnType<typeof createPermissionMiddleware>,\n maxFileSize: number | undefined,\n): void {\n // POST /admin/api/media/image\n app.post(\n '/admin/api/media/image',\n requirePermission('media:create'),\n async (c) => {\n return handleDirectUpload('image', IMAGE_MIME_TYPES, mediaRepo, storage, c, false, maxFileSize)\n }\n )\n\n // POST /admin/api/media/video\n app.post(\n '/admin/api/media/video',\n requirePermission('media:create'),\n async (c) => {\n return handleDirectUpload('video', VIDEO_MIME_TYPES, mediaRepo, storage, c, true, maxFileSize)\n }\n )\n\n // POST /admin/api/media/file\n app.post(\n '/admin/api/media/file',\n requirePermission('media:create'),\n async (c) => {\n return handleDirectUpload('file', FILE_MIME_TYPES, mediaRepo, storage, c, true, maxFileSize)\n }\n )\n\n // GET /admin/api/media/presigned-url\n app.get('/admin/api/media/presigned-url', requirePermission('media:create'), async (c) => {\n const type = c.req.query('type') as 'image' | 'video' | 'file' | undefined\n const filename = c.req.query('filename')\n const mimeType = c.req.query('mime_type')\n\n if (!type || !['image', 'video', 'file'].includes(type)) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'VALIDATION_ERROR',\n message: 'type must be one of: image, video, file',\n details: [{ field: 'type', message: 'type is required' }],\n },\n },\n 422\n )\n }\n\n if (!filename || filename.trim() === '') {\n return c.json(\n {\n ok: false,\n error: {\n code: 'VALIDATION_ERROR',\n message: 'filename is required',\n details: [{ field: 'filename', message: 'filename is required' }],\n },\n },\n 422\n )\n }\n\n if (!mimeType || mimeType.trim() === '') {\n return c.json(\n {\n ok: false,\n error: {\n code: 'VALIDATION_ERROR',\n message: 'mime_type is required',\n details: [{ field: 'mime_type', message: 'mime_type is required' }],\n },\n },\n 422\n )\n }\n\n const accepted =\n type === 'image' ? IMAGE_MIME_TYPES : type === 'video' ? VIDEO_MIME_TYPES : FILE_MIME_TYPES\n\n if (!accepted.has(mimeType)) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'UNSUPPORTED_MIME_TYPE',\n message: `MIME type '${mimeType}' is not accepted for ${type} uploads`,\n },\n },\n 415\n )\n }\n\n let presigned: Awaited<ReturnType<StorageAdapter['getPresignedUploadUrl']>>\n try {\n presigned = await storage.getPresignedUploadUrl({\n folder: type,\n filename,\n mime_type: mimeType,\n })\n } catch {\n return c.json(\n { ok: false, error: { code: 'STORAGE_ERROR', message: 'Failed to generate upload URL' } },\n 502\n )\n }\n\n const media_id = await signPendingUpload(\n { key: presigned.key, folder: type, mime_type: mimeType },\n presigned.expires_at,\n )\n\n return c.json({\n ok: true,\n data: {\n upload_url: presigned.upload_url,\n // Signed, self-contained token the client posts back to /confirm/:id.\n media_id,\n expires_at: presigned.expires_at,\n // Present for storages that need a multipart POST with signed fields\n // (Cloudinary); absent for a raw PUT (S3).\n ...(presigned.method && { method: presigned.method }),\n ...(presigned.fields && { fields: presigned.fields }),\n },\n })\n })\n\n // POST /admin/api/media/confirm/:id\n app.post('/admin/api/media/confirm/:id', requirePermission('media:create'), async (c) => {\n const id = c.req.param('id')\n\n const pending = await verifyPendingUpload(id)\n if (!pending) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'PRESIGNED_URL_EXPIRED',\n message: 'Presigned upload not found or has expired',\n },\n },\n 410\n )\n }\n\n const body = (await c.req.json().catch(() => ({}))) as Record<string, unknown>\n const altValue = typeof body['alt'] === 'string' ? body['alt'].trim() : undefined\n\n const altRequired = pending.folder === 'video' || pending.folder === 'file'\n if (altRequired && !altValue) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'VALIDATION_ERROR',\n message: 'alt is required for video and file uploads',\n details: [{ field: 'alt', message: 'alt is required' }],\n },\n },\n 422\n )\n }\n\n // Validate the actual uploaded object where the adapter supports it.\n let fileSize = 0\n if (storage.stat) {\n const meta = await storage.stat(pending.key)\n if (!meta) {\n return c.json(\n { ok: false, error: { code: 'STORAGE_ERROR', message: 'Uploaded object not found' } },\n 502,\n )\n }\n const accepted =\n pending.folder === 'image' ? IMAGE_MIME_TYPES\n : pending.folder === 'video' ? VIDEO_MIME_TYPES\n : FILE_MIME_TYPES\n if (meta.content_type && !accepted.has(meta.content_type)) {\n await storage.delete(pending.key)\n return c.json(\n { ok: false, error: { code: 'UNSUPPORTED_MIME_TYPE', message: `Stored object type '${meta.content_type}' is not accepted` } },\n 415,\n )\n }\n if (maxFileSize !== undefined && meta.size > maxFileSize) {\n await storage.delete(pending.key)\n return c.json(\n { ok: false, error: { code: 'FILE_TOO_LARGE', message: `Uploaded file exceeds the ${maxFileSize} byte limit` } },\n 413,\n )\n }\n fileSize = meta.size\n }\n\n const url = storage.getUrl(pending.key)\n\n const item = await mediaRepo.create({\n type: pending.folder,\n url,\n mime_type: pending.mime_type,\n ...(altValue !== undefined && { alt: altValue }),\n file_size: fileSize,\n })\n\n return c.json({ ok: true, data: item }, 201)\n })\n\n // PATCH /admin/api/media/:id\n app.patch(\n '/admin/api/media/:id',\n requirePermission('media:edit'),\n async (c) => {\n const id = c.req.param('id')\n const existing = await mediaRepo.findOne(id)\n\n if (!existing) {\n return c.json(\n { ok: false, error: { code: 'NOT_FOUND', message: 'Media item not found' } },\n 404\n )\n }\n\n const body = (await c.req.json()) as Record<string, unknown>\n const alt = typeof body['alt'] === 'string' ? body['alt'].trim() : undefined\n\n const updateData: Parameters<typeof mediaRepo.update>[1] = {}\n if (alt !== undefined) updateData.alt = alt\n\n const updated = await mediaRepo.update(id, updateData)\n if (!updated) {\n return c.json(\n { ok: false, error: { code: 'NOT_FOUND', message: 'Media item not found' } },\n 404\n )\n }\n\n return c.json({ ok: true, data: updated })\n }\n )\n\n // DELETE /admin/api/media/:id\n app.delete(\n '/admin/api/media/:id',\n requirePermission('media:delete'),\n async (c) => {\n const id = c.req.param('id')\n const item = await mediaRepo.findOne(id)\n\n if (!item) {\n return c.json(\n { ok: false, error: { code: 'NOT_FOUND', message: 'Media item not found' } },\n 404\n )\n }\n\n if (item.reference_count > 0) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'MEDIA_IN_USE',\n message: 'Cannot delete media that is referenced by content items',\n },\n },\n 409\n )\n }\n\n // Derive storage key from URL.\n // Cloudinary delivery URLs: https://res.cloudinary.com/{cloud}/{type}/upload/{public_id}\n // Local delivery URLs: http://localhost/uploads/{key}\n // For Cloudinary, the adapter key is the public_id — everything after \"/upload/\".\n // For local (and S3), the adapter key is the full path after the host.\n const pathname = new URL(item.url).pathname\n const uploadIdx = pathname.indexOf('/upload/')\n const key = uploadIdx >= 0\n ? pathname.slice(uploadIdx + '/upload/'.length)\n : pathname.replace(/^\\//, '')\n\n try {\n await storage.delete(key)\n } catch (err) {\n console.error('[media] storage.delete error:', err)\n return c.json(\n {\n ok: false,\n error: { code: 'STORAGE_ERROR', message: 'Failed to delete file from storage' },\n },\n 502\n )\n }\n\n await mediaRepo.delete(id)\n\n return c.json({ ok: true })\n }\n )\n\n // GET /admin/api/media\n app.get('/admin/api/media', requirePermission('media:read'), async (c) => {\n const pageStr = c.req.query('page')\n const perPageStr = c.req.query('per_page')\n const page = pageStr !== undefined ? Number(pageStr) : 1\n const per_page = perPageStr !== undefined ? Number(perPageStr) : 10\n\n if (\n !Number.isInteger(page) ||\n page < 1 ||\n !Number.isInteger(per_page) ||\n per_page < 1 ||\n per_page > 100\n ) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'INVALID_PAGINATION',\n message: 'page must be ≥ 1 and per_page must be between 1 and 100',\n },\n },\n 400\n )\n }\n\n const typeParam = c.req.query('type')\n if (typeParam !== undefined && !['image', 'video', 'file'].includes(typeParam)) {\n return c.json(\n {\n ok: false,\n error: { code: 'VALIDATION_ERROR', message: 'type must be one of: image, video, file' },\n },\n 422\n )\n }\n\n const orphanedParam = c.req.query('orphaned')\n const orphaned =\n orphanedParam === 'true' ? true : orphanedParam === 'false' ? false : undefined\n\n const opts: Parameters<typeof mediaRepo.findMany>[0] = { page, per_page }\n if (typeParam !== undefined) opts.type = typeParam as 'image' | 'video' | 'file'\n if (orphaned !== undefined) opts.orphaned = orphaned\n\n const result = await mediaRepo.findMany(opts)\n\n return c.json(result)\n })\n\n // GET /admin/api/media/:id\n app.get('/admin/api/media/:id', requirePermission('media:read'), async (c) => {\n const id = c.req.param('id')\n const item = await mediaRepo.findOne(id)\n\n if (!item) {\n return c.json(\n { ok: false, error: { code: 'NOT_FOUND', message: 'Media item not found' } },\n 404\n )\n }\n\n return c.json({ ok: true, data: item })\n })\n\n}\n","import type { Hono } from 'hono'\nimport { getCookie } from 'hono/cookie'\nimport { sql } from 'drizzle-orm'\nimport type { DrizzlePostgresInstance } from '@bobbykim/manguito-cms-db'\nimport { verifyPassword } from '../../auth/password.js'\nimport {\n signToken,\n verifyToken,\n setAuthCookie,\n clearAuthCookie,\n AUTH_TOKEN_TTL,\n REFRESH_TOKEN_TTL,\n} from '../../auth/jwt.js'\nimport { createAuthMiddleware } from '../../middleware/auth.js'\n\n// ─── Rate limiting ────────────────────────────────────────────────────────────\n\nconst loginAttempts = new Map<string, number[]>()\nconst globalLoginAttempts: number[] = []\n\nconst RATE_LIMIT_MAX = 10\nconst RATE_LIMIT_WINDOW_MS = 15 * 60 * 1000\n// Global backstop across all IPs/emails — blunts distributed spraying without\n// per-account lockout (ADR api/0005). Sized well above legitimate concurrent\n// login volume for a self-hosted CMS.\nconst GLOBAL_LOGIN_MAX = 100\n\n// A valid cost-12 bcrypt hash used only to equalize timing on the\n// user-not-found path, so login response time cannot enumerate accounts.\nconst DUMMY_PASSWORD_HASH =\n '$2a$12$y.MZw/Q4Ceg3x1y3xrRaeuTaM09zSDx1nn78guO3bqc9vgOqGda42'\n\nfunction checkRateLimit(key: string): { allowed: boolean; retryAfterSeconds: number } {\n const now = Date.now()\n const windowStart = now - RATE_LIMIT_WINDOW_MS\n\n // Global ceiling first — purge, then evaluate before recording the per-key hit.\n while (globalLoginAttempts.length > 0 && globalLoginAttempts[0]! <= windowStart) {\n globalLoginAttempts.shift()\n }\n if (globalLoginAttempts.length >= GLOBAL_LOGIN_MAX) {\n const retryAfterSeconds = Math.ceil((globalLoginAttempts[0]! + RATE_LIMIT_WINDOW_MS - now) / 1000)\n return { allowed: false, retryAfterSeconds }\n }\n\n const attempts = (loginAttempts.get(key) ?? []).filter((t) => t > windowStart)\n attempts.push(now)\n loginAttempts.set(key, attempts)\n globalLoginAttempts.push(now)\n\n if (attempts.length > RATE_LIMIT_MAX) {\n const oldestInWindow = attempts[0]!\n const retryAfterSeconds = Math.ceil((oldestInWindow + RATE_LIMIT_WINDOW_MS - now) / 1000)\n return { allowed: false, retryAfterSeconds }\n }\n\n return { allowed: true, retryAfterSeconds: 0 }\n}\n\n/** Test-only: clears in-process login rate-limit state. Not part of the public API. */\nexport function __resetLoginRateLimitStateForTests(): void {\n loginAttempts.clear()\n globalLoginAttempts.length = 0\n}\n\n// ─── DB row types ─────────────────────────────────────────────────────────────\n\ntype LoginUserRow = {\n id: string\n email: string\n password_hash: string\n role_id: string\n token_version: number\n}\n\ntype RefreshRow = {\n token_version: number\n role: string\n}\n\n// ─── Register ─────────────────────────────────────────────────────────────────\n\nexport function registerAuthRoutes(app: Hono, db: DrizzlePostgresInstance): void {\n const invalidCredentials = {\n ok: false,\n error: { code: 'INVALID_CREDENTIALS', message: 'Invalid email or password.' },\n } as const\n\n // POST /admin/api/auth/login\n app.post('/admin/api/auth/login', async (c) => {\n let body: { email?: unknown; password?: unknown }\n try {\n body = (await c.req.json()) as { email?: unknown; password?: unknown }\n } catch {\n return c.json(invalidCredentials, 401)\n }\n\n const email =\n typeof body.email === 'string' ? body.email.trim().toLowerCase() : null\n const password = typeof body.password === 'string' ? body.password : null\n\n if (!email || !password) {\n return c.json(invalidCredentials, 401)\n }\n\n const ip =\n c.req.header('x-forwarded-for')?.split(',')[0]?.trim() ?? 'unknown'\n const rateCheck = checkRateLimit(`${ip}:${email}`)\n if (!rateCheck.allowed) {\n c.header('Retry-After', String(rateCheck.retryAfterSeconds))\n return c.json(\n {\n ok: false,\n error: {\n code: 'RATE_LIMITED',\n message: 'Too many login attempts. Please try again later.',\n },\n },\n 429,\n )\n }\n\n const userResult = await db.execute(\n sql`SELECT id, email, password_hash, role_id, token_version\n FROM users WHERE email = ${email} LIMIT 1`,\n )\n const user = userResult.rows[0] as LoginUserRow | undefined\n\n if (!user) {\n // Run a dummy comparison so this path pays the same bcrypt cost as a\n // real user, closing the user-enumeration timing side-channel.\n await verifyPassword(password, DUMMY_PASSWORD_HASH)\n return c.json(invalidCredentials, 401)\n }\n\n const passwordValid = await verifyPassword(password, user.password_hash)\n if (!passwordValid) {\n return c.json(invalidCredentials, 401)\n }\n\n const roleResult = await db.execute(\n sql`SELECT r.name AS name, u.must_change_password\n FROM roles r\n JOIN users u ON u.role_id = r.id\n WHERE r.id = ${user.role_id} AND u.id = ${user.id}\n LIMIT 1`,\n )\n const roleRow = roleResult.rows[0] as { name: string; must_change_password: boolean } | undefined\n\n if (!roleRow) {\n return c.json(\n { ok: false, error: { code: 'INVALID_ROLE', message: 'User role not found.' } },\n 500,\n )\n }\n\n const [authToken, refreshToken] = await Promise.all([\n signToken(\n { user_id: user.id, role: roleRow.name, token_version: user.token_version },\n AUTH_TOKEN_TTL,\n ),\n signToken(\n { user_id: user.id, role: roleRow.name, token_version: user.token_version },\n REFRESH_TOKEN_TTL,\n ),\n ])\n\n setAuthCookie(c, 'auth_token', authToken, { path: '/' })\n setAuthCookie(c, 'refresh_token', refreshToken, { path: '/admin/api/auth' })\n\n return c.json({\n ok: true,\n data: {\n id: user.id,\n email: user.email,\n role: roleRow.name,\n must_change_password: roleRow.must_change_password,\n },\n })\n })\n\n // POST /admin/api/auth/refresh\n app.post('/admin/api/auth/refresh', async (c) => {\n const token = getCookie(c, 'refresh_token')\n if (!token) {\n return c.json(\n { ok: false, error: { code: 'UNAUTHORIZED', message: 'Authentication required.' } },\n 401,\n )\n }\n\n let payload\n try {\n payload = await verifyToken(token)\n } catch (err) {\n const code = (err as { code?: string }).code\n if (code === 'TOKEN_EXPIRED') {\n return c.json(\n { ok: false, error: { code: 'TOKEN_EXPIRED', message: 'Token has expired.' } },\n 401,\n )\n }\n return c.json(\n { ok: false, error: { code: 'TOKEN_INVALID', message: 'Token signature is invalid.' } },\n 401,\n )\n }\n\n const rowResult = await db.execute(\n sql`SELECT u.token_version, r.name AS role\n FROM users u\n JOIN roles r ON r.id = u.role_id\n WHERE u.id = ${payload.user_id}\n LIMIT 1`,\n )\n const row = rowResult.rows[0] as RefreshRow | undefined\n\n if (!row || payload.token_version !== row.token_version) {\n return c.json(\n { ok: false, error: { code: 'TOKEN_INVALID', message: 'Token has been invalidated.' } },\n 401,\n )\n }\n\n const newAuthToken = await signToken(\n { user_id: payload.user_id, role: row.role, token_version: row.token_version },\n AUTH_TOKEN_TTL,\n )\n setAuthCookie(c, 'auth_token', newAuthToken, { path: '/' })\n\n return c.json({ ok: true })\n })\n\n // POST /admin/api/auth/logout\n const authMiddleware = createAuthMiddleware(db)\n app.post('/admin/api/auth/logout', authMiddleware, async (c) => {\n const user = (c as unknown as { get(k: 'user'): { id: string; role: string } }).get('user')\n\n await db.execute(\n sql`UPDATE users SET token_version = token_version + 1 WHERE id = ${user.id}`,\n )\n\n clearAuthCookie(c, 'auth_token')\n clearAuthCookie(c, 'refresh_token', '/admin/api/auth')\n\n return c.json({ ok: true })\n })\n}\n","export { hashPassword, verifyPassword } from '@bobbykim/manguito-cms-core'\n","import type { Hono } from 'hono'\nimport { sql } from 'drizzle-orm'\nimport { randomBytes } from 'node:crypto'\nimport type { DrizzlePostgresInstance } from '@bobbykim/manguito-cms-db'\nimport { hashPassword, verifyPassword } from '../../auth/password.js'\nimport type { createPermissionMiddleware } from '../../middleware/permission.js'\nimport type { createHierarchyMiddleware } from '../../middleware/hierarchy.js'\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\ntype ActingUser = { id: string; role: string }\n\ntype UserRow = {\n id: string\n email: string\n role: string\n must_change_password: boolean\n created_at: string\n updated_at: string\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\nfunction getActingUser(c: unknown): ActingUser {\n return (c as { get(k: 'user'): ActingUser }).get('user')\n}\n\nfunction generateTempPassword(): string {\n return randomBytes(8).toString('base64url')\n}\n\n// ─── Register ─────────────────────────────────────────────────────────────────\n\nexport function registerUserRoutes(\n app: Hono,\n db: DrizzlePostgresInstance,\n requirePermission: ReturnType<typeof createPermissionMiddleware>,\n requireHierarchy: ReturnType<typeof createHierarchyMiddleware>,\n): void {\n // POST /admin/api/users/change-password\n // Registered first — exempt from mustChangePasswordCheck (enforced by path check in that middleware).\n // No requirePermission — available to any authenticated user.\n app.post('/admin/api/users/change-password', async (c) => {\n const actingUser = getActingUser(c)\n\n let body: { current_password?: unknown; new_password?: unknown }\n try {\n body = (await c.req.json()) as { current_password?: unknown; new_password?: unknown }\n } catch {\n return c.json(\n { ok: false, error: { code: 'INVALID_CREDENTIALS', message: 'Invalid credentials.' } },\n 401,\n )\n }\n\n const currentPassword =\n typeof body.current_password === 'string' ? body.current_password : null\n const newPassword = typeof body.new_password === 'string' ? body.new_password : null\n\n if (!currentPassword || !newPassword) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'VALIDATION_ERROR',\n message: 'current_password and new_password are required.',\n },\n },\n 422,\n )\n }\n\n const hashResult = await db.execute(\n sql`SELECT password_hash FROM users WHERE id = ${actingUser.id} LIMIT 1`,\n )\n const hashRow = hashResult.rows[0] as { password_hash: string } | undefined\n\n if (!hashRow || !(await verifyPassword(currentPassword, hashRow.password_hash))) {\n return c.json(\n { ok: false, error: { code: 'INVALID_CREDENTIALS', message: 'Invalid credentials.' } },\n 401,\n )\n }\n\n const newHash = await hashPassword(newPassword)\n\n await db.execute(\n sql`UPDATE users\n SET password_hash = ${newHash},\n must_change_password = false,\n token_version = token_version + 1,\n updated_at = NOW()\n WHERE id = ${actingUser.id}`,\n )\n\n return c.json({ ok: true })\n })\n\n // GET /admin/api/users\n app.get('/admin/api/users', requirePermission('users:read'), async (c) => {\n const result = await db.execute(\n sql`SELECT u.id, u.email, r.name AS role, u.must_change_password,\n u.created_at, u.updated_at\n FROM users u\n JOIN roles r ON r.id = u.role_id\n ORDER BY u.created_at ASC`,\n )\n\n return c.json({ ok: true, data: result.rows as UserRow[] })\n })\n\n // GET /admin/api/users/:id\n app.get('/admin/api/users/:id', requirePermission('users:read'), async (c) => {\n const id = c.req.param('id')\n\n const result = await db.execute(\n sql`SELECT u.id, u.email, r.name AS role, u.must_change_password,\n u.created_at, u.updated_at\n FROM users u\n JOIN roles r ON r.id = u.role_id\n WHERE u.id = ${id}\n LIMIT 1`,\n )\n const row = result.rows[0] as UserRow | undefined\n\n if (!row) {\n return c.json(\n { ok: false, error: { code: 'NOT_FOUND', message: 'User not found.' } },\n 404,\n )\n }\n\n return c.json({ ok: true, data: row })\n })\n\n // POST /admin/api/users\n app.post(\n '/admin/api/users',\n requirePermission('users:create'),\n requireHierarchy(),\n async (c) => {\n let body: { email?: unknown; role?: unknown }\n try {\n body = (await c.req.json()) as { email?: unknown; role?: unknown }\n } catch {\n return c.json(\n { ok: false, error: { code: 'VALIDATION_ERROR', message: 'Request body is required.' } },\n 422,\n )\n }\n\n const email =\n typeof body.email === 'string' ? body.email.trim().toLowerCase() : null\n const roleName = typeof body.role === 'string' ? body.role : null\n\n if (!email) {\n return c.json(\n { ok: false, error: { code: 'VALIDATION_ERROR', message: 'email is required.' } },\n 422,\n )\n }\n if (!roleName) {\n return c.json(\n { ok: false, error: { code: 'INVALID_ROLE', message: 'role is required.' } },\n 400,\n )\n }\n\n const roleResult = await db.execute(\n sql`SELECT id FROM roles WHERE name = ${roleName} LIMIT 1`,\n )\n const roleRow = roleResult.rows[0] as { id: string } | undefined\n\n if (!roleRow) {\n return c.json(\n {\n ok: false,\n error: { code: 'INVALID_ROLE', message: `Role \"${roleName}\" does not exist.` },\n },\n 400,\n )\n }\n\n const tempPassword = generateTempPassword()\n const passwordHash = await hashPassword(tempPassword)\n\n const insertResult = await db.execute(\n sql`INSERT INTO users\n (id, email, password_hash, role_id, must_change_password, token_version,\n created_at, updated_at)\n VALUES\n (gen_random_uuid(), ${email}, ${passwordHash}, ${roleRow.id},\n true, 0, NOW(), NOW())\n RETURNING id, email, must_change_password, created_at, updated_at`,\n )\n const newUser = insertResult.rows[0] as\n | {\n id: string\n email: string\n must_change_password: boolean\n created_at: string\n updated_at: string\n }\n | undefined\n\n if (!newUser) {\n return c.json(\n { ok: false, error: { code: 'INTERNAL_ERROR', message: 'Failed to create user.' } },\n 500,\n )\n }\n\n return c.json(\n {\n ok: true,\n data: {\n id: newUser.id,\n email: newUser.email,\n role: roleName,\n must_change_password: newUser.must_change_password,\n temporary_password: tempPassword,\n created_at: newUser.created_at,\n updated_at: newUser.updated_at,\n },\n },\n 201,\n )\n },\n )\n\n // PATCH /admin/api/users/:id\n app.patch(\n '/admin/api/users/:id',\n requirePermission('users:edit'),\n requireHierarchy(),\n async (c) => {\n const id = c.req.param('id')\n const actingUser = getActingUser(c)\n\n let body: { email?: unknown; role?: unknown }\n try {\n body = (await c.req.json()) as { email?: unknown; role?: unknown }\n } catch {\n body = {}\n }\n\n // Self role change is never permitted\n if (actingUser.id === id && body.role !== undefined) {\n return c.json(\n {\n ok: false,\n error: { code: 'INSUFFICIENT_PRIVILEGE', message: 'You cannot change your own role.' },\n },\n 403,\n )\n }\n\n const existingResult = await db.execute(\n sql`SELECT id, email, role_id FROM users WHERE id = ${id} LIMIT 1`,\n )\n const existing = existingResult.rows[0] as\n | { id: string; email: string; role_id: string }\n | undefined\n\n if (!existing) {\n return c.json(\n { ok: false, error: { code: 'NOT_FOUND', message: 'User not found.' } },\n 404,\n )\n }\n\n const newEmail =\n typeof body.email === 'string' ? body.email.trim().toLowerCase() : existing.email\n\n let newRoleId = existing.role_id\n let changeRole = false\n\n if (typeof body.role === 'string' && body.role) {\n const roleResult = await db.execute(\n sql`SELECT id FROM roles WHERE name = ${body.role} LIMIT 1`,\n )\n const roleRow = roleResult.rows[0] as { id: string } | undefined\n\n if (!roleRow) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'INVALID_ROLE',\n message: `Role \"${body.role}\" does not exist.`,\n },\n },\n 400,\n )\n }\n\n newRoleId = roleRow.id\n changeRole = true\n }\n\n if (changeRole) {\n await db.execute(\n sql`UPDATE users\n SET email = ${newEmail},\n role_id = ${newRoleId},\n token_version = token_version + 1,\n updated_at = NOW()\n WHERE id = ${id}`,\n )\n } else {\n await db.execute(\n sql`UPDATE users\n SET email = ${newEmail},\n role_id = ${newRoleId},\n updated_at = NOW()\n WHERE id = ${id}`,\n )\n }\n\n const updatedResult = await db.execute(\n sql`SELECT u.id, u.email, r.name AS role, u.must_change_password,\n u.created_at, u.updated_at\n FROM users u\n JOIN roles r ON r.id = u.role_id\n WHERE u.id = ${id}\n LIMIT 1`,\n )\n const updated = updatedResult.rows[0] as UserRow | undefined\n\n if (!updated) {\n return c.json(\n { ok: false, error: { code: 'NOT_FOUND', message: 'User not found.' } },\n 404,\n )\n }\n\n return c.json({ ok: true, data: updated })\n },\n )\n\n // DELETE /admin/api/users/:id\n app.delete(\n '/admin/api/users/:id',\n requirePermission('users:delete'),\n requireHierarchy(),\n async (c) => {\n const id = c.req.param('id')\n const actingUser = getActingUser(c)\n\n if (actingUser.id === id) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'INSUFFICIENT_PRIVILEGE',\n message: 'You cannot delete your own account.',\n },\n },\n 403,\n )\n }\n\n const result = await db.execute(\n sql`DELETE FROM users WHERE id = ${id} RETURNING id`,\n )\n\n if ((result.rows as unknown[]).length === 0) {\n return c.json(\n { ok: false, error: { code: 'NOT_FOUND', message: 'User not found.' } },\n 404,\n )\n }\n\n return c.json({ ok: true })\n },\n )\n\n // POST /admin/api/users/:id/reset-password\n app.post(\n '/admin/api/users/:id/reset-password',\n requirePermission('users:edit'),\n requireHierarchy(),\n async (c) => {\n const id = c.req.param('id')\n const actingUser = getActingUser(c)\n\n if (actingUser.id === id) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'INSUFFICIENT_PRIVILEGE',\n message: 'You cannot reset your own password via this endpoint.',\n },\n },\n 403,\n )\n }\n\n const existingResult = await db.execute(\n sql`SELECT id FROM users WHERE id = ${id} LIMIT 1`,\n )\n const existing = existingResult.rows[0] as { id: string } | undefined\n\n if (!existing) {\n return c.json(\n { ok: false, error: { code: 'NOT_FOUND', message: 'User not found.' } },\n 404,\n )\n }\n\n const tempPassword = generateTempPassword()\n const passwordHash = await hashPassword(tempPassword)\n\n await db.execute(\n sql`UPDATE users\n SET password_hash = ${passwordHash},\n must_change_password = true,\n token_version = token_version + 1,\n updated_at = NOW()\n WHERE id = ${id}`,\n )\n\n return c.json({ ok: true, data: { temporary_password: tempPassword } })\n },\n )\n}\n","import { sql } from 'drizzle-orm'\nimport type { Hono } from 'hono'\nimport type { DrizzlePostgresInstance } from '@bobbykim/manguito-cms-db'\nimport type { RolesRegistry } from '../../auth/registry.js'\n\n// ─── Version — injected by tsup at build time; falls back in test/dev ─────────\n\ndeclare const __API_VERSION__: string\n\nconst API_VERSION: string = (() => {\n try { return __API_VERSION__ } catch { return '0.0.0' }\n})()\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\ntype ActingUser = { id: string; role: string }\n\ntype ConfigOptions = {\n name: string\n maxFileSize?: number\n presignedUploads?: boolean\n}\n\n// ─── Register ─────────────────────────────────────────────────────────────────\n\nexport function registerConfigRoute(\n app: Hono,\n config: ConfigOptions,\n registry: RolesRegistry,\n db: DrizzlePostgresInstance,\n): void {\n app.get('/admin/api/config', async (c) => {\n const actingUser = (c as unknown as { get(k: 'user'): ActingUser }).get('user')\n const actingRole = registry[actingUser.role]\n const actingLevel = actingRole?.hierarchy_level ?? Infinity\n\n // Subordinate roles only — used by user management forms for role assignment.\n const roles = Object.values(registry)\n .filter((r) => r.name !== 'admin' && r.hierarchy_level > actingLevel)\n .sort((a, b) => a.hierarchy_level - b.hierarchy_level)\n .map((r) => ({ name: r.name, label: r.label, hierarchy_level: r.hierarchy_level }))\n\n // All roles with full data — used by the admin panel to resolve permissions.\n const all_roles = Object.values(registry)\n .sort((a, b) => a.hierarchy_level - b.hierarchy_level)\n .map((r) => ({\n name: r.name,\n label: r.label,\n hierarchy_level: r.hierarchy_level,\n is_system: r.is_system,\n permissions: r.permissions,\n }))\n\n const userResult = await db.execute(\n sql`SELECT email, must_change_password FROM users WHERE id = ${actingUser.id} LIMIT 1`\n )\n const userRow = userResult.rows[0] as\n | { email: string; must_change_password: boolean }\n | undefined\n\n return c.json({\n ok: true,\n data: {\n cms_name: config.name ?? 'Manguito CMS',\n version: API_VERSION,\n roles,\n all_roles,\n user: {\n id: actingUser.id,\n email: userRow?.email ?? '',\n role: actingUser.role,\n must_change_password: userRow?.must_change_password ?? false,\n },\n media: {\n max_file_size: config.maxFileSize ?? null,\n presigned_uploads: config.presignedUploads ?? false,\n },\n },\n })\n })\n}\n","import { sql } from 'drizzle-orm'\nimport type { Hono } from 'hono'\nimport type { ParsedField, SchemaRegistry } from '@bobbykim/manguito-cms-core'\nimport type { DrizzlePostgresInstance } from '@bobbykim/manguito-cms-db'\n\n// A tombstone (`removed: true`) keeps its column alive for older live\n// versions but must not be exposed by the CURRENT version — which is all\n// this endpoint ever serves. The admin panel has no other path to schema\n// data (packages/admin/src/stores/schema.ts's setFromApiSchema is the only\n// consumer), so filtering here is what keeps a dead column out of every\n// admin form and list view.\nfunction liveFields(fields: ParsedField[]): ParsedField[] {\n return fields.filter((f) => f.removed !== true)\n}\n\n// ─── Register ─────────────────────────────────────────────────────────────────\n\nexport function registerSchemaRoute(\n app: Hono,\n registry: SchemaRegistry,\n db: DrizzlePostgresInstance,\n): void {\n // Full schema — used by the admin panel for form rendering, validation, and navigation.\n // Returns complete ParsedField objects (validation, ui_component) so the admin\n // forms can render inputs and run client-side validation without extra round-trips.\n app.get('/admin/api/schema', (c) => {\n const content_types = Object.values(registry.content_types).map((ct) => ({\n name: ct.name,\n label: ct.label,\n only_one: ct.only_one,\n ui: ct.ui,\n system_fields: ct.system_fields,\n fields: liveFields(ct.fields),\n }))\n\n const taxonomy_types = Object.values(registry.taxonomy_types).map((tt) => ({\n name: tt.name,\n label: tt.label,\n system_fields: tt.system_fields,\n fields: liveFields(tt.fields),\n }))\n\n const paragraph_types = Object.values(registry.paragraph_types).map((pt) => ({\n name: pt.name,\n label: pt.label,\n system_fields: pt.system_fields,\n fields: liveFields(pt.fields),\n }))\n\n const enum_types = Object.values(registry.enum_types).map((et) => ({\n name: et.name,\n label: et.label,\n values: et.values,\n }))\n\n return c.json({\n ok: true,\n data: { content_types, taxonomy_types, paragraph_types, enum_types },\n })\n })\n\n // Navigation meta-endpoints — list types with item counts for admin sidebar\n app.get('/admin/api/content', async (c) => {\n const data = await Promise.all(\n Object.values(registry.content_types).map(async (ct) => {\n const result = await db.execute(\n sql`SELECT COUNT(*)::int AS count FROM ${sql.raw(`\"${ct.db.table_name}\"`)}`\n )\n const count = Number((result.rows[0] as { count: string | number }).count)\n return { name: ct.name, label: ct.label, only_one: ct.only_one, count }\n })\n )\n return c.json({ ok: true, data })\n })\n\n app.get('/admin/api/taxonomy', async (c) => {\n const data = await Promise.all(\n Object.values(registry.taxonomy_types).map(async (tt) => {\n const result = await db.execute(\n sql`SELECT COUNT(*)::int AS count FROM ${sql.raw(`\"${tt.db.table_name}\"`)}`\n )\n const count = Number((result.rows[0] as { count: string | number }).count)\n return { name: tt.name, label: tt.label, count }\n })\n )\n return c.json({ ok: true, data })\n })\n}\n","import crypto from 'node:crypto'\nimport { sql, type SQL } from 'drizzle-orm'\nimport type { DrizzlePostgresInstance } from '@bobbykim/manguito-cms-db'\nimport type {\n ContentRepository,\n PaginatedResult,\n FindManyOptions,\n FindAllOptions,\n CreateInput,\n UpdateInput,\n FilterOperator,\n FilterValue,\n} from '@bobbykim/manguito-cms-core'\nimport {\n resolveRelationField,\n resolveRelationBareIds,\n type RelationDef,\n} from '../relations.js'\n\n// ─── Repository options ───────────────────────────────────────────────────────\n\nexport type ContentRepositoryOptions = {\n relations?: Record<string, RelationDef>\n /** When true, relation targets (reference/junction) are filtered to published\n * rows only. Set by the public repos in app.ts; admin repos leave this false\n * so drafts remain visible to admin. */\n publishedRelations?: boolean\n /** Storage columns valid for ORDER BY, supplied by the route once it has\n * mapped a validated label to its column. Optional for backward\n * compatibility: when absent, falls back to validating sort_by against\n * SORTABLE_FIELDS (labels), the pre-versioning behavior. */\n sortableColumns?: Set<string>\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\nconst SORTABLE_FIELDS = new Set(['title', 'created_at', 'updated_at'])\n\nfunction codeError(code: string, message: string): Error {\n const err = new Error(message)\n ;(err as Error & { code: string }).code = code\n return err\n}\n\n// Only allow table/column identifiers that are developer-controlled.\n// Table names can include hyphens (e.g. content--blog_post).\nfunction quoteIdent(name: string): string {\n if (!/^[a-z][a-z0-9_-]*$/.test(name)) {\n throw new Error(`Unsafe SQL identifier: ${name}`)\n }\n return `\"${name.replace(/\"/g, '\"\"')}\"`\n}\n\n// User-supplied filter field names — strict, no hyphens.\nfunction quoteField(name: string): string {\n if (!/^[a-z][a-z0-9_]*$/.test(name)) {\n throw codeError('INVALID_FILTER_FIELD', `Invalid filter field name: ${name}`)\n }\n return `\"${name}\"`\n}\n\nfunction buildConditions(\n published_only: boolean | undefined,\n filters: Record<string, FilterValue> | undefined,\n search: { term: string; columns: string[] } | undefined\n): SQL[] {\n const conds: SQL[] = []\n\n if (published_only) {\n conds.push(sql`published = true`)\n }\n\n if (search && search.term.trim() !== '' && search.columns.length > 0) {\n const pattern = `%${search.term.trim()}%`\n const orConds = search.columns.map((col) => sql`${sql.raw(quoteField(col))} ILIKE ${pattern}`)\n conds.push(sql`(${sql.join(orConds, sql` OR `)})`)\n }\n\n if (filters) {\n for (const [field, value] of Object.entries(filters)) {\n const col = sql.raw(quoteField(field))\n\n if (Array.isArray(value)) {\n if (value.length === 0) continue\n const inList = sql.join(\n value.map((v) => sql`${v}`),\n sql`, `\n )\n conds.push(sql`${col} IN (${inList})`)\n } else if (\n typeof value === 'object' &&\n value !== null\n ) {\n const op = value as FilterOperator\n if (op.gt !== undefined) conds.push(sql`${col} > ${op.gt}`)\n if (op.gte !== undefined) conds.push(sql`${col} >= ${op.gte}`)\n if (op.lt !== undefined) conds.push(sql`${col} < ${op.lt}`)\n if (op.lte !== undefined) conds.push(sql`${col} <= ${op.lte}`)\n } else {\n conds.push(sql`${col} = ${value}`)\n }\n }\n }\n\n return conds\n}\n\nfunction whereFragment(conds: SQL[]): SQL {\n return conds.length > 0\n ? sql` WHERE ${sql.join(conds, sql` AND `)}`\n : sql``\n}\n\n// ─── Factory ──────────────────────────────────────────────────────────────────\n\nexport function createDrizzleContentRepository<T>(\n db: DrizzlePostgresInstance,\n tableName: string,\n options: ContentRepositoryOptions = {}\n): ContentRepository<T> {\n const { relations = {}, publishedRelations = false, sortableColumns } = options\n\n function tableRaw(): SQL {\n return sql.raw(quoteIdent(tableName))\n }\n\n async function resolveRows(\n rows: Record<string, unknown>[],\n include: string[],\n cache: Map<string, unknown>\n ): Promise<Record<string, unknown>[]> {\n if (rows.length === 0) return rows\n\n // Validate include fields\n for (const fieldName of include) {\n if (!relations[fieldName]) {\n throw codeError('INVALID_INCLUDE_FIELD', `'${fieldName}' is not a valid relation field`)\n }\n }\n\n // Always resolve media fields; for all other relations not explicitly\n // included, populate bare IDs so the field is never silently dropped.\n for (const [fieldName, rel] of Object.entries(relations)) {\n if (rel.type === 'media') {\n await resolveRelationField(db, rows, fieldName, rel, cache, publishedRelations)\n } else if (!include.includes(fieldName)) {\n await resolveRelationBareIds(db, rows, fieldName, rel)\n }\n }\n\n // Resolve explicitly included non-media fields\n for (const fieldName of include) {\n const rel = relations[fieldName]!\n if (rel.type !== 'media') {\n await resolveRelationField(db, rows, fieldName, rel, cache, publishedRelations)\n }\n }\n\n return rows\n }\n\n return {\n async findMany(opts: FindManyOptions): Promise<PaginatedResult<T>> {\n const {\n page = 1,\n per_page = 10,\n include = [],\n published_only,\n filters,\n sort_by = 'created_at',\n sort_order = 'asc',\n search,\n } = opts\n\n // Sorting is validated against COLUMNS when the caller supplies the set\n // (the route has already validated the label and mapped it). Without one,\n // fall back to the label allowlist — the pre-versioning behavior.\n const allowed = sortableColumns ?? SORTABLE_FIELDS\n if (!allowed.has(sort_by as string)) {\n // Deliberately does NOT enumerate `allowed`: when the caller supplied\n // `sortableColumns` that set holds STORAGE COLUMNS, which are private\n // to the database and must never reach a client envelope. The REST\n // routes validate the label before calling in and emit their own\n // message naming the valid LABELS (routes/content.ts,\n // routes/admin/content.ts); this guard is the backstop behind them.\n throw codeError('INVALID_SORT_FIELD', `'${sort_by}' is not sortable.`)\n }\n\n const offset = (page - 1) * per_page\n const conds = buildConditions(published_only, filters, search)\n const where = whereFragment(conds)\n const sortCol = sql.raw(quoteIdent(sort_by))\n const sortDir = sql.raw(sort_order === 'desc' ? 'DESC' : 'ASC')\n\n const countResult = await db.execute(\n sql`SELECT COUNT(*) AS total FROM ${tableRaw()}${where}`\n )\n const total = Number((countResult.rows[0] as Record<string, unknown>)?.['total'] ?? 0)\n\n const dataResult = await db.execute(\n sql`SELECT * FROM ${tableRaw()}${where} ORDER BY ${sortCol} ${sortDir} LIMIT ${per_page} OFFSET ${offset}`\n )\n\n const rows = dataResult.rows as Record<string, unknown>[]\n const cache = new Map<string, unknown>()\n await resolveRows(rows, include, cache)\n\n const total_pages = Math.ceil(total / per_page)\n return {\n ok: true,\n data: rows as T[],\n meta: {\n total,\n page,\n per_page,\n total_pages,\n has_next: page < total_pages,\n has_prev: page > 1,\n },\n }\n },\n\n async findOne(id: string, include: string[] = []): Promise<T | null> {\n const result = await db.execute(\n sql`SELECT * FROM ${tableRaw()} WHERE id = ${id} LIMIT 1`\n )\n if (result.rows.length === 0) return null\n\n const rows = result.rows as Record<string, unknown>[]\n const cache = new Map<string, unknown>()\n await resolveRows(rows, include, cache)\n return rows[0] as T\n },\n\n async findBySlug(slug: string, include: string[] = []): Promise<T | null> {\n const result = await db.execute(\n sql`SELECT * FROM ${tableRaw()} WHERE slug = ${slug} LIMIT 1`\n )\n if (result.rows.length === 0) return null\n\n const rows = result.rows as Record<string, unknown>[]\n const cache = new Map<string, unknown>()\n await resolveRows(rows, include, cache)\n return rows[0] as T\n },\n\n async create(data: CreateInput<T>): Promise<T> {\n const record: Record<string, unknown> = {\n ...(data as Record<string, unknown>),\n id: crypto.randomUUID(),\n created_at: new Date(),\n updated_at: new Date(),\n }\n const entries = Object.entries(record).filter(([, v]) => v !== undefined)\n const colsSql = sql.join(entries.map(([k]) => sql.raw(quoteIdent(k))), sql`, `)\n const valsSql = sql.join(entries.map(([, v]) => sql`${v}`), sql`, `)\n\n const result = await db.execute(\n sql`INSERT INTO ${tableRaw()} (${colsSql}) VALUES (${valsSql}) RETURNING *`\n )\n return result.rows[0] as T\n },\n\n async update(id: string, data: UpdateInput<T>): Promise<T | null> {\n const record: Record<string, unknown> = {\n ...(data as Record<string, unknown>),\n updated_at: new Date(),\n }\n const entries = Object.entries(record).filter(([k, v]) => k !== 'id' && v !== undefined)\n if (entries.length === 0) {\n return this.findOne(id)\n }\n\n const setClauses = sql.join(\n entries.map(([k, v]) => sql`${sql.raw(quoteIdent(k))} = ${v}`),\n sql`, `\n )\n\n const result = await db.execute(\n sql`UPDATE ${tableRaw()} SET ${setClauses} WHERE id = ${id} RETURNING *`\n )\n if (result.rows.length === 0) return null\n return result.rows[0] as T\n },\n\n async delete(id: string): Promise<void> {\n await db.execute(sql`DELETE FROM ${tableRaw()} WHERE id = ${id}`)\n },\n\n async findAll(opts: FindAllOptions = {}): Promise<T[]> {\n const { include = [], published_only } = opts\n const conds = buildConditions(published_only, undefined, undefined)\n const where = whereFragment(conds)\n\n const result = await db.execute(\n sql`SELECT * FROM ${tableRaw()}${where} ORDER BY created_at ASC`\n )\n\n const rows = result.rows as Record<string, unknown>[]\n const cache = new Map<string, unknown>()\n await resolveRows(rows, include, cache)\n return rows as T[]\n },\n }\n}\n","import crypto from 'node:crypto'\nimport { sql } from 'drizzle-orm'\nimport type { DrizzlePostgresInstance } from '@bobbykim/manguito-cms-db'\nimport type {\n MediaRepository,\n MediaItem,\n CreateMediaInput,\n MediaFindManyOptions,\n PaginatedResult,\n} from '@bobbykim/manguito-cms-core'\n\nexport function createMediaRepository(db: DrizzlePostgresInstance): MediaRepository {\n const TABLE = sql.raw('\"media\"')\n\n return {\n async findMany(opts: MediaFindManyOptions = {}): Promise<PaginatedResult<MediaItem>> {\n const { page = 1, per_page = 10, type, orphaned } = opts\n const offset = (page - 1) * per_page\n\n const conds = []\n if (type) conds.push(sql`type = ${type}`)\n if (orphaned) conds.push(sql`reference_count = 0`)\n\n const where =\n conds.length > 0 ? sql` WHERE ${sql.join(conds, sql` AND `)}` : sql``\n\n const countResult = await db.execute(sql`SELECT COUNT(*) AS total FROM ${TABLE}${where}`)\n const total = Number(\n (countResult.rows[0] as Record<string, unknown>)?.['total'] ?? 0\n )\n\n const dataResult = await db.execute(\n sql`SELECT * FROM ${TABLE}${where} ORDER BY created_at DESC LIMIT ${per_page} OFFSET ${offset}`\n )\n\n const total_pages = Math.ceil(total / per_page)\n return {\n ok: true,\n data: dataResult.rows as MediaItem[],\n meta: {\n total,\n page,\n per_page,\n total_pages,\n has_next: page < total_pages,\n has_prev: page > 1,\n },\n }\n },\n\n async findOne(id: string): Promise<MediaItem | null> {\n const result = await db.execute(\n sql`SELECT * FROM ${TABLE} WHERE id = ${id} LIMIT 1`\n )\n if (result.rows.length === 0) return null\n return result.rows[0] as MediaItem\n },\n\n async create(data: CreateMediaInput): Promise<MediaItem> {\n const record: Record<string, unknown> = {\n ...data,\n id: crypto.randomUUID(),\n reference_count: 0,\n created_at: new Date(),\n updated_at: new Date(),\n }\n const entries = Object.entries(record).filter(([, v]) => v !== undefined)\n const colsSql = sql.join(entries.map(([k]) => sql.raw(`\"${k}\"`)), sql`, `)\n const valsSql = sql.join(entries.map(([, v]) => sql`${v}`), sql`, `)\n\n const result = await db.execute(\n sql`INSERT INTO ${TABLE} (${colsSql}) VALUES (${valsSql}) RETURNING *`\n )\n return result.rows[0] as MediaItem\n },\n\n async update(id: string, data: Partial<MediaItem>): Promise<MediaItem | null> {\n const record: Record<string, unknown> = { ...data, updated_at: new Date() }\n const entries = Object.entries(record).filter(\n ([k, v]) => k !== 'id' && v !== undefined\n )\n if (entries.length === 0) return this.findOne(id)\n\n const setClauses = sql.join(\n entries.map(([k, v]) => sql`${sql.raw(`\"${k}\"`)} = ${v}`),\n sql`, `\n )\n\n const result = await db.execute(\n sql`UPDATE ${TABLE} SET ${setClauses} WHERE id = ${id} RETURNING *`\n )\n if (result.rows.length === 0) return null\n return result.rows[0] as MediaItem\n },\n\n async delete(id: string): Promise<void> {\n await db.execute(sql`DELETE FROM ${TABLE} WHERE id = ${id}`)\n },\n\n async incrementReferenceCount(ids: string[]): Promise<void> {\n if (ids.length === 0) return\n const inList = sql.join(ids.map((id) => sql`${id}`), sql`, `)\n await db.execute(\n sql`UPDATE ${TABLE} SET reference_count = reference_count + 1, updated_at = NOW() WHERE id IN (${inList})`\n )\n },\n\n async decrementReferenceCount(ids: string[]): Promise<void> {\n if (ids.length === 0) return\n const inList = sql.join(ids.map((id) => sql`${id}`), sql`, `)\n await db.execute(\n sql`UPDATE ${TABLE} SET reference_count = GREATEST(reference_count - 1, 0), updated_at = NOW() WHERE id IN (${inList})`\n )\n },\n }\n}\n","import type { ServerAdapter, CorsConfig } from '@bobbykim/manguito-cms-core'\n\nexport type NodeServerOptions = {\n port?: number\n base_url?: string\n cors?: Partial<CorsConfig>\n}\n\nexport function createServer(options: NodeServerOptions = {}): ServerAdapter {\n const port = options.port ?? Number(process.env['PORT'] ?? 3000)\n const _base_url = options.base_url ?? `http://localhost:${port}`\n\n const cors: CorsConfig = {\n origin: options.cors?.origin ?? (process.env['ALLOWED_ORIGIN'] ?? '*'),\n methods: options.cors?.methods ?? [\n 'GET',\n 'POST',\n 'PUT',\n 'PATCH',\n 'DELETE',\n ],\n credentials: options.cors?.credentials ?? true,\n }\n\n return {\n type: 'node',\n cors,\n getEntryPoint(): string {\n return 'node'\n },\n }\n}\n"],"mappings":"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","names":["crypto","import_drizzle_orm","import_graphql","import_graphql","codeError","isPublished","import_graphql","import_graphql","DataLoader","import_graphql","import_drizzle_orm","import_cookie","jwtVerify","value","parsePagination","import_drizzle_orm","quoteIdent","row","import_jwt","import_cookie","import_drizzle_orm","import_drizzle_orm","import_node_crypto","import_drizzle_orm","import_drizzle_orm","import_node_crypto","import_drizzle_orm","SORTABLE_FIELDS","quoteIdent","crypto","import_node_crypto","import_drizzle_orm","crypto","createGraphQLHandler"]}
1
+ {"version":3,"sources":["../src/field-keys.ts","../src/relations.ts","../src/graphql/scalars.ts","../src/graphql/type-mapping.ts","../src/graphql/naming.ts","../src/graphql/filters.ts","../src/graphql/resolvers.ts","../src/graphql/schema.ts","../src/graphql/version-view.ts","../src/graphql/dataloaders.ts","../src/graphql/security.ts","../src/graphql/handler.ts","../src/index.ts","../src/paths.ts","../src/app.ts","../src/middleware/cors.ts","../src/middleware/security-headers.ts","../src/middleware/error.ts","../src/middleware/auth.ts","../src/auth/jwt.ts","../src/middleware/must-change-password.ts","../src/middleware/permission.ts","../src/middleware/hierarchy.ts","../src/middleware/rate-limit.ts","../src/auth/registry.ts","../src/programmatic/resolve.ts","../src/projector.ts","../src/routes/content.ts","../src/routes/query-params.ts","../src/routes/media.ts","../src/routes/admin/content.ts","../src/media-references.ts","../src/routes/admin/media.ts","../src/routes/admin/auth.ts","../src/auth/password.ts","../src/routes/admin/users.ts","../src/routes/admin/config.ts","../src/routes/admin/schema.ts","../src/repositories/content.ts","../src/repositories/media.ts","../src/versions.ts","../src/server/node.ts"],"sourcesContent":["import type { DbColumn, ParsedField } from '@bobbykim/manguito-cms-core'\n\n// ─── Label ↔ storage key mapping ──────────────────────────────────────────────\n//\n// A field has two names: its public LABEL (`field.name`, what API consumers and\n// the admin panel see) and its STORAGE key (`db_column.column_name`, the actual\n// Postgres column). They are identical today, but schema versioning makes a\n// rename change only the label — the column keeps its original name for the life\n// of the data. Every place that reads or writes a DB row must therefore use the\n// storage key, and every place that reads a request body or writes a response\n// must use the label.\n//\n// Only column-backed fields participate. Paragraph fields have no column\n// (their association lives on the paragraph table via parent_field) and\n// many-to-many references have no column either (the junction table owns the\n// association), so both keep the field name as their identity and are excluded.\n\n// A type predicate, not a plain boolean: narrowing `db_column` to non-null lets\n// the compiler enforce the invariant at every call site instead of each one\n// re-asserting it with `db_column!`.\nexport function isColumnBacked(field: ParsedField): field is ParsedField & { db_column: DbColumn } {\n const col = field.db_column\n if (col === null) return false\n if (col.column_name === '') return false\n if (col.junction) return false\n return true\n}\n\nexport type FieldKeyMap = {\n /** Request body (label-keyed) → storage-keyed. Unknown keys pass through. */\n toStorage(input: Record<string, unknown>): Record<string, unknown>\n /** DB row (storage-keyed) → label-keyed. Unknown keys pass through. */\n toLabels(row: Record<string, unknown>): Record<string, unknown>\n columnFor(label: string): string | undefined\n labelFor(column: string): string | undefined\n /** Labels of column-backed fields — the valid filter/sort surface. */\n labels: string[]\n /** False when every label equals its column, letting callers skip the copy. */\n diverges: boolean\n}\n\n/**\n * One field's place in the label space: the label it is known by, plus the\n * storage column it owns. `ownColumn` is ABSENT — never present-and-undefined,\n * per exactOptionalPropertyTypes — for a field with no column of its own.\n */\ntype LabelEntry = { label: string; ownColumn?: string }\n\n/**\n * The shared core: given a label↔column mapping and the FULL label space to\n * check against, produce a FieldKeyMap. Both `createFieldKeyMap` and\n * `createFieldKeyMapFromProjection` are thin adapters over this — the\n * collision check, the `diverges` computation and `remap` live here exactly\n * once.\n *\n * `pairs` may include entries that must NOT survive into the final map —\n * `createFieldKeyMap` passes tombstoned columns in alongside live ones,\n * exactly as the original single-function implementation did, so that the\n * collision check below (which runs against the unfiltered map) still\n * catches a live field's label colliding with a TOMBSTONE's column. Any\n * pair whose label or column appears in `droppedKeys` is stripped from\n * `labelToColumn`/`columnToLabel` immediately after the check, before\n * `diverges` and `labels` are computed — so the caller does not need to\n * pre-filter `pairs` itself, only tell this function what to drop.\n *\n * `allLabels` is every field's label, including fields with no column of\n * their own. That is not tidiness: a paragraph, many-to-many or programmatic\n * field's label is written into the same key space as storage columns before\n * `toLabels` runs, so one named after another field's column would overwrite\n * that column's value and then be renamed onto the other field's label.\n * Both constructors must pass the complete set.\n *\n * Each entry carries `ownColumn` — the column that field itself is backed by,\n * absent for a paragraph, many-to-many or programmatic field. The collision\n * check below needs it: a label that equals a column is a collision only when\n * the column belongs to a DIFFERENT field, and strings alone cannot tell the\n * two apart.\n *\n * `droppedKeys` also drives `remap`: it must actively remove a key from a\n * mapped object rather than merely leave it unmapped, because `remap` passes\n * an unmapped key through unchanged — a retained-but-unexposed column would\n * otherwise reach the output under its raw name.\n */\nfunction buildFieldKeyMap(\n pairs: Array<{ label: string; column: string }>,\n allLabels: LabelEntry[],\n droppedKeys: Set<string>\n): FieldKeyMap {\n const labelToColumn = new Map<string, string>()\n const columnToLabel = new Map<string, string>()\n for (const { label, column } of pairs) {\n labelToColumn.set(label, column)\n columnToLabel.set(column, label)\n }\n\n // A label that is also some OTHER field's column name would make toLabels\n // ambiguous: two source keys would map onto one destination key.\n //\n // Checked against EVERY label in `allLabels`, not just the ones with a\n // pair of their own. Paragraph, many-to-many and programmatic fields have\n // no column of their own, but their labels are written into the same key\n // space as storage columns before toLabels runs (routes/admin/content.ts,\n // relations.ts, programmatic/resolve.ts). A paragraph field named after\n // another field's column would overwrite that column's value on the row\n // and then be renamed onto the other field's label — serving the wrong\n // value under the wrong key.\n //\n // Deliberately run BEFORE dropped pairs are stripped from `columnToLabel`\n // below: a live field's label may collide with a TOMBSTONE's column (the\n // column still physically exists on the row), and that must keep\n // throwing. Stripping first would silently pass this configuration, and\n // the exclusion step afterward would then delete the live field's column\n // from every response instead of the tombstone's.\n //\n // Not detected: two fields declaring the SAME column name. A field can now\n // declare its own `column`, so this is no longer structurally impossible —\n // but core rejects it with `DUPLICATE_COLUMN` at parse time, so it is still\n // unreachable here.\n for (const { label, ownColumn } of allLabels) {\n // The label is this field's OWN column, exposed by this version under a\n // different name. That is not an ambiguity — it is one field seen twice,\n // under current's name and under the label this version uses — and there\n // is still exactly one column holding exactly one value. It happens when a\n // field is renamed away and then renamed BACK onto its column's name while\n // the intermediate version is still live. Without this skip the map refuses\n // to build and createCmsApp throws at startup for every version at once.\n //\n // Only the SAME field is exempted: `ownColumn` is absent for a field with\n // no column of its own (a paragraph, many-to-many or programmatic field's\n // label named after some other field's column is precisely the ambiguity\n // this check exists for), and differs from `label` when the name belongs to\n // one field while the column belongs to another.\n if (ownColumn === label) continue\n const columnOwner = columnToLabel.get(label)\n if (columnOwner !== undefined && columnOwner !== label) {\n throw new Error(\n `Fatal: field key map failed to build — field label \"${label}\" collides with the ` +\n `storage column of field \"${columnOwner}\". A field label may not reuse another ` +\n `field's column name. Rename the field, then run \\`manguito validate\\` to check your schema.`\n )\n }\n }\n\n // Only now strip dropped pairs — the collision check above has already run\n // against the full map. A dropped pair is removed from BOTH maps: from\n // `labelToColumn` by its label, from `columnToLabel` by its column.\n for (const label of labelToColumn.keys()) {\n if (droppedKeys.has(label)) labelToColumn.delete(label)\n }\n for (const column of columnToLabel.keys()) {\n if (droppedKeys.has(column)) columnToLabel.delete(column)\n }\n\n const diverges = [...labelToColumn].some(([label, column]) => label !== column)\n\n function remap(\n input: Record<string, unknown>,\n lookup: Map<string, string>\n ): Record<string, unknown> {\n // Always returns a NEW object, even when nothing diverges. Returning `input`\n // unchanged would make aliasing depend on the schema, so a bug where a\n // caller mutates a row after mapping would reproduce only on renamed\n // fields. A shallow copy per row is not worth that class of bug.\n //\n // Unknown keys pass through unchanged — which is exactly why a dropped\n // key must be checked and skipped explicitly here, rather than relying on\n // its absence from `lookup`: absence alone would let it pass through under\n // its raw key instead of being dropped.\n const out: Record<string, unknown> = {}\n for (const key of Object.keys(input)) {\n if (droppedKeys.has(key)) continue\n out[lookup.get(key) ?? key] = input[key]\n }\n return out\n }\n\n return {\n toStorage: (input) => remap(input, labelToColumn),\n toLabels: (row) => remap(row, columnToLabel),\n columnFor: (label) => labelToColumn.get(label),\n labelFor: (column) => columnToLabel.get(column),\n labels: [...labelToColumn.keys()],\n diverges,\n }\n}\n\n/**\n * Built ONCE per content type at startup, not per request. Throws on a key\n * collision, matching how createCmsApp refuses to boot on a broken roles\n * registry: an ambiguous mapping would silently corrupt responses.\n *\n * Tombstones (`field.removed === true`) are column-backed — the parser keeps\n * their column alive for older live versions — but the CURRENT version must\n * never serve or accept them. Their pair is passed into the shared core\n * alongside the live ones (so the collision check below still sees them —\n * see `buildFieldKeyMap`'s doc comment), and then stripped from the returned\n * map via `droppedKeys` — which also drives `remap`: without that, an\n * unmapped retained column would pass through `remap` unchanged and reach\n * the response under its raw column name.\n */\nexport function createFieldKeyMap(fields: ParsedField[]): FieldKeyMap {\n // Both the name and the column of every tombstone, so the shared core can\n // strip a tombstone under either key. They differ when a field was renamed\n // and THEN removed: it carries both `column` (the original, still-live\n // column) and `removed`.\n const droppedKeys = new Set<string>()\n const pairs: Array<{ label: string; column: string }> = []\n\n for (const f of fields) {\n if (!isColumnBacked(f)) continue\n // Keyed by `f.name` for every column-backed field, live or tombstoned, so\n // buildFieldKeyMap's post-check stripping (which deletes from\n // `labelToColumn` by the labels in `droppedKeys`) can find and remove a\n // tombstone's own entry. That leans on field names being unique across\n // live and tombstoned fields within one type — true today (core rejects\n // a duplicate name at parse time) but only load-bearing here because this\n // refactor now writes a tombstone's pair into the shared map at all; the\n // original single-function implementation `continue`d past a tombstone\n // before ever writing one, so the collision was structurally impossible.\n pairs.push({ label: f.name, column: f.db_column.column_name })\n if (f.removed === true) {\n droppedKeys.add(f.name)\n droppedKeys.add(f.db_column.column_name)\n }\n }\n\n // `ownColumn` is a strict no-op on this path and is passed only so the two\n // constructors share one core: here a label IS a field's name, so a field\n // whose own column equals its name already maps that column back to itself\n // (`columnOwner === label`) and never reached the throw. It matters only for\n // a projection, which can expose a column under some other label.\n return buildFieldKeyMap(\n pairs,\n fields.map((f) => ({\n label: f.name,\n // Conditional spread, not a plain assignment — exactOptionalPropertyTypes\n // forbids present-and-undefined.\n ...(isColumnBacked(f) && { ownColumn: f.db_column.column_name }),\n })),\n droppedKeys\n )\n}\n\n/**\n * From a version's projection. The projection is already the label↔column\n * mapping and already excludes tombstones and non-column-backed fields, so\n * there is nothing to filter — but the collision check still needs every\n * field's label, which is why `allFields` is required.\n */\nexport function createFieldKeyMapFromProjection(\n projectionType: { fields: Array<{ column_name: string; exposed_as: string; fallback?: unknown }> },\n allFields: ParsedField[]\n): FieldKeyMap {\n const pairs = projectionType.fields.map((f) => ({ label: f.exposed_as, column: f.column_name }))\n const projected = new Set(pairs.map((p) => p.label))\n // Every column-backed field this projection does NOT expose must be\n // actively dropped — not just tombstones. A tombstone's column is one way\n // a column can be absent from the projection, but it is not the only one:\n // a field added to the CURRENT schema after this version was cut is live\n // (`removed !== true`) and is just as absent from an older version's\n // projection, and `remap` passes an unknown key through unchanged — so\n // without this, a column added later would leak into every older live\n // version's response straight off `SELECT *`. Building the drop-set from\n // \"every column-backed field, then un-drop what this projection exposes\"\n // subsumes the tombstone case instead of special-casing it.\n const dropped = new Set<string>()\n for (const f of allFields) {\n if (!isColumnBacked(f)) continue\n dropped.add(f.name)\n dropped.add(f.db_column.column_name)\n }\n // A column this version DOES expose must not be dropped — protects both a\n // retained tombstone column (the original case) and a column that is live\n // in the current schema but simply predates this version (the general case\n // above). Same for its label.\n for (const p of pairs) dropped.delete(p.column)\n for (const label of projected) dropped.delete(label)\n\n return buildFieldKeyMap(\n pairs,\n allFields.map((f) => ({\n label: f.name,\n // See buildFieldKeyMap's collision check: this is what lets it tell a\n // version exposing a field's own column under a new label (fine) from a\n // label that reuses another field's column (not fine).\n ...(isColumnBacked(f) && { ownColumn: f.db_column.column_name }),\n })),\n dropped\n )\n}\n","import crypto from 'node:crypto'\nimport { sql } from 'drizzle-orm'\nimport type { ParsedField, ParsedParagraphType, SchemaRegistry } from '@bobbykim/manguito-cms-core'\nimport type { DrizzlePostgresInstance } from '@bobbykim/manguito-cms-db'\nimport type { MediaDelta } from './media-references.js'\n\n// ─── Relation persistence ─────────────────────────────────────────────────────\n//\n// Owns paragraph/junction/reference/media relation table SQL — both the write\n// side (persist/delete below) and the read side (descriptors + resolvers further\n// down). Each persist/delete returns the MediaDelta of media ids it gained and\n// lost (see media-references.ts), which the caller merges and reconciles once.\n// The repository's resolveRows() drives the read resolvers.\n\nfunction quoteIdent(name: string): string {\n if (!/^[a-z][a-z0-9_-]*$/.test(name)) throw new Error(`Unsafe identifier: ${name}`)\n return `\"${name}\"`\n}\n\n// Paragraph rows live on a child table with their own image/video/file columns\n// (e.g. a \"photo card\" block embeds an image) — these need the same reference-count\n// bookkeeping as top-level content fields, one level down.\nfunction paragraphMediaFieldNames(pType: ParsedParagraphType): Set<string> {\n return new Set(\n pType.fields\n .filter((f) => f.field_type === 'image' || f.field_type === 'video' || f.field_type === 'file')\n .map((f) => f.name)\n )\n}\n\nasync function fetchParagraphMediaIds(\n db: DrizzlePostgresInstance,\n pType: ParsedParagraphType,\n itemId: string,\n fieldName: string\n): Promise<string[]> {\n const mediaColumns = pType.fields\n .filter((f) => f.field_type === 'image' || f.field_type === 'video' || f.field_type === 'file')\n .map((f) => f.db_column!.column_name)\n if (mediaColumns.length === 0) return []\n\n const tbl = sql.raw(quoteIdent(pType.db.table_name))\n const cols = sql.join(mediaColumns.map((c) => sql.raw(quoteIdent(c))), sql`, `)\n const result = await db.execute(\n sql`SELECT ${cols} FROM ${tbl} WHERE parent_id = ${itemId} AND parent_field = ${fieldName}`\n )\n\n const ids: string[] = []\n for (const row of result.rows as Record<string, unknown>[]) {\n for (const col of mediaColumns) {\n const v = row[col]\n if (typeof v === 'string' && v !== '') ids.push(v)\n }\n }\n return ids\n}\n\n// A paragraph type's own paragraph fields — the one level of nesting ADR\n// core/0005 permits. Empty without a registry, which is how callers that have no\n// nesting to persist opt out.\nfunction nestedParagraphFields(\n pType: ParsedParagraphType,\n registry?: SchemaRegistry\n): Array<{ fieldName: string; nType: ParsedParagraphType }> {\n if (!registry) return []\n const out: Array<{ fieldName: string; nType: ParsedParagraphType }> = []\n for (const f of pType.fields) {\n if (f.db_column !== null) continue\n const comp = f.ui_component as { component: string; ref?: string }\n if (comp.component !== 'paragraph-embed' || !comp.ref) continue\n const nType = registry.paragraph_types[comp.ref]\n if (nType) out.push({ fieldName: f.name, nType })\n }\n return out\n}\n\n// Ids of the paragraph rows currently stored for one field of one parent.\nasync function fetchParagraphRowIds(\n db: DrizzlePostgresInstance,\n pType: ParsedParagraphType,\n itemId: string,\n fieldName: string\n): Promise<string[]> {\n const tbl = sql.raw(quoteIdent(pType.db.table_name))\n const result = await db.execute(\n sql`SELECT id FROM ${tbl} WHERE parent_id = ${itemId} AND parent_field = ${fieldName}`\n )\n return (result.rows as Record<string, unknown>[])\n .map((r) => r['id'])\n .filter((id): id is string => typeof id === 'string')\n}\n\n// Replaces every paragraph row for one field of one parent (delete + reinsert),\n// returning the media ids removed (old rows) and added (new rows).\n//\n// `registry` enables one level of paragraph nesting (ADR core/0005): a nested\n// paragraph field has no column on its parent's table, so without this its items\n// were silently dropped on write. Nested rows hang off the *paragraph* row's id,\n// and are torn down with it so the delete+reinsert cannot orphan them.\nexport async function persistParagraphField(\n db: DrizzlePostgresInstance,\n itemId: string,\n parentType: string,\n fieldName: string,\n pType: ParsedParagraphType,\n items: unknown[],\n registry?: SchemaRegistry\n): Promise<MediaDelta> {\n const removed = await fetchParagraphMediaIds(db, pType, itemId, fieldName)\n const nested = nestedParagraphFields(pType, registry)\n\n // Tear down nested rows before their parents disappear, collecting the media\n // they referenced so reference counts stay correct.\n if (nested.length > 0) {\n const staleIds = await fetchParagraphRowIds(db, pType, itemId, fieldName)\n for (const rowId of staleIds) {\n for (const n of nested) {\n const d = await deleteParagraphField(db, rowId, n.fieldName, n.nType, registry)\n removed.push(...d.removed)\n }\n }\n }\n\n const tbl = sql.raw(quoteIdent(pType.db.table_name))\n await db.execute(sql`DELETE FROM ${tbl} WHERE parent_id = ${itemId} AND parent_field = ${fieldName}`)\n\n const mediaFieldNames = paragraphMediaFieldNames(pType)\n const added: string[] = []\n\n for (let i = 0; i < items.length; i++) {\n const pItem = items[i] as Record<string, unknown>\n const rowId = crypto.randomUUID()\n const data: Record<string, unknown> = {\n id: rowId,\n parent_id: itemId,\n parent_type: parentType,\n parent_field: fieldName,\n order: i,\n created_at: new Date(),\n updated_at: new Date(),\n }\n for (const pf of pType.fields) {\n if (!pf.db_column || pf.db_column.junction) continue\n data[pf.db_column.column_name] = pItem[pf.name] ?? null\n }\n const cols = sql.join(Object.keys(data).map((k) => sql.raw(quoteIdent(k))), sql`, `)\n const vals = sql.join(Object.values(data).map((v) => sql`${v}`), sql`, `)\n await db.execute(sql`INSERT INTO ${tbl} (${cols}) VALUES (${vals})`)\n\n for (const name of mediaFieldNames) {\n const val = pItem[name]\n if (typeof val === 'string' && val !== '') added.push(val)\n }\n\n // Persist this row's nested paragraph items against the row just inserted.\n for (const n of nested) {\n const nestedItems = Array.isArray(pItem[n.fieldName]) ? (pItem[n.fieldName] as unknown[]) : []\n const d = await persistParagraphField(\n db,\n rowId,\n pType.db.table_name,\n n.fieldName,\n n.nType,\n nestedItems,\n registry\n )\n added.push(...d.added)\n removed.push(...d.removed)\n }\n }\n\n return { added, removed }\n}\n\n// Deletes all paragraph rows for one field of one parent (used when the parent is\n// being deleted — paragraph rows have no FK/cascade back to their parent, so they'd\n// leak otherwise). Returns the media those rows referenced, all removed.\nexport async function deleteParagraphField(\n db: DrizzlePostgresInstance,\n itemId: string,\n fieldName: string,\n pType: ParsedParagraphType,\n registry?: SchemaRegistry\n): Promise<MediaDelta> {\n const removed = await fetchParagraphMediaIds(db, pType, itemId, fieldName)\n\n // Cascade to nested paragraph rows, which hang off these rows' ids.\n const nested = nestedParagraphFields(pType, registry)\n if (nested.length > 0) {\n const rowIds = await fetchParagraphRowIds(db, pType, itemId, fieldName)\n for (const rowId of rowIds) {\n for (const n of nested) {\n const d = await deleteParagraphField(db, rowId, n.fieldName, n.nType, registry)\n removed.push(...d.removed)\n }\n }\n }\n\n const tbl = sql.raw(quoteIdent(pType.db.table_name))\n await db.execute(sql`DELETE FROM ${tbl} WHERE parent_id = ${itemId} AND parent_field = ${fieldName}`)\n return { added: [], removed }\n}\n\n// Replaces every junction row for one many-to-many field of one parent.\nexport async function persistJunctionField(\n db: DrizzlePostgresInstance,\n itemId: string,\n junction: { table_name: string; left_column: string; right_column: string },\n relatedIds: string[]\n): Promise<void> {\n const tbl = sql.raw(quoteIdent(junction.table_name))\n const left = sql.raw(quoteIdent(junction.left_column))\n const right = sql.raw(quoteIdent(junction.right_column))\n await db.execute(sql`DELETE FROM ${tbl} WHERE ${left} = ${itemId}`)\n for (const rid of relatedIds) {\n await db.execute(sql`INSERT INTO ${tbl} (${left}, ${right}) VALUES (${itemId}, ${rid})`)\n }\n}\n\n// ─── Relation descriptors and resolution (read side) ──────────────────────────\n//\n// Relation read resolution. These were lifted out of the repository so that all\n// paragraph/junction/reference/media table SQL lives in one module alongside the\n// write persistence above. The repository's resolveRows() calls into here.\n\nexport type ParagraphRelationDef = {\n type: 'paragraph'\n table: string\n}\n\nexport type ReferenceRelationDef = {\n type: 'reference'\n table: string\n fk_column: string\n}\n\nexport type JunctionRelationDef = {\n type: 'junction'\n table: string\n junction_table: string\n left_column: string\n right_column: string\n // Only ordered junctions have an \"order\" column (db codegen emits it only when\n // order_column is true) — the resolver must not ORDER BY it otherwise.\n order_column: boolean\n}\n\nexport type MediaRelationDef = {\n type: 'media'\n fk_column: string\n}\n\nexport type RelationDef =\n | ParagraphRelationDef\n | ReferenceRelationDef\n | JunctionRelationDef\n | MediaRelationDef\n\n// Derives a content type's relations map from its parsed fields, so paragraph/\n// reference/media fields are actually resolved instead of silently dropped.\n// Junction and foreign_key info is already on each field's db_column; paragraph\n// fields store their target table on the paragraph type, hence the registry.\nexport function buildRelationsMap(\n fields: ParsedField[],\n registry: SchemaRegistry\n): Record<string, RelationDef> {\n const relations: Record<string, RelationDef> = {}\n\n for (const field of fields) {\n if (field.field_type === 'image' || field.field_type === 'video' || field.field_type === 'file') {\n if (!field.db_column) continue\n relations[field.name] = { type: 'media', fk_column: field.db_column.column_name }\n } else if (field.field_type === 'paragraph') {\n const ref = (field.ui_component as { ref?: string }).ref\n const pType = ref ? registry.paragraph_types[ref] : undefined\n if (!pType) continue\n relations[field.name] = { type: 'paragraph', table: pType.db.table_name }\n } else if (field.field_type === 'reference') {\n if (!field.db_column) continue\n if (field.db_column.junction) {\n const j = field.db_column.junction\n relations[field.name] = {\n type: 'junction',\n table: j.right_table,\n junction_table: j.table_name,\n left_column: j.left_column,\n right_column: j.right_column,\n order_column: j.order_column,\n }\n } else if (field.db_column.foreign_key) {\n relations[field.name] = {\n type: 'reference',\n table: field.db_column.foreign_key.table,\n fk_column: field.db_column.column_name,\n }\n }\n }\n }\n\n return relations\n}\n\nfunction groupBy<T extends Record<string, unknown>>(\n items: T[],\n key: string\n): Record<string, T[]> {\n const result: Record<string, T[]> = {}\n for (const item of items) {\n const k = String(item[key])\n if (!result[k]) result[k] = []\n result[k]!.push(item)\n }\n return result\n}\n\n// A reference/media pass is DESTRUCTIVE: the resolved object replaces the raw FK\n// value, and when the field's label differs from its FK column (a renamed field)\n// the raw FK key is deleted outright — otherwise the response would carry both\n// the column name and the label.\n//\n// That makes a second pass over the same row object dangerous: the FK the first\n// pass consumed is gone, so the row would be nulled out. And the same object\n// really does recur — reference/junction targets are cached by `table:id`, so two\n// parents receive ONE shared object, and the GraphQL dataloaders deliberately do\n// not memoize by parent identity (\"rows are mutated and may recur across nesting\n// levels\", dataloaders.ts). So a row that already holds a resolved value is\n// skipped, which makes the pass idempotent.\nfunction needsFkResolution(\n row: Record<string, unknown>,\n fkColumn: string,\n fieldName: string\n): boolean {\n // Label diverges from the column: a missing FK key means it was already dropped.\n if (fkColumn !== fieldName && !(fkColumn in row)) return false\n // Label equals the column: the resolved object sits where the raw id was, so an\n // object (never a bare id) means this row is already done.\n const current = row[fieldName]\n return !(typeof current === 'object' && current !== null)\n}\n\n// Fully resolves a relation field on a batch of rows: paragraph → ordered rows,\n// reference → the target row, junction → target rows, media → the media row.\n// The cache (keyed table:id) dedupes target lookups within a request.\n//\n// `publishedOnly` (default false, preserving prior behavior for admin callers)\n// adds `AND published = true` to the reference/junction target-row SELECTs.\n// Reference and junction targets are always content or taxonomy tables, which\n// always carry a `published` boolean system field — see CONTENT_SYSTEM_FIELDS /\n// TAXONOMY_SYSTEM_FIELDS in packages/core/src/parser/parseSchema.ts. Paragraph\n// and media targets are deliberately excluded: paragraph rows are owned by\n// their parent (no independent publish state) and media rows have no\n// `published` column at all.\nexport async function resolveRelationField(\n db: DrizzlePostgresInstance,\n rows: Record<string, unknown>[],\n fieldName: string,\n rel: RelationDef,\n cache: Map<string, unknown>,\n publishedOnly = false\n): Promise<void> {\n if (rows.length === 0) return\n\n // Deduped by object identity: one batch can hold the same row twice (see\n // needsFkResolution above), and the destructive branches would then read an FK\n // the first visit already consumed. The caller's array is left untouched — the\n // GraphQL dataloader maps over it to build its results.\n const batch = [...new Set(rows)]\n\n if (rel.type === 'paragraph') {\n const parentIds = batch.map((r) => r['id'] as string)\n const inList = sql.join(parentIds.map((id) => sql`${id}`), sql`, `)\n // Scoped by parent_field as well as parent_id: two paragraph fields of the\n // same paragraph type share one table, so filtering on the parent alone\n // hands each field the other's rows.\n const result = await db.execute(\n sql`SELECT * FROM ${sql.raw(quoteIdent(rel.table))} WHERE parent_id IN (${inList}) AND parent_field = ${fieldName} ORDER BY \"order\" ASC`\n )\n const byParent = groupBy(result.rows as Record<string, unknown>[], 'parent_id')\n for (const row of batch) {\n row[fieldName] = byParent[row['id'] as string] ?? []\n }\n } else if (rel.type === 'reference') {\n const dropFk = rel.fk_column !== fieldName\n const pending = batch.filter((r) => needsFkResolution(r, rel.fk_column, fieldName))\n if (pending.length === 0) return\n\n const fkValues = pending.map((r) => r[rel.fk_column] as string).filter(Boolean)\n if (fkValues.length === 0) {\n for (const row of pending) {\n row[fieldName] = null\n if (dropFk) delete row[rel.fk_column]\n }\n return\n }\n const unique = [...new Set(fkValues)]\n const uncached = unique.filter((id) => !cache.has(`${rel.table}:${id}`))\n if (uncached.length > 0) {\n const inList = sql.join(uncached.map((id) => sql`${id}`), sql`, `)\n const publishedCond = publishedOnly ? sql` AND published = true` : sql``\n const result = await db.execute(\n sql`SELECT * FROM ${sql.raw(quoteIdent(rel.table))} WHERE id IN (${inList})${publishedCond}`\n )\n for (const item of result.rows as Record<string, unknown>[]) {\n cache.set(`${rel.table}:${item['id']}`, item)\n }\n }\n for (const row of pending) {\n const fkVal = row[rel.fk_column] as string\n row[fieldName] = fkVal ? (cache.get(`${rel.table}:${fkVal}`) ?? null) : null\n if (dropFk) delete row[rel.fk_column]\n }\n } else if (rel.type === 'junction') {\n const parentIds = batch.map((r) => r['id'] as string)\n const inList = sql.join(parentIds.map((id) => sql`${id}`), sql`, `)\n const orderBy = rel.order_column ? sql` ORDER BY \"order\" ASC` : sql``\n const junctionResult = await db.execute(\n sql`SELECT * FROM ${sql.raw(quoteIdent(rel.junction_table))} WHERE ${sql.raw(quoteIdent(rel.left_column))} IN (${inList})${orderBy}`\n )\n const jRows = junctionResult.rows as Record<string, unknown>[]\n const rightIds = [...new Set(jRows.map((r) => r[rel.right_column] as string))]\n const uncached = rightIds.filter((id) => !cache.has(`${rel.table}:${id}`))\n if (uncached.length > 0) {\n const rightInList = sql.join(uncached.map((id) => sql`${id}`), sql`, `)\n const publishedCond = publishedOnly ? sql` AND published = true` : sql``\n const entitiesResult = await db.execute(\n sql`SELECT * FROM ${sql.raw(quoteIdent(rel.table))} WHERE id IN (${rightInList})${publishedCond}`\n )\n for (const item of entitiesResult.rows as Record<string, unknown>[]) {\n cache.set(`${rel.table}:${item['id']}`, item)\n }\n }\n const byLeft = groupBy(jRows, rel.left_column)\n for (const row of batch) {\n const jEntries = byLeft[row['id'] as string] ?? []\n row[fieldName] = jEntries\n .map((jr) => cache.get(`${rel.table}:${jr[rel.right_column] as string}`))\n .filter(Boolean)\n }\n } else if (rel.type === 'media') {\n const dropFk = rel.fk_column !== fieldName\n const pending = batch.filter((r) => needsFkResolution(r, rel.fk_column, fieldName))\n if (pending.length === 0) return\n\n const fkValues = pending.map((r) => r[rel.fk_column] as string).filter(Boolean)\n if (fkValues.length === 0) {\n for (const row of pending) {\n row[fieldName] = null\n if (dropFk) delete row[rel.fk_column]\n }\n return\n }\n const unique = [...new Set(fkValues)]\n const uncached = unique.filter((id) => !cache.has(`media:${id}`))\n if (uncached.length > 0) {\n const inList = sql.join(uncached.map((id) => sql`${id}`), sql`, `)\n const result = await db.execute(sql`SELECT * FROM \"media\" WHERE id IN (${inList})`)\n for (const item of result.rows as Record<string, unknown>[]) {\n cache.set(`media:${item['id']}`, item)\n }\n }\n for (const row of pending) {\n const fkVal = row[rel.fk_column] as string\n row[fieldName] = fkVal ? (cache.get(`media:${fkVal}`) ?? null) : null\n if (dropFk) delete row[rel.fk_column]\n }\n }\n}\n\n// Populates paragraph/junction fields with bare ID arrays when they are not\n// explicitly included — they have no column on the owning table, so without this\n// they'd be silently absent. Reference fields already carry their bare ID as a\n// real column from `SELECT *`, so they need no extra query.\nexport async function resolveRelationBareIds(\n db: DrizzlePostgresInstance,\n rows: Record<string, unknown>[],\n fieldName: string,\n rel: RelationDef\n): Promise<void> {\n if (rows.length === 0) return\n\n if (rel.type === 'paragraph') {\n const parentIds = rows.map((r) => r['id'] as string)\n const inList = sql.join(parentIds.map((id) => sql`${id}`), sql`, `)\n const result = await db.execute(\n sql`SELECT id, parent_id FROM ${sql.raw(quoteIdent(rel.table))} WHERE parent_id IN (${inList}) AND parent_field = ${fieldName} ORDER BY \"order\" ASC`\n )\n const byParent = groupBy(result.rows as Record<string, unknown>[], 'parent_id')\n for (const row of rows) {\n row[fieldName] = (byParent[row['id'] as string] ?? []).map((r) => r['id'])\n }\n } else if (rel.type === 'junction') {\n const parentIds = rows.map((r) => r['id'] as string)\n const inList = sql.join(parentIds.map((id) => sql`${id}`), sql`, `)\n const result = await db.execute(\n sql`SELECT ${sql.raw(quoteIdent(rel.left_column))} AS left_id, ${sql.raw(quoteIdent(rel.right_column))} AS right_id FROM ${sql.raw(quoteIdent(rel.junction_table))} WHERE ${sql.raw(quoteIdent(rel.left_column))} IN (${inList})`\n )\n const byLeft = groupBy(result.rows as Record<string, unknown>[], 'left_id')\n for (const row of rows) {\n row[fieldName] = (byLeft[row['id'] as string] ?? []).map((r) => r['right_id'])\n }\n }\n}\n","import { GraphQLScalarType, Kind, valueFromASTUntyped } from 'graphql'\n\nexport const DateTimeScalar = new GraphQLScalarType({\n name: 'DateTime',\n description: 'ISO-8601 date-time string',\n serialize(value) {\n if (value instanceof Date) return value.toISOString()\n if (typeof value === 'string') return value\n if (typeof value === 'number') return new Date(value).toISOString()\n throw new TypeError(`DateTime cannot serialize value: ${String(value)}`)\n },\n parseValue(value) {\n if (typeof value !== 'string') throw new TypeError('DateTime must be a string')\n return value\n },\n parseLiteral(ast) {\n if (ast.kind !== Kind.STRING) throw new TypeError('DateTime must be a string')\n return ast.value\n },\n})\n\nexport const JSONScalar = new GraphQLScalarType({\n name: 'JSON',\n description: 'Arbitrary JSON value',\n serialize: (value) => value,\n parseValue: (value) => value,\n parseLiteral: (ast, variables) => valueFromASTUntyped(ast, variables ?? undefined),\n})\n","import {\n GraphQLString,\n GraphQLInt,\n GraphQLFloat,\n GraphQLBoolean,\n type GraphQLScalarType,\n} from 'graphql'\nimport type { FieldType } from '@bobbykim/manguito-cms-core'\nimport { DateTimeScalar, JSONScalar } from './scalars.js'\n\n// Scalar output type for a field. Relation/enum fields return null — schema.ts\n// resolves those against its type caches.\nexport function scalarOutputType(fieldType: FieldType): GraphQLScalarType | null {\n switch (fieldType) {\n case 'text/plain':\n case 'text/rich':\n return GraphQLString\n case 'integer':\n return GraphQLInt\n case 'float':\n return GraphQLFloat\n case 'boolean':\n return GraphQLBoolean\n case 'date':\n return DateTimeScalar\n case 'programmatic':\n return JSONScalar\n default:\n return null\n }\n}\n","// \"content--blog_post\" → \"blog_post\"; \"category\" → \"category\"\nexport function schemaSegment(machineName: string): string {\n const idx = machineName.indexOf('--')\n return idx === -1 ? machineName : machineName.slice(idx + 2)\n}\n\nfunction words(input: string): string[] {\n return input.split(/[_\\s-]+/).filter(Boolean)\n}\n\nexport function toPascalCase(input: string): string {\n return words(input)\n .map((w) => w.charAt(0).toUpperCase() + w.slice(1))\n .join('')\n}\n\nexport function toCamelCase(input: string): string {\n const pascal = toPascalCase(input)\n return pascal.charAt(0).toLowerCase() + pascal.slice(1)\n}\n\n// Deterministic English pluralization for the common cases; irregular plurals\n// (person→people) are a known limitation — an optional per-type override can be\n// added later if one is wrong.\nexport function pluralize(word: string): string {\n if (/[^aeiou]y$/.test(word)) return word.slice(0, -1) + 'ies'\n if (/(s|x|z|ch|sh)$/.test(word)) return word + 'es'\n return word + 's'\n}\n\nconst GRAPHQL_NAME = /^[_A-Za-z][_0-9A-Za-z]*$/\nexport function isValidGraphQLName(name: string): boolean {\n return GRAPHQL_NAME.test(name)\n}\n\nexport function graphqlTypeName(machineName: string): string {\n return toPascalCase(schemaSegment(machineName))\n}\n\nexport function singleQueryName(machineName: string): string {\n return toCamelCase(schemaSegment(machineName))\n}\n\nexport function collectionQueryName(machineName: string): string {\n return pluralize(singleQueryName(machineName))\n}\n\nexport function buildFieldNameMap(schemaNames: string[]): {\n toGraphql(schemaName: string): string\n toSchema(graphqlName: string): string\n} {\n const toG = new Map<string, string>()\n const toS = new Map<string, string>()\n for (const name of schemaNames) {\n const g = toCamelCase(name)\n toG.set(name, g)\n toS.set(g, name)\n }\n return {\n toGraphql: (schemaName) => toG.get(schemaName) ?? toCamelCase(schemaName),\n toSchema: (graphqlName) => toS.get(graphqlName) ?? graphqlName,\n }\n}\n","import {\n GraphQLEnumType,\n GraphQLInputObjectType,\n GraphQLList,\n GraphQLNonNull,\n GraphQLString,\n GraphQLBoolean,\n GraphQLInt,\n GraphQLFloat,\n type GraphQLInputType,\n} from 'graphql'\nimport type {\n ParsedContentType,\n ParsedTaxonomyType,\n ParsedField,\n FilterValue,\n FilterOperator,\n FieldType,\n} from '@bobbykim/manguito-cms-core'\nimport { graphqlTypeName, buildFieldNameMap } from './naming.js'\nimport { scalarOutputType } from './type-mapping.js'\nimport { DateTimeScalar } from './scalars.js'\nimport type { FieldKeyMap } from '../field-keys.js'\nimport type { ViewField } from './version-view.js'\n\nexport const SortOrderEnum = new GraphQLEnumType({\n name: 'SortOrder',\n values: { ASC: { value: 'asc' }, DESC: { value: 'desc' } },\n})\n\n// Only these fields are sortable (mirrors the REST SORTABLE_FIELDS).\n//\n// `value` is the enum's internal value, and it is NOT uniformly a storage\n// column: `created_at` / `updated_at` are the snake_case forms of real system\n// columns, but `title` is a schema field's LABEL — the same value REST accepts\n// in `?sort_by=`. `collectionResolver` maps it to a column through the type's\n// FieldKeyMap before the repository builds its ORDER BY.\nconst SORTABLE: Array<{ gql: string; value: string }> = [\n { gql: 'title', value: 'title' },\n { gql: 'createdAt', value: 'created_at' },\n { gql: 'updatedAt', value: 'updated_at' },\n]\n\n/**\n * `fieldKeys` is THIS version's map. `title` in SORTABLE is a schema field's\n * LABEL, not a column, so a version that has no field with that label cannot\n * resolve it — and offering it anyway hands the repository a label that maps\n * to nothing, which surfaces as a 500 from SQL rather than a rejected query.\n * Mirrors `sortableColumnsFor` in versions.ts.\n *\n * `createdAt`/`updatedAt` are real system columns on every content type, so\n * they are always offered and the enum can never end up empty — which\n * GraphQL rejects.\n *\n * With no map, every value is kept: callers that have not threaded a\n * FieldKeyMap through behave exactly as before.\n */\nexport function buildSortFieldEnum(typeName: string, fieldKeys?: FieldKeyMap): GraphQLEnumType {\n const resolvable = SORTABLE.filter(\n (s) =>\n s.gql === 'createdAt' ||\n s.gql === 'updatedAt' ||\n fieldKeys === undefined ||\n fieldKeys.columnFor(s.value) !== undefined\n )\n return new GraphQLEnumType({\n name: `${typeName}SortField`,\n values: Object.fromEntries(resolvable.map((s) => [s.gql, { value: s.value }])),\n })\n}\n\n// A comparable filter input (eq/in/gt/gte/lt/lte) for a given scalar input type.\nfunction comparableFilterInput(name: string, scalar: GraphQLInputType): GraphQLInputObjectType {\n return new GraphQLInputObjectType({\n name,\n fields: {\n eq: { type: scalar },\n in: { type: new GraphQLList(new GraphQLNonNull(scalar)) },\n gt: { type: scalar },\n gte: { type: scalar },\n lt: { type: scalar },\n lte: { type: scalar },\n },\n })\n}\n\nfunction equalityFilterInput(name: string, scalar: GraphQLInputType): GraphQLInputObjectType {\n return new GraphQLInputObjectType({\n name,\n fields: {\n eq: { type: scalar },\n in: { type: new GraphQLList(new GraphQLNonNull(scalar)) },\n },\n })\n}\n\n// Shared per-scalar filter inputs (built once).\nconst StringFilter = equalityFilterInput('StringFilter', GraphQLString)\nconst BooleanFilter = new GraphQLInputObjectType({\n name: 'BooleanFilter',\n fields: { eq: { type: GraphQLBoolean } },\n})\nconst DateTimeFilter = comparableFilterInput('DateTimeFilter', DateTimeScalar)\nconst IDFilter = equalityFilterInput('IDFilter', GraphQLString)\nconst IntFilter = comparableFilterInput('IntFilter', GraphQLInt)\nconst FloatFilter = comparableFilterInput('FloatFilter', GraphQLFloat)\n\n// Choose a filter input for a field based on its scalar output type.\nfunction filterInputForField(fieldType: FieldType): GraphQLInputObjectType | null {\n const scalar = scalarOutputType(fieldType)\n if (fieldType === 'boolean') return BooleanFilter\n if (fieldType === 'date') return DateTimeFilter\n if (fieldType === 'integer') return IntFilter\n if (fieldType === 'float') return FloatFilter\n if (fieldType === 'reference') return IDFilter\n if (scalar === null) return null // only 'enum' reaches here — not filterable via a scalar input\n return StringFilter\n}\n\n// Build the <Type>Filter input. Programmatic fields are excluded (no column).\nexport function buildFilterInputType(\n type: ParsedContentType | ParsedTaxonomyType,\n viewFields?: ViewField[]\n): GraphQLInputObjectType | null {\n const fields: Record<string, { type: GraphQLInputObjectType }> = {}\n\n // System fields that are filterable columns.\n fields['id'] = { type: IDFilter }\n fields['published'] = { type: BooleanFilter }\n fields['createdAt'] = { type: DateTimeFilter }\n fields['updatedAt'] = { type: DateTimeFilter }\n if (type.schema_type === 'content-type') fields['slug'] = { type: StringFilter }\n\n // A view states the label THIS version exposes each field under; without\n // one, fall back to the field's own name minus tombstones (Task 2's rule).\n const entries: Array<{ field: ParsedField; exposedAs: string }> =\n viewFields !== undefined\n ? viewFields.map((v) => ({ field: v.field, exposedAs: v.exposedAs }))\n : type.fields.filter((f) => f.removed !== true).map((f) => ({ field: f, exposedAs: f.name }))\n\n for (const { field: f, exposedAs } of entries) {\n if (f.field_type === 'programmatic' || f.field_type === 'paragraph') continue\n if (f.field_type === 'image' || f.field_type === 'video' || f.field_type === 'file') continue\n const input = filterInputForField(f.field_type)\n if (input) fields[buildFieldNameMap([exposedAs]).toGraphql(exposedAs)] = { type: input }\n }\n\n if (Object.keys(fields).length === 0) return null\n return new GraphQLInputObjectType({ name: `${graphqlTypeName(type.name)}Filter`, fields })\n}\n\n// Convert a GraphQL filter argument into the repository's filters map, keyed by\n// snake_case column. eq → scalar, in → array (OR/IN), gt/gte/lt/lte → operator.\n//\n// `nameMap.toSchema` only ever recovers the schema field's LABEL (`field.name`)\n// from the GraphQL name — that mapping is unaware of storage columns. `columnFor`\n// (typically `FieldKeyMap.columnFor`) takes that label the rest of the way to the\n// storage column the repository actually filters on. It's optional so callers\n// that haven't threaded a FieldKeyMap through yet keep working unchanged: with no\n// mapper, or when it returns nothing for a given label, the label itself is used —\n// correct as long as it equals its column.\nexport function translateFilters(\n input: Record<string, unknown> | undefined,\n nameMap: { toSchema(g: string): string },\n columnFor?: (label: string) => string | undefined\n): Record<string, FilterValue> {\n const out: Record<string, FilterValue> = {}\n if (!input) return out\n\n for (const [gqlField, raw] of Object.entries(input)) {\n if (raw === null || typeof raw !== 'object') continue\n const label = nameMap.toSchema(gqlField)\n const column = columnFor?.(label) ?? label\n const spec = raw as Record<string, unknown>\n\n // eq/in take priority over range operators (gt/gte/lt/lte) when a client sends\n // both in one field filter — the range operators are silently dropped.\n if (spec['eq'] !== undefined) {\n out[column] = spec['eq'] as FilterValue\n } else if (Array.isArray(spec['in'])) {\n out[column] = spec['in'] as FilterValue\n } else {\n const op: FilterOperator = {}\n for (const k of ['gt', 'gte', 'lt', 'lte'] as const) {\n if (spec[k] !== undefined) op[k] = spec[k] as number | string\n }\n if (Object.keys(op).length > 0) out[column] = op\n }\n }\n return out\n}\n","import { GraphQLError } from 'graphql'\nimport type { ParsedField } from '@bobbykim/manguito-cms-core'\nimport type { GraphQLContext } from './context.js'\nimport { translateFilters } from './filters.js'\nimport { isColumnBacked, type FieldKeyMap } from '../field-keys.js'\n\ntype Row = Record<string, unknown>\n\nfunction codeError(code: string, message: string): GraphQLError {\n return new GraphQLError(message, { extensions: { code } })\n}\n\nfunction isPublished(item: Row | null): boolean {\n return !!item && item['published'] === true\n}\n\n/**\n * A GraphQL field's value on a content row. The GraphQL field NAME comes from\n * the field's label (see naming.ts), but the value lives under its storage\n * column, which differs once a field has been renamed.\n *\n * `fallback` is the value a version declares for a column that stopped being\n * written: rows created since the removal hold null there. Substituted on\n * null/undefined ONLY — never on '' or 0 — matching projectRow's rule, so the\n * REST and GraphQL surfaces cannot disagree about the same version.\n */\nexport function resolveFieldValue(\n field: ParsedField,\n row: Record<string, unknown>,\n fallback?: unknown\n): unknown {\n const key = isColumnBacked(field) ? field.db_column.column_name : field.name\n const value = row[key]\n if ((value === null || value === undefined) && fallback !== undefined) return fallback\n return value ?? null\n}\n\nexport function scalarFieldResolver(field: ParsedField, fallback?: unknown) {\n return (parent: Row): unknown => resolveFieldValue(field, parent, fallback)\n}\n\nexport function relationFieldResolver(typeName: string, schemaFieldName: string) {\n return (parent: Row, _args: unknown, ctx: GraphQLContext): Promise<unknown> =>\n ctx.loaders.load(typeName, schemaFieldName, parent)\n}\n\n/**\n * A media field, in both key spaces the programmatic record straddles.\n *\n * `name` is the field's REGISTRY name — the only key `ctx.loaders.load` can\n * look a field up by, and therefore the key the resolved object lands on.\n * `exposedAs` is the label the version being served uses, which is where\n * `ctx.get()` will look for it. They differ exactly when this version renames\n * the field, and that is the whole reason both are needed.\n */\nexport type MediaFieldKey = { name: string; exposedAs: string }\n\nexport function programmaticFieldResolver(\n typeName: string,\n schemaFieldName: string,\n mediaFields: readonly MediaFieldKey[] = [],\n fieldKeys?: FieldKeyMap\n) {\n return async (parent: Row, _args: unknown, ctx: GraphQLContext): Promise<unknown> => {\n let p = ctx.programmaticMemo.get(parent)\n if (!p) {\n p = resolveProgrammaticRow(typeName, parent, ctx, mediaFields, fieldKeys)\n ctx.programmaticMemo.set(parent, p)\n }\n return (await p)[schemaFieldName]\n }\n}\n\n// A programmatic resolver reads its record through `ctx.get()`, so that record\n// must look the same on both public surfaces. REST resolves media fields to full\n// objects before running resolvers; GraphQL reads through repositories that\n// resolve nothing, so without this `ctx.get('hero').url` would work over REST and\n// silently fall back to null over GraphQL.\n//\n// Media is resolved into a COPY, never the row itself: the dataloaders write\n// their results back into the row they are handed, and the real row must keep the\n// raw id so a query that also selects the media field still resolves. Handing an\n// already-resolved object back to the loader is exactly what broke media here in\n// the first place. Copies still batch — one media query per request, not per row.\n//\n// `ctx.get(fieldName)` takes the schema field name (the LABEL), so the record is\n// projected to labels before the resolver runs — the same order REST uses (see\n// \"Response projection order\" in routes/content.ts). GraphQL has no route-level\n// projection to double up with: every other field resolves per field by column\n// (resolveFieldValue), so this copy is the only mapped row, and it is mapped\n// strictly after the media loaders have run.\n//\n// The loaders write onto a field's REGISTRY name, but `toLabels` speaks the\n// served version's labels, and a version that renames a media field agrees with\n// neither: createFieldKeyMapFromProjection un-drops only a projected COLUMN or\n// LABEL, so the registry name of a renamed field stays in the drop-set and\n// `remap` deletes the resolved object outright (and the raw FK column is\n// already gone — resolveRelationField deletes it whenever the field's name\n// differs from its column). Re-attaching each resolved value under this\n// version's own label AFTER the remap is what closes that gap. Done by explicit\n// assignment rather than by letting `remap` carry the key, because the raw\n// column and the resolved object can map onto the same label and key order must\n// not decide which one wins: the resolved object always does.\nasync function resolveProgrammaticRow(\n typeName: string,\n parent: Row,\n ctx: GraphQLContext,\n mediaFields: readonly MediaFieldKey[],\n fieldKeys?: FieldKeyMap\n): Promise<Record<string, unknown>> {\n const toLabels = (row: Row): Row => (fieldKeys ? fieldKeys.toLabels(row) : row)\n\n if (mediaFields.length === 0) return ctx.resolver.resolveItem(typeName, toLabels(parent))\n\n const enriched: Row = { ...parent }\n await Promise.all(mediaFields.map((m) => ctx.loaders.load(typeName, m.name, enriched)))\n return ctx.resolver.resolveItem(typeName, relabelMedia(toLabels(enriched), enriched, mediaFields))\n}\n\n/**\n * `record` (already label-keyed) with each resolved media value restored under\n * the label this version exposes it as. Writes only into `record` — either the\n * fresh object `remap` just returned, or, with no FieldKeyMap, the `enriched`\n * copy itself. Never the caller's row, which must keep its raw FK ids.\n *\n * A field the loaders never wrote is skipped rather than written as undefined —\n * `resolveRelationField` returns early when no row needs FK resolution, and a\n * present-but-undefined key is not the same record shape REST produces.\n */\nfunction relabelMedia(record: Row, enriched: Row, mediaFields: readonly MediaFieldKey[]): Row {\n for (const { name, exposedAs } of mediaFields) {\n if (!(name in enriched)) continue\n record[exposedAs] = enriched[name]\n }\n return record\n}\n\ntype CollectionArgs = {\n page?: number\n perPage?: number\n /**\n * The sort enum's internal value (filters.ts `SORTABLE`), which mixes two key\n * spaces: `created_at` / `updated_at` are system COLUMNS, but `title` is a\n * schema field's LABEL. It must be mapped before it reaches the repository.\n */\n sortBy?: string\n sortOrder?: 'asc' | 'desc'\n filter?: Record<string, unknown>\n}\n\nexport function collectionResolver(\n typeName: string,\n nameMap: { toSchema(g: string): string },\n fieldKeys?: FieldKeyMap\n) {\n return async (_root: unknown, args: CollectionArgs, ctx: GraphQLContext) => {\n const page = args.page ?? 1\n const perPage = args.perPage ?? 10\n if (!Number.isInteger(page) || page < 1) {\n throw codeError('INVALID_PAGINATION', 'page must be ≥ 1')\n }\n if (!Number.isInteger(perPage) || perPage < 1 || perPage > 100) {\n throw codeError('INVALID_PAGINATION', 'perPage must be between 1 and 100')\n }\n const repo = ctx.repos[typeName]!\n // `ORDER BY` takes a storage column, never a label. `columnFor` resolves a\n // schema field's label to its column; a system column (`created_at`,\n // `updated_at`) is not a label at all, so it has no entry and falls\n // through unchanged. Identity today, since every label equals its column.\n const sortBy = args.sortBy ?? 'created_at'\n const sortColumn = fieldKeys?.columnFor(sortBy) ?? sortBy\n const result = await repo.findMany({\n published_only: true,\n page,\n per_page: perPage,\n sort_by: sortColumn as 'title' | 'created_at' | 'updated_at',\n sort_order: args.sortOrder ?? 'asc',\n filters: translateFilters(args.filter, nameMap, fieldKeys?.columnFor),\n })\n return { data: result.data as Row[], meta: result.meta }\n }\n}\n\nexport function singleBySlugResolver(typeName: string) {\n return async (_root: unknown, args: { slug: string }, ctx: GraphQLContext): Promise<Row | null> => {\n const repo = ctx.repos[typeName]!\n const item = (await repo.findBySlug(args.slug)) as Row | null\n return isPublished(item) ? item : null\n }\n}\n\nexport function singletonResolver(typeName: string) {\n return async (_root: unknown, _args: unknown, ctx: GraphQLContext): Promise<Row | null> => {\n const repo = ctx.repos[typeName]!\n const result = await repo.findMany({ published_only: true, page: 1, per_page: 1 })\n return (result.data[0] as Row | undefined) ?? null\n }\n}\n\nexport function taxonomySingleResolver(typeName: string) {\n return async (_root: unknown, args: { id: string }, ctx: GraphQLContext): Promise<Row | null> => {\n const repo = ctx.repos[typeName]!\n const item = (await repo.findOne(args.id)) as Row | null\n return isPublished(item) ? item : null\n }\n}\n","import {\n GraphQLSchema,\n GraphQLObjectType,\n GraphQLEnumType,\n GraphQLNonNull,\n GraphQLList,\n GraphQLID,\n GraphQLString,\n GraphQLInt,\n GraphQLBoolean,\n type GraphQLFieldConfig,\n type GraphQLOutputType,\n type GraphQLFieldConfigMap,\n} from 'graphql'\nimport type {\n SchemaRegistry,\n ParsedContentType,\n ParsedTaxonomyType,\n ParsedParagraphType,\n ParsedField,\n ParsedEnumType,\n} from '@bobbykim/manguito-cms-core'\nimport type { GraphQLContext } from './context.js'\nimport type { FieldKeyMap } from '../field-keys.js'\nimport type { VersionView, ViewField } from './version-view.js'\nimport { DateTimeScalar } from './scalars.js'\nimport { scalarOutputType } from './type-mapping.js'\nimport {\n graphqlTypeName,\n singleQueryName,\n collectionQueryName,\n toCamelCase,\n isValidGraphQLName,\n buildFieldNameMap,\n} from './naming.js'\nimport { SortOrderEnum, buildSortFieldEnum, buildFilterInputType } from './filters.js'\nimport {\n scalarFieldResolver,\n relationFieldResolver,\n programmaticFieldResolver,\n collectionResolver,\n singleBySlugResolver,\n singletonResolver,\n taxonomySingleResolver,\n} from './resolvers.js'\nimport type { MediaFieldKey } from './resolvers.js'\n\nconst PAGE_META = new GraphQLObjectType({\n name: 'PageMeta',\n fields: {\n total: { type: new GraphQLNonNull(GraphQLInt), resolve: (m) => m.total },\n page: { type: new GraphQLNonNull(GraphQLInt), resolve: (m) => m.page },\n perPage: { type: new GraphQLNonNull(GraphQLInt), resolve: (m) => m.per_page },\n totalPages: { type: new GraphQLNonNull(GraphQLInt), resolve: (m) => m.total_pages },\n hasNext: { type: new GraphQLNonNull(GraphQLBoolean), resolve: (m) => m.has_next },\n hasPrev: { type: new GraphQLNonNull(GraphQLBoolean), resolve: (m) => m.has_prev },\n },\n})\n\nconst MEDIA = new GraphQLObjectType({\n name: 'Media',\n fields: {\n id: { type: new GraphQLNonNull(GraphQLID), resolve: (m) => m.id },\n type: { type: new GraphQLNonNull(GraphQLString), resolve: (m) => m.type },\n url: { type: new GraphQLNonNull(GraphQLString), resolve: (m) => m.url },\n mimeType: { type: new GraphQLNonNull(GraphQLString), resolve: (m) => m.mime_type },\n alt: { type: GraphQLString, resolve: (m) => m.alt },\n fileSize: { type: GraphQLInt, resolve: (m) => m.file_size },\n width: { type: GraphQLInt, resolve: (m) => m.width },\n height: { type: GraphQLInt, resolve: (m) => m.height },\n duration: { type: GraphQLInt, resolve: (m) => m.duration },\n },\n})\n\nexport function buildGraphQLSchema(\n registry: SchemaRegistry,\n fieldKeyMaps: Record<string, FieldKeyMap> = {},\n view?: VersionView\n): GraphQLSchema {\n const objectTypes = new Map<string, GraphQLObjectType>() // machineName → type\n const enumTypes = new Map<string, GraphQLEnumType>() // enum machineName → type (only when valid)\n\n // Enum types (only when every value is a valid GraphQL identifier).\n for (const [name, enumType] of Object.entries(registry.enum_types) as [string, ParsedEnumType][]) {\n if (enumType.values.every(isValidGraphQLName)) {\n enumTypes.set(\n name,\n new GraphQLEnumType({\n name: graphqlTypeName(name),\n values: Object.fromEntries(enumType.values.map((v) => [v, { value: v }])),\n })\n )\n } else {\n process.stderr.write(\n `⚠ enum '${name}' has values that aren't valid GraphQL identifiers; exposing as String\\n`\n )\n }\n }\n\n // Output type for one field (scalar, enum, relation, media, programmatic).\n function outputTypeForField(field: ParsedField, required: boolean): GraphQLOutputType {\n const scalar = scalarOutputType(field.field_type)\n if (scalar) return required ? new GraphQLNonNull(scalar) : scalar\n\n if (field.field_type === 'enum') {\n const ref = field.ui_component.component === 'select' ? field.ui_component.enum_ref : undefined\n const et = ref ? enumTypes.get(ref) : undefined\n const t = et ?? GraphQLString\n return required ? new GraphQLNonNull(t) : t\n }\n\n if (field.field_type === 'image' || field.field_type === 'video' || field.field_type === 'file') {\n return MEDIA\n }\n\n if (field.field_type === 'paragraph') {\n const ref = field.ui_component.component === 'paragraph-embed' ? field.ui_component.ref : undefined\n const target = ref ? objectTypes.get(ref) : undefined\n if (ref && !target) {\n process.stderr.write(\n `⚠ paragraph field '${field.name}' targets unknown paragraph type '${ref}'; exposing as Media\\n`\n )\n }\n return new GraphQLList(new GraphQLNonNull(target ?? MEDIA))\n }\n\n if (field.field_type === 'reference') {\n const ref = field.ui_component.component === 'typeahead-select' ? field.ui_component.ref : undefined\n const target = ref ? objectTypes.get(ref) : undefined\n if (ref && !target) {\n process.stderr.write(`⚠ reference field '${field.name}' targets unknown type '${ref}'; exposing as Media\\n`)\n }\n const rel = field.ui_component.component === 'typeahead-select' ? field.ui_component.rel : undefined\n const isMany = rel === 'many-to-many' || rel === 'one-to-many'\n const t = (target ?? MEDIA) as GraphQLObjectType\n return isMany ? new GraphQLList(new GraphQLNonNull(t)) : t\n }\n\n return GraphQLString\n }\n\n /**\n * A type's fields minus its tombstones.\n *\n * A tombstone (`removed: true`) is column-backed — the parser keeps its\n * column alive for older live versions — but the version being served must\n * never expose it. REST drops it via the FieldKeyMap's drop-set, which\n * `remap` applies; GraphQL resolves per field by column through\n * `resolveFieldValue` and never touches that map, so the exclusion has to\n * happen where the schema is BUILT. A field absent from the schema is a\n * validation error, so the retained value cannot reach a response at all.\n */\n function exposedFields(type: { fields: ParsedField[] }): ParsedField[] {\n return type.fields.filter((f) => f.removed !== true)\n }\n\n /**\n * The fields one schema exposes for a type, as ViewFields. With a view, the\n * served version's own list; without one, the registry's minus tombstones.\n */\n function viewFieldsFor(machineName: string, type: { fields: ParsedField[] }): ViewField[] {\n return (\n view?.[machineName]?.fields ??\n exposedFields(type).map((f) => ({ field: f, exposedAs: f.name, required: f.required }))\n )\n }\n\n // Build the object type for a content/taxonomy/paragraph type. Fields are a\n // thunk so relations can reference types created later (circular graphs).\n function buildObjectType(\n machineName: string,\n type: ParsedContentType | ParsedTaxonomyType | ParsedParagraphType\n ): GraphQLObjectType {\n const visible = viewFieldsFor(machineName, type)\n\n // Handed to the programmatic resolvers so they can present the same record\n // shape REST does, where media fields are resolved objects (see resolvers.ts).\n //\n // BOTH names, because that record straddles two key spaces. `name` is the\n // field's REAL name — it goes straight to ctx.loaders.load(typeName, name,\n // row), which looks the field up in the registry, and is therefore the key\n // the resolved object lands on. `exposedAs` is the label this version\n // serves, which is where `ctx.get()` looks for it. A version that renames a\n // media field makes them differ, and passing only one of the two loses the\n // resolved object (see relabelMedia in resolvers.ts).\n const mediaFields: MediaFieldKey[] = visible\n .filter(\n (v) =>\n v.field.field_type === 'image' || v.field.field_type === 'video' || v.field.field_type === 'file'\n )\n .map((v) => ({ name: v.field.name, exposedAs: v.exposedAs }))\n\n return new GraphQLObjectType({\n name: graphqlTypeName(machineName),\n fields: () => {\n const fields: GraphQLFieldConfigMap<Record<string, unknown>, GraphQLContext> = {}\n // System fields.\n fields['id'] = { type: new GraphQLNonNull(GraphQLID), resolve: (p) => p['id'] }\n if (type.schema_type !== 'paragraph-type') {\n fields['published'] = { type: new GraphQLNonNull(GraphQLBoolean), resolve: (p) => p['published'] }\n }\n if (type.schema_type === 'content-type') {\n fields['slug'] = { type: new GraphQLNonNull(GraphQLString), resolve: (p) => p['slug'] }\n }\n fields['createdAt'] = { type: new GraphQLNonNull(DateTimeScalar), resolve: (p) => p['created_at'] }\n fields['updatedAt'] = { type: new GraphQLNonNull(DateTimeScalar), resolve: (p) => p['updated_at'] }\n\n for (const vf of visible) {\n const field = vf.field\n const gqlName = toCamelCase(vf.exposedAs)\n const outType = outputTypeForField(field, vf.required)\n let resolve: GraphQLFieldConfig<Record<string, unknown>, GraphQLContext>['resolve']\n if (field.field_type === 'programmatic') {\n resolve = programmaticFieldResolver(machineName, field.name, mediaFields, fieldKeyMaps[machineName])\n } else if (\n field.field_type === 'reference' ||\n field.field_type === 'paragraph' ||\n field.field_type === 'image' ||\n field.field_type === 'video' ||\n field.field_type === 'file'\n ) {\n resolve = relationFieldResolver(machineName, field.name)\n } else {\n resolve = scalarFieldResolver(field, vf.fallback)\n }\n fields[gqlName] = {\n type: outType,\n resolve,\n // Conditional spread, not `deprecationReason: vf.deprecationReason`\n // — exactOptionalPropertyTypes forbids present-and-undefined.\n ...(vf.deprecationReason !== undefined && { deprecationReason: vf.deprecationReason }),\n }\n }\n return fields\n },\n })\n }\n\n // Pass 1: create every object type (empty-safe thunks resolve later).\n // Track GraphQL type name → machine name so two schemas that collapse to the\n // same GraphQL type name (e.g. content--author / taxonomy--author both → \"Author\")\n // fail loud here instead of surfacing as a cryptic GraphQLSchema build error.\n const typeNameOwners = new Map<string, string>() // graphqlTypeName → machineName\n function registerTypeName(machineName: string): void {\n const gqlName = graphqlTypeName(machineName)\n const owner = typeNameOwners.get(gqlName)\n if (owner !== undefined && owner !== machineName) {\n throw new Error(\n `GraphQL type name collision: '${gqlName}' is produced by both '${owner}' and '${machineName}'. ` +\n `Rename one schema's machine name segment so their GraphQL type names differ.`\n )\n }\n typeNameOwners.set(gqlName, machineName)\n }\n\n for (const [name, ct] of Object.entries(registry.content_types)) {\n registerTypeName(name)\n objectTypes.set(name, buildObjectType(name, ct))\n }\n for (const [name, tt] of Object.entries(registry.taxonomy_types)) {\n registerTypeName(name)\n objectTypes.set(name, buildObjectType(name, tt))\n }\n for (const [name, pt] of Object.entries(registry.paragraph_types)) {\n registerTypeName(name)\n objectTypes.set(name, buildObjectType(name, pt))\n }\n\n // Pass 2: build the root Query type.\n const queryFields: GraphQLFieldConfigMap<unknown, GraphQLContext> = {}\n\n for (const [name, ct] of Object.entries(registry.content_types) as [string, ParsedContentType][]) {\n const objType = objectTypes.get(name)!\n const visible = viewFieldsFor(name, ct)\n // The version's own labels: translateFilters maps a GraphQL name back to a\n // label, and columnFor then takes that label to the storage column.\n const nameMap = buildFieldNameMap(visible.map((v) => v.exposedAs))\n\n if (ct.only_one) {\n queryFields[singleQueryName(name)] = { type: objType, resolve: singletonResolver(name) }\n continue\n }\n\n const listType = new GraphQLObjectType({\n name: `${graphqlTypeName(name)}List`,\n fields: {\n data: { type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(objType))), resolve: (r) => r.data },\n meta: { type: new GraphQLNonNull(PAGE_META), resolve: (r) => r.meta },\n },\n })\n const filterType = buildFilterInputType(ct, visible)\n\n queryFields[collectionQueryName(name)] = {\n type: new GraphQLNonNull(listType),\n args: {\n page: { type: GraphQLInt },\n perPage: { type: GraphQLInt },\n sortBy: { type: buildSortFieldEnum(graphqlTypeName(name), fieldKeyMaps[name]) },\n sortOrder: { type: SortOrderEnum },\n ...(filterType ? { filter: { type: filterType } } : {}),\n },\n resolve: collectionResolver(name, nameMap, fieldKeyMaps[name]),\n }\n queryFields[singleQueryName(name)] = {\n type: objType,\n args: { slug: { type: new GraphQLNonNull(GraphQLString) } },\n resolve: singleBySlugResolver(name),\n }\n }\n\n for (const [name, tt] of Object.entries(registry.taxonomy_types) as [string, ParsedTaxonomyType][]) {\n const objType = objectTypes.get(name)!\n const listType = new GraphQLObjectType({\n name: `${graphqlTypeName(name)}List`,\n fields: {\n data: { type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(objType))), resolve: (r) => r.data },\n meta: { type: new GraphQLNonNull(PAGE_META), resolve: (r) => r.meta },\n },\n })\n queryFields[collectionQueryName(name)] = {\n type: new GraphQLNonNull(listType),\n args: { page: { type: GraphQLInt }, perPage: { type: GraphQLInt } },\n resolve: collectionResolver(\n name,\n buildFieldNameMap(viewFieldsFor(name, tt).map((v) => v.exposedAs)),\n fieldKeyMaps[name]\n ),\n }\n queryFields[singleQueryName(name)] = {\n type: objType,\n args: { id: { type: new GraphQLNonNull(GraphQLID) } },\n resolve: taxonomySingleResolver(name),\n }\n }\n\n return new GraphQLSchema({\n query: new GraphQLObjectType({ name: 'Query', fields: queryFields }),\n })\n}\n","import type { ParsedField, SchemaRegistry, VersionProjection } from '@bobbykim/manguito-cms-core'\nimport { isColumnBacked } from '../field-keys.js'\n\n// ─── One live version's view of the schema ────────────────────────────────────\n//\n// A VersionProjection says WHICH columns a version exposes, under WHICH labels,\n// and whether each is required. It deliberately says nothing about a field's\n// type: `field_type` and `ui_component` are recovered from CURRENT's field for\n// the same column instead.\n//\n// That join is sound, not convenient. `VERSION_COLUMN_MISSING` guarantees every\n// column a live version exposes still exists in current, and\n// `FIELD_TYPE_CHANGED_WHILE_LIVE` guarantees its type has not moved under it —\n// so current's field is a faithful source for everything except the label and\n// the requiredness, which the projection carries precisely because they DO\n// move. See the 2e design, \"Deriving a version's types\".\n\nexport type ViewField = {\n /** Current's field for this column — the source of `field_type` and `ui_component`. */\n field: ParsedField\n /** The name THIS version exposes the column under. */\n exposedAs: string\n /** THIS version's requiredness. Never current's — see VersionProjection. */\n required: boolean\n /**\n * The value to serve when the column holds null, for a column that stopped\n * being written. REST substitutes this in `projectRow`; GraphQL has no\n * route-level projection, so its scalar resolver has to do it or the two\n * surfaces disagree about the same version's contract.\n */\n fallback?: unknown\n /** Present only when this version's contract differs from current's. */\n deprecationReason?: string\n}\n\n/** Keyed by machine name, for content, taxonomy AND paragraph types. */\nexport type VersionView = Record<string, { fields: ViewField[] }>\n\n/** A type's column-backed fields keyed by column. Tombstones included — they hold a real column. */\nfunction byColumn(fields: ParsedField[]): Map<string, ParsedField> {\n const out = new Map<string, ParsedField>()\n for (const f of fields) {\n if (!isColumnBacked(f)) continue\n out.set(f.db_column.column_name, f)\n }\n return out\n}\n\n/**\n * Why this version's contract differs from current's, or undefined when it\n * does not. Keyed by COLUMN, because the label is exactly what may have moved.\n *\n * The reason names CURRENT, not the intermediate version where the change\n * happened: pinpointing that would mean walking every snapshot in between, and\n * \"what it is called now\" is the actionable fact for someone deciding whether\n * to upgrade.\n */\nfunction deprecationFor(\n field: { column_name: string; exposed_as: string },\n currentLabels: Map<string, string>,\n currentVersion: string\n): string | undefined {\n const currentLabel = currentLabels.get(field.column_name)\n if (currentLabel === undefined) {\n return `Removed in ${currentVersion}; column retained while this version is live.`\n }\n if (currentLabel !== field.exposed_as) {\n return `Renamed to '${currentLabel}' in ${currentVersion}.`\n }\n return undefined\n}\n\n/**\n * `projection` undefined means this version has no projection for a type — or\n * none at all. Those types fall back to the registry's own fields minus\n * tombstones, which is the inherited 2d boundary: paragraph types are never\n * projected, so nested paragraph content follows current's shape on every\n * version, by design.\n */\nexport function buildVersionView(input: {\n registry: SchemaRegistry\n projection: VersionProjection | undefined\n currentProjection: VersionProjection | undefined\n currentVersion: string\n}): VersionView {\n const { registry, projection, currentProjection, currentVersion } = input\n const out: VersionView = {}\n\n const sources: Array<Record<string, { fields: ParsedField[] }>> = [\n registry.content_types,\n registry.taxonomy_types,\n registry.paragraph_types,\n ]\n\n for (const source of sources) {\n for (const [typeName, type] of Object.entries(source)) {\n const projectionType = projection?.types[typeName]\n\n // No projection for this type: follow current, minus tombstones.\n if (projectionType === undefined) {\n out[typeName] = {\n fields: type.fields\n .filter((f) => f.removed !== true)\n .map((f) => ({ field: f, exposedAs: f.name, required: f.required })),\n }\n continue\n }\n\n const columns = byColumn(type.fields)\n const currentLabels = new Map<string, string>()\n for (const f of currentProjection?.types[typeName]?.fields ?? []) {\n currentLabels.set(f.column_name, f.exposed_as)\n }\n\n const fields: ViewField[] = []\n\n for (const pf of projectionType.fields) {\n const field = columns.get(pf.column_name)\n // Unreachable on a model that loaded — VERSION_COLUMN_MISSING rejects\n // it. Skipped rather than thrown so a hand-built baked model cannot\n // take the whole schema down over one field.\n if (field === undefined) continue\n const reason = deprecationFor(pf, currentLabels, currentVersion)\n fields.push({\n field,\n exposedAs: pf.exposed_as,\n required: pf.required,\n ...(pf.fallback !== undefined && { fallback: pf.fallback }),\n ...(reason !== undefined && { deprecationReason: reason }),\n })\n }\n\n // Fields with no column of their own — paragraph, many-to-many,\n // programmatic — never appear in a projection, so they must be appended\n // from the registry or an older version's schema would silently lose\n // every relation and computed field. They follow current on every\n // version (the 2d boundary), hence no deprecation reason.\n for (const f of type.fields) {\n if (isColumnBacked(f) || f.removed === true) continue\n fields.push({ field: f, exposedAs: f.name, required: f.required })\n }\n\n out[typeName] = { fields }\n }\n }\n\n return out\n}\n","import DataLoader from 'dataloader'\nimport type { DrizzlePostgresInstance } from '@bobbykim/manguito-cms-db'\nimport type {\n SchemaRegistry,\n ParsedContentType,\n ParsedTaxonomyType,\n ParsedParagraphType,\n} from '@bobbykim/manguito-cms-core'\nimport { buildRelationsMap, resolveRelationField, type RelationDef } from '../relations.js'\n\nexport interface RelationLoaders {\n load(typeName: string, fieldName: string, parent: Record<string, unknown>): Promise<unknown>\n}\n\ntype ParentRow = Record<string, unknown>\n\n// One RelationLoaders instance per request. Loaders batch sibling parents per\n// (type, field); the shared cache dedupes target lookups across all loaders in\n// the request, mirroring the repository's resolveRows cache.\nexport function createRelationLoaders(\n db: DrizzlePostgresInstance,\n registry: SchemaRegistry\n): RelationLoaders {\n const relMaps = new Map<string, Record<string, RelationDef>>()\n const loaders = new Map<string, DataLoader<ParentRow, unknown>>()\n const cache = new Map<string, unknown>()\n\n function relationsFor(typeName: string): Record<string, RelationDef> {\n let m = relMaps.get(typeName)\n if (!m) {\n const type = (registry.content_types[typeName] ??\n registry.taxonomy_types[typeName] ??\n registry.paragraph_types[typeName]) as\n | ParsedContentType\n | ParsedTaxonomyType\n | ParsedParagraphType\n | undefined\n m = type ? buildRelationsMap(type.fields, registry) : {}\n relMaps.set(typeName, m)\n }\n return m\n }\n\n function loaderFor(typeName: string, fieldName: string, rel: RelationDef): DataLoader<ParentRow, unknown> {\n const key = `${typeName}:${fieldName}`\n let dl = loaders.get(key)\n if (!dl) {\n dl = new DataLoader<ParentRow, unknown>(\n async (parents) => {\n const rows = parents as ParentRow[]\n // GraphQL is public-only (see app.ts) — always filter relation targets\n // to published rows, mirroring the REST public repos' publishedRelations.\n await resolveRelationField(db, rows, fieldName, rel, cache, true)\n return rows.map((r) => r[fieldName])\n },\n // Batch within a tick; do not memoize by parent identity (rows are mutated\n // and may recur across nesting levels).\n { cache: false }\n )\n loaders.set(key, dl)\n }\n return dl\n }\n\n return {\n async load(typeName, fieldName, parent) {\n const rel = relationsFor(typeName)[fieldName]\n if (!rel) return null\n return loaderFor(typeName, fieldName, rel).load(parent)\n },\n }\n}\n","import { EnvelopArmor } from '@escape.tech/graphql-armor'\nimport { NoSchemaIntrospectionCustomRule } from 'graphql'\n\n// Depth + cost + alias + directive limits via GraphQL Armor. Returns { plugins } to\n// spread into Yoga's `plugins` array.\n//\n// Note: the installed `@escape.tech/graphql-armor` (3.2.0) exports `EnvelopArmor`, a\n// class whose `.protect()` returns `{ plugins }` — the standalone `EnvelopArmorPlugin`\n// export is a function that returns a single plugin, not a class with `.protect()`.\nexport function buildArmorPlugin(options: { maxDepth: number; maxComplexity: number }): {\n plugins: unknown[]\n} {\n const armor = new EnvelopArmor({\n maxDepth: { n: options.maxDepth },\n costLimit: { maxCost: options.maxComplexity },\n maxAliases: { n: 15 },\n maxDirectives: { n: 50 },\n blockFieldSuggestion: { enabled: true },\n // maxTokens is intentionally left at Armor's secure default (n=1000) to bound\n // GraphQL document token count at parse time, a layer the depth/cost/alias/directive\n // limits do not cover.\n })\n return armor.protect()\n}\n\n// Disables introspection (and the __schema/__type meta-fields) in production by\n// adding graphql's built-in validation rule when `enabled` is false.\nexport function introspectionPlugin(enabled: boolean): {\n onValidate(payload: { addValidationRule: (rule: unknown) => void }): void\n} {\n return {\n onValidate({ addValidationRule }) {\n if (!enabled) addValidationRule(NoSchemaIntrospectionCustomRule)\n },\n }\n}\n","import { createYoga, maskError } from 'graphql-yoga'\nimport type { Handler } from 'hono'\nimport type { SchemaRegistry, ContentRepository, VersionProjection } from '@bobbykim/manguito-cms-core'\nimport type { DrizzlePostgresInstance } from '@bobbykim/manguito-cms-db'\nimport type { ProgrammaticResolver } from '../programmatic/resolve.js'\nimport type { FieldKeyMap } from '../field-keys.js'\nimport type { GraphQLContext } from './context.js'\nimport { buildGraphQLSchema } from './schema.js'\nimport { buildVersionView, type VersionView } from './version-view.js'\nimport { createRelationLoaders } from './dataloaders.js'\nimport { buildArmorPlugin, introspectionPlugin } from './security.js'\n\nexport type ResolvedGraphQLOptions = {\n enabled: boolean\n maxDepth: number\n maxComplexity: number\n graphiql: boolean\n introspection: boolean\n}\n\n// Yoga decides whether an error is safe to show a client with\n// `error instanceof GraphQLError`. That check is realm-sensitive: GraphQL Armor\n// is CJS and throws a GraphQLError built from graphql's CJS entry, while Yoga is\n// ESM and compares against the class from graphql's ESM entry. Same version, two\n// classes — so `instanceof` fails and a depth/complexity rejection, which is the\n// client's own query being too large, was masked as\n// `INTERNAL_SERVER_ERROR: Unexpected error.` and logged server-side as a fault.\n//\n// Identify by name instead (what envelop's own masking does), walking the\n// originalError chain the same way. A GraphQLError with no originalError was\n// constructed deliberately by a validation rule or by us and carries no internals;\n// anything else — a resolver throwing, a driver error — still goes to Yoga's\n// masking untouched, dev-mode originalError detail included.\nfunction isClientSafeGraphQLError(error: unknown): boolean {\n let current = error as { name?: string; originalError?: unknown } | null | undefined\n while (current != null && typeof current === 'object' && current.name === 'GraphQLError') {\n if (current.originalError == null) return true\n current = current.originalError as typeof current\n }\n return false\n}\n\nexport function createGraphQLHandler(\n registry: SchemaRegistry,\n repos: Record<string, ContentRepository<unknown>>,\n fieldKeyMaps: Record<string, FieldKeyMap>,\n resolver: ProgrammaticResolver,\n db: DrizzlePostgresInstance,\n options: ResolvedGraphQLOptions,\n // The view is built HERE, not in app.ts: the `.` entry must keep no static\n // import of anything under graphql/ (ADR api/0006), so app.ts passes the\n // plain projection data it already imports as types from core.\n versioning: {\n endpoint?: string\n projection?: VersionProjection\n currentProjection?: VersionProjection\n currentVersion?: string\n } = {}\n): Handler {\n let view: VersionView | undefined\n if (versioning.projection !== undefined) {\n if (versioning.currentVersion === undefined) {\n throw new Error(\n 'createGraphQLHandler: currentVersion is required when a projection is supplied — ' +\n 'deprecation reasons name it, and an empty one ships \"Removed in .\" into published SDL.'\n )\n }\n view = buildVersionView({\n registry,\n projection: versioning.projection,\n currentProjection: versioning.currentProjection,\n currentVersion: versioning.currentVersion,\n })\n }\n const schema = buildGraphQLSchema(registry, fieldKeyMaps, view)\n const { plugins } = buildArmorPlugin({\n maxDepth: options.maxDepth,\n maxComplexity: options.maxComplexity,\n })\n\n const yoga = createYoga<Record<string, never>, GraphQLContext>({\n schema,\n graphqlEndpoint: versioning.endpoint ?? '/graphql',\n graphiql: options.graphiql,\n landingPage: false,\n maskedErrors: {\n maskError: (error, message, isDev) =>\n isClientSafeGraphQLError(error) ? (error as Error) : maskError(error, message, isDev),\n },\n // buildArmorPlugin's return type is `unknown[]` (see security.ts) since Armor's\n // plugin shapes aren't part of its public typings; Yoga's `plugins` option only\n // accepts its own `Plugin` type, so the cast bridges the two.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n plugins: [...(plugins as any[]), introspectionPlugin(options.introspection)],\n context: (): GraphQLContext => ({\n db,\n registry,\n repos,\n resolver,\n loaders: createRelationLoaders(db, registry),\n programmaticMemo: new WeakMap(),\n }),\n })\n\n return (c) => yoga.fetch(c.req.raw, {})\n}\n","import type {\n APIAdapter,\n ResolvedRateLimitConfig,\n GraphQLModuleConfig,\n ResolvedGraphQLConfig,\n} from '@bobbykim/manguito-cms-core'\nimport { normalizePrefix } from './paths.js'\n\nexport { createCmsApp } from './app.js'\nexport type { CreateCmsAppOptions } from './app.js'\n\nexport { createServer } from './server/node.js'\nexport type { NodeServerOptions } from './server/node.js'\n\nexport { createProgrammaticResolver, validateResolverBindings, resolverKey } from './programmatic/resolve.js'\nexport type { ResolverMap, ProgrammaticResolver } from './programmatic/resolve.js'\n\nexport type { BakedVersionModel } from './versions.js'\n\n// ─── User-facing config factory ───────────────────────────────────────────────\n\nexport type APIAdapterOptions = {\n prefix?: string\n media?: {\n max_file_size?: number\n }\n rateLimit?: ResolvedRateLimitConfig\n graphql?: GraphQLModuleConfig\n}\n\nfunction resolveGraphQL(cfg: GraphQLModuleConfig): ResolvedGraphQLConfig {\n const devDefault = process.env['NODE_ENV'] !== 'production'\n return {\n enabled: cfg.enabled ?? false,\n maxDepth: cfg.maxDepth ?? 8,\n maxComplexity: cfg.maxComplexity ?? 1000,\n graphiql: cfg.graphiql ?? devDefault,\n introspection: cfg.introspection ?? cfg.graphiql ?? devDefault,\n }\n}\n\nexport function createAPIAdapter(options: APIAdapterOptions = {}): APIAdapter {\n const prefix = normalizePrefix(options.prefix)\n const media = options.media?.max_file_size !== undefined\n ? { max_file_size: options.media.max_file_size }\n : undefined\n\n return {\n prefix,\n ...(media !== undefined && { media }),\n ...(options.rateLimit !== undefined && { rateLimit: options.rateLimit }),\n ...(options.graphql !== undefined && { graphql: resolveGraphQL(options.graphql) }),\n }\n}\n","// ─── Public route path construction ───────────────────────────────────────────\n//\n// The single place public route paths are built. Registrators must not hardcode\n// '/api/...': the prefix is configurable (api.prefix), and the schema-driven\n// surface carries a version segment.\n//\n// The two shapes are separate types on purpose. Content and taxonomy are\n// schema-driven and therefore versioned; media and the OpenAPI document are\n// not — MediaItem is a fixed shape the schema never touches, so a version\n// segment would duplicate byte-identical routes per live version. Keeping them\n// in different types makes versioning media UNEXPRESSIBLE rather than merely\n// discouraged.\n\nexport function normalizePrefix(prefix: string | undefined): string {\n if (prefix === undefined) return '/api'\n const withSlash = prefix.startsWith('/') ? prefix : `/${prefix}`\n const trimmed = withSlash.replace(/\\/+$/, '')\n return trimmed === '' ? '/api' : trimmed\n}\n\n/** The fixed public surface. Never versioned. */\nexport type PublicPaths = {\n mediaCollection(): string\n mediaItem(): string\n openapi(): string\n}\n\n/** The schema-driven public surface. Versioned, or unversioned when `version` is null. */\nexport type VersionedPaths = {\n collection(basePath: string): string\n item(basePath: string): string\n taxonomyCollection(typeName: string): string\n taxonomyItem(typeName: string): string\n}\n\nexport function createPublicPaths(prefix: string): PublicPaths {\n return {\n mediaCollection: () => `${prefix}/media`,\n mediaItem: () => `${prefix}/media/:id`,\n openapi: () => `${prefix}/openapi.json`,\n }\n}\n\n/**\n * `version` null means no segment — the unversioned pass, whose paths must be\n * byte-identical to what the app served before versioning existed.\n */\nexport function createVersionedPaths(prefix: string, version: string | null): VersionedPaths {\n const root = version === null ? prefix : `${prefix}/${version}`\n return {\n collection: (basePath) => `${root}/${basePath}`,\n item: (basePath) => `${root}/${basePath}/:slug`,\n taxonomyCollection: (typeName) => `${root}/taxonomy/${typeName}`,\n taxonomyItem: (typeName) => `${root}/taxonomy/${typeName}/:id`,\n }\n}\n","import { Hono } from 'hono'\nimport type { Handler, MiddlewareHandler } from 'hono'\nimport { sql } from 'drizzle-orm'\nimport type { StorageAdapter, SchemaRegistry, ParsedContentType, ParsedTaxonomyType, ParsedParagraphType, ResolvedRateLimitConfig, CorsConfig, ResolvedGraphQLConfig } from '@bobbykim/manguito-cms-core'\nimport type { DrizzlePostgresInstance } from '@bobbykim/manguito-cms-db'\nimport { createCorsMiddleware } from './middleware/cors.js'\nimport { createSecurityHeadersMiddleware } from './middleware/security-headers.js'\nimport { errorHandler } from './middleware/error.js'\nimport { createAuthMiddleware } from './middleware/auth.js'\nimport { mustChangePasswordCheck } from './middleware/must-change-password.js'\nimport { createPermissionMiddleware } from './middleware/permission.js'\nimport { createHierarchyMiddleware } from './middleware/hierarchy.js'\nimport { resolveListRateLimit } from './middleware/rate-limit.js'\nimport { buildRolesRegistry } from './auth/registry.js'\nimport { createProgrammaticResolver, validateResolverBindings, type ResolverMap } from './programmatic/resolve.js'\nimport { registerPublicContentRoutes } from './routes/content.js'\nimport { registerPublicMediaRoutes } from './routes/media.js'\nimport { registerAdminContentRoutes } from './routes/admin/content.js'\nimport { registerAdminMediaRoutes } from './routes/admin/media.js'\nimport { registerAuthRoutes } from './routes/admin/auth.js'\nimport { registerUserRoutes } from './routes/admin/users.js'\nimport { registerConfigRoute } from './routes/admin/config.js'\nimport { registerSchemaRoute } from './routes/admin/schema.js'\nimport { createDrizzleContentRepository } from './repositories/content.js'\nimport { buildRelationsMap } from './relations.js'\nimport { createMediaRepository } from './repositories/media.js'\nimport { createFieldKeyMap, type FieldKeyMap } from './field-keys.js'\nimport { SORTABLE_FIELDS } from './routes/query-params.js'\nimport { buildProjectors, type Projectors } from './projector.js'\nimport { normalizePrefix, createPublicPaths, createVersionedPaths, type VersionedPaths } from './paths.js'\nimport { buildVersionSurface, classifyVersion, deprecationHeaders, type BakedVersionModel } from './versions.js'\n\nexport type CreateCmsAppOptions = {\n /** CMS display name shown in GET /admin/api/config. Defaults to 'Manguito CMS'. */\n name?: string\n prefix?: string\n storage: StorageAdapter\n registry: SchemaRegistry\n db: DrizzlePostgresInstance\n media?: {\n /** Optional upload size cap in bytes. Returned by GET /admin/api/config for client-side UX validation. */\n max_file_size?: number\n }\n rateLimit?: ResolvedRateLimitConfig\n cors?: CorsConfig\n /** Programmatic field resolvers, keyed `${schema}::${field}`. */\n resolvers?: ResolverMap\n /** GraphQL module config (resolved). When enabled, mounts `/graphql` (and one `/graphql/<version>` per live version) via `app.all`. */\n graphql?: ResolvedGraphQLConfig\n /**\n * The baked version model — what `manguito build` writes into\n * .manguito/version-model.ts. When absent, the app behaves exactly as it\n * did before versioning existed: one unversioned pass at the current\n * schema, no version routes, no deprecation headers, no catch-all.\n */\n versions?: BakedVersionModel\n}\n\nexport interface ManguitoCmsAPIAdapter {\n readonly prefix: string\n readonly app: Hono\n}\n\n/**\n * Every exact route path one version's public surface registers — the two\n * meta-list endpoints, one entry per content type (its collection, plus its\n * item route unless `only_one`), and a collection/item pair per taxonomy\n * type. Mirrors `registerPublicContentRoutes`' own enumeration exactly,\n * because the deprecation-header middleware must attach to precisely these\n * paths — never a `${prefix}/*` wildcard, which would also catch\n * `/api/media` where \"pin a version\" is meaningless.\n */\nfunction versionRoutePaths(registry: SchemaRegistry, paths: VersionedPaths): string[] {\n const out: string[] = [paths.collection('content'), paths.collection('taxonomy')]\n for (const ct of Object.values(registry.content_types) as ParsedContentType[]) {\n out.push(paths.collection(ct.default_base_path))\n if (!ct.only_one) out.push(paths.item(ct.default_base_path))\n }\n for (const tt of Object.values(registry.taxonomy_types) as ParsedTaxonomyType[]) {\n out.push(paths.taxonomyCollection(tt.name), paths.taxonomyItem(tt.name))\n }\n return out\n}\n\nconst MISSING_STORAGE_ERROR = `✗ api.storage is required but not configured.\n Add a storage adapter to your manguito.config.ts:\n\n api: createAPIAdapter({\n storage: createLocalAdapter(), // dev\n // storage: createS3Adapter({ bucket: '...', region: '...' }) // production\n })\n\nExiting.`\n\nexport function createCmsApp(options: CreateCmsAppOptions): ManguitoCmsAPIAdapter {\n if (!options.storage) {\n throw new Error(MISSING_STORAGE_ERROR)\n }\n\n const prefix = normalizePrefix(options.prefix)\n const publicPaths = createPublicPaths(prefix)\n // Unversioned paths — used only to document the OpenAPI spec below, which\n // always describes the current schema's own shape regardless of how many\n // versions are live. The actual public routes are registered per version\n // further down (one pass per live version, plus one unversioned pass).\n const versionedPaths = createVersionedPaths(prefix, null)\n const { storage, registry, db, rateLimit, media, cors } = options\n const cmsName = options.name ?? 'Manguito CMS'\n const maxFileSize = media?.max_file_size\n\n // Build roles registry — throws immediately if roles are missing or invalid.\n // The server must not start with a broken registry.\n const rolesRegistry = buildRolesRegistry(registry.roles.roles)\n\n // Validate programmatic-field bindings at startup (throws on mismatch, like\n // the storage/roles checks above). Undefined resolvers ⇒ empty map.\n const resolverMap = options.resolvers ?? new Map()\n validateResolverBindings(registry, resolverMap)\n const programmaticResolver = createProgrammaticResolver(resolverMap)\n\n const app = new Hono()\n\n // Security headers for all routes — registered before CORS so normal and\n // CORS-handled responses carry the conservative defaults. Responses produced\n // by app.onError (thrown errors) bypass this middleware's post-next step;\n // those are JSON error envelopes with no clickjacking/XSS surface. CSP\n // connect-src is derived from the storage adapter: presigned uploads go\n // browser→storage directly, so that host must be allowlisted.\n // The GraphiQL explorer (dev-only by default) boots from a CDN bundle plus\n // inline scripts, which the strict script-src blocks — so when it is enabled\n // the /graphql subtree (the unversioned endpoint plus every /graphql/vN\n // mount below) gets a relaxed CSP (ADR api/0010).\n const uploadOrigins = storage.getUploadOrigins?.() ?? []\n const graphiqlEnabled = options.graphql?.enabled === true && options.graphql.graphiql === true\n app.use(\n '*',\n createSecurityHeadersMiddleware({\n connectSrc: uploadOrigins,\n ...(graphiqlEnabled && { graphiqlPath: '/graphql' }),\n }),\n )\n\n // CORS for all routes\n app.use('*', createCorsMiddleware(cors ?? { origin: '*', enabled: true }))\n app.onError(errorHandler)\n\n // Rate limiter for public list endpoints — threaded into route registrators,\n // applied only to paginated collection routes (not single-item lookups).\n // `undefined` when disabled via rateLimit.findAll === '*'.\n const listRateLimit = resolveListRateLimit(rateLimit)\n\n // ── Middleware factories — all close over rolesRegistry built once at startup ──\n\n const authMiddleware = createAuthMiddleware(db)\n const requirePermission = createPermissionMiddleware(rolesRegistry)\n\n const getUserRole = async (userId: string): Promise<string | null> => {\n const result = await db.execute(\n sql`SELECT r.name AS role\n FROM users u\n JOIN roles r ON r.id = u.role_id\n WHERE u.id = ${userId}\n LIMIT 1`\n )\n return (result.rows[0] as { role: string } | undefined)?.role ?? null\n }\n\n const requireHierarchy = createHierarchyMiddleware(rolesRegistry, getUserRole)\n\n // ── Field key maps ──────────────────────────────────────────────────────────\n //\n // One per content, taxonomy AND paragraph type, built once at startup, keyed\n // by machine name. Throws on a label / column collision — the server must not\n // boot with an ambiguous mapping.\n //\n // Paragraph types belong in the same object as the other two, not a sidecar:\n // both consumers index it by machine name over a key space that includes\n // them. `buildProjectors` walks `registry.paragraph_types` to project\n // paragraph children, and `graphql/schema.ts`'s `buildObjectType` runs over\n // paragraph types too — so a programmatic field on a paragraph type needs its\n // map here to be handed a label-keyed record, exactly like one on a content\n // type. Every other GraphQL field resolves per field by column and is\n // indifferent to this map.\n const fieldKeyMaps: Record<string, FieldKeyMap> = Object.fromEntries([\n ...Object.entries(registry.content_types).map(([typeName, ct]) => [\n typeName,\n createFieldKeyMap((ct as ParsedContentType).fields),\n ]),\n ...Object.entries(registry.taxonomy_types).map(([typeName, tt]) => [\n typeName,\n createFieldKeyMap((tt as ParsedTaxonomyType).fields),\n ]),\n ...Object.entries(registry.paragraph_types).map(([typeName, pt]) => [\n typeName,\n createFieldKeyMap((pt as ParsedParagraphType).fields),\n ]),\n ])\n\n // Recursive outbound projection. Every read response is projected through\n // this rather than through a bare toLabels.\n const projectors: Projectors = buildProjectors(registry, fieldKeyMaps)\n\n // Maps SORTABLE_FIELDS' labels to this type's storage columns, so the\n // repository can validate sort_by against columns once the route has mapped\n // it. Identity for a schema where no field declares its own `column:` — but\n // a field can already declare one (this branch's own fixtures rely on it),\n // so this map can diverge even without schema versioning being involved.\n //\n // A label absent from this map is EXCLUDED, not passed through as a bogus\n // literal: `columnFor` only returns undefined for a label this type has no\n // field for at all (SORTABLE_FIELDS is shared across every type), or a\n // system field (id/slug/created_at/...), which is not part of the\n // field-key map but is still legitimately sortable — its column IS its own\n // name. Anything else unresolved has no business in the repository's\n // sortable-columns allow-set. Mirrors versions.ts's per-version copy of\n // this same function exactly — kept duplicated rather than shared, since\n // this one always reads the top-level (current-only) `fieldKeyMaps`.\n const sortableColumnsFor = (typeName: string): Set<string> => {\n const map = fieldKeyMaps[typeName]!\n const type = registry.content_types[typeName] ?? registry.taxonomy_types[typeName]\n const systemFieldNames = new Set((type?.system_fields ?? []).map((f) => f.name))\n const out = new Set<string>()\n for (const label of SORTABLE_FIELDS) {\n const column = map.columnFor(label)\n if (column !== undefined) out.add(column)\n else if (systemFieldNames.has(label)) out.add(label)\n }\n return out\n }\n\n // ── Repositories ──────────────────────────────────────────────────────────────\n\n const contentRepos = Object.fromEntries(\n Object.entries(registry.content_types).map(([typeName, ct]) => [\n typeName,\n createDrizzleContentRepository(db, (ct as ParsedContentType).db.table_name, {\n sortableColumns: sortableColumnsFor(typeName),\n }),\n ])\n )\n const taxonomyRepos = Object.fromEntries(\n Object.entries(registry.taxonomy_types).map(([typeName, tt]) => [\n typeName,\n createDrizzleContentRepository(db, (tt as ParsedTaxonomyType).db.table_name, {\n sortableColumns: sortableColumnsFor(typeName),\n }),\n ])\n )\n const repos = { ...contentRepos, ...taxonomyRepos }\n const mediaRepo = createMediaRepository(db)\n\n // ── Public repo factory ──────────────────────────────────────────────────────\n //\n // Relation resolution (?include=, always-resolved media) is wired only for\n // the public read API. Admin routes reuse `repos` above and must keep\n // receiving raw foreign-key IDs back from findOne/update — the edit forms\n // (e.g. MediaUpload.vue) expect a plain ID string, not a resolved object.\n //\n // Passed as `makeRepo` into `buildVersionSurface` below — once per live\n // version, plus once more for the unversioned pass — so every version of\n // the public surface keeps this same relation-resolution distinction.\n // Relations are keyed by field name and derived from the registry's own\n // (current) fields, which describe STRUCTURE (foreign keys, junction\n // tables) rather than a version's exposed labels — nested/related content\n // follows current's shape on every version, by design, same as paragraph\n // fields (see versions.ts). Only the field-key mapping varies per version.\n const makeRepo = (typeName: string, tableName: string, sortableColumns: Set<string>) => {\n const type = registry.content_types[typeName] ?? registry.taxonomy_types[typeName]\n return createDrizzleContentRepository(db, tableName, {\n relations: buildRelationsMap(type!.fields, registry),\n publishedRelations: true,\n sortableColumns,\n })\n }\n\n // ── Auth routes registered directly on app BEFORE the blanket use() calls ───────\n //\n // registerAuthRoutes uses full paths (/admin/api/auth/login etc.). Mounting via\n // app.route('/admin/api/auth', subRouter) would strip that prefix and make the\n // routes unreachable, so we register directly on app.\n //\n // In Hono, handlers registered before a use() call are reached first. A route\n // handler that returns a Response does not call next(), so the blanket\n // authMiddleware registered below never runs for matched auth paths.\n registerAuthRoutes(app, db)\n\n // ── Blanket middleware for all /admin/api/* (registered after auth routes) ─────\n app.use('/admin/api/*', authMiddleware)\n app.use('/admin/api/*', mustChangePasswordCheck)\n\n // ── OpenAPI spec endpoints ────────────────────────────────────────────────────\n\n app.get(publicPaths.openapi(), (c) => {\n const paths: Record<string, unknown> = {}\n\n for (const ct of Object.values(registry.content_types) as ParsedContentType[]) {\n const base = ct.default_base_path\n if (ct.only_one) {\n paths[versionedPaths.collection(base)] = { get: { summary: `Get ${ct.label}`, tags: [ct.label] } }\n } else {\n paths[versionedPaths.collection(base)] = { get: { summary: `List published ${ct.label}`, tags: [ct.label] } }\n paths[versionedPaths.item(base).replace(':slug', '{slug}')] = {\n get: { summary: `Get ${ct.label} by slug`, tags: [ct.label] },\n }\n }\n }\n\n for (const tt of Object.values(registry.taxonomy_types) as ParsedTaxonomyType[]) {\n paths[versionedPaths.taxonomyCollection(tt.name)] = { get: { summary: `List published ${tt.label}`, tags: [tt.label] } }\n paths[versionedPaths.taxonomyItem(tt.name).replace(':id', '{id}')] = {\n get: { summary: `Get ${tt.label} by id`, tags: [tt.label] },\n }\n }\n\n paths[publicPaths.mediaCollection()] = { get: { summary: 'List media items', tags: ['Media'] } }\n paths[publicPaths.mediaItem().replace(':id', '{id}')] = { get: { summary: 'Get media item by id', tags: ['Media'] } }\n\n return c.json({\n openapi: '3.0.3',\n info: { title: 'Manguito CMS Public API', version: '1.0.0' },\n paths,\n })\n })\n // Admin spec — auth covered by the blanket use() above\n app.get('/admin/api/openapi.json', (c) =>\n c.json({\n openapi: '3.0.3',\n info: { title: 'Manguito CMS Admin API', version: '1.0.0' },\n paths: {},\n })\n )\n\n // ── Public routes ─────────────────────────────────────────────────────────────\n //\n // A project that never cut a version gets the identity model, so the\n // unversioned pass below is the only registration and behaviour is\n // unchanged from before versioning existed.\n const model: BakedVersionModel = options.versions ?? {\n current: 'v1',\n live: ['v1'],\n projections: {},\n }\n\n // One pass per live version, then one unversioned pass at the latest.\n const passes: Array<{ version: string | null; projectionVersion: string }> = [\n ...(options.versions !== undefined\n ? model.live.map((v) => ({ version: v, projectionVersion: v }))\n : []),\n { version: null, projectionVersion: model.current },\n ]\n\n for (const pass of passes) {\n const surface = buildVersionSurface({ ...pass, prefix, registry, model, makeRepo })\n const headers = deprecationHeaders({\n requested: pass.version,\n model,\n // The version root, with no trailing slash — never\n // createVersionedPaths(prefix, model.current).collection(''), which\n // would yield `${prefix}/${model.current}/` in every Link header.\n successor: `${prefix}/${model.current}`,\n })\n // Header middleware registers on this pass's exact paths — never a\n // ${prefix}/* wildcard, which would also catch /api/media where \"pin a\n // version\" is meaningless, and would need a guard kept in sync with the\n // catch-all's.\n if (headers !== null) {\n const attachDeprecationHeaders: MiddlewareHandler = async (c, next) => {\n await next()\n for (const [key, value] of Object.entries(headers)) c.res.headers.set(key, value)\n }\n for (const path of versionRoutePaths(registry, surface.paths)) {\n app.use(path, attachDeprecationHeaders)\n }\n }\n registerPublicContentRoutes(app, registry, surface.repos, surface.projectors, surface.paths, listRateLimit, programmaticResolver)\n }\n\n // Registered last. Only ever reached by a request that matched no live\n // version's routes. `not-a-version` falls through, which is what protects\n // /api/media/:id — order-independent rather than dependent on registration\n // sequence.\n //\n // That order-independence is specifically about a SEGMENT that merely\n // isn't version-shaped, like 'media'. A content type whose own\n // `default_base_path` happens to LOOK version-shaped (e.g. a base path of\n // 'v2') is protected by registration order instead: every pass above\n // (every live version's, plus the unversioned one) registers that type's\n // concrete routes before this catch-all is ever added, and Hono matches in\n // registration order — so a request for that path is already answered\n // long before `classifyVersion` would get a chance to misread its first\n // segment as a version number.\n if (options.versions !== undefined) {\n app.all(`${prefix}/:version/*`, async (c, next) => {\n const kind = classifyVersion(c.req.param('version'), model)\n if (kind === 'not-a-version' || kind === 'live') return next()\n const live = model.live.join(', ')\n return kind === 'retired'\n ? c.json(\n {\n ok: false,\n error: {\n code: 'VERSION_RETIRED',\n message: `Version ${c.req.param('version')} is no longer served. Live versions: ${live}.`,\n },\n },\n 410\n )\n : c.json(\n {\n ok: false,\n error: {\n code: 'VERSION_NOT_FOUND',\n message: `No version ${c.req.param('version')} is served. Live versions: ${live}.`,\n },\n },\n 404\n )\n })\n }\n\n registerPublicMediaRoutes(app, mediaRepo, publicPaths, listRateLimit)\n\n // ── GraphQL (opt-in) ──────────────────────────────────────────────────────────\n //\n // Loaded via a DYNAMIC import so `graphql`/`graphql-yoga` never load unless a\n // consumer opts in — the `.` entry (this file) must stay free of a static\n // dependency on the graphql/ subpath (ADR api/0006). The import kicks off\n // immediately (schema-building and Armor-plugin setup are synchronous once the\n // module is loaded; only the `import()` itself is async), and any request that\n // lands on /graphql before it resolves awaits the same in-flight promise —\n // there's a single shared `ready` promise, never a re-import per request.\n // Unauthenticated by design: it sits alongside /api/*, not behind the\n // /admin/api/* auth middleware registered above, and only ever reads through\n // `makeGraphqlRepo` (published-only, same as the REST public routes).\n //\n // `buildGraphQLSchema` can throw synchronously (e.g. a GraphQL type-name\n // collision between a content type and a taxonomy type) for any ONE\n // version's schema. Each version below builds inside its own try/catch, so\n // that throw is contained to that version's mount returning a 500 — it\n // does not take any other live version's endpoint down with it, and does\n // NOT reject the shared `ready` promise (the try/catch swallows it first).\n // The outer `.catch` attached to `ready` covers a narrower failure: the\n // dynamic `import()` itself rejecting (e.g. the graphql/ module failing to\n // load at all) — a case no per-version try/catch can reach, since it\n // happens before any version's schema-building code even runs. Either way\n // the rejection is HANDLED here: an unhandled one would otherwise crash the\n // entire Node process at startup, taking down all REST routes with it,\n // regardless of whether any client ever hits /graphql.\n if (options.graphql?.enabled) {\n const gqlOptions = options.graphql\n\n // GraphQL resolves relations lazily, per selected field, through its own\n // request-scoped dataloaders — so its repos must NOT resolve relations\n // eagerly the way `makeRepo` does. Reusing that factory is actively wrong:\n // its eager pass overwrites a media field's column (the FK column and the\n // field share a name) with the resolved media object, and the dataloader\n // then reads that object back where a UUID is expected.\n //\n // Still public reads: every GraphQL resolver passes `published_only: true`\n // and the dataloaders filter relation targets by published (ADR api/0002).\n // Kept a separate factory from the admin `repos` so a future change there\n // can never silently widen the public surface.\n //\n // They do take `sortableColumns`, like every other repo here: the GraphQL\n // sort enum's values are a schema field's LABELS, which `collectionResolver`\n // maps to columns before calling findMany — and this commit makes that\n // mapping per-version, since `sortableColumnsFor` (via `buildVersionSurface`)\n // now reads each version's OWN field-key map, not just current's.\n const makeGraphqlRepo = (_typeName: string, tableName: string, sortableColumns: Set<string>) =>\n createDrizzleContentRepository(db, tableName, { sortableColumns })\n\n // The same pass list the REST loop uses: one per live version when\n // versioning is configured, then the unversioned pass at current.\n const gqlPasses: Array<{ version: string | null; projectionVersion: string }> = [\n ...(options.versions !== undefined\n ? model.live.map((v) => ({ version: v, projectionVersion: v }))\n : []),\n { version: null, projectionVersion: model.current },\n ]\n\n type GqlMount = { path: string; version: string | null; handler: Handler | null; error: unknown }\n const mounts: GqlMount[] = gqlPasses.map((pass) => ({\n path: pass.version === null ? '/graphql' : `/graphql/${pass.version}`,\n version: pass.version,\n handler: null,\n error: null,\n }))\n\n // Loaded via a DYNAMIC import so `graphql`/`graphql-yoga` never load unless\n // a consumer opts in — the `.` entry must stay free of a static dependency\n // on the graphql/ subpath (ADR api/0006). One shared `ready` promise, never\n // a re-import per request.\n const ready = import('./graphql/handler.js')\n .then(({ createGraphQLHandler }) => {\n gqlPasses.forEach((pass, i) => {\n const mount = mounts[i]!\n // Each version builds inside its OWN try/catch. A synchronous throw\n // from one version's schema (e.g. a type-name collision) must not\n // reject the shared promise and take every other version's endpoint\n // down with it.\n try {\n const surface = buildVersionSurface<unknown>({\n ...pass,\n prefix,\n registry,\n model,\n makeRepo: makeGraphqlRepo,\n })\n // Hoisted into locals and spread under their own guards.\n // `noUncheckedIndexedAccess` makes each index\n // `VersionProjection | undefined`, and\n // `exactOptionalPropertyTypes` then rejects assigning that to an\n // optional property — a present-and-undefined key. This is also\n // why no `options.versions` check is needed: with versioning off\n // `model.projections` is `{}`, so both are absent and the handler\n // builds from the registry, exactly the pre-versioning behaviour.\n const projection = model.projections[pass.projectionVersion]\n const currentProjection = model.projections[model.current]\n\n mount.handler = createGraphQLHandler(\n registry,\n surface.repos,\n surface.fieldKeyMaps,\n programmaticResolver,\n db,\n gqlOptions,\n {\n endpoint: mount.path,\n currentVersion: model.current,\n ...(projection !== undefined && { projection }),\n ...(currentProjection !== undefined && { currentProjection }),\n }\n )\n } catch (err) {\n mount.error = err\n const message = err instanceof Error ? err.message : String(err)\n process.stderr.write(\n `✗ GraphQL schema failed to initialize for ${mount.path}; it will return 500. ${message}\\n`\n )\n }\n })\n })\n .catch((err: unknown) => {\n // The import itself failed, so nothing can serve. Caught so the\n // rejection is HANDLED: an unhandled one would crash the whole process\n // at startup, taking every REST route with it.\n for (const m of mounts) m.error = err\n const message = err instanceof Error ? err.message : String(err)\n const paths = mounts.map((m) => m.path).join(', ')\n process.stderr.write(`✗ GraphQL module failed to load; ${paths} will return 500. ${message}\\n`)\n })\n\n const invokeFor =\n (mount: GqlMount): Handler =>\n async (c) => {\n if (!mount.handler && !mount.error) await ready\n if (!mount.handler) {\n return c.json(\n { ok: false, error: { code: 'GRAPHQL_INIT_FAILED', message: 'GraphQL schema failed to initialize' } },\n 500\n )\n }\n return mount.handler(c, async () => {})\n }\n\n // Reuses the REST helper, so the two surfaces stay diagnosable the same\n // way — including its decision not to nag a project that never opted into\n // versioning. Registered BEFORE the route handlers below: Hono matches in\n // registration order and a handler terminates the chain, so middleware\n // added afterwards would never run.\n for (const mount of mounts) {\n const headers = deprecationHeaders({\n requested: mount.version,\n model,\n successor: `/graphql/${model.current}`,\n })\n if (headers === null) continue\n const attach: MiddlewareHandler = async (c, next) => {\n await next()\n for (const [key, value] of Object.entries(headers)) c.res.headers.set(key, value)\n }\n app.use(mount.path, attach)\n }\n\n // Concrete paths registered before the catch-all below, but unlike the\n // REST surface this ordering is NOT load-bearing here: the catch-all\n // `return next()`s for any live version, so Hono falls through to the\n // mount handler regardless of which was registered first (confirmed by\n // moving the catch-all before this loop — every test stayed green). What\n // IS load-bearing, and is tested, is the deprecation-header middleware's\n // ordering above: it must run before the mount handlers so it can attach\n // headers to the response they produce.\n for (const mount of mounts) {\n if (listRateLimit) app.all(mount.path, listRateLimit, invokeFor(mount))\n else app.all(mount.path, invokeFor(mount))\n }\n\n // Registered last, so it is only reached by a request that matched no live\n // version's endpoint above. `not-a-version` falls through, which keeps the\n // guard from claiming any non-version segment under /graphql.\n if (options.versions !== undefined) {\n app.all('/graphql/:version', async (c, next) => {\n const segment = c.req.param('version')\n const kind = classifyVersion(segment, model)\n if (kind === 'not-a-version' || kind === 'live') return next()\n\n const live = model.live.join(', ')\n // GraphQL's own error shape, not the { ok, error } envelope: the caller\n // is a GraphQL client, and Apollo or urql handed a body they do not\n // recognise reports \"unexpected response\" — hiding the one message a\n // pinned consumer actually needs to read.\n //\n // Status 200, unlike REST's 410/404 for the same conditions. A\n // GraphQL client surfaces `errors` from a 200 as readable GraphQL\n // errors but a 4xx as an opaque network error, which would defeat the\n // point of choosing this shape. The divergence is deliberate and\n // recorded in the 2e design's residuals.\n return c.json({\n errors: [\n {\n message:\n kind === 'retired'\n ? `Version ${segment} is no longer served. Live versions: ${live}.`\n : `Version ${segment} does not exist. Live versions: ${live}.`,\n extensions: {\n code: kind === 'retired' ? 'VERSION_RETIRED' : 'VERSION_UNKNOWN',\n current: model.current,\n live: model.live,\n },\n },\n ],\n })\n })\n }\n }\n\n // ── Admin routes — all registered AFTER the blanket use() so auth middleware\n // runs before every handler. registerConfigRoute is called first so its\n // GET /admin/api/config handler wins over the stale stub in content.ts.\n registerConfigRoute(\n app,\n {\n name: cmsName,\n ...(maxFileSize !== undefined && { maxFileSize }),\n // Cloud storage (s3/cloudinary) issues real presigned URLs, so the admin\n // uploads straight to it — never routing the file through the server\n // (which breaks on serverless). Local storage has no presigned receiver,\n // so it uses the direct upload endpoint.\n presignedUploads: storage.type !== 'local',\n },\n rolesRegistry,\n db,\n )\n registerSchemaRoute(app, registry, db)\n registerUserRoutes(app, db, requirePermission, requireHierarchy)\n\n registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo, requirePermission, db)\n registerAdminMediaRoutes(app, mediaRepo, storage, requirePermission, maxFileSize)\n\n return { prefix, app }\n}\n","import type { MiddlewareHandler } from 'hono'\nimport type { CorsConfig } from '@bobbykim/manguito-cms-core'\n\nexport function createCorsMiddleware(corsConfig: CorsConfig): MiddlewareHandler {\n const allowList = Array.isArray(corsConfig.origin)\n ? corsConfig.origin\n : [corsConfig.origin]\n const wildcard = allowList.includes('*')\n const methods = corsConfig.methods?.join(',') ?? 'GET,POST,PATCH,DELETE,OPTIONS'\n\n return async function corsMiddleware(c, next) {\n if (corsConfig.enabled === false) {\n return next()\n }\n\n const requestOrigin = c.req.header('origin')\n\n if (wildcard) {\n // Wildcard cannot be combined with credentials per the CORS spec.\n c.res.headers.set('Access-Control-Allow-Origin', '*')\n } else if (requestOrigin && allowList.includes(requestOrigin)) {\n c.res.headers.set('Access-Control-Allow-Origin', requestOrigin)\n c.res.headers.set('Vary', 'Origin')\n if (corsConfig.credentials === true) {\n c.res.headers.set('Access-Control-Allow-Credentials', 'true')\n }\n }\n // Non-matching origins: emit no Allow-Origin (browser blocks the read).\n\n c.res.headers.set('Access-Control-Allow-Methods', methods)\n c.res.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization')\n\n if (c.req.method === 'OPTIONS') {\n return c.newResponse(null, 204)\n }\n return next()\n }\n}\n","import type { MiddlewareHandler } from 'hono'\n\nexport type SecurityHeadersOptions = {\n /** Extra origins allowed for connect-src (e.g. the storage upload host). */\n connectSrc?: string[]\n /**\n * Path of the GraphiQL explorer (e.g. `/graphql`), set ONLY when GraphiQL is\n * enabled. That path AND its version segments (`/graphql/v1`, ...) get a\n * relaxed CSP so the explorer can boot on every mounted endpoint; every\n * other route keeps the strict policy. Leave undefined to disable entirely.\n */\n graphiqlPath?: string\n}\n\n/** CDN origin Yoga's GraphiQL loads its UI bundle and stylesheet from. */\nconst GRAPHIQL_CDN = 'https://unpkg.com'\n\n/**\n * Conservative security headers. CSP allows same-origin scripts/styles/fonts\n * for the admin SPA (served same-origin) and blocks framing. connect-src is\n * 'self' plus any storage upload origins passed in — presigned uploads go\n * browser→storage directly (ADR api/0004), so that host must be allowlisted.\n *\n * The GraphiQL explorer is the one documented exception: its UI is a CDN bundle\n * booted by inline scripts, which the strict policy blocks. When `graphiqlPath`\n * is set (i.e. GraphiQL is enabled — off in production by default), requests to\n * that path OR any path under it (`${graphiqlPath}/...`, e.g. per-version\n * mounts like `/graphql/v1`) get `'unsafe-inline'` + the CDN origin for\n * script/style. The relaxation is scoped to that path and its subtree only —\n * it never applies to the admin SPA, the REST API, or a sibling path that\n * merely starts with the same characters (e.g. `/graphqlfoo` stays strict).\n */\nexport function createSecurityHeadersMiddleware(\n options: SecurityHeadersOptions = {},\n): MiddlewareHandler {\n const connectSrc = [\"'self'\", ...(options.connectSrc ?? [])].join(' ')\n\n type CspParts = {\n scriptSrc: string\n styleSrc: string\n connectSrc: string\n /** Emitted only for GraphiQL, which runs Monaco editor workers off blob: URLs. */\n workerSrc?: string\n }\n\n function buildCsp(parts: CspParts): string {\n return [\n \"default-src 'self'\",\n \"img-src 'self' data: https:\",\n \"media-src 'self' https:\",\n `style-src ${parts.styleSrc}`,\n `script-src ${parts.scriptSrc}`,\n \"font-src 'self' data:\",\n `connect-src ${parts.connectSrc}`,\n ...(parts.workerSrc ? [`worker-src ${parts.workerSrc}`] : []),\n \"frame-ancestors 'none'\",\n \"base-uri 'self'\",\n \"form-action 'self'\",\n ].join('; ')\n }\n\n const csp = buildCsp({\n scriptSrc: \"'self'\",\n styleSrc: \"'self' 'unsafe-inline'\",\n connectSrc,\n })\n\n // GraphiQL needs four exceptions, all confined to its own path and subtree:\n // the CDN for the UI bundle + stylesheet, 'unsafe-inline' for its bootstrap\n // scripts, connect-src for the Monaco worker bundles it fetches from that\n // CDN, and worker-src blob: because it spawns those workers from Blob URLs.\n const graphiqlCsp = buildCsp({\n scriptSrc: `'self' 'unsafe-inline' ${GRAPHIQL_CDN}`,\n styleSrc: `'self' 'unsafe-inline' ${GRAPHIQL_CDN}`,\n connectSrc: `${connectSrc} ${GRAPHIQL_CDN}`,\n workerSrc: \"'self' blob:\",\n })\n const graphiqlPath = options.graphiqlPath\n\n return async function securityHeaders(c, next) {\n await next()\n c.res.headers.set('X-Content-Type-Options', 'nosniff')\n c.res.headers.set('X-Frame-Options', 'DENY')\n c.res.headers.set('Referrer-Policy', 'no-referrer')\n const isGraphiql =\n graphiqlPath !== undefined &&\n (c.req.path === graphiqlPath || c.req.path.startsWith(`${graphiqlPath}/`))\n c.res.headers.set('Content-Security-Policy', isGraphiql ? graphiqlCsp : csp)\n }\n}\n","import type { ErrorHandler } from 'hono'\nimport type { ErrorCode } from '@bobbykim/manguito-cms-core'\n\nconst ERROR_STATUS_MAP: Partial<Record<ErrorCode, number>> = {\n NOT_FOUND: 404,\n SLUG_NOT_FOUND: 404,\n METHOD_NOT_ALLOWED: 405,\n VALIDATION_ERROR: 422,\n INVALID_SLUG_FORMAT: 422,\n SLUG_CONFLICT: 409,\n PUBLISH_VALIDATION_ERROR: 422,\n SINGLETON_ALREADY_EXISTS: 409,\n UNAUTHORIZED: 401,\n TOKEN_EXPIRED: 401,\n TOKEN_INVALID: 401,\n INSUFFICIENT_PERMISSION: 403,\n INSUFFICIENT_PRIVILEGE: 403,\n INVALID_FILTER_FIELD: 400,\n INVALID_FILTER_OPERATOR: 400,\n INVALID_SORT_FIELD: 400,\n INVALID_PAGINATION: 400,\n INVALID_INCLUDE_FIELD: 400,\n UNSUPPORTED_MIME_TYPE: 415,\n STORAGE_ERROR: 502,\n MEDIA_IN_USE: 409,\n PRESIGNED_URL_EXPIRED: 410,\n RATE_LIMITED: 429,\n INTERNAL_ERROR: 500,\n}\n\ntype ApiError = Error & { code?: ErrorCode }\n\nexport const errorHandler: ErrorHandler = (err, c) => {\n console.error(err.stack ?? err.message)\n\n const apiErr = err as ApiError\n const code: ErrorCode = apiErr.code ?? 'INTERNAL_ERROR'\n const status = ERROR_STATUS_MAP[code] ?? 500\n\n return c.json({ ok: false, error: { code, message: err.message } }, status as Parameters<typeof c.json>[1])\n}\n","import type { MiddlewareHandler } from 'hono'\nimport { getCookie } from 'hono/cookie'\nimport { sql } from 'drizzle-orm'\nimport type { DrizzlePostgresInstance } from '@bobbykim/manguito-cms-db'\nimport {\n verifyToken,\n signToken,\n setAuthCookie,\n AUTH_TOKEN_TTL,\n PROACTIVE_REFRESH_THRESHOLD,\n} from '../auth/jwt.js'\n\ntype UserRow = {\n token_version: number\n must_change_password: boolean\n}\n\nexport function createAuthMiddleware(db: DrizzlePostgresInstance): MiddlewareHandler {\n return async (c, next) => {\n const token = getCookie(c, 'auth_token')\n if (!token) {\n return c.json(\n { ok: false, error: { code: 'UNAUTHORIZED', message: 'Authentication required' } },\n 401,\n )\n }\n\n let payload\n try {\n payload = await verifyToken(token)\n } catch (err) {\n const code = (err as { code?: string }).code\n if (code === 'TOKEN_EXPIRED') {\n return c.json(\n { ok: false, error: { code: 'TOKEN_EXPIRED', message: 'Token has expired' } },\n 401,\n )\n }\n return c.json(\n { ok: false, error: { code: 'TOKEN_INVALID', message: 'Token signature is invalid' } },\n 401,\n )\n }\n\n const result = await db.execute(\n sql`SELECT token_version, must_change_password FROM users WHERE id = ${payload.user_id} LIMIT 1`,\n )\n const row = result.rows[0] as UserRow | undefined\n\n if (!row) {\n return c.json(\n { ok: false, error: { code: 'TOKEN_INVALID', message: 'Token signature is invalid' } },\n 401,\n )\n }\n\n if (payload.token_version !== row.token_version) {\n return c.json(\n { ok: false, error: { code: 'TOKEN_INVALID', message: 'Token has been invalidated' } },\n 401,\n )\n }\n\n c.set('user', {\n id: payload.user_id,\n role: payload.role,\n must_change_password: row.must_change_password,\n })\n\n const now = Math.floor(Date.now() / 1000)\n if (payload.expires_at < now + PROACTIVE_REFRESH_THRESHOLD) {\n const newToken = await signToken(\n {\n user_id: payload.user_id,\n role: payload.role,\n token_version: payload.token_version,\n },\n AUTH_TOKEN_TTL,\n )\n setAuthCookie(c, 'auth_token', newToken, {})\n }\n\n await next()\n }\n}\n","import type { Context } from 'hono'\nimport { sign, verify as jwtVerify } from 'hono/jwt'\nimport { setCookie, deleteCookie } from 'hono/cookie'\nimport type { JWTPayload, ErrorCode } from '@bobbykim/manguito-cms-core'\n\ntype CookieOptions = NonNullable<Parameters<typeof setCookie>[3]>\n\n// ─── Token lifetimes ──────────────────────────────────────────────────────────\n\nexport const AUTH_TOKEN_TTL = 2 * 60 * 60\nexport const REFRESH_TOKEN_TTL = 7 * 24 * 60 * 60\nexport const PROACTIVE_REFRESH_THRESHOLD = 30 * 60\n\n// ─── JWT helpers ──────────────────────────────────────────────────────────────\n\nexport async function signToken(\n payload: Omit<JWTPayload, 'expires_at'>,\n expiresInSeconds: number,\n): Promise<string> {\n const secret = process.env['AUTH_SECRET']\n if (!secret) throw new Error('AUTH_SECRET environment variable is not set')\n const fullPayload: JWTPayload = {\n ...payload,\n expires_at: Math.floor(Date.now() / 1000) + expiresInSeconds,\n }\n return sign(fullPayload as Record<string, unknown>, secret)\n}\n\nexport async function verifyToken(token: string): Promise<JWTPayload> {\n const secret = process.env['AUTH_SECRET']\n if (!secret) throw new Error('AUTH_SECRET environment variable is not set')\n let raw: Record<string, unknown>\n try {\n raw = (await jwtVerify(token, secret, 'HS256')) as Record<string, unknown>\n } catch {\n throw Object.assign(new Error('Token signature is invalid'), {\n code: 'TOKEN_INVALID' as ErrorCode,\n })\n }\n const payload = raw as JWTPayload\n if (payload.expires_at < Math.floor(Date.now() / 1000)) {\n throw Object.assign(new Error('Token has expired'), {\n code: 'TOKEN_EXPIRED' as ErrorCode,\n })\n }\n return payload\n}\n\n// ─── Cookie helpers ───────────────────────────────────────────────────────────\n\nexport function setAuthCookie(\n c: Context,\n name: string,\n token: string,\n options: CookieOptions,\n): void {\n setCookie(c, name, token, {\n ...options,\n httpOnly: true,\n secure: true,\n sameSite: 'Strict',\n })\n}\n\nexport function clearAuthCookie(c: Context, name: string, path?: string): void {\n deleteCookie(c, name, path !== undefined ? { path } : undefined)\n}\n","import type { MiddlewareHandler } from 'hono'\n\nexport const mustChangePasswordCheck: MiddlewareHandler = async (c, next) => {\n const user = c.get('user') as { must_change_password: boolean } | undefined\n\n if (\n user?.must_change_password === true &&\n c.req.path !== '/admin/api/users/change-password'\n ) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'PASSWORD_CHANGE_REQUIRED',\n message: 'You must change your password before continuing.',\n },\n },\n 403,\n )\n }\n\n await next()\n}\n","import type { MiddlewareHandler } from 'hono'\nimport type { Permission } from '@bobbykim/manguito-cms-core'\nimport type { RolesRegistry } from '../auth/registry.js'\n\ntype AuthUser = { role: string }\n\nexport function createPermissionMiddleware(registry: RolesRegistry) {\n return function requirePermission(permission: Permission): MiddlewareHandler {\n return async (c, next) => {\n const user = c.get('user') as AuthUser | undefined\n const role = user ? registry[user.role] : undefined\n\n if (!role || !role.permissions.includes(permission)) {\n return c.json(\n {\n ok: false,\n error: { code: 'INSUFFICIENT_PERMISSION', message: 'Insufficient permission' },\n },\n 403,\n )\n }\n\n await next()\n }\n }\n}\n","import type { MiddlewareHandler } from 'hono'\nimport type { RolesRegistry } from '../auth/registry.js'\n\ntype AuthUser = { role: string }\n\nexport function createHierarchyMiddleware(\n registry: RolesRegistry,\n getUserRole: (id: string) => Promise<string | null>,\n) {\n return function requireHierarchy(): MiddlewareHandler {\n return async (c, next) => {\n const user = c.get('user') as AuthUser | undefined\n const actingRole = user ? registry[user.role] : undefined\n\n if (!actingRole) {\n return c.json(\n {\n ok: false,\n error: { code: 'INSUFFICIENT_PRIVILEGE', message: 'Insufficient privilege' },\n },\n 403,\n )\n }\n\n const method = c.req.method\n const targetId = c.req.param('id') as string | undefined\n\n let targetRoleName: string | null = null\n\n if (method === 'POST' && !targetId) {\n // POST /admin/api/users — create; role must be in request body\n let body: { role?: unknown } = {}\n try {\n body = await c.req.json<{ role?: unknown }>()\n } catch {\n // malformed body — role is absent, treated as INVALID_ROLE below\n }\n targetRoleName = typeof body.role === 'string' ? body.role : null\n if (!targetRoleName) {\n return c.json(\n {\n ok: false,\n error: { code: 'INVALID_ROLE', message: 'Request body must include a valid role' },\n },\n 400,\n )\n }\n } else if (method === 'PATCH' && targetId) {\n // PATCH /admin/api/users/:id — update; role from body if present, else from DB\n let bodyRole: string | undefined\n try {\n const body = await c.req.json<{ role?: unknown }>()\n if (typeof body.role === 'string') bodyRole = body.role\n } catch {\n // no JSON body — fall through to DB lookup\n }\n targetRoleName = bodyRole ?? (await getUserRole(targetId))\n } else if (targetId) {\n // DELETE /admin/api/users/:id or POST /admin/api/users/:id/reset-password\n targetRoleName = await getUserRole(targetId)\n }\n\n // Target user not found — let the route handler return 404\n if (targetRoleName === null) {\n return next()\n }\n\n const targetRole = registry[targetRoleName]\n if (!targetRole) {\n return c.json(\n {\n ok: false,\n error: { code: 'INVALID_ROLE', message: `Role \"${targetRoleName}\" is not valid` },\n },\n 400,\n )\n }\n\n if (actingRole.hierarchy_level >= targetRole.hierarchy_level) {\n return c.json(\n {\n ok: false,\n error: { code: 'INSUFFICIENT_PRIVILEGE', message: 'Insufficient privilege' },\n },\n 403,\n )\n }\n\n await next()\n }\n }\n}\n","import type { MiddlewareHandler } from 'hono'\nimport type { ResolvedRateLimitConfig } from '@bobbykim/manguito-cms-core'\n\n// Defaults for the public list-endpoint limiter. Single source of truth —\n// createCmsApp resolves its limiter through resolveListRateLimit below.\nconst DEFAULT_WINDOW_MS = 60_000\nconst DEFAULT_MAX_PER_IP = 30\nconst DEFAULT_MAX_GLOBAL = 500\n\nexport type RateLimitOptions = {\n windowMs: number\n maxPerIp: number\n maxGlobal: number\n}\n\nexport function createRateLimitMiddleware(options: RateLimitOptions): MiddlewareHandler {\n const { windowMs, maxPerIp, maxGlobal } = options\n const ipRequests = new Map<string, number[]>()\n const globalRequests: number[] = []\n\n return async function rateLimitMiddleware(c, next) {\n const authToken = c.req.raw.headers.get('cookie')\n ?.split(';')\n .map((s) => s.trim())\n .find((s) => s.startsWith('auth_token='))\n\n if (authToken) {\n return next()\n }\n\n const now = Date.now()\n const cutoff = now - windowMs\n\n // purge stale global entries\n let gi = 0\n while (gi < globalRequests.length && globalRequests[gi]! < cutoff) gi++\n globalRequests.splice(0, gi)\n\n // get client IP\n const forwarded = c.req.header('x-forwarded-for')\n const ip = forwarded ? forwarded.split(',')[0]!.trim() : (c.env?.incoming?.socket?.remoteAddress ?? 'unknown')\n\n // purge stale IP entries\n const ipTimes = ipRequests.get(ip) ?? []\n let ii = 0\n while (ii < ipTimes.length && ipTimes[ii]! < cutoff) ii++\n ipTimes.splice(0, ii)\n ipRequests.set(ip, ipTimes)\n\n const retryAfterFromGlobal =\n globalRequests.length > 0\n ? Math.ceil((globalRequests[0]! + windowMs - now) / 1000)\n : 0\n const retryAfterFromIp =\n ipTimes.length > 0 ? Math.ceil((ipTimes[0]! + windowMs - now) / 1000) : 0\n\n if (globalRequests.length >= maxGlobal) {\n const retryAfter = Math.max(retryAfterFromGlobal, 1)\n const reset = Math.floor((now + retryAfter * 1000) / 1000)\n return c.json(\n { ok: false, error: { code: 'RATE_LIMITED', message: `Too many requests. Please retry after ${retryAfter} seconds.` } },\n 429,\n {\n 'Retry-After': String(retryAfter),\n 'X-RateLimit-Limit': String(maxGlobal),\n 'X-RateLimit-Remaining': '0',\n 'X-RateLimit-Reset': String(reset),\n }\n )\n }\n\n if (ipTimes.length >= maxPerIp) {\n const retryAfter = Math.max(retryAfterFromIp, 1)\n const reset = Math.floor((now + retryAfter * 1000) / 1000)\n return c.json(\n { ok: false, error: { code: 'RATE_LIMITED', message: `Too many requests. Please retry after ${retryAfter} seconds.` } },\n 429,\n {\n 'Retry-After': String(retryAfter),\n 'X-RateLimit-Limit': String(maxPerIp),\n 'X-RateLimit-Remaining': '0',\n 'X-RateLimit-Reset': String(reset),\n }\n )\n }\n\n globalRequests.push(now)\n ipTimes.push(now)\n\n return next()\n }\n}\n\n/**\n * Resolves the public list-endpoint config into a middleware, or `undefined`\n * when the limiter is disabled via the `findAll: '*'` wildcard. Route\n * registrators skip registration when this is `undefined`, so a disabled\n * limiter has zero request-path overhead.\n */\nexport function resolveListRateLimit(\n rateLimit?: ResolvedRateLimitConfig,\n): MiddlewareHandler | undefined {\n const findAll = rateLimit?.findAll\n if (findAll === '*') {\n return undefined\n }\n return createRateLimitMiddleware({\n windowMs: findAll?.windowMs ?? DEFAULT_WINDOW_MS,\n maxPerIp: findAll?.maxPerIp ?? DEFAULT_MAX_PER_IP,\n maxGlobal: findAll?.maxGlobal ?? DEFAULT_MAX_GLOBAL,\n })\n}\n","import type { ParsedRole } from '@bobbykim/manguito-cms-core'\n\nexport type RolesRegistry = Record<string, ParsedRole>\n\nconst SYSTEM_ROLES = ['admin', 'manager', 'editor', 'writer', 'viewer'] as const\n\nfunction validateRoles(roles: ParsedRole[]): void {\n if (roles.length === 0) {\n throw new Error(\n 'Fatal: roles registry failed to build — roles array is empty. Run `manguito validate` to check your roles schema.',\n )\n }\n\n const nameSet = new Set(roles.map((r) => r.name))\n for (const systemRole of SYSTEM_ROLES) {\n if (!nameSet.has(systemRole)) {\n throw new Error(\n `Fatal: roles registry failed to build — missing system role \"${systemRole}\". Run \\`manguito validate\\` to check your roles schema.`,\n )\n }\n }\n\n const levelsSeen = new Map<number, string>()\n for (const role of roles) {\n const existing = levelsSeen.get(role.hierarchy_level)\n if (existing !== undefined) {\n throw new Error(\n `Fatal: roles registry failed to build — duplicate hierarchy_level ${role.hierarchy_level} on roles \"${existing}\" and \"${role.name}\". Run \\`manguito validate\\` to check your roles schema.`,\n )\n }\n levelsSeen.set(role.hierarchy_level, role.name)\n }\n\n const namesSeen = new Set<string>()\n for (const role of roles) {\n if (namesSeen.has(role.name)) {\n throw new Error(\n `Fatal: roles registry failed to build — duplicate role name \"${role.name}\". Run \\`manguito validate\\` to check your roles schema.`,\n )\n }\n namesSeen.add(role.name)\n }\n}\n\nexport function buildRolesRegistry(roles: ParsedRole[]): RolesRegistry {\n validateRoles(roles)\n return Object.fromEntries(roles.map((r) => [r.name, r]))\n}\n","import type {\n ProgrammaticFieldDefinition,\n ResolverContext,\n SchemaRegistry,\n ParsedContentType,\n ParsedTaxonomyType,\n} from '@bobbykim/manguito-cms-core'\n\nexport type ResolverMap = Map<string, ProgrammaticFieldDefinition>\n\nconst DEFAULT_TIMEOUT_MS = 5000\nconst LIST_CONCURRENCY = 10\n\nexport function resolverKey(schema: string, field: string): string {\n return `${schema}::${field}`\n}\n\n// ─── Boot-time binding validation ─────────────────────────────────────────────\n\n// Startup guard (throws, like createCmsApp's storage/roles checks): every\n// programmatic field must have exactly one resolver, and every resolver must\n// target an existing programmatic field.\nexport function validateResolverBindings(\n registry: SchemaRegistry,\n resolvers: ResolverMap,\n): void {\n const declared = new Set<string>()\n const schemas: Array<ParsedContentType | ParsedTaxonomyType> = [\n ...(Object.values(registry.content_types) as ParsedContentType[]),\n ...(Object.values(registry.taxonomy_types) as ParsedTaxonomyType[]),\n ]\n for (const schema of schemas) {\n for (const field of schema.fields) {\n if (field.field_type === 'programmatic') {\n declared.add(resolverKey(schema.name, field.name))\n }\n }\n }\n\n const missing: string[] = []\n for (const key of declared) if (!resolvers.has(key)) missing.push(key)\n\n const orphans: string[] = []\n for (const key of resolvers.keys()) if (!declared.has(key)) orphans.push(key)\n\n if (missing.length === 0 && orphans.length === 0) return\n\n const lines: string[] = ['✗ Programmatic field resolver bindings are invalid.']\n if (missing.length > 0) {\n lines.push('', ' Declared as `type: \"programmatic\"` but no resolver found:')\n for (const k of missing) lines.push(` - ${k} (add a programmaticField in src/programmatic)`)\n }\n if (orphans.length > 0) {\n lines.push('', ' Resolver has no matching programmatic field in the schema:')\n for (const k of orphans) lines.push(` - ${k}`)\n }\n lines.push('', 'Exiting.')\n throw new Error(lines.join('\\n'))\n}\n\n// ─── Runtime resolution ───────────────────────────────────────────────────────\n\nfunction buildContext(row: Record<string, unknown>): ResolverContext {\n return {\n get: (name) => row[name],\n record: row,\n }\n}\n\nasync function withTimeout<T>(p: Promise<T>, ms: number): Promise<T> {\n let timer: ReturnType<typeof setTimeout>\n const timeout = new Promise<never>((_, reject) => {\n timer = setTimeout(() => reject(new Error('resolver timeout')), ms)\n })\n try {\n return await Promise.race([p, timeout])\n } finally {\n clearTimeout(timer!)\n }\n}\n\n// Runs one resolver against one row; never throws. Failure/timeout → fallback.\nasync function runOne(\n def: ProgrammaticFieldDefinition,\n row: Record<string, unknown>,\n onError: (message: string) => void,\n): Promise<{ value: unknown; failed: boolean }> {\n const fallback = def.fallback ?? null\n try {\n const result = await withTimeout(\n Promise.resolve(def.resolve(buildContext(row))),\n def.timeout ?? DEFAULT_TIMEOUT_MS,\n )\n return { value: result === undefined ? null : result, failed: false }\n } catch (err) {\n onError(\n `⚠ programmatic field ${resolverKey(def.schema, def.field)} failed: ${err instanceof Error ? err.message : String(err)}`,\n )\n return { value: fallback, failed: true }\n }\n}\n\n// Fixed-size worker pool.\nasync function runPool<T>(items: T[], limit: number, fn: (item: T) => Promise<void>): Promise<void> {\n let i = 0\n async function worker(): Promise<void> {\n while (i < items.length) {\n const idx = i++\n await fn(items[idx]!)\n }\n }\n const workers = Array.from({ length: Math.min(limit, items.length) }, worker)\n await Promise.all(workers)\n}\n\nexport function createProgrammaticResolver(\n resolvers: ResolverMap,\n options?: { onError?: (message: string) => void },\n) {\n const onError =\n options?.onError ??\n ((message: string) => {\n process.stderr.write(message + '\\n')\n })\n\n // Group definitions by schema once.\n const bySchema = new Map<string, ProgrammaticFieldDefinition[]>()\n for (const def of resolvers.values()) {\n const arr = bySchema.get(def.schema) ?? []\n arr.push(def)\n bySchema.set(def.schema, arr)\n }\n\n // Per-process cache. Key = schema::field::itemId.\n const cache = new Map<string, { value: unknown; expires: number }>()\n\n async function resolveField(\n def: ProgrammaticFieldDefinition,\n row: Record<string, unknown>,\n ): Promise<unknown> {\n const itemId = row['id'] !== undefined ? String(row['id']) : ''\n if (def.cache && itemId) {\n const key = `${resolverKey(def.schema, def.field)}::${itemId}`\n const now = Date.now()\n const hit = cache.get(key)\n if (hit && hit.expires > now) return hit.value\n const { value, failed } = await runOne(def, row, onError)\n if (!failed) cache.set(key, { value, expires: now + def.cache.ttl * 1000 })\n return value\n }\n const { value } = await runOne(def, row, onError)\n return value\n }\n\n async function resolveItem(\n schema: string,\n row: Record<string, unknown>,\n ): Promise<Record<string, unknown>> {\n const defs = bySchema.get(schema)\n if (!defs || defs.length === 0) return { ...row }\n const out: Record<string, unknown> = { ...row }\n await Promise.all(\n defs.map(async (def) => {\n out[def.field] = await resolveField(def, row)\n }),\n )\n return out\n }\n\n async function resolveList(\n schema: string,\n rows: Record<string, unknown>[],\n ): Promise<Record<string, unknown>[]> {\n const defs = (bySchema.get(schema) ?? []).filter((d) => d.on_list === true)\n if (defs.length === 0) return rows.map((r) => ({ ...r }))\n const out = rows.map((r) => ({ ...r }))\n const tasks: Array<{ rowIndex: number; def: ProgrammaticFieldDefinition }> = []\n for (let i = 0; i < rows.length; i++) {\n for (const def of defs) tasks.push({ rowIndex: i, def })\n }\n await runPool(tasks, LIST_CONCURRENCY, async ({ rowIndex, def }) => {\n out[rowIndex]![def.field] = await resolveField(def, rows[rowIndex]!)\n })\n return out\n }\n\n function hasSchema(schema: string): boolean {\n return bySchema.has(schema)\n }\n\n return { resolveItem, resolveList, hasSchema }\n}\n\nexport type ProgrammaticResolver = ReturnType<typeof createProgrammaticResolver>\n","import type { ParsedField, SchemaRegistry } from '@bobbykim/manguito-cms-core'\nimport type { FieldKeyMap } from './field-keys.js'\n\n// ─── Recursive outbound projection ────────────────────────────────────────────\n//\n// Stage 1 mapped a response row's TOP-LEVEL keys from storage columns to public\n// labels. Nested rows — paragraph children, resolved reference and junction\n// targets — were attached straight from SELECT * and never mapped, so they\n// served column names. This walks them.\n//\n// Applied at exactly the points that called FieldKeyMap.toLabels before, which\n// is why it fixes both the public and admin paths without touching either\n// resolver: both already map at the right moment, after relation resolution.\n//\n// GraphQL does not use this. It resolves each field individually by column\n// through resolveFieldValue, so it is already correct at every depth.\n\n/**\n * Is this a resolved row worth recursing into? A relation that was not\n * `?include=`d holds a bare uuid string (or an array of them), and timestamps\n * arrive from the driver as Date instances — neither is a row.\n */\nexport function isPlainRow(v: unknown): v is Record<string, unknown> {\n return typeof v === 'object' && v !== null && !Array.isArray(v) && !(v instanceof Date)\n}\n\nexport type TypeProjector = {\n /** This type's own label↔column map. */\n map: FieldKeyMap\n /** Relation fields worth recursing into: the field's LABEL and its target type. */\n nested: Array<{ label: string; target: string }>\n /** Fallback values for retained columns, keyed by label. */\n fallbacks?: Record<string, unknown>\n}\n\nexport type Projectors = Record<string, TypeProjector>\n\n// Media relations resolve to rows of the `media` system table — fixed columns,\n// no schema fields — so there is nothing to project and they stay out of `nested`.\nconst MEDIA_FIELD_TYPES = new Set(['image', 'video', 'file'])\n\nfunction nestedTargets(fields: ParsedField[]): Array<{ label: string; target: string }> {\n const out: Array<{ label: string; target: string }> = []\n for (const f of fields) {\n if (MEDIA_FIELD_TYPES.has(f.field_type)) continue\n if (f.field_type !== 'paragraph' && f.field_type !== 'reference') continue\n // Both kinds name their target the same way.\n const ref = (f.ui_component as { ref?: string }).ref\n if (!ref) continue\n out.push({ label: f.name, target: ref })\n }\n return out\n}\n\n/**\n * Built ONCE at startup, not per request. Covers content, taxonomy and\n * paragraph types, keyed by machine name — the same key space `fieldKeyMaps`\n * uses and the same one a field's `ui_component.ref` points into.\n *\n * `fallbacks`, keyed the same way (type name → label → value), supplies what\n * a retained-but-unexposed column serves in place of null on THIS version's\n * responses — see `fallbacksFor` in versions.ts, the only caller that\n * actually populates it. Every other caller (the unversioned/current pass,\n * and every non-versioned use of this function) omits it, since a tombstone\n * is never retained-and-exposed outside an older live version's projection.\n */\nexport function buildProjectors(\n registry: SchemaRegistry,\n fieldKeyMaps: Record<string, FieldKeyMap>,\n fallbacks?: Record<string, Record<string, unknown>>\n): Projectors {\n const projectors: Projectors = {}\n const sources: Array<Record<string, { fields: ParsedField[] }>> = [\n registry.content_types,\n registry.taxonomy_types,\n registry.paragraph_types,\n ]\n\n for (const source of sources) {\n for (const [typeName, type] of Object.entries(source)) {\n const map = fieldKeyMaps[typeName]\n if (!map) continue\n const typeFallbacks = fallbacks?.[typeName]\n projectors[typeName] = {\n map,\n nested: nestedTargets(type.fields),\n // Spread rather than assign: with exactOptionalPropertyTypes, an optional\n // property may be ABSENT but not present-and-undefined.\n ...(typeFallbacks !== undefined && { fallbacks: typeFallbacks }),\n }\n }\n }\n\n return projectors\n}\n\n/**\n * Storage-keyed row → label-keyed, recursively. Never mutates its input: the\n * relation cache hands the SAME nested object to several parents, so editing in\n * place would project a shared row twice and lose its values on the second pass.\n */\nexport function projectRow(\n row: Record<string, unknown>,\n typeName: string,\n projectors: Projectors\n): Record<string, unknown> {\n const p = projectors[typeName]\n if (!p) return row\n\n // Top level first: `nested` is keyed by label, so the keys must be labels\n // before the loop below reads them. toLabels returns a fresh object.\n const out = p.map.toLabels(row)\n\n // A retained column reads null for every row written since the tombstone.\n // Substituting the declared fallback is what lets an older live version keep\n // serving a coherent shape. ONLY for null/undefined: 0, '' and false are\n // legitimate stored values, and replacing them would destroy real data.\n if (p.fallbacks !== undefined) {\n for (const [label, value] of Object.entries(p.fallbacks)) {\n if (out[label] === null || out[label] === undefined) out[label] = value\n }\n }\n\n for (const { label, target } of p.nested) {\n const v = out[label]\n if (v === null || v === undefined) continue\n if (Array.isArray(v)) {\n out[label] = v.map((item) => (isPlainRow(item) ? projectRow(item, target, projectors) : item))\n } else if (isPlainRow(v)) {\n out[label] = projectRow(v, target, projectors)\n }\n }\n\n return out\n}\n","import type { Hono, Handler, MiddlewareHandler } from 'hono'\nimport type {\n SchemaRegistry,\n ContentRepository,\n} from '@bobbykim/manguito-cms-core'\nimport type { ProgrammaticResolver } from '../programmatic/resolve.js'\nimport type { Projectors } from '../projector.js'\nimport { projectRow } from '../projector.js'\nimport { isColumnBacked } from '../field-keys.js'\nimport type { VersionedPaths } from '../paths.js'\nimport {\n SORTABLE_FIELDS,\n RELATION_FIELD_TYPES,\n parsePagination,\n parseInclude,\n parseFilters,\n} from './query-params.js'\n\nexport type ContentRepos = Record<string, ContentRepository<unknown>>\n\n// ─── Response projection order ────────────────────────────────────────────────\n//\n// Every read response maps storage keys → labels exactly ONCE, and that mapping\n// runs BEFORE the programmatic resolver. Two reasons it sits there rather than\n// after:\n// - a programmatic resolver reads its record through `ctx.get(fieldName)`,\n// documented (docs/programmatic-fields.md) as the schema field name — the\n// LABEL — so the row handed to it must already speak labels;\n// - programmatic fields are not column-backed, so their output keys are labels\n// already; mapping afterwards would be a no-op on them.\n// Relations are resolved inside the repository, upstream of both, so the mapping\n// still lands strictly after relation resolution.\n\nfunction isPublished(item: unknown): boolean {\n return (item as Record<string, unknown>)['published'] === true\n}\n\nexport function registerPublicContentRoutes(\n app: Hono,\n registry: SchemaRegistry,\n repos: ContentRepos,\n projectors: Projectors,\n paths: VersionedPaths,\n listRateLimit?: MiddlewareHandler,\n resolver?: ProgrammaticResolver\n): void {\n // ── Meta-endpoints: list available schema types ───────────────────────────\n // Registered before the dynamic per-type routes to avoid path conflicts.\n\n function registerListRoute(path: string, handler: Handler): void {\n if (listRateLimit) {\n app.get(path, listRateLimit, handler)\n } else {\n app.get(path, handler)\n }\n }\n\n registerListRoute(paths.collection('content'), (c) => {\n const data = Object.values(registry.content_types).map((ct) => ({\n name: ct.name,\n label: ct.label,\n only_one: ct.only_one,\n }))\n return c.json({ ok: true, data })\n })\n\n registerListRoute(paths.collection('taxonomy'), (c) => {\n const data = Object.values(registry.taxonomy_types).map((tt) => ({\n name: tt.name,\n label: tt.label,\n }))\n return c.json({ ok: true, data })\n })\n\n // ── Per-type content routes ───────────────────────────────────────────────\n\n for (const [typeName, contentType] of Object.entries(registry.content_types)) {\n const basePath = contentType.default_base_path\n const repo = repos[typeName]\n if (!repo) continue\n\n // This version's own field-key map. `projectors` is built per version by\n // the caller (versions.ts/app.ts), so this is what lets the boundary\n // below speak THIS version's labels rather than current's.\n const fieldKeys = projectors[typeName]!.map\n\n // Relation fields worth allowing in `?include=`: a column-backed relation\n // (single reference, image, video, file) diverges the same way an\n // ordinary field can, so it is validated under THIS version's own label\n // via `fieldKeys`. Paragraph and many-to-many reference fields have no\n // column at all — they are outside the label↔column map entirely and\n // always follow the current schema's field name, by design (see\n // versions.ts's \"nested/related content follows current's shape\" note) —\n // so their name is taken straight from the registry.\n const relationFieldNames = new Set<string>()\n for (const f of contentType.fields) {\n if (!RELATION_FIELD_TYPES.has(f.field_type)) continue\n if (isColumnBacked(f)) {\n const label = fieldKeys.labelFor(f.db_column.column_name)\n if (label !== undefined) relationFieldNames.add(label)\n } else {\n relationFieldNames.add(f.name)\n }\n }\n\n // This version's own filter/sort surface: the labels THIS version\n // exposes (`fieldKeys.labels` — column-backed fields only, so\n // programmatic/paragraph/many-to-many fields are already excluded) plus\n // system fields, which are identical on every version. Sourcing this from\n // `contentType.fields` (always CURRENT) instead would reject a pinned\n // version's own label and accept current's — a name this version's map\n // cannot resolve, which would otherwise reach SQL unresolved.\n const filterableFieldNames = new Set<string>([\n ...fieldKeys.labels,\n ...contentType.system_fields.map((f) => f.name),\n ])\n // SORTABLE_FIELDS is the same across every version; only the subset THIS\n // version can actually resolve (its own labels, plus system fields) is\n // safe to accept from a request. A label absent from this version's map\n // must be rejected here, not discovered as a 500 once it reaches SQL.\n const sortableFieldNames = new Set(\n [...SORTABLE_FIELDS].filter((f) => filterableFieldNames.has(f))\n )\n\n if (contentType.only_one) {\n app.get(paths.collection(basePath), async (c) => {\n const result = await repo.findMany({ published_only: true, page: 1, per_page: 1 })\n if (result.data.length === 0) {\n return c.json(\n { ok: false, error: { code: 'NOT_FOUND', message: 'Not found' } },\n 404\n )\n }\n // Outbound boundary (see \"Response projection order\" above).\n let data = projectRow(result.data[0] as Record<string, unknown>, typeName, projectors)\n if (resolver?.hasSchema(typeName)) data = await resolver.resolveItem(typeName, data)\n return c.json({ ok: true, data })\n })\n } else {\n registerListRoute(paths.collection(basePath), async (c) => {\n // Inbound boundary: query params speak labels; filters query storage keys.\n const pagination = parsePagination(c.req.query('page'), c.req.query('per_page'))\n if (!pagination.ok) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'INVALID_PAGINATION',\n message: 'page must be ≥ 1 and per_page must be between 1 and 100',\n },\n },\n 400\n )\n }\n\n const sortBy = c.req.query('sort_by') ?? 'created_at'\n if (!sortableFieldNames.has(sortBy)) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'INVALID_SORT_FIELD',\n message: `'${sortBy}' is not sortable. Allowed: ${[...sortableFieldNames].join(', ')}`,\n },\n },\n 400\n )\n }\n\n const sortOrder = c.req.query('sort_order') ?? 'asc'\n if (sortOrder !== 'asc' && sortOrder !== 'desc') {\n return c.json(\n {\n ok: false,\n error: {\n code: 'INVALID_SORT_FIELD',\n message: `sort_order must be 'asc' or 'desc'`,\n },\n },\n 400\n )\n }\n\n const filtersResult = parseFilters(c.req.url, filterableFieldNames, fieldKeys.columnFor)\n if (!filtersResult.ok) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'INVALID_FILTER_FIELD',\n message: `Filter field '${filtersResult.invalidField}' does not exist on this content type`,\n },\n },\n 400\n )\n }\n\n const include = parseInclude(c.req.query('include'))\n for (const field of include) {\n if (!relationFieldNames.has(field)) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'INVALID_INCLUDE_FIELD',\n message: `'${field}' is not a valid relation field`,\n },\n },\n 400\n )\n }\n }\n\n // sortBy is validated above against sortableFieldNames — this\n // version's own labels plus its (version-invariant) system fields —\n // so it is either a label fieldKeys can resolve, or a system field\n // name that already IS its own storage column. The cast on\n // `sort_by` below is a narrow lie — core types it as the label\n // union, but the repository immediately re-validates the mapped\n // value against sortableColumns.\n const sortColumn = fieldKeys.labels.includes(sortBy)\n ? fieldKeys.columnFor(sortBy)!\n : sortBy\n\n const result = await repo.findMany({\n published_only: true,\n page: pagination.page,\n per_page: pagination.per_page,\n sort_by: sortColumn as 'title' | 'created_at' | 'updated_at',\n sort_order: sortOrder as 'asc' | 'desc',\n filters: filtersResult.filters,\n include,\n })\n\n // Outbound boundary (see \"Response projection order\" above).\n const labeled = (result.data as Record<string, unknown>[]).map((row) =>\n projectRow(row, typeName, projectors)\n )\n const data = resolver?.hasSchema(typeName)\n ? await resolver.resolveList(typeName, labeled)\n : labeled\n return c.json({ ...result, data })\n })\n\n app.get(paths.item(basePath), async (c) => {\n // paths.item() always appends a literal ':slug' segment, but its return\n // type is the widened `string` from PublicPaths — not a template literal\n // type — so Hono can no longer statically prove the param is present.\n const slug = c.req.param('slug')!\n\n const include = parseInclude(c.req.query('include'))\n for (const field of include) {\n if (!relationFieldNames.has(field)) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'INVALID_INCLUDE_FIELD',\n message: `'${field}' is not a valid relation field`,\n },\n },\n 400\n )\n }\n }\n\n const item = await repo.findBySlug(slug, include)\n\n if (!item || !isPublished(item)) {\n return c.json(\n {\n ok: false,\n error: { code: 'SLUG_NOT_FOUND', message: `No item found with slug '${slug}'` },\n },\n 404\n )\n }\n\n // Outbound boundary (see \"Response projection order\" above).\n let data = projectRow(item as Record<string, unknown>, typeName, projectors)\n if (resolver?.hasSchema(typeName)) data = await resolver.resolveItem(typeName, data)\n return c.json({ ok: true, data })\n })\n }\n }\n\n for (const [typeName] of Object.entries(registry.taxonomy_types)) {\n const repo = repos[typeName]\n if (!repo) continue\n\n registerListRoute(paths.taxonomyCollection(typeName), async (c) => {\n const pagination = parsePagination(c.req.query('page'), c.req.query('per_page'))\n if (!pagination.ok) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'INVALID_PAGINATION',\n message: 'page must be ≥ 1 and per_page must be between 1 and 100',\n },\n },\n 400\n )\n }\n\n const result = await repo.findMany({\n published_only: true,\n page: pagination.page,\n per_page: pagination.per_page,\n })\n\n // Outbound boundary (see \"Response projection order\" above).\n const labeled = (result.data as Record<string, unknown>[]).map((row) =>\n projectRow(row, typeName, projectors)\n )\n const data = resolver?.hasSchema(typeName)\n ? await resolver.resolveList(typeName, labeled)\n : labeled\n return c.json({ ...result, data })\n })\n\n app.get(paths.taxonomyItem(typeName), async (c) => {\n // See the ':slug' comment above — same widened-string-return caveat applies.\n const id = c.req.param('id')!\n const item = await repo.findOne(id)\n\n if (!item || !isPublished(item)) {\n return c.json(\n {\n ok: false,\n error: { code: 'NOT_FOUND', message: 'Taxonomy term not found' },\n },\n 404\n )\n }\n\n // Outbound boundary (see \"Response projection order\" above).\n let data = projectRow(item as Record<string, unknown>, typeName, projectors)\n if (resolver?.hasSchema(typeName)) data = await resolver.resolveItem(typeName, data)\n return c.json({ ok: true, data })\n })\n }\n}\n","import type { FilterValue, FilterOperator } from '@bobbykim/manguito-cms-core'\n\n// Query-param parsing shared by the public and admin content/taxonomy list routes.\n\n// The sortable set. NOT \"indexed system fields\", despite what this comment used\n// to say: db codegen generates no indexes at all, and `title` is an ordinary\n// schema field, not a system field — nothing guarantees a content type has one.\n// Sorting by a label a type lacks still reaches Postgres and 500s; widening this\n// set to any column-backed field is a separate design (see the Stage 1.5 spec).\nexport const SORTABLE_FIELDS = new Set<string>(['title', 'created_at', 'updated_at'])\n\n// Field types whose values are relations rather than plain columns.\nexport const RELATION_FIELD_TYPES = new Set([\n 'paragraph',\n 'reference',\n 'image',\n 'video',\n 'file',\n])\n\nexport function parsePagination(\n pageStr: string | undefined,\n perPageStr: string | undefined\n): { ok: true; page: number; per_page: number } | { ok: false } {\n const page = pageStr !== undefined ? Number(pageStr) : 1\n const per_page = perPageStr !== undefined ? Number(perPageStr) : 10\n\n if (!Number.isInteger(page) || page < 1) return { ok: false }\n if (!Number.isInteger(per_page) || per_page < 1 || per_page > 100) return { ok: false }\n return { ok: true, page, per_page }\n}\n\nexport function parseInclude(includeParam: string | undefined): string[] {\n if (!includeParam) return []\n return includeParam\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean)\n}\n\nexport function parseFilters(\n url: string,\n validFields: Set<string>,\n columnFor?: (label: string) => string | undefined\n): { ok: true; filters: Record<string, FilterValue> } | { ok: false; invalidField: string } {\n const { searchParams } = new URL(url)\n const filters: Record<string, FilterValue> = {}\n\n // Filters are validated against LABELS (what the client sends) and emitted as\n // storage keys (what the repository queries). Without a mapper the two are\n // the same, which is the pre-versioning behavior.\n const toKey = (label: string): string => columnFor?.(label) ?? label\n\n for (const [key, value] of searchParams.entries()) {\n const simpleMatch = /^filter\\[([^\\]]+)\\]$/.exec(key)\n const opMatch = /^filter\\[([^\\]]+)\\]\\[([^\\]]+)\\]$/.exec(key)\n\n if (simpleMatch) {\n const field = simpleMatch[1]!\n if (!validFields.has(field)) return { ok: false, invalidField: field }\n const storageKey = toKey(field)\n const existing = filters[storageKey]\n if (existing !== undefined) {\n filters[storageKey] = Array.isArray(existing)\n ? [...existing, value]\n : [existing as string | number | boolean, value]\n } else {\n filters[storageKey] = value\n }\n } else if (opMatch) {\n const field = opMatch[1]!\n const operator = opMatch[2]!\n if (!validFields.has(field)) return { ok: false, invalidField: field }\n if (!['gt', 'gte', 'lt', 'lte'].includes(operator)) continue\n const storageKey = toKey(field)\n const existing = (filters[storageKey] as FilterOperator | undefined) ?? {}\n filters[storageKey] = { ...(existing as FilterOperator), [operator]: value }\n }\n }\n\n return { ok: true, filters }\n}\n","import type { Hono, Handler, MiddlewareHandler } from 'hono'\nimport type { MediaRepository } from '@bobbykim/manguito-cms-core'\nimport type { PublicPaths } from '../paths.js'\n\nconst VALID_MEDIA_TYPES = new Set(['image', 'video', 'file'])\n\nfunction parsePagination(\n pageStr: string | undefined,\n perPageStr: string | undefined\n): { ok: true; page: number; per_page: number } | { ok: false } {\n const page = pageStr !== undefined ? Number(pageStr) : 1\n const per_page = perPageStr !== undefined ? Number(perPageStr) : 10\n\n if (!Number.isInteger(page) || page < 1) return { ok: false }\n if (!Number.isInteger(per_page) || per_page < 1 || per_page > 100) return { ok: false }\n return { ok: true, page, per_page }\n}\n\nexport function registerPublicMediaRoutes(\n app: Hono,\n mediaRepo: MediaRepository,\n paths: PublicPaths,\n listRateLimit?: MiddlewareHandler\n): void {\n const mediaListHandler: Handler = async (c) => {\n const pagination = parsePagination(c.req.query('page'), c.req.query('per_page'))\n if (!pagination.ok) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'INVALID_PAGINATION',\n message: 'page must be ≥ 1 and per_page must be between 1 and 100',\n },\n },\n 400\n )\n }\n\n const typeParam = c.req.query('type')\n if (typeParam !== undefined && !VALID_MEDIA_TYPES.has(typeParam)) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'VALIDATION_ERROR',\n message: `type must be one of: image, video, file`,\n },\n },\n 422\n )\n }\n\n const opts: Parameters<typeof mediaRepo.findMany>[0] = {\n page: pagination.page,\n per_page: pagination.per_page,\n }\n if (typeParam !== undefined) {\n opts.type = typeParam as 'image' | 'video' | 'file'\n }\n\n const result = await mediaRepo.findMany(opts)\n return c.json(result)\n }\n\n if (listRateLimit) {\n app.get(paths.mediaCollection(), listRateLimit, mediaListHandler)\n } else {\n app.get(paths.mediaCollection(), mediaListHandler)\n }\n\n app.get(paths.mediaItem(), async (c) => {\n // paths.mediaItem() always appends a literal ':id' segment, but its return\n // type is the widened `string` from PublicPaths, so Hono can't statically\n // prove the param is present.\n const id = c.req.param('id')!\n const item = await mediaRepo.findOne(id)\n\n if (!item) {\n return c.json(\n { ok: false, error: { code: 'NOT_FOUND', message: 'Media item not found' } },\n 404\n )\n }\n\n return c.json({ ok: true, data: item })\n })\n}\n","import { sql } from 'drizzle-orm'\nimport type { Context, Hono } from 'hono'\nimport type {\n SchemaRegistry,\n MediaRepository,\n FilterValue,\n ParsedField,\n ParsedParagraphType,\n} from '@bobbykim/manguito-cms-core'\nimport {\n SORTABLE_FIELDS,\n RELATION_FIELD_TYPES,\n parsePagination,\n parseInclude,\n parseFilters,\n} from '../query-params.js'\nimport type { DrizzlePostgresInstance } from '@bobbykim/manguito-cms-db'\nimport {\n topLevelMediaDelta,\n mergeMediaDeltas,\n applyMediaReferenceDelta,\n type MediaDelta,\n} from '../../media-references.js'\nimport {\n persistParagraphField,\n deleteParagraphField,\n persistJunctionField,\n} from '../../relations.js'\nimport type { createPermissionMiddleware } from '../../middleware/permission.js'\nimport type { ContentRepos } from '../content.js'\nimport { isColumnBacked } from '../../field-keys.js'\nimport { projectRow, type Projectors } from '../../projector.js'\n\n// ─── SQL helpers ─────────────────────────────────────────────────────────────\n\nfunction quoteIdent(name: string): string {\n if (!/^[a-z][a-z0-9_-]*$/.test(name)) throw new Error(`Unsafe identifier: ${name}`)\n return `\"${name}\"`\n}\n\n// Loads the paragraph rows for one field of one parent, descending into the one\n// level of nested paragraph fields ADR core/0005 permits. Scoped by parent_id AND\n// parent_field so two paragraph fields sharing a paragraph type never mix — the\n// reason this read is not delegated to resolveRelationField.\nasync function loadParagraphRows(\n db: DrizzlePostgresInstance,\n registry: SchemaRegistry,\n pType: ParsedParagraphType,\n parentId: string,\n fieldName: string\n): Promise<Record<string, unknown>[]> {\n const result = await db.execute(\n sql`SELECT * FROM ${sql.raw(quoteIdent(pType.db.table_name))} WHERE parent_id = ${parentId} AND parent_field = ${fieldName} ORDER BY \"order\" ASC`\n )\n const rows = result.rows as Record<string, unknown>[]\n\n for (const nf of pType.fields) {\n if (nf.db_column !== null) continue\n const comp = nf.ui_component as { component: string; ref?: string }\n if (comp.component !== 'paragraph-embed' || !comp.ref) continue\n const nType = registry.paragraph_types[comp.ref]\n if (!nType) continue\n for (const row of rows) {\n row[nf.name] = await loadParagraphRows(db, registry, nType, row['id'] as string, nf.name)\n }\n }\n\n return rows\n}\n\nasync function lookupBasePathId(db: DrizzlePostgresInstance, pathOrName: string): Promise<string | null> {\n const r = await db.execute(\n sql`SELECT id FROM base_paths WHERE path = ${pathOrName} OR name = ${pathOrName} LIMIT 1`\n )\n return (r.rows[0] as { id: string } | undefined)?.id ?? null\n}\n\n// ─── Shared query-param helpers ───────────────────────────────────────────────\n\nconst SLUG_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/\n\n// ─── Validation helpers ───────────────────────────────────────────────────────\n\nfunction isEmpty(value: unknown): boolean {\n if (value === null || value === undefined) return true\n if (typeof value === 'string' && value.trim() === '') return true\n if (Array.isArray(value) && value.length === 0) return true\n return false\n}\n\nfunction checkRequiredFields(\n fields: ParsedField[],\n data: Record<string, unknown>\n): { field: string; message: string }[] {\n return fields\n .filter((f) => f.required && isEmpty(data[f.name]))\n .map((f) => ({ field: f.name, message: `${f.label} is required` }))\n}\n\n// Extract IDs from already-resolved media field values ({ id: string } objects).\n// Admin repos are created without relation resolution (see app.ts) so edit forms get\n// plain foreign-key id strings back — media fields here are raw ids, not resolved\n// { id, url, ... } objects.\n// ─── List query helpers (shared between content and taxonomy list routes) ─────\n\ntype ListQueryResult =\n | {\n ok: true\n pagination: { page: number; per_page: number }\n sortBy: string\n sortOrder: string\n filters: Record<string, FilterValue>\n include: string[]\n search: string\n }\n | { ok: false; response: { code: string; message: string }; status: 400 }\n\n// `columnFor` maps a validated label to its storage column. Filters are\n// validated against labels (what the client sends) and emitted as columns (what\n// the repository queries) — without it a renamed field would be filtered on a\n// column that does not exist.\nfunction parseListQuery(\n url: string,\n schemaFieldNames: Set<string>,\n relationFieldNames: Set<string>,\n columnFor: (label: string) => string | undefined\n): ListQueryResult {\n const searchParams = new URL(url).searchParams\n\n const pagination = parsePagination(\n searchParams.get('page') ?? undefined,\n searchParams.get('per_page') ?? undefined\n )\n if (!pagination.ok) {\n return {\n ok: false,\n response: {\n code: 'INVALID_PAGINATION',\n message: 'page must be ≥ 1 and per_page must be between 1 and 100',\n },\n status: 400,\n }\n }\n\n const sortBy = searchParams.get('sort_by') ?? 'created_at'\n if (!SORTABLE_FIELDS.has(sortBy)) {\n return {\n ok: false,\n response: {\n code: 'INVALID_SORT_FIELD',\n message: `'${sortBy}' is not sortable. Allowed: title, created_at, updated_at`,\n },\n status: 400,\n }\n }\n\n const sortOrder = searchParams.get('sort_order') ?? 'asc'\n if (sortOrder !== 'asc' && sortOrder !== 'desc') {\n return {\n ok: false,\n response: { code: 'INVALID_SORT_FIELD', message: `sort_order must be 'asc' or 'desc'` },\n status: 400,\n }\n }\n\n const filtersResult = parseFilters(url, schemaFieldNames, columnFor)\n if (!filtersResult.ok) {\n return {\n ok: false,\n response: {\n code: 'INVALID_FILTER_FIELD',\n message: `Filter field '${filtersResult.invalidField}' does not exist on this schema`,\n },\n status: 400,\n }\n }\n\n const include = parseInclude(searchParams.get('include') ?? undefined)\n for (const field of include) {\n if (!relationFieldNames.has(field)) {\n return {\n ok: false,\n response: { code: 'INVALID_INCLUDE_FIELD', message: `'${field}' is not a valid relation field` },\n status: 400,\n }\n }\n }\n\n const search = (searchParams.get('search') ?? '').trim()\n\n return {\n ok: true,\n pagination,\n sortBy,\n sortOrder,\n filters: filtersResult.filters,\n include,\n search,\n }\n}\n\n// ─── Register ─────────────────────────────────────────────────────────────────\n\nexport function registerAdminContentRoutes(\n app: Hono,\n registry: SchemaRegistry,\n repos: ContentRepos,\n projectors: Projectors,\n mediaRepo: MediaRepository,\n requirePermission: ReturnType<typeof createPermissionMiddleware>,\n db?: DrizzlePostgresInstance,\n): void {\n // ── Content type routes ───────────────────────────────────────────────────\n\n for (const [typeName, contentType] of Object.entries(registry.content_types)) {\n const basePath = `content/${typeName}`\n const repo = repos[typeName]\n if (!repo) continue\n\n const schemaFieldNames = new Set<string>([\n ...contentType.fields.map((f) => f.name),\n ...contentType.system_fields.map((f) => f.name),\n ])\n\n const relationFieldNames = new Set<string>(\n contentType.fields\n .filter((f) => RELATION_FIELD_TYPES.has(f.field_type))\n .map((f) => f.name)\n )\n\n const projector = projectors[typeName]!\n const fieldKeys = projector.map\n\n const requiredFields = contentType.fields.filter((f) => f.required)\n\n const mediaFields = contentType.fields.filter(\n (f) => f.field_type === 'image' || f.field_type === 'video' || f.field_type === 'file'\n )\n\n const paragraphFieldDefs = contentType.fields.filter((f) => f.db_column === null)\n\n // Free-text search target — text/plain fields (mirrors the \"first text field is\n // the title\" convention the admin frontend already uses) plus slug, when present.\n // Singleton (only_one) content types have no slug column at all.\n const searchableColumns = [\n ...contentType.fields\n .filter((f) => f.field_type === 'text/plain' && f.db_column !== null)\n .map((f) => f.db_column!.column_name),\n ...(contentType.system_fields.some((f) => f.name === 'slug') ? ['slug'] : []),\n ]\n\n // GET /admin/api/{base_path}\n app.get(\n `/admin/api/${basePath}`,\n requirePermission('content:read'),\n async (c) => {\n const parsed = parseListQuery(c.req.url, schemaFieldNames, relationFieldNames, fieldKeys.columnFor)\n if (!parsed.ok) {\n return c.json({ ok: false, error: parsed.response }, parsed.status)\n }\n\n const publishedParam = c.req.query('published')\n const extraFilters: Record<string, FilterValue> = {}\n if (publishedParam === 'false') extraFilters['published'] = false\n\n // parsed.sortBy is a validated label; map it to its storage column\n // before it reaches the repository. The cast is a narrow lie — core\n // types sort_by as the label union, but the repository immediately\n // re-validates the mapped value against sortableColumns.\n const sortColumn = fieldKeys.columnFor(parsed.sortBy) ?? parsed.sortBy\n\n const findOpts: Parameters<typeof repo.findMany>[0] = {\n page: parsed.pagination.page,\n per_page: parsed.pagination.per_page,\n sort_by: sortColumn as 'title' | 'created_at' | 'updated_at',\n sort_order: parsed.sortOrder as 'asc' | 'desc',\n filters: { ...parsed.filters, ...extraFilters },\n include: parsed.include,\n }\n if (publishedParam === 'true') findOpts.published_only = true\n if (parsed.search !== '' && searchableColumns.length > 0) {\n findOpts.search = { term: parsed.search, columns: searchableColumns }\n }\n\n const result = await repo.findMany(findOpts)\n\n // Outbound boundary: rows are storage-keyed; responses speak labels.\n const data = result.data.map((row) =>\n projectRow(row as Record<string, unknown>, typeName, projectors)\n )\n\n return c.json({ ...result, data })\n }\n )\n\n // GET /admin/api/{base_path}/:id\n app.get(\n `/admin/api/${basePath}/:id`,\n requirePermission('content:read'),\n async (c) => {\n const id = c.req.param('id')\n const item = await repo.findOne(id)\n\n if (!item) {\n return c.json(\n { ok: false, error: { code: 'NOT_FOUND', message: 'Not found' } },\n 404\n )\n }\n\n // Resolve paragraph and junction fields so the edit form loads real data.\n // This deliberately does NOT reuse the relation module's resolvers: the\n // edit read scopes paragraph rows by parent_id AND parent_field, whereas\n // resolveRelationField scopes by parent_id only. Two paragraph fields of\n // the same paragraph type would be mixed by the resolver — so the more\n // precise per-field read stays here on purpose.\n if (db) {\n const row = item as Record<string, unknown>\n for (const f of contentType.fields) {\n if (f.db_column === null) {\n const comp = f.ui_component as { component: string; ref?: string }\n if (comp.component !== 'paragraph-embed' || !comp.ref) continue\n const pType = registry.paragraph_types[comp.ref]\n if (!pType) continue\n row[f.name] = await loadParagraphRows(db, registry, pType, id, f.name)\n } else if (f.db_column.junction) {\n const j = f.db_column.junction\n const r = await db.execute(\n sql`SELECT ${sql.raw(quoteIdent(j.right_column))} FROM ${sql.raw(quoteIdent(j.table_name))} WHERE ${sql.raw(quoteIdent(j.left_column))} = ${id}`\n )\n row[f.name] = r.rows.map(\n (row) => (row as Record<string, unknown>)[j.right_column] as string\n )\n }\n }\n }\n\n // Outbound boundary: rows are storage-keyed; responses speak labels.\n // Mapped after the paragraph/junction population above so those\n // label-keyed additions survive.\n const data = projectRow(item as Record<string, unknown>, typeName, projectors)\n\n return c.json({ ok: true, data })\n }\n )\n\n // POST /admin/api/{base_path}\n app.post(\n `/admin/api/${basePath}`,\n requirePermission('content:create'),\n async (c) => {\n const body = (await c.req.json()) as Record<string, unknown>\n\n if (contentType.only_one) {\n const existing = await repo.findMany({ page: 1, per_page: 1 })\n if (existing.meta.total > 0) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'SINGLETON_ALREADY_EXISTS',\n message: `Only one instance of '${contentType.label}' is allowed`,\n },\n },\n 409\n )\n }\n } else {\n const slug = body['slug']\n if (typeof slug !== 'string' || slug.trim() === '') {\n return c.json(\n {\n ok: false,\n error: {\n code: 'VALIDATION_ERROR',\n message: 'Required fields are missing',\n details: [{ field: 'slug', message: 'Slug is required' }],\n },\n },\n 422\n )\n }\n\n if (!SLUG_PATTERN.test(slug)) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'INVALID_SLUG_FORMAT',\n message:\n 'Slug must be lowercase alphanumeric with hyphens only — no leading or trailing hyphens',\n },\n },\n 422\n )\n }\n\n const conflict = await repo.findBySlug(slug)\n if (conflict) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'SLUG_CONFLICT',\n message: `Slug '${slug}' already exists for this content type`,\n },\n },\n 409\n )\n }\n }\n\n return writeNewItem(c, body)\n }\n )\n\n // Shared write paths. POST, PATCH and the singleton PUT all funnel through\n // these two functions so the verbs cannot drift apart — the verb-specific\n // pre-checks (slug rules, singleton existence, 404s) stay in the routes.\n\n const writeNewItem = async (c: Context, body: Record<string, unknown>) => {\n // Inbound boundary: the request body arrives label-keyed; everything\n // downstream (insert data, media delta) works in storage keys.\n const storageBody = fieldKeys.toStorage(body)\n\n const fieldErrors = checkRequiredFields(requiredFields, body)\n if (fieldErrors.length > 0) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'VALIDATION_ERROR',\n message: 'Required fields are missing',\n details: fieldErrors,\n },\n },\n 422\n )\n }\n\n if (body['published'] === true) {\n const publishDeny = await requirePermission('content:edit')(c, async () => {})\n if (publishDeny) return publishDeny\n }\n\n // Classify fields\n const paragraphFields = contentType.fields.filter((f) => f.db_column === null)\n const junctionFields = contentType.fields.filter(\n (f) => f.db_column !== null && f.db_column.junction !== undefined\n )\n\n // Storage-keyed payload. `storageBody` was normalized at the inbound\n // boundary, so each value is found under its column name.\n const columnBackedFields = contentType.fields.filter(isColumnBacked)\n const columnData: Record<string, unknown> = {\n published: body['published'] ?? false,\n }\n if (contentType.only_one) {\n columnData['slug'] = typeName\n } else {\n columnData['slug'] = body['slug']\n }\n for (const f of columnBackedFields) {\n const key = f.db_column.column_name\n if (!(key in storageBody)) continue\n columnData[key] = storageBody[key]\n }\n\n // Resolve base_path_id (seeded from routes.json at startup)\n if (db) {\n const basePathId = await lookupBasePathId(db, contentType.default_base_path)\n if (!basePathId) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'BASE_PATH_NOT_FOUND',\n message: `Base path '${contentType.default_base_path}' not found — run manguito migrate`,\n },\n },\n 500\n )\n }\n columnData['base_path_id'] = basePathId\n }\n\n const item = await repo.create(columnData as Parameters<typeof repo.create>[0])\n const itemId = (item as Record<string, unknown>)['id'] as string\n\n // Reconcile media reference counts across top-level fields and paragraphs.\n const mediaDeltas: MediaDelta[] = [topLevelMediaDelta(mediaFields, null, storageBody)]\n\n // Save paragraph and junction rows\n if (db) {\n for (const f of paragraphFields) {\n const comp = f.ui_component as { component: string; ref?: string }\n if (comp.component !== 'paragraph-embed' || !comp.ref) continue\n const pType = registry.paragraph_types[comp.ref]\n if (!pType) continue\n const items = Array.isArray(body[f.name]) ? (body[f.name] as unknown[]) : []\n mediaDeltas.push(await persistParagraphField(db, itemId, contentType.db.table_name, f.name, pType, items, registry))\n }\n for (const f of junctionFields) {\n const junction = f.db_column!.junction!\n const relatedIds = Array.isArray(body[f.name])\n ? (body[f.name] as unknown[]).filter((v): v is string => typeof v === 'string')\n : []\n await persistJunctionField(db, itemId, junction, relatedIds)\n }\n }\n\n await applyMediaReferenceDelta(mergeMediaDeltas(...mediaDeltas), mediaRepo)\n\n // Outbound boundary: rows are storage-keyed; responses speak labels.\n const data = projectRow(item as Record<string, unknown>, typeName, projectors)\n\n return c.json({ ok: true, data }, 201)\n }\n\n // PATCH /admin/api/{base_path}/:id\n app.patch(\n `/admin/api/${basePath}/:id`,\n requirePermission('content:edit'),\n async (c) => {\n const id = c.req.param('id')\n const body = (await c.req.json()) as Record<string, unknown>\n\n const existing = await repo.findOne(id)\n if (!existing) {\n return c.json(\n { ok: false, error: { code: 'NOT_FOUND', message: 'Not found' } },\n 404\n )\n }\n\n if (!contentType.only_one && 'slug' in body) {\n const slug = body['slug']\n if (typeof slug !== 'string' || slug.trim() === '') {\n return c.json(\n {\n ok: false,\n error: {\n code: 'VALIDATION_ERROR',\n message: 'Required fields are missing',\n details: [{ field: 'slug', message: 'Slug cannot be empty' }],\n },\n },\n 422\n )\n }\n\n if (!SLUG_PATTERN.test(slug)) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'INVALID_SLUG_FORMAT',\n message:\n 'Slug must be lowercase alphanumeric with hyphens only — no leading or trailing hyphens',\n },\n },\n 422\n )\n }\n\n const conflict = await repo.findBySlug(slug)\n if (conflict && (conflict as Record<string, unknown>)['id'] !== id) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'SLUG_CONFLICT',\n message: `Slug '${slug}' already exists for this content type`,\n },\n },\n 409\n )\n }\n }\n\n return writeExistingItem(c, id, existing as Record<string, unknown>, body)\n }\n )\n\n const writeExistingItem = async (\n c: Context,\n id: string,\n existing: Record<string, unknown>,\n body: Record<string, unknown>,\n ) => {\n // Inbound boundary: the request body arrives label-keyed; everything\n // downstream (insert data, media delta) works in storage keys.\n const storageBody = fieldKeys.toStorage(body)\n\n if (body['published'] === true) {\n const publishDeny = await requirePermission('content:edit')(c, async () => {})\n if (publishDeny) return publishDeny\n\n // `existing` is a raw SELECT * row (storage-keyed) but\n // checkRequiredFields reads labels, as does `body` — project the row\n // to labels first so a renamed field's stored value is actually seen.\n const merged = { ...fieldKeys.toLabels(existing as Record<string, unknown>), ...body }\n const fieldErrors = checkRequiredFields(requiredFields, merged)\n if (fieldErrors.length > 0) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'PUBLISH_VALIDATION_ERROR',\n message: 'Cannot publish — required fields are missing',\n details: fieldErrors,\n },\n },\n 422\n )\n }\n }\n\n // Classify fields\n const patchParagraphFields = contentType.fields.filter((f) => f.db_column === null)\n const patchJunctionFields = contentType.fields.filter(\n (f) => f.db_column !== null && f.db_column.junction !== undefined\n )\n\n // Storage-keyed payload. `storageBody` was normalized at the inbound\n // boundary, so each value is found under its column name.\n const patchColumnBackedFields = contentType.fields.filter(isColumnBacked)\n const patchData: Record<string, unknown> = {}\n if (!contentType.only_one && 'slug' in body) patchData['slug'] = body['slug']\n if ('published' in body) patchData['published'] = body['published']\n for (const f of patchColumnBackedFields) {\n const key = f.db_column.column_name\n if (!(key in storageBody)) continue\n patchData[key] = storageBody[key]\n }\n\n const updated = await repo.update(id, patchData as Parameters<typeof repo.update>[1])\n if (!updated) {\n return c.json(\n { ok: false, error: { code: 'NOT_FOUND', message: 'Not found' } },\n 404\n )\n }\n\n // Reconcile media reference counts across top-level fields and paragraphs.\n const mediaDeltas: MediaDelta[] = [\n topLevelMediaDelta(mediaFields, existing as Record<string, unknown>, storageBody),\n ]\n\n // Delete+reinsert paragraph and junction rows\n if (db) {\n for (const f of patchParagraphFields) {\n const comp = f.ui_component as { component: string; ref?: string }\n if (comp.component !== 'paragraph-embed' || !comp.ref) continue\n const pType = registry.paragraph_types[comp.ref]\n if (!pType) continue\n const items = Array.isArray(body[f.name]) ? (body[f.name] as unknown[]) : []\n mediaDeltas.push(await persistParagraphField(db, id, contentType.db.table_name, f.name, pType, items, registry))\n }\n for (const f of patchJunctionFields) {\n const junction = f.db_column!.junction!\n const relatedIds = Array.isArray(body[f.name])\n ? (body[f.name] as unknown[]).filter((v): v is string => typeof v === 'string')\n : []\n await persistJunctionField(db, id, junction, relatedIds)\n }\n }\n\n await applyMediaReferenceDelta(mergeMediaDeltas(...mediaDeltas), mediaRepo)\n\n // Outbound boundary: rows are storage-keyed; responses speak labels.\n const data = projectRow(updated as Record<string, unknown>, typeName, projectors)\n\n return c.json({ ok: true, data })\n }\n\n // PUT /admin/api/{base_path} — singleton upsert.\n //\n // A singleton has no id in its admin route, so it cannot be addressed by the\n // `/:id` PATCH. The parser declares `http_methods: ['GET','PUT','PATCH']` for\n // `only_one` types and the admin form posts here, so this is the verb that\n // completes that contract: create the row on first save, update it after.\n // Registered only for singletons — collections keep POST/PATCH.\n if (contentType.only_one) {\n app.put(\n `/admin/api/${basePath}`,\n requirePermission('content:edit'),\n async (c) => {\n const body = (await c.req.json()) as Record<string, unknown>\n\n const existingResult = await repo.findMany({ page: 1, per_page: 1 })\n const existing = existingResult.data[0] as Record<string, unknown> | undefined\n\n if (!existing) {\n // First save materialises the row, which is a create — gate it on\n // content:create in addition to the route's content:edit.\n const createDeny = await requirePermission('content:create')(c, async () => {})\n if (createDeny) return createDeny\n return writeNewItem(c, body)\n }\n\n return writeExistingItem(c, existing['id'] as string, existing, body)\n }\n )\n }\n\n // DELETE /admin/api/{base_path}/:id\n app.delete(\n `/admin/api/${basePath}/:id`,\n requirePermission('content:delete'),\n async (c) => {\n const id = c.req.param('id')\n const item = await repo.findOne(id)\n\n if (!item) {\n return c.json(\n { ok: false, error: { code: 'NOT_FOUND', message: 'Not found' } },\n 404\n )\n }\n\n // A delete removes every media reference the item held (top-level + paragraphs).\n const mediaDeltas: MediaDelta[] = [\n topLevelMediaDelta(mediaFields, item as Record<string, unknown>, null),\n ]\n\n // Paragraph rows have no FK/cascade back to their parent — clean them up\n // explicitly so they don't leak, and collect their media for decrementing.\n if (db) {\n for (const f of paragraphFieldDefs) {\n const comp = f.ui_component as { component: string; ref?: string }\n if (comp.component !== 'paragraph-embed' || !comp.ref) continue\n const pType = registry.paragraph_types[comp.ref]\n if (!pType) continue\n mediaDeltas.push(await deleteParagraphField(db, id, f.name, pType, registry))\n }\n }\n\n await applyMediaReferenceDelta(mergeMediaDeltas(...mediaDeltas), mediaRepo)\n\n await repo.delete(id)\n return c.json({ ok: true })\n }\n )\n }\n\n // ── Taxonomy type routes ──────────────────────────────────────────────────\n\n for (const [typeName, taxonomyType] of Object.entries(registry.taxonomy_types)) {\n const repo = repos[typeName]\n if (!repo) continue\n\n const schemaFieldNames = new Set<string>([\n ...taxonomyType.fields.map((f) => f.name),\n ...taxonomyType.system_fields.map((f) => f.name),\n ])\n\n const relationFieldNames = new Set<string>(\n taxonomyType.fields\n .filter((f) => RELATION_FIELD_TYPES.has(f.field_type))\n .map((f) => f.name)\n )\n\n const projector = projectors[typeName]!\n const fieldKeys = projector.map\n\n const requiredFields = taxonomyType.fields.filter((f) => f.required)\n\n const mediaFields = taxonomyType.fields.filter(\n (f) => f.field_type === 'image' || f.field_type === 'video' || f.field_type === 'file'\n )\n\n const paragraphFieldDefs = taxonomyType.fields.filter((f) => f.db_column === null)\n\n const searchableColumns = [\n ...taxonomyType.fields\n .filter((f) => f.field_type === 'text/plain' && f.db_column !== null)\n .map((f) => f.db_column!.column_name),\n ...(taxonomyType.system_fields.some((f) => f.name === 'slug') ? ['slug'] : []),\n ]\n\n // GET /admin/api/taxonomy/{type}\n app.get(\n `/admin/api/taxonomy/${typeName}`,\n requirePermission('content:read'),\n async (c) => {\n const parsed = parseListQuery(c.req.url, schemaFieldNames, relationFieldNames, fieldKeys.columnFor)\n if (!parsed.ok) {\n return c.json({ ok: false, error: parsed.response }, parsed.status)\n }\n\n const publishedParam = c.req.query('published')\n const extraFilters: Record<string, FilterValue> = {}\n if (publishedParam === 'false') extraFilters['published'] = false\n\n // parsed.sortBy is a validated label; map it to its storage column\n // before it reaches the repository. The cast is a narrow lie — core\n // types sort_by as the label union, but the repository immediately\n // re-validates the mapped value against sortableColumns.\n const sortColumn = fieldKeys.columnFor(parsed.sortBy) ?? parsed.sortBy\n\n const findOpts: Parameters<typeof repo.findMany>[0] = {\n page: parsed.pagination.page,\n per_page: parsed.pagination.per_page,\n sort_by: sortColumn as 'title' | 'created_at' | 'updated_at',\n sort_order: parsed.sortOrder as 'asc' | 'desc',\n filters: { ...parsed.filters, ...extraFilters },\n include: parsed.include,\n }\n if (publishedParam === 'true') findOpts.published_only = true\n if (parsed.search !== '' && searchableColumns.length > 0) {\n findOpts.search = { term: parsed.search, columns: searchableColumns }\n }\n\n const result = await repo.findMany(findOpts)\n\n // Outbound boundary: rows are storage-keyed; responses speak labels.\n const data = result.data.map((row) =>\n projectRow(row as Record<string, unknown>, typeName, projectors)\n )\n\n return c.json({ ...result, data })\n }\n )\n\n // GET /admin/api/taxonomy/{type}/:id\n app.get(\n `/admin/api/taxonomy/${typeName}/:id`,\n requirePermission('content:read'),\n async (c) => {\n const id = c.req.param('id')\n const item = await repo.findOne(id)\n\n if (!item) {\n return c.json(\n { ok: false, error: { code: 'NOT_FOUND', message: 'Taxonomy term not found' } },\n 404\n )\n }\n\n // Outbound boundary: rows are storage-keyed; responses speak labels.\n const data = projectRow(item as Record<string, unknown>, typeName, projectors)\n\n return c.json({ ok: true, data })\n }\n )\n\n // POST /admin/api/taxonomy/{type}\n app.post(\n `/admin/api/taxonomy/${typeName}`,\n requirePermission('content:create'),\n async (c) => {\n const body = (await c.req.json()) as Record<string, unknown>\n\n // Inbound boundary: the request body arrives label-keyed; everything\n // downstream (insert data, media delta) works in storage keys.\n const storageBody = fieldKeys.toStorage(body)\n\n const fieldErrors = checkRequiredFields(requiredFields, body)\n if (fieldErrors.length > 0) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'VALIDATION_ERROR',\n message: 'Required fields are missing',\n details: fieldErrors,\n },\n },\n 422\n )\n }\n\n if (body['published'] === true) {\n const publishDeny = await requirePermission('content:edit')(c, async () => {})\n if (publishDeny) return publishDeny\n }\n\n // Filter to column-only fields\n const taxParagraphFields = taxonomyType.fields.filter((f) => f.db_column === null)\n\n // Storage-keyed payload. `storageBody` was normalized at the inbound\n // boundary, so each value is found under its column name.\n const taxColumnBackedFields = taxonomyType.fields.filter(isColumnBacked)\n const taxColumnData: Record<string, unknown> = {\n published: body['published'] ?? false,\n }\n for (const f of taxColumnBackedFields) {\n const key = f.db_column.column_name\n if (!(key in storageBody)) continue\n taxColumnData[key] = storageBody[key]\n }\n\n const item = await repo.create(taxColumnData as Parameters<typeof repo.create>[0])\n const taxItemId = (item as Record<string, unknown>)['id'] as string\n\n // Reconcile media reference counts across top-level fields and paragraphs.\n const mediaDeltas: MediaDelta[] = [topLevelMediaDelta(mediaFields, null, storageBody)]\n\n if (db) {\n for (const f of taxParagraphFields) {\n const comp = f.ui_component as { component: string; ref?: string }\n if (comp.component !== 'paragraph-embed' || !comp.ref) continue\n const pType = registry.paragraph_types[comp.ref]\n if (!pType) continue\n const items = Array.isArray(body[f.name]) ? (body[f.name] as unknown[]) : []\n mediaDeltas.push(await persistParagraphField(db, taxItemId, taxonomyType.db.table_name, f.name, pType, items, registry))\n }\n }\n\n await applyMediaReferenceDelta(mergeMediaDeltas(...mediaDeltas), mediaRepo)\n\n // Outbound boundary: rows are storage-keyed; responses speak labels.\n const data = projectRow(item as Record<string, unknown>, typeName, projectors)\n\n return c.json({ ok: true, data }, 201)\n }\n )\n\n // PATCH /admin/api/taxonomy/{type}/:id\n app.patch(\n `/admin/api/taxonomy/${typeName}/:id`,\n requirePermission('content:edit'),\n async (c) => {\n const id = c.req.param('id')\n const body = (await c.req.json()) as Record<string, unknown>\n\n // Inbound boundary: the request body arrives label-keyed; everything\n // downstream (insert data, media delta) works in storage keys.\n const storageBody = fieldKeys.toStorage(body)\n\n const existing = await repo.findOne(id)\n if (!existing) {\n return c.json(\n { ok: false, error: { code: 'NOT_FOUND', message: 'Taxonomy term not found' } },\n 404\n )\n }\n\n if (body['published'] === true) {\n const publishDeny = await requirePermission('content:edit')(c, async () => {})\n if (publishDeny) return publishDeny\n\n // `existing` is a raw SELECT * row (storage-keyed) but\n // checkRequiredFields reads labels, as does `body` — project the row\n // to labels first so a renamed field's stored value is actually seen.\n const merged = { ...fieldKeys.toLabels(existing as Record<string, unknown>), ...body }\n const fieldErrors = checkRequiredFields(requiredFields, merged)\n if (fieldErrors.length > 0) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'PUBLISH_VALIDATION_ERROR',\n message: 'Cannot publish — required fields are missing',\n details: fieldErrors,\n },\n },\n 422\n )\n }\n }\n\n // Filter to column-only fields\n const taxPatchParagraphFields = taxonomyType.fields.filter((f) => f.db_column === null)\n\n // Storage-keyed payload. `storageBody` was normalized at the inbound\n // boundary, so each value is found under its column name.\n const taxPatchColumnBackedFields = taxonomyType.fields.filter(isColumnBacked)\n const taxPatchData: Record<string, unknown> = {}\n if ('published' in body) taxPatchData['published'] = body['published']\n for (const f of taxPatchColumnBackedFields) {\n const key = f.db_column.column_name\n if (!(key in storageBody)) continue\n taxPatchData[key] = storageBody[key]\n }\n\n const updated = await repo.update(id, taxPatchData as Parameters<typeof repo.update>[1])\n if (!updated) {\n return c.json(\n { ok: false, error: { code: 'NOT_FOUND', message: 'Taxonomy term not found' } },\n 404\n )\n }\n\n // Reconcile media reference counts across top-level fields and paragraphs.\n const mediaDeltas: MediaDelta[] = [\n topLevelMediaDelta(mediaFields, existing as Record<string, unknown>, storageBody),\n ]\n\n if (db) {\n for (const f of taxPatchParagraphFields) {\n const comp = f.ui_component as { component: string; ref?: string }\n if (comp.component !== 'paragraph-embed' || !comp.ref) continue\n const pType = registry.paragraph_types[comp.ref]\n if (!pType) continue\n const items = Array.isArray(body[f.name]) ? (body[f.name] as unknown[]) : []\n mediaDeltas.push(await persistParagraphField(db, id, taxonomyType.db.table_name, f.name, pType, items, registry))\n }\n }\n\n await applyMediaReferenceDelta(mergeMediaDeltas(...mediaDeltas), mediaRepo)\n\n // Outbound boundary: rows are storage-keyed; responses speak labels.\n const data = projectRow(updated as Record<string, unknown>, typeName, projectors)\n\n return c.json({ ok: true, data })\n }\n )\n\n // DELETE /admin/api/taxonomy/{type}/:id\n app.delete(\n `/admin/api/taxonomy/${typeName}/:id`,\n requirePermission('content:delete'),\n async (c) => {\n const id = c.req.param('id')\n const item = await repo.findOne(id)\n\n if (!item) {\n return c.json(\n { ok: false, error: { code: 'NOT_FOUND', message: 'Taxonomy term not found' } },\n 404\n )\n }\n\n // A delete removes every media reference the item held (top-level + paragraphs).\n const mediaDeltas: MediaDelta[] = [\n topLevelMediaDelta(mediaFields, item as Record<string, unknown>, null),\n ]\n\n if (db) {\n for (const f of paragraphFieldDefs) {\n const comp = f.ui_component as { component: string; ref?: string }\n if (comp.component !== 'paragraph-embed' || !comp.ref) continue\n const pType = registry.paragraph_types[comp.ref]\n if (!pType) continue\n mediaDeltas.push(await deleteParagraphField(db, id, f.name, pType, registry))\n }\n }\n\n await applyMediaReferenceDelta(mergeMediaDeltas(...mediaDeltas), mediaRepo)\n\n await repo.delete(id)\n return c.json({ ok: true })\n }\n )\n }\n\n // ── Config endpoint ───────────────────────────────────────────────────────\n\n app.get('/admin/api/config', (c) => {\n return c.json({\n ok: true,\n data: {\n content_types: Object.values(registry.content_types).map((ct) => ({\n name: ct.name,\n label: ct.label,\n only_one: ct.only_one,\n default_base_path: ct.default_base_path,\n fields: ct.fields,\n system_fields: ct.system_fields,\n })),\n taxonomy_types: Object.values(registry.taxonomy_types).map((tt) => ({\n name: tt.name,\n label: tt.label,\n fields: tt.fields,\n system_fields: tt.system_fields,\n })),\n enum_types: Object.values(registry.enum_types).map((et) => ({\n name: et.name,\n label: et.label,\n values: et.values,\n })),\n },\n })\n })\n}\n","import type { MediaRepository, ParsedField } from '@bobbykim/manguito-cms-core'\n\n// ─── Media reference tracking ─────────────────────────────────────────────────\n//\n// Reconciles media.reference_count on a content write. reference_count counts\n// *content items* that reference a media row (see CONTEXT.md \"Orphaned media\"\n// and docs/adr/api/0007), not reference slots — so a media id used by several\n// fields/paragraphs of one item counts once, and an id moved between slots in\n// one write is a no-op.\n//\n// A write produces a MediaDelta of the ids it gains and loses. Top-level field\n// deltas are computed here (pure); paragraph deltas are produced by the paragraph\n// persistence path and merged in. The merged delta is applied once.\n\nexport type MediaDelta = {\n added: string[]\n removed: string[]\n}\n\n// A media field holds a raw id string (not a resolved { id, url } object) on the\n// content/paragraph row. Empty string and non-strings mean \"no media\".\nfunction extractMediaId(value: unknown): string | null {\n return typeof value === 'string' && value !== '' ? value : null\n}\n\n// Media ids gained and lost between two content states for the top-level media\n// fields. BOTH `before` and `after` are STORAGE-KEYED (column names), never\n// label-keyed — callers normalize a request body with FieldKeyMap.toStorage\n// before calling. Unifies all three writes:\n// create — before = null (no prior ids; set fields are added)\n// update — before/after are rows (a field absent from `after` is untouched)\n// delete — after = null (every current id is removed)\nexport function topLevelMediaDelta(\n mediaFields: ParsedField[],\n before: Record<string, unknown> | null,\n after: Record<string, unknown> | null\n): MediaDelta {\n const added: string[] = []\n const removed: string[] = []\n\n for (const f of mediaFields) {\n // Media fields are always column-backed, so the storage key always exists.\n const key = f.db_column?.column_name\n if (key === undefined || key === '') continue\n\n // On a partial update, a media field not present in the patch is untouched.\n // (after === null is a delete — every field is in scope.)\n if (after !== null && !(key in after)) continue\n\n const oldId = before ? extractMediaId(before[key]) : null\n const newId = after ? extractMediaId(after[key]) : null\n if (newId === oldId) continue\n if (oldId) removed.push(oldId)\n if (newId) added.push(newId)\n }\n\n return { added, removed }\n}\n\n// Combine the top-level delta with each paragraph field's delta into one delta.\nexport function mergeMediaDeltas(...deltas: MediaDelta[]): MediaDelta {\n return {\n added: deltas.flatMap((d) => d.added),\n removed: deltas.flatMap((d) => d.removed),\n }\n}\n\n// Apply a delta to reference counts, per content item:\n// - dedup each side (an id referenced by many slots counts once)\n// - cancel ids that are both added and removed (moved between slots → no-op)\n// - skip the repo call when a side is empty\nexport async function applyMediaReferenceDelta(\n delta: MediaDelta,\n mediaRepo: MediaRepository\n): Promise<void> {\n const added = new Set(delta.added)\n const removed = new Set(delta.removed)\n\n const netAdd = [...added].filter((id) => !removed.has(id))\n const netRemove = [...removed].filter((id) => !added.has(id))\n\n if (netAdd.length > 0) await mediaRepo.incrementReferenceCount(netAdd)\n if (netRemove.length > 0) await mediaRepo.decrementReferenceCount(netRemove)\n}\n","// Admin media routes — upload, presigned URL, update, delete\nimport type { Context, Hono } from 'hono'\nimport type { MediaRepository, StorageAdapter } from '@bobbykim/manguito-cms-core'\nimport { sign, verify } from 'hono/jwt'\nimport type { createPermissionMiddleware } from '../../middleware/permission.js'\n\n// ─── Accepted MIME types ──────────────────────────────────────────────────────\n\nconst IMAGE_MIME_TYPES = new Set([\n 'image/jpeg',\n 'image/png',\n 'image/webp',\n 'image/gif',\n])\n\nconst VIDEO_MIME_TYPES = new Set(['video/mp4', 'video/webm', 'video/quicktime'])\n\nconst FILE_MIME_TYPES = new Set(['application/pdf'])\n\n// ─── Pending upload token ─────────────────────────────────────────────────────\n// The presigned-url request and the confirm request can land on different\n// serverless instances, so pending-upload state cannot live in memory. Encode it\n// in a short-lived token signed with AUTH_SECRET — stateless and tamper-proof —\n// and hand it to the client as media_id; confirm verifies it.\n\ntype PendingUpload = {\n key: string\n folder: 'image' | 'video' | 'file'\n mime_type: string\n}\n\nfunction authSecret(): string {\n const secret = process.env['AUTH_SECRET']\n if (!secret) throw new Error('AUTH_SECRET environment variable is not set')\n return secret\n}\n\nasync function signPendingUpload(data: PendingUpload, expiresAt: number): Promise<string> {\n return sign({ ...data, exp: expiresAt }, authSecret())\n}\n\nasync function verifyPendingUpload(token: string): Promise<PendingUpload | null> {\n try {\n const p = await verify(token, authSecret(), 'HS256') as {\n key: string\n folder: 'image' | 'video' | 'file'\n mime_type: string\n }\n return { key: p.key, folder: p.folder, mime_type: p.mime_type }\n } catch {\n return null\n }\n}\n\n\nasync function handleDirectUpload(\n folder: 'image' | 'video' | 'file',\n acceptedTypes: Set<string>,\n mediaRepo: MediaRepository,\n storage: StorageAdapter,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n c: Context<any, any, any>,\n altRequired: boolean,\n maxFileSize: number | undefined\n): Promise<Response> {\n // Direct uploads buffer the whole file in the CMS server's memory (unlike the\n // presigned flow, which uploads straight to storage). Reject oversized bodies\n // up front via Content-Length so we never buffer them — and keep max_file_size\n // under the platform payload limit on serverless (Lambda ~6 MB).\n if (maxFileSize !== undefined) {\n const contentLength = Number(c.req.header('content-length'))\n if (Number.isFinite(contentLength) && contentLength > maxFileSize) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'FILE_TOO_LARGE',\n message: `Upload exceeds the maximum size of ${maxFileSize} bytes`,\n },\n },\n 413\n )\n }\n }\n\n let formData: FormData\n try {\n formData = await c.req.formData()\n } catch {\n return c.json(\n { ok: false, error: { code: 'VALIDATION_ERROR', message: 'Expected multipart/form-data' } },\n 422\n )\n }\n\n const fileField = formData.get('file')\n if (!(fileField instanceof File)) {\n return c.json(\n { ok: false, error: { code: 'VALIDATION_ERROR', message: 'file field is required' } },\n 422\n )\n }\n\n // Defensive second check: Content-Length may be absent (chunked) or count\n // multipart overhead, so also reject on the actual decoded file size.\n if (maxFileSize !== undefined && fileField.size > maxFileSize) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'FILE_TOO_LARGE',\n message: `File exceeds the maximum size of ${maxFileSize} bytes`,\n },\n },\n 413\n )\n }\n\n const mimeType = fileField.type\n if (!acceptedTypes.has(mimeType)) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'UNSUPPORTED_MIME_TYPE',\n message: `MIME type '${mimeType}' is not accepted by this endpoint`,\n },\n },\n 415\n )\n }\n\n const altValue = formData.get('alt')\n const alt = typeof altValue === 'string' && altValue.trim() !== '' ? altValue.trim() : undefined\n\n if (altRequired && !alt) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'VALIDATION_ERROR',\n message: 'alt is required for this media type',\n details: [{ field: 'alt', message: 'alt is required' }],\n },\n },\n 422\n )\n }\n\n let presigned: Awaited<ReturnType<StorageAdapter['getPresignedUploadUrl']>>\n try {\n presigned = await storage.getPresignedUploadUrl({\n folder,\n filename: fileField.name,\n mime_type: mimeType,\n })\n } catch {\n return c.json(\n { ok: false, error: { code: 'STORAGE_ERROR', message: 'Failed to generate upload URL' } },\n 502\n )\n }\n\n if (storage.upload) {\n try {\n const bytes = new Uint8Array(await fileField.arrayBuffer())\n await storage.upload(presigned.key, bytes, mimeType)\n } catch (err) {\n console.error('[media] storage.upload error:', err)\n return c.json(\n { ok: false, error: { code: 'STORAGE_ERROR', message: 'Storage upload failed' } },\n 502\n )\n }\n }\n\n const url = storage.getUrl(presigned.key)\n const fileSize = fileField.size\n\n const item = await mediaRepo.create({\n type: folder,\n url,\n mime_type: mimeType,\n ...(alt !== undefined && { alt }),\n file_size: fileSize,\n })\n\n return c.json({ ok: true, data: item }, 201)\n}\n\n// ─── Register ─────────────────────────────────────────────────────────────────\n\nexport function registerAdminMediaRoutes(\n app: Hono,\n mediaRepo: MediaRepository,\n storage: StorageAdapter,\n requirePermission: ReturnType<typeof createPermissionMiddleware>,\n maxFileSize: number | undefined,\n): void {\n // POST /admin/api/media/image\n app.post(\n '/admin/api/media/image',\n requirePermission('media:create'),\n async (c) => {\n return handleDirectUpload('image', IMAGE_MIME_TYPES, mediaRepo, storage, c, false, maxFileSize)\n }\n )\n\n // POST /admin/api/media/video\n app.post(\n '/admin/api/media/video',\n requirePermission('media:create'),\n async (c) => {\n return handleDirectUpload('video', VIDEO_MIME_TYPES, mediaRepo, storage, c, true, maxFileSize)\n }\n )\n\n // POST /admin/api/media/file\n app.post(\n '/admin/api/media/file',\n requirePermission('media:create'),\n async (c) => {\n return handleDirectUpload('file', FILE_MIME_TYPES, mediaRepo, storage, c, true, maxFileSize)\n }\n )\n\n // GET /admin/api/media/presigned-url\n app.get('/admin/api/media/presigned-url', requirePermission('media:create'), async (c) => {\n const type = c.req.query('type') as 'image' | 'video' | 'file' | undefined\n const filename = c.req.query('filename')\n const mimeType = c.req.query('mime_type')\n\n if (!type || !['image', 'video', 'file'].includes(type)) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'VALIDATION_ERROR',\n message: 'type must be one of: image, video, file',\n details: [{ field: 'type', message: 'type is required' }],\n },\n },\n 422\n )\n }\n\n if (!filename || filename.trim() === '') {\n return c.json(\n {\n ok: false,\n error: {\n code: 'VALIDATION_ERROR',\n message: 'filename is required',\n details: [{ field: 'filename', message: 'filename is required' }],\n },\n },\n 422\n )\n }\n\n if (!mimeType || mimeType.trim() === '') {\n return c.json(\n {\n ok: false,\n error: {\n code: 'VALIDATION_ERROR',\n message: 'mime_type is required',\n details: [{ field: 'mime_type', message: 'mime_type is required' }],\n },\n },\n 422\n )\n }\n\n const accepted =\n type === 'image' ? IMAGE_MIME_TYPES : type === 'video' ? VIDEO_MIME_TYPES : FILE_MIME_TYPES\n\n if (!accepted.has(mimeType)) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'UNSUPPORTED_MIME_TYPE',\n message: `MIME type '${mimeType}' is not accepted for ${type} uploads`,\n },\n },\n 415\n )\n }\n\n let presigned: Awaited<ReturnType<StorageAdapter['getPresignedUploadUrl']>>\n try {\n presigned = await storage.getPresignedUploadUrl({\n folder: type,\n filename,\n mime_type: mimeType,\n })\n } catch {\n return c.json(\n { ok: false, error: { code: 'STORAGE_ERROR', message: 'Failed to generate upload URL' } },\n 502\n )\n }\n\n const media_id = await signPendingUpload(\n { key: presigned.key, folder: type, mime_type: mimeType },\n presigned.expires_at,\n )\n\n return c.json({\n ok: true,\n data: {\n upload_url: presigned.upload_url,\n // Signed, self-contained token the client posts back to /confirm/:id.\n media_id,\n expires_at: presigned.expires_at,\n // Present for storages that need a multipart POST with signed fields\n // (Cloudinary); absent for a raw PUT (S3).\n ...(presigned.method && { method: presigned.method }),\n ...(presigned.fields && { fields: presigned.fields }),\n },\n })\n })\n\n // POST /admin/api/media/confirm/:id\n app.post('/admin/api/media/confirm/:id', requirePermission('media:create'), async (c) => {\n const id = c.req.param('id')\n\n const pending = await verifyPendingUpload(id)\n if (!pending) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'PRESIGNED_URL_EXPIRED',\n message: 'Presigned upload not found or has expired',\n },\n },\n 410\n )\n }\n\n const body = (await c.req.json().catch(() => ({}))) as Record<string, unknown>\n const altValue = typeof body['alt'] === 'string' ? body['alt'].trim() : undefined\n\n const altRequired = pending.folder === 'video' || pending.folder === 'file'\n if (altRequired && !altValue) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'VALIDATION_ERROR',\n message: 'alt is required for video and file uploads',\n details: [{ field: 'alt', message: 'alt is required' }],\n },\n },\n 422\n )\n }\n\n // Validate the actual uploaded object where the adapter supports it.\n let fileSize = 0\n if (storage.stat) {\n const meta = await storage.stat(pending.key)\n if (!meta) {\n return c.json(\n { ok: false, error: { code: 'STORAGE_ERROR', message: 'Uploaded object not found' } },\n 502,\n )\n }\n const accepted =\n pending.folder === 'image' ? IMAGE_MIME_TYPES\n : pending.folder === 'video' ? VIDEO_MIME_TYPES\n : FILE_MIME_TYPES\n if (meta.content_type && !accepted.has(meta.content_type)) {\n await storage.delete(pending.key)\n return c.json(\n { ok: false, error: { code: 'UNSUPPORTED_MIME_TYPE', message: `Stored object type '${meta.content_type}' is not accepted` } },\n 415,\n )\n }\n if (maxFileSize !== undefined && meta.size > maxFileSize) {\n await storage.delete(pending.key)\n return c.json(\n { ok: false, error: { code: 'FILE_TOO_LARGE', message: `Uploaded file exceeds the ${maxFileSize} byte limit` } },\n 413,\n )\n }\n fileSize = meta.size\n }\n\n const url = storage.getUrl(pending.key)\n\n const item = await mediaRepo.create({\n type: pending.folder,\n url,\n mime_type: pending.mime_type,\n ...(altValue !== undefined && { alt: altValue }),\n file_size: fileSize,\n })\n\n return c.json({ ok: true, data: item }, 201)\n })\n\n // PATCH /admin/api/media/:id\n app.patch(\n '/admin/api/media/:id',\n requirePermission('media:edit'),\n async (c) => {\n const id = c.req.param('id')\n const existing = await mediaRepo.findOne(id)\n\n if (!existing) {\n return c.json(\n { ok: false, error: { code: 'NOT_FOUND', message: 'Media item not found' } },\n 404\n )\n }\n\n const body = (await c.req.json()) as Record<string, unknown>\n const alt = typeof body['alt'] === 'string' ? body['alt'].trim() : undefined\n\n const updateData: Parameters<typeof mediaRepo.update>[1] = {}\n if (alt !== undefined) updateData.alt = alt\n\n const updated = await mediaRepo.update(id, updateData)\n if (!updated) {\n return c.json(\n { ok: false, error: { code: 'NOT_FOUND', message: 'Media item not found' } },\n 404\n )\n }\n\n return c.json({ ok: true, data: updated })\n }\n )\n\n // DELETE /admin/api/media/:id\n app.delete(\n '/admin/api/media/:id',\n requirePermission('media:delete'),\n async (c) => {\n const id = c.req.param('id')\n const item = await mediaRepo.findOne(id)\n\n if (!item) {\n return c.json(\n { ok: false, error: { code: 'NOT_FOUND', message: 'Media item not found' } },\n 404\n )\n }\n\n if (item.reference_count > 0) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'MEDIA_IN_USE',\n message: 'Cannot delete media that is referenced by content items',\n },\n },\n 409\n )\n }\n\n // Derive storage key from URL.\n // Cloudinary delivery URLs: https://res.cloudinary.com/{cloud}/{type}/upload/{public_id}\n // Local delivery URLs: http://localhost/uploads/{key}\n // For Cloudinary, the adapter key is the public_id — everything after \"/upload/\".\n // For local (and S3), the adapter key is the full path after the host.\n const pathname = new URL(item.url).pathname\n const uploadIdx = pathname.indexOf('/upload/')\n const key = uploadIdx >= 0\n ? pathname.slice(uploadIdx + '/upload/'.length)\n : pathname.replace(/^\\//, '')\n\n try {\n await storage.delete(key)\n } catch (err) {\n console.error('[media] storage.delete error:', err)\n return c.json(\n {\n ok: false,\n error: { code: 'STORAGE_ERROR', message: 'Failed to delete file from storage' },\n },\n 502\n )\n }\n\n await mediaRepo.delete(id)\n\n return c.json({ ok: true })\n }\n )\n\n // GET /admin/api/media\n app.get('/admin/api/media', requirePermission('media:read'), async (c) => {\n const pageStr = c.req.query('page')\n const perPageStr = c.req.query('per_page')\n const page = pageStr !== undefined ? Number(pageStr) : 1\n const per_page = perPageStr !== undefined ? Number(perPageStr) : 10\n\n if (\n !Number.isInteger(page) ||\n page < 1 ||\n !Number.isInteger(per_page) ||\n per_page < 1 ||\n per_page > 100\n ) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'INVALID_PAGINATION',\n message: 'page must be ≥ 1 and per_page must be between 1 and 100',\n },\n },\n 400\n )\n }\n\n const typeParam = c.req.query('type')\n if (typeParam !== undefined && !['image', 'video', 'file'].includes(typeParam)) {\n return c.json(\n {\n ok: false,\n error: { code: 'VALIDATION_ERROR', message: 'type must be one of: image, video, file' },\n },\n 422\n )\n }\n\n const orphanedParam = c.req.query('orphaned')\n const orphaned =\n orphanedParam === 'true' ? true : orphanedParam === 'false' ? false : undefined\n\n const opts: Parameters<typeof mediaRepo.findMany>[0] = { page, per_page }\n if (typeParam !== undefined) opts.type = typeParam as 'image' | 'video' | 'file'\n if (orphaned !== undefined) opts.orphaned = orphaned\n\n const result = await mediaRepo.findMany(opts)\n\n return c.json(result)\n })\n\n // GET /admin/api/media/:id\n app.get('/admin/api/media/:id', requirePermission('media:read'), async (c) => {\n const id = c.req.param('id')\n const item = await mediaRepo.findOne(id)\n\n if (!item) {\n return c.json(\n { ok: false, error: { code: 'NOT_FOUND', message: 'Media item not found' } },\n 404\n )\n }\n\n return c.json({ ok: true, data: item })\n })\n\n}\n","import type { Hono } from 'hono'\nimport { getCookie } from 'hono/cookie'\nimport { sql } from 'drizzle-orm'\nimport type { DrizzlePostgresInstance } from '@bobbykim/manguito-cms-db'\nimport { verifyPassword } from '../../auth/password.js'\nimport {\n signToken,\n verifyToken,\n setAuthCookie,\n clearAuthCookie,\n AUTH_TOKEN_TTL,\n REFRESH_TOKEN_TTL,\n} from '../../auth/jwt.js'\nimport { createAuthMiddleware } from '../../middleware/auth.js'\n\n// ─── Rate limiting ────────────────────────────────────────────────────────────\n\nconst loginAttempts = new Map<string, number[]>()\nconst globalLoginAttempts: number[] = []\n\nconst RATE_LIMIT_MAX = 10\nconst RATE_LIMIT_WINDOW_MS = 15 * 60 * 1000\n// Global backstop across all IPs/emails — blunts distributed spraying without\n// per-account lockout (ADR api/0005). Sized well above legitimate concurrent\n// login volume for a self-hosted CMS.\nconst GLOBAL_LOGIN_MAX = 100\n\n// A valid cost-12 bcrypt hash used only to equalize timing on the\n// user-not-found path, so login response time cannot enumerate accounts.\nconst DUMMY_PASSWORD_HASH =\n '$2a$12$y.MZw/Q4Ceg3x1y3xrRaeuTaM09zSDx1nn78guO3bqc9vgOqGda42'\n\nfunction checkRateLimit(key: string): { allowed: boolean; retryAfterSeconds: number } {\n const now = Date.now()\n const windowStart = now - RATE_LIMIT_WINDOW_MS\n\n // Global ceiling first — purge, then evaluate before recording the per-key hit.\n while (globalLoginAttempts.length > 0 && globalLoginAttempts[0]! <= windowStart) {\n globalLoginAttempts.shift()\n }\n if (globalLoginAttempts.length >= GLOBAL_LOGIN_MAX) {\n const retryAfterSeconds = Math.ceil((globalLoginAttempts[0]! + RATE_LIMIT_WINDOW_MS - now) / 1000)\n return { allowed: false, retryAfterSeconds }\n }\n\n const attempts = (loginAttempts.get(key) ?? []).filter((t) => t > windowStart)\n attempts.push(now)\n loginAttempts.set(key, attempts)\n globalLoginAttempts.push(now)\n\n if (attempts.length > RATE_LIMIT_MAX) {\n const oldestInWindow = attempts[0]!\n const retryAfterSeconds = Math.ceil((oldestInWindow + RATE_LIMIT_WINDOW_MS - now) / 1000)\n return { allowed: false, retryAfterSeconds }\n }\n\n return { allowed: true, retryAfterSeconds: 0 }\n}\n\n/** Test-only: clears in-process login rate-limit state. Not part of the public API. */\nexport function __resetLoginRateLimitStateForTests(): void {\n loginAttempts.clear()\n globalLoginAttempts.length = 0\n}\n\n// ─── DB row types ─────────────────────────────────────────────────────────────\n\ntype LoginUserRow = {\n id: string\n email: string\n password_hash: string\n role_id: string\n token_version: number\n}\n\ntype RefreshRow = {\n token_version: number\n role: string\n}\n\n// ─── Register ─────────────────────────────────────────────────────────────────\n\nexport function registerAuthRoutes(app: Hono, db: DrizzlePostgresInstance): void {\n const invalidCredentials = {\n ok: false,\n error: { code: 'INVALID_CREDENTIALS', message: 'Invalid email or password.' },\n } as const\n\n // POST /admin/api/auth/login\n app.post('/admin/api/auth/login', async (c) => {\n let body: { email?: unknown; password?: unknown }\n try {\n body = (await c.req.json()) as { email?: unknown; password?: unknown }\n } catch {\n return c.json(invalidCredentials, 401)\n }\n\n const email =\n typeof body.email === 'string' ? body.email.trim().toLowerCase() : null\n const password = typeof body.password === 'string' ? body.password : null\n\n if (!email || !password) {\n return c.json(invalidCredentials, 401)\n }\n\n const ip =\n c.req.header('x-forwarded-for')?.split(',')[0]?.trim() ?? 'unknown'\n const rateCheck = checkRateLimit(`${ip}:${email}`)\n if (!rateCheck.allowed) {\n c.header('Retry-After', String(rateCheck.retryAfterSeconds))\n return c.json(\n {\n ok: false,\n error: {\n code: 'RATE_LIMITED',\n message: 'Too many login attempts. Please try again later.',\n },\n },\n 429,\n )\n }\n\n const userResult = await db.execute(\n sql`SELECT id, email, password_hash, role_id, token_version\n FROM users WHERE email = ${email} LIMIT 1`,\n )\n const user = userResult.rows[0] as LoginUserRow | undefined\n\n if (!user) {\n // Run a dummy comparison so this path pays the same bcrypt cost as a\n // real user, closing the user-enumeration timing side-channel.\n await verifyPassword(password, DUMMY_PASSWORD_HASH)\n return c.json(invalidCredentials, 401)\n }\n\n const passwordValid = await verifyPassword(password, user.password_hash)\n if (!passwordValid) {\n return c.json(invalidCredentials, 401)\n }\n\n const roleResult = await db.execute(\n sql`SELECT r.name AS name, u.must_change_password\n FROM roles r\n JOIN users u ON u.role_id = r.id\n WHERE r.id = ${user.role_id} AND u.id = ${user.id}\n LIMIT 1`,\n )\n const roleRow = roleResult.rows[0] as { name: string; must_change_password: boolean } | undefined\n\n if (!roleRow) {\n return c.json(\n { ok: false, error: { code: 'INVALID_ROLE', message: 'User role not found.' } },\n 500,\n )\n }\n\n const [authToken, refreshToken] = await Promise.all([\n signToken(\n { user_id: user.id, role: roleRow.name, token_version: user.token_version },\n AUTH_TOKEN_TTL,\n ),\n signToken(\n { user_id: user.id, role: roleRow.name, token_version: user.token_version },\n REFRESH_TOKEN_TTL,\n ),\n ])\n\n setAuthCookie(c, 'auth_token', authToken, { path: '/' })\n setAuthCookie(c, 'refresh_token', refreshToken, { path: '/admin/api/auth' })\n\n return c.json({\n ok: true,\n data: {\n id: user.id,\n email: user.email,\n role: roleRow.name,\n must_change_password: roleRow.must_change_password,\n },\n })\n })\n\n // POST /admin/api/auth/refresh\n app.post('/admin/api/auth/refresh', async (c) => {\n const token = getCookie(c, 'refresh_token')\n if (!token) {\n return c.json(\n { ok: false, error: { code: 'UNAUTHORIZED', message: 'Authentication required.' } },\n 401,\n )\n }\n\n let payload\n try {\n payload = await verifyToken(token)\n } catch (err) {\n const code = (err as { code?: string }).code\n if (code === 'TOKEN_EXPIRED') {\n return c.json(\n { ok: false, error: { code: 'TOKEN_EXPIRED', message: 'Token has expired.' } },\n 401,\n )\n }\n return c.json(\n { ok: false, error: { code: 'TOKEN_INVALID', message: 'Token signature is invalid.' } },\n 401,\n )\n }\n\n const rowResult = await db.execute(\n sql`SELECT u.token_version, r.name AS role\n FROM users u\n JOIN roles r ON r.id = u.role_id\n WHERE u.id = ${payload.user_id}\n LIMIT 1`,\n )\n const row = rowResult.rows[0] as RefreshRow | undefined\n\n if (!row || payload.token_version !== row.token_version) {\n return c.json(\n { ok: false, error: { code: 'TOKEN_INVALID', message: 'Token has been invalidated.' } },\n 401,\n )\n }\n\n const newAuthToken = await signToken(\n { user_id: payload.user_id, role: row.role, token_version: row.token_version },\n AUTH_TOKEN_TTL,\n )\n setAuthCookie(c, 'auth_token', newAuthToken, { path: '/' })\n\n return c.json({ ok: true })\n })\n\n // POST /admin/api/auth/logout\n const authMiddleware = createAuthMiddleware(db)\n app.post('/admin/api/auth/logout', authMiddleware, async (c) => {\n const user = (c as unknown as { get(k: 'user'): { id: string; role: string } }).get('user')\n\n await db.execute(\n sql`UPDATE users SET token_version = token_version + 1 WHERE id = ${user.id}`,\n )\n\n clearAuthCookie(c, 'auth_token')\n clearAuthCookie(c, 'refresh_token', '/admin/api/auth')\n\n return c.json({ ok: true })\n })\n}\n","export { hashPassword, verifyPassword } from '@bobbykim/manguito-cms-core'\n","import type { Hono } from 'hono'\nimport { sql } from 'drizzle-orm'\nimport { randomBytes } from 'node:crypto'\nimport type { DrizzlePostgresInstance } from '@bobbykim/manguito-cms-db'\nimport { hashPassword, verifyPassword } from '../../auth/password.js'\nimport type { createPermissionMiddleware } from '../../middleware/permission.js'\nimport type { createHierarchyMiddleware } from '../../middleware/hierarchy.js'\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\ntype ActingUser = { id: string; role: string }\n\ntype UserRow = {\n id: string\n email: string\n role: string\n must_change_password: boolean\n created_at: string\n updated_at: string\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\nfunction getActingUser(c: unknown): ActingUser {\n return (c as { get(k: 'user'): ActingUser }).get('user')\n}\n\nfunction generateTempPassword(): string {\n return randomBytes(8).toString('base64url')\n}\n\n// ─── Register ─────────────────────────────────────────────────────────────────\n\nexport function registerUserRoutes(\n app: Hono,\n db: DrizzlePostgresInstance,\n requirePermission: ReturnType<typeof createPermissionMiddleware>,\n requireHierarchy: ReturnType<typeof createHierarchyMiddleware>,\n): void {\n // POST /admin/api/users/change-password\n // Registered first — exempt from mustChangePasswordCheck (enforced by path check in that middleware).\n // No requirePermission — available to any authenticated user.\n app.post('/admin/api/users/change-password', async (c) => {\n const actingUser = getActingUser(c)\n\n let body: { current_password?: unknown; new_password?: unknown }\n try {\n body = (await c.req.json()) as { current_password?: unknown; new_password?: unknown }\n } catch {\n return c.json(\n { ok: false, error: { code: 'INVALID_CREDENTIALS', message: 'Invalid credentials.' } },\n 401,\n )\n }\n\n const currentPassword =\n typeof body.current_password === 'string' ? body.current_password : null\n const newPassword = typeof body.new_password === 'string' ? body.new_password : null\n\n if (!currentPassword || !newPassword) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'VALIDATION_ERROR',\n message: 'current_password and new_password are required.',\n },\n },\n 422,\n )\n }\n\n const hashResult = await db.execute(\n sql`SELECT password_hash FROM users WHERE id = ${actingUser.id} LIMIT 1`,\n )\n const hashRow = hashResult.rows[0] as { password_hash: string } | undefined\n\n if (!hashRow || !(await verifyPassword(currentPassword, hashRow.password_hash))) {\n return c.json(\n { ok: false, error: { code: 'INVALID_CREDENTIALS', message: 'Invalid credentials.' } },\n 401,\n )\n }\n\n const newHash = await hashPassword(newPassword)\n\n await db.execute(\n sql`UPDATE users\n SET password_hash = ${newHash},\n must_change_password = false,\n token_version = token_version + 1,\n updated_at = NOW()\n WHERE id = ${actingUser.id}`,\n )\n\n return c.json({ ok: true })\n })\n\n // GET /admin/api/users\n app.get('/admin/api/users', requirePermission('users:read'), async (c) => {\n const result = await db.execute(\n sql`SELECT u.id, u.email, r.name AS role, u.must_change_password,\n u.created_at, u.updated_at\n FROM users u\n JOIN roles r ON r.id = u.role_id\n ORDER BY u.created_at ASC`,\n )\n\n return c.json({ ok: true, data: result.rows as UserRow[] })\n })\n\n // GET /admin/api/users/:id\n app.get('/admin/api/users/:id', requirePermission('users:read'), async (c) => {\n const id = c.req.param('id')\n\n const result = await db.execute(\n sql`SELECT u.id, u.email, r.name AS role, u.must_change_password,\n u.created_at, u.updated_at\n FROM users u\n JOIN roles r ON r.id = u.role_id\n WHERE u.id = ${id}\n LIMIT 1`,\n )\n const row = result.rows[0] as UserRow | undefined\n\n if (!row) {\n return c.json(\n { ok: false, error: { code: 'NOT_FOUND', message: 'User not found.' } },\n 404,\n )\n }\n\n return c.json({ ok: true, data: row })\n })\n\n // POST /admin/api/users\n app.post(\n '/admin/api/users',\n requirePermission('users:create'),\n requireHierarchy(),\n async (c) => {\n let body: { email?: unknown; role?: unknown }\n try {\n body = (await c.req.json()) as { email?: unknown; role?: unknown }\n } catch {\n return c.json(\n { ok: false, error: { code: 'VALIDATION_ERROR', message: 'Request body is required.' } },\n 422,\n )\n }\n\n const email =\n typeof body.email === 'string' ? body.email.trim().toLowerCase() : null\n const roleName = typeof body.role === 'string' ? body.role : null\n\n if (!email) {\n return c.json(\n { ok: false, error: { code: 'VALIDATION_ERROR', message: 'email is required.' } },\n 422,\n )\n }\n if (!roleName) {\n return c.json(\n { ok: false, error: { code: 'INVALID_ROLE', message: 'role is required.' } },\n 400,\n )\n }\n\n const roleResult = await db.execute(\n sql`SELECT id FROM roles WHERE name = ${roleName} LIMIT 1`,\n )\n const roleRow = roleResult.rows[0] as { id: string } | undefined\n\n if (!roleRow) {\n return c.json(\n {\n ok: false,\n error: { code: 'INVALID_ROLE', message: `Role \"${roleName}\" does not exist.` },\n },\n 400,\n )\n }\n\n const tempPassword = generateTempPassword()\n const passwordHash = await hashPassword(tempPassword)\n\n const insertResult = await db.execute(\n sql`INSERT INTO users\n (id, email, password_hash, role_id, must_change_password, token_version,\n created_at, updated_at)\n VALUES\n (gen_random_uuid(), ${email}, ${passwordHash}, ${roleRow.id},\n true, 0, NOW(), NOW())\n RETURNING id, email, must_change_password, created_at, updated_at`,\n )\n const newUser = insertResult.rows[0] as\n | {\n id: string\n email: string\n must_change_password: boolean\n created_at: string\n updated_at: string\n }\n | undefined\n\n if (!newUser) {\n return c.json(\n { ok: false, error: { code: 'INTERNAL_ERROR', message: 'Failed to create user.' } },\n 500,\n )\n }\n\n return c.json(\n {\n ok: true,\n data: {\n id: newUser.id,\n email: newUser.email,\n role: roleName,\n must_change_password: newUser.must_change_password,\n temporary_password: tempPassword,\n created_at: newUser.created_at,\n updated_at: newUser.updated_at,\n },\n },\n 201,\n )\n },\n )\n\n // PATCH /admin/api/users/:id\n app.patch(\n '/admin/api/users/:id',\n requirePermission('users:edit'),\n requireHierarchy(),\n async (c) => {\n const id = c.req.param('id')\n const actingUser = getActingUser(c)\n\n let body: { email?: unknown; role?: unknown }\n try {\n body = (await c.req.json()) as { email?: unknown; role?: unknown }\n } catch {\n body = {}\n }\n\n // Self role change is never permitted\n if (actingUser.id === id && body.role !== undefined) {\n return c.json(\n {\n ok: false,\n error: { code: 'INSUFFICIENT_PRIVILEGE', message: 'You cannot change your own role.' },\n },\n 403,\n )\n }\n\n const existingResult = await db.execute(\n sql`SELECT id, email, role_id FROM users WHERE id = ${id} LIMIT 1`,\n )\n const existing = existingResult.rows[0] as\n | { id: string; email: string; role_id: string }\n | undefined\n\n if (!existing) {\n return c.json(\n { ok: false, error: { code: 'NOT_FOUND', message: 'User not found.' } },\n 404,\n )\n }\n\n const newEmail =\n typeof body.email === 'string' ? body.email.trim().toLowerCase() : existing.email\n\n let newRoleId = existing.role_id\n let changeRole = false\n\n if (typeof body.role === 'string' && body.role) {\n const roleResult = await db.execute(\n sql`SELECT id FROM roles WHERE name = ${body.role} LIMIT 1`,\n )\n const roleRow = roleResult.rows[0] as { id: string } | undefined\n\n if (!roleRow) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'INVALID_ROLE',\n message: `Role \"${body.role}\" does not exist.`,\n },\n },\n 400,\n )\n }\n\n newRoleId = roleRow.id\n changeRole = true\n }\n\n if (changeRole) {\n await db.execute(\n sql`UPDATE users\n SET email = ${newEmail},\n role_id = ${newRoleId},\n token_version = token_version + 1,\n updated_at = NOW()\n WHERE id = ${id}`,\n )\n } else {\n await db.execute(\n sql`UPDATE users\n SET email = ${newEmail},\n role_id = ${newRoleId},\n updated_at = NOW()\n WHERE id = ${id}`,\n )\n }\n\n const updatedResult = await db.execute(\n sql`SELECT u.id, u.email, r.name AS role, u.must_change_password,\n u.created_at, u.updated_at\n FROM users u\n JOIN roles r ON r.id = u.role_id\n WHERE u.id = ${id}\n LIMIT 1`,\n )\n const updated = updatedResult.rows[0] as UserRow | undefined\n\n if (!updated) {\n return c.json(\n { ok: false, error: { code: 'NOT_FOUND', message: 'User not found.' } },\n 404,\n )\n }\n\n return c.json({ ok: true, data: updated })\n },\n )\n\n // DELETE /admin/api/users/:id\n app.delete(\n '/admin/api/users/:id',\n requirePermission('users:delete'),\n requireHierarchy(),\n async (c) => {\n const id = c.req.param('id')\n const actingUser = getActingUser(c)\n\n if (actingUser.id === id) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'INSUFFICIENT_PRIVILEGE',\n message: 'You cannot delete your own account.',\n },\n },\n 403,\n )\n }\n\n const result = await db.execute(\n sql`DELETE FROM users WHERE id = ${id} RETURNING id`,\n )\n\n if ((result.rows as unknown[]).length === 0) {\n return c.json(\n { ok: false, error: { code: 'NOT_FOUND', message: 'User not found.' } },\n 404,\n )\n }\n\n return c.json({ ok: true })\n },\n )\n\n // POST /admin/api/users/:id/reset-password\n app.post(\n '/admin/api/users/:id/reset-password',\n requirePermission('users:edit'),\n requireHierarchy(),\n async (c) => {\n const id = c.req.param('id')\n const actingUser = getActingUser(c)\n\n if (actingUser.id === id) {\n return c.json(\n {\n ok: false,\n error: {\n code: 'INSUFFICIENT_PRIVILEGE',\n message: 'You cannot reset your own password via this endpoint.',\n },\n },\n 403,\n )\n }\n\n const existingResult = await db.execute(\n sql`SELECT id FROM users WHERE id = ${id} LIMIT 1`,\n )\n const existing = existingResult.rows[0] as { id: string } | undefined\n\n if (!existing) {\n return c.json(\n { ok: false, error: { code: 'NOT_FOUND', message: 'User not found.' } },\n 404,\n )\n }\n\n const tempPassword = generateTempPassword()\n const passwordHash = await hashPassword(tempPassword)\n\n await db.execute(\n sql`UPDATE users\n SET password_hash = ${passwordHash},\n must_change_password = true,\n token_version = token_version + 1,\n updated_at = NOW()\n WHERE id = ${id}`,\n )\n\n return c.json({ ok: true, data: { temporary_password: tempPassword } })\n },\n )\n}\n","import { sql } from 'drizzle-orm'\nimport type { Hono } from 'hono'\nimport type { DrizzlePostgresInstance } from '@bobbykim/manguito-cms-db'\nimport type { RolesRegistry } from '../../auth/registry.js'\n\n// ─── Version — injected by tsup at build time; falls back in test/dev ─────────\n\ndeclare const __API_VERSION__: string\n\nconst API_VERSION: string = (() => {\n try { return __API_VERSION__ } catch { return '0.0.0' }\n})()\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\ntype ActingUser = { id: string; role: string }\n\ntype ConfigOptions = {\n name: string\n maxFileSize?: number\n presignedUploads?: boolean\n}\n\n// ─── Register ─────────────────────────────────────────────────────────────────\n\nexport function registerConfigRoute(\n app: Hono,\n config: ConfigOptions,\n registry: RolesRegistry,\n db: DrizzlePostgresInstance,\n): void {\n app.get('/admin/api/config', async (c) => {\n const actingUser = (c as unknown as { get(k: 'user'): ActingUser }).get('user')\n const actingRole = registry[actingUser.role]\n const actingLevel = actingRole?.hierarchy_level ?? Infinity\n\n // Subordinate roles only — used by user management forms for role assignment.\n const roles = Object.values(registry)\n .filter((r) => r.name !== 'admin' && r.hierarchy_level > actingLevel)\n .sort((a, b) => a.hierarchy_level - b.hierarchy_level)\n .map((r) => ({ name: r.name, label: r.label, hierarchy_level: r.hierarchy_level }))\n\n // All roles with full data — used by the admin panel to resolve permissions.\n const all_roles = Object.values(registry)\n .sort((a, b) => a.hierarchy_level - b.hierarchy_level)\n .map((r) => ({\n name: r.name,\n label: r.label,\n hierarchy_level: r.hierarchy_level,\n is_system: r.is_system,\n permissions: r.permissions,\n }))\n\n const userResult = await db.execute(\n sql`SELECT email, must_change_password FROM users WHERE id = ${actingUser.id} LIMIT 1`\n )\n const userRow = userResult.rows[0] as\n | { email: string; must_change_password: boolean }\n | undefined\n\n return c.json({\n ok: true,\n data: {\n cms_name: config.name ?? 'Manguito CMS',\n version: API_VERSION,\n roles,\n all_roles,\n user: {\n id: actingUser.id,\n email: userRow?.email ?? '',\n role: actingUser.role,\n must_change_password: userRow?.must_change_password ?? false,\n },\n media: {\n max_file_size: config.maxFileSize ?? null,\n presigned_uploads: config.presignedUploads ?? false,\n },\n },\n })\n })\n}\n","import { sql } from 'drizzle-orm'\nimport type { Hono } from 'hono'\nimport type { ParsedField, SchemaRegistry } from '@bobbykim/manguito-cms-core'\nimport type { DrizzlePostgresInstance } from '@bobbykim/manguito-cms-db'\n\n// A tombstone (`removed: true`) keeps its column alive for older live\n// versions but must not be exposed by the CURRENT version — which is all\n// this endpoint ever serves. The admin panel has no other path to schema\n// data (packages/admin/src/stores/schema.ts's setFromApiSchema is the only\n// consumer), so filtering here is what keeps a dead column out of every\n// admin form and list view.\nfunction liveFields(fields: ParsedField[]): ParsedField[] {\n return fields.filter((f) => f.removed !== true)\n}\n\n// ─── Register ─────────────────────────────────────────────────────────────────\n\nexport function registerSchemaRoute(\n app: Hono,\n registry: SchemaRegistry,\n db: DrizzlePostgresInstance,\n): void {\n // Full schema — used by the admin panel for form rendering, validation, and navigation.\n // Returns complete ParsedField objects (validation, ui_component) so the admin\n // forms can render inputs and run client-side validation without extra round-trips.\n app.get('/admin/api/schema', (c) => {\n const content_types = Object.values(registry.content_types).map((ct) => ({\n name: ct.name,\n label: ct.label,\n only_one: ct.only_one,\n ui: ct.ui,\n system_fields: ct.system_fields,\n fields: liveFields(ct.fields),\n }))\n\n const taxonomy_types = Object.values(registry.taxonomy_types).map((tt) => ({\n name: tt.name,\n label: tt.label,\n system_fields: tt.system_fields,\n fields: liveFields(tt.fields),\n }))\n\n const paragraph_types = Object.values(registry.paragraph_types).map((pt) => ({\n name: pt.name,\n label: pt.label,\n system_fields: pt.system_fields,\n fields: liveFields(pt.fields),\n }))\n\n const enum_types = Object.values(registry.enum_types).map((et) => ({\n name: et.name,\n label: et.label,\n values: et.values,\n }))\n\n return c.json({\n ok: true,\n data: { content_types, taxonomy_types, paragraph_types, enum_types },\n })\n })\n\n // Navigation meta-endpoints — list types with item counts for admin sidebar\n app.get('/admin/api/content', async (c) => {\n const data = await Promise.all(\n Object.values(registry.content_types).map(async (ct) => {\n const result = await db.execute(\n sql`SELECT COUNT(*)::int AS count FROM ${sql.raw(`\"${ct.db.table_name}\"`)}`\n )\n const count = Number((result.rows[0] as { count: string | number }).count)\n return { name: ct.name, label: ct.label, only_one: ct.only_one, count }\n })\n )\n return c.json({ ok: true, data })\n })\n\n app.get('/admin/api/taxonomy', async (c) => {\n const data = await Promise.all(\n Object.values(registry.taxonomy_types).map(async (tt) => {\n const result = await db.execute(\n sql`SELECT COUNT(*)::int AS count FROM ${sql.raw(`\"${tt.db.table_name}\"`)}`\n )\n const count = Number((result.rows[0] as { count: string | number }).count)\n return { name: tt.name, label: tt.label, count }\n })\n )\n return c.json({ ok: true, data })\n })\n}\n","import crypto from 'node:crypto'\nimport { sql, type SQL } from 'drizzle-orm'\nimport type { DrizzlePostgresInstance } from '@bobbykim/manguito-cms-db'\nimport type {\n ContentRepository,\n PaginatedResult,\n FindManyOptions,\n FindAllOptions,\n CreateInput,\n UpdateInput,\n FilterOperator,\n FilterValue,\n} from '@bobbykim/manguito-cms-core'\nimport {\n resolveRelationField,\n resolveRelationBareIds,\n type RelationDef,\n} from '../relations.js'\n\n// ─── Repository options ───────────────────────────────────────────────────────\n\nexport type ContentRepositoryOptions = {\n relations?: Record<string, RelationDef>\n /** When true, relation targets (reference/junction) are filtered to published\n * rows only. Set by the public repos in app.ts; admin repos leave this false\n * so drafts remain visible to admin. */\n publishedRelations?: boolean\n /** Storage columns valid for ORDER BY, supplied by the route once it has\n * mapped a validated label to its column. Optional for backward\n * compatibility: when absent, falls back to validating sort_by against\n * SORTABLE_FIELDS (labels), the pre-versioning behavior. */\n sortableColumns?: Set<string>\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\nconst SORTABLE_FIELDS = new Set(['title', 'created_at', 'updated_at'])\n\nfunction codeError(code: string, message: string): Error {\n const err = new Error(message)\n ;(err as Error & { code: string }).code = code\n return err\n}\n\n// Only allow table/column identifiers that are developer-controlled.\n// Table names can include hyphens (e.g. content--blog_post).\nfunction quoteIdent(name: string): string {\n if (!/^[a-z][a-z0-9_-]*$/.test(name)) {\n throw new Error(`Unsafe SQL identifier: ${name}`)\n }\n return `\"${name.replace(/\"/g, '\"\"')}\"`\n}\n\n// User-supplied filter field names — strict, no hyphens.\nfunction quoteField(name: string): string {\n if (!/^[a-z][a-z0-9_]*$/.test(name)) {\n throw codeError('INVALID_FILTER_FIELD', `Invalid filter field name: ${name}`)\n }\n return `\"${name}\"`\n}\n\nfunction buildConditions(\n published_only: boolean | undefined,\n filters: Record<string, FilterValue> | undefined,\n search: { term: string; columns: string[] } | undefined\n): SQL[] {\n const conds: SQL[] = []\n\n if (published_only) {\n conds.push(sql`published = true`)\n }\n\n if (search && search.term.trim() !== '' && search.columns.length > 0) {\n const pattern = `%${search.term.trim()}%`\n const orConds = search.columns.map((col) => sql`${sql.raw(quoteField(col))} ILIKE ${pattern}`)\n conds.push(sql`(${sql.join(orConds, sql` OR `)})`)\n }\n\n if (filters) {\n for (const [field, value] of Object.entries(filters)) {\n const col = sql.raw(quoteField(field))\n\n if (Array.isArray(value)) {\n if (value.length === 0) continue\n const inList = sql.join(\n value.map((v) => sql`${v}`),\n sql`, `\n )\n conds.push(sql`${col} IN (${inList})`)\n } else if (\n typeof value === 'object' &&\n value !== null\n ) {\n const op = value as FilterOperator\n if (op.gt !== undefined) conds.push(sql`${col} > ${op.gt}`)\n if (op.gte !== undefined) conds.push(sql`${col} >= ${op.gte}`)\n if (op.lt !== undefined) conds.push(sql`${col} < ${op.lt}`)\n if (op.lte !== undefined) conds.push(sql`${col} <= ${op.lte}`)\n } else {\n conds.push(sql`${col} = ${value}`)\n }\n }\n }\n\n return conds\n}\n\nfunction whereFragment(conds: SQL[]): SQL {\n return conds.length > 0\n ? sql` WHERE ${sql.join(conds, sql` AND `)}`\n : sql``\n}\n\n// ─── Factory ──────────────────────────────────────────────────────────────────\n\nexport function createDrizzleContentRepository<T>(\n db: DrizzlePostgresInstance,\n tableName: string,\n options: ContentRepositoryOptions = {}\n): ContentRepository<T> {\n const { relations = {}, publishedRelations = false, sortableColumns } = options\n\n function tableRaw(): SQL {\n return sql.raw(quoteIdent(tableName))\n }\n\n async function resolveRows(\n rows: Record<string, unknown>[],\n include: string[],\n cache: Map<string, unknown>\n ): Promise<Record<string, unknown>[]> {\n if (rows.length === 0) return rows\n\n // Validate include fields\n for (const fieldName of include) {\n if (!relations[fieldName]) {\n throw codeError('INVALID_INCLUDE_FIELD', `'${fieldName}' is not a valid relation field`)\n }\n }\n\n // Always resolve media fields; for all other relations not explicitly\n // included, populate bare IDs so the field is never silently dropped.\n for (const [fieldName, rel] of Object.entries(relations)) {\n if (rel.type === 'media') {\n await resolveRelationField(db, rows, fieldName, rel, cache, publishedRelations)\n } else if (!include.includes(fieldName)) {\n await resolveRelationBareIds(db, rows, fieldName, rel)\n }\n }\n\n // Resolve explicitly included non-media fields\n for (const fieldName of include) {\n const rel = relations[fieldName]!\n if (rel.type !== 'media') {\n await resolveRelationField(db, rows, fieldName, rel, cache, publishedRelations)\n }\n }\n\n return rows\n }\n\n return {\n async findMany(opts: FindManyOptions): Promise<PaginatedResult<T>> {\n const {\n page = 1,\n per_page = 10,\n include = [],\n published_only,\n filters,\n sort_by = 'created_at',\n sort_order = 'asc',\n search,\n } = opts\n\n // Sorting is validated against COLUMNS when the caller supplies the set\n // (the route has already validated the label and mapped it). Without one,\n // fall back to the label allowlist — the pre-versioning behavior.\n const allowed = sortableColumns ?? SORTABLE_FIELDS\n if (!allowed.has(sort_by as string)) {\n // Deliberately does NOT enumerate `allowed`: when the caller supplied\n // `sortableColumns` that set holds STORAGE COLUMNS, which are private\n // to the database and must never reach a client envelope. The REST\n // routes validate the label before calling in and emit their own\n // message naming the valid LABELS (routes/content.ts,\n // routes/admin/content.ts); this guard is the backstop behind them.\n throw codeError('INVALID_SORT_FIELD', `'${sort_by}' is not sortable.`)\n }\n\n const offset = (page - 1) * per_page\n const conds = buildConditions(published_only, filters, search)\n const where = whereFragment(conds)\n const sortCol = sql.raw(quoteIdent(sort_by))\n const sortDir = sql.raw(sort_order === 'desc' ? 'DESC' : 'ASC')\n\n const countResult = await db.execute(\n sql`SELECT COUNT(*) AS total FROM ${tableRaw()}${where}`\n )\n const total = Number((countResult.rows[0] as Record<string, unknown>)?.['total'] ?? 0)\n\n const dataResult = await db.execute(\n sql`SELECT * FROM ${tableRaw()}${where} ORDER BY ${sortCol} ${sortDir} LIMIT ${per_page} OFFSET ${offset}`\n )\n\n const rows = dataResult.rows as Record<string, unknown>[]\n const cache = new Map<string, unknown>()\n await resolveRows(rows, include, cache)\n\n const total_pages = Math.ceil(total / per_page)\n return {\n ok: true,\n data: rows as T[],\n meta: {\n total,\n page,\n per_page,\n total_pages,\n has_next: page < total_pages,\n has_prev: page > 1,\n },\n }\n },\n\n async findOne(id: string, include: string[] = []): Promise<T | null> {\n const result = await db.execute(\n sql`SELECT * FROM ${tableRaw()} WHERE id = ${id} LIMIT 1`\n )\n if (result.rows.length === 0) return null\n\n const rows = result.rows as Record<string, unknown>[]\n const cache = new Map<string, unknown>()\n await resolveRows(rows, include, cache)\n return rows[0] as T\n },\n\n async findBySlug(slug: string, include: string[] = []): Promise<T | null> {\n const result = await db.execute(\n sql`SELECT * FROM ${tableRaw()} WHERE slug = ${slug} LIMIT 1`\n )\n if (result.rows.length === 0) return null\n\n const rows = result.rows as Record<string, unknown>[]\n const cache = new Map<string, unknown>()\n await resolveRows(rows, include, cache)\n return rows[0] as T\n },\n\n async create(data: CreateInput<T>): Promise<T> {\n const record: Record<string, unknown> = {\n ...(data as Record<string, unknown>),\n id: crypto.randomUUID(),\n created_at: new Date(),\n updated_at: new Date(),\n }\n const entries = Object.entries(record).filter(([, v]) => v !== undefined)\n const colsSql = sql.join(entries.map(([k]) => sql.raw(quoteIdent(k))), sql`, `)\n const valsSql = sql.join(entries.map(([, v]) => sql`${v}`), sql`, `)\n\n const result = await db.execute(\n sql`INSERT INTO ${tableRaw()} (${colsSql}) VALUES (${valsSql}) RETURNING *`\n )\n return result.rows[0] as T\n },\n\n async update(id: string, data: UpdateInput<T>): Promise<T | null> {\n const record: Record<string, unknown> = {\n ...(data as Record<string, unknown>),\n updated_at: new Date(),\n }\n const entries = Object.entries(record).filter(([k, v]) => k !== 'id' && v !== undefined)\n if (entries.length === 0) {\n return this.findOne(id)\n }\n\n const setClauses = sql.join(\n entries.map(([k, v]) => sql`${sql.raw(quoteIdent(k))} = ${v}`),\n sql`, `\n )\n\n const result = await db.execute(\n sql`UPDATE ${tableRaw()} SET ${setClauses} WHERE id = ${id} RETURNING *`\n )\n if (result.rows.length === 0) return null\n return result.rows[0] as T\n },\n\n async delete(id: string): Promise<void> {\n await db.execute(sql`DELETE FROM ${tableRaw()} WHERE id = ${id}`)\n },\n\n async findAll(opts: FindAllOptions = {}): Promise<T[]> {\n const { include = [], published_only } = opts\n const conds = buildConditions(published_only, undefined, undefined)\n const where = whereFragment(conds)\n\n const result = await db.execute(\n sql`SELECT * FROM ${tableRaw()}${where} ORDER BY created_at ASC`\n )\n\n const rows = result.rows as Record<string, unknown>[]\n const cache = new Map<string, unknown>()\n await resolveRows(rows, include, cache)\n return rows as T[]\n },\n }\n}\n","import crypto from 'node:crypto'\nimport { sql } from 'drizzle-orm'\nimport type { DrizzlePostgresInstance } from '@bobbykim/manguito-cms-db'\nimport type {\n MediaRepository,\n MediaItem,\n CreateMediaInput,\n MediaFindManyOptions,\n PaginatedResult,\n} from '@bobbykim/manguito-cms-core'\n\nexport function createMediaRepository(db: DrizzlePostgresInstance): MediaRepository {\n const TABLE = sql.raw('\"media\"')\n\n return {\n async findMany(opts: MediaFindManyOptions = {}): Promise<PaginatedResult<MediaItem>> {\n const { page = 1, per_page = 10, type, orphaned } = opts\n const offset = (page - 1) * per_page\n\n const conds = []\n if (type) conds.push(sql`type = ${type}`)\n if (orphaned) conds.push(sql`reference_count = 0`)\n\n const where =\n conds.length > 0 ? sql` WHERE ${sql.join(conds, sql` AND `)}` : sql``\n\n const countResult = await db.execute(sql`SELECT COUNT(*) AS total FROM ${TABLE}${where}`)\n const total = Number(\n (countResult.rows[0] as Record<string, unknown>)?.['total'] ?? 0\n )\n\n const dataResult = await db.execute(\n sql`SELECT * FROM ${TABLE}${where} ORDER BY created_at DESC LIMIT ${per_page} OFFSET ${offset}`\n )\n\n const total_pages = Math.ceil(total / per_page)\n return {\n ok: true,\n data: dataResult.rows as MediaItem[],\n meta: {\n total,\n page,\n per_page,\n total_pages,\n has_next: page < total_pages,\n has_prev: page > 1,\n },\n }\n },\n\n async findOne(id: string): Promise<MediaItem | null> {\n const result = await db.execute(\n sql`SELECT * FROM ${TABLE} WHERE id = ${id} LIMIT 1`\n )\n if (result.rows.length === 0) return null\n return result.rows[0] as MediaItem\n },\n\n async create(data: CreateMediaInput): Promise<MediaItem> {\n const record: Record<string, unknown> = {\n ...data,\n id: crypto.randomUUID(),\n reference_count: 0,\n created_at: new Date(),\n updated_at: new Date(),\n }\n const entries = Object.entries(record).filter(([, v]) => v !== undefined)\n const colsSql = sql.join(entries.map(([k]) => sql.raw(`\"${k}\"`)), sql`, `)\n const valsSql = sql.join(entries.map(([, v]) => sql`${v}`), sql`, `)\n\n const result = await db.execute(\n sql`INSERT INTO ${TABLE} (${colsSql}) VALUES (${valsSql}) RETURNING *`\n )\n return result.rows[0] as MediaItem\n },\n\n async update(id: string, data: Partial<MediaItem>): Promise<MediaItem | null> {\n const record: Record<string, unknown> = { ...data, updated_at: new Date() }\n const entries = Object.entries(record).filter(\n ([k, v]) => k !== 'id' && v !== undefined\n )\n if (entries.length === 0) return this.findOne(id)\n\n const setClauses = sql.join(\n entries.map(([k, v]) => sql`${sql.raw(`\"${k}\"`)} = ${v}`),\n sql`, `\n )\n\n const result = await db.execute(\n sql`UPDATE ${TABLE} SET ${setClauses} WHERE id = ${id} RETURNING *`\n )\n if (result.rows.length === 0) return null\n return result.rows[0] as MediaItem\n },\n\n async delete(id: string): Promise<void> {\n await db.execute(sql`DELETE FROM ${TABLE} WHERE id = ${id}`)\n },\n\n async incrementReferenceCount(ids: string[]): Promise<void> {\n if (ids.length === 0) return\n const inList = sql.join(ids.map((id) => sql`${id}`), sql`, `)\n await db.execute(\n sql`UPDATE ${TABLE} SET reference_count = reference_count + 1, updated_at = NOW() WHERE id IN (${inList})`\n )\n },\n\n async decrementReferenceCount(ids: string[]): Promise<void> {\n if (ids.length === 0) return\n const inList = sql.join(ids.map((id) => sql`${id}`), sql`, `)\n await db.execute(\n sql`UPDATE ${TABLE} SET reference_count = GREATEST(reference_count - 1, 0), updated_at = NOW() WHERE id IN (${inList})`\n )\n },\n }\n}\n","import type { ContentRepository, ParsedField, SchemaRegistry, VersionProjection } from '@bobbykim/manguito-cms-core'\nimport { createFieldKeyMap, createFieldKeyMapFromProjection, type FieldKeyMap } from './field-keys.js'\nimport { buildProjectors, type Projectors } from './projector.js'\nimport { createVersionedPaths, type VersionedPaths } from './paths.js'\nimport { SORTABLE_FIELDS } from './routes/query-params.js'\n\n// ─── The baked version model ──────────────────────────────────────────────────\n//\n// What `manguito build` writes into .manguito/version-model.ts and the\n// generated server entry passes to createCmsApp.\n//\n// This is `Omit<VersionModel, 'union'>`. The union IS the current registry\n// (`union === current` in core, by reference), and createCmsApp already\n// receives that registry — so baking the union too would duplicate the whole\n// registry in the generated bundle, and the reference identity that made it\n// free in memory does not survive serialization.\nexport type BakedVersionModel = {\n /** e.g. 'v4' — the working schema's version. */\n current: string\n /** Oldest first, including current. */\n live: string[]\n /** Keyed by version name. */\n projections: Record<string, VersionProjection>\n}\n\nexport type VersionClass = 'not-a-version' | 'live' | 'retired' | 'unknown'\n\nconst VERSION_SEGMENT = /^v\\d+$/\n\nfunction versionNumber(version: string): number {\n return Number.parseInt(version.slice(1), 10)\n}\n\n/**\n * What a URL's version segment is.\n *\n * The retired/unknown split is arithmetic on the model alone, with nothing\n * persisted: `current` is the highest snapshot plus one, so a number BELOW it\n * that is not live must have been cut and later retired, while a number at or\n * above it was never cut. A retired version answering 404 would read as \"wrong\n * URL\" and send a pinned consumer hunting for a typo instead of upgrading.\n *\n * `not-a-version` is load-bearing, not defensive: media stays unversioned, so\n * the catch-all sees `/api/media/:id` too and must fall through for it.\n */\nexport function classifyVersion(segment: string, model: BakedVersionModel): VersionClass {\n if (!VERSION_SEGMENT.test(segment)) return 'not-a-version'\n if (model.live.includes(segment)) return 'live'\n return versionNumber(segment) < versionNumber(model.current) ? 'retired' : 'unknown'\n}\n\n// ─── The version-scoped surface ────────────────────────────────────────────────\n//\n// Everything one live version needs to serve its own slice of the schema-driven\n// public API: its paths (under its own segment, or none for the unversioned\n// pass), a field-key map per type, projectors built from those maps, and one\n// repository per content/taxonomy type. Task 6 calls this once per live\n// version — plus once more for the unversioned pass — and mounts the result.\n\n/**\n * A projection's declared fallbacks, re-keyed for `buildProjectors`: type name\n * → exposed label → fallback value. The projection already keys fallbacks by\n * column (`fallback` sits beside `column_name`/`exposed_as` per field); this\n * flips them onto the label space `TypeProjector.fallbacks` and `projectRow`\n * actually read against.\n */\nfunction fallbacksFor(projection: VersionProjection): Record<string, Record<string, unknown>> {\n const out: Record<string, Record<string, unknown>> = {}\n for (const [typeName, type] of Object.entries(projection.types)) {\n const perLabel: Record<string, unknown> = {}\n for (const f of type.fields) {\n if (f.fallback !== undefined) perLabel[f.exposed_as] = f.fallback\n }\n if (Object.keys(perLabel).length > 0) out[typeName] = perLabel\n }\n return out\n}\n\nexport function buildVersionSurface<Row>(input: {\n /** null = the unversioned pass: paths carry no version segment. */\n version: string | null\n /** Which projection's labels this surface serves. For the unversioned pass,\n * the caller passes `model.current` — the working schema's own labels. */\n projectionVersion: string\n prefix: string\n registry: SchemaRegistry\n model: BakedVersionModel\n /** Injected so this module needs no database import: it stays testable\n * without one, and app.ts keeps ownership of repository construction. */\n makeRepo: (typeName: string, tableName: string, sortableColumns: Set<string>) => ContentRepository<Row>\n}): {\n paths: VersionedPaths\n fieldKeyMaps: Record<string, FieldKeyMap>\n projectors: Projectors\n repos: Record<string, ContentRepository<Row>>\n} {\n const { version, projectionVersion, prefix, registry, model, makeRepo } = input\n\n const paths = createVersionedPaths(prefix, version)\n const projection = model.projections[projectionVersion]\n\n // One map per content, taxonomy AND paragraph type — the same key space\n // `buildProjectors` and `sortableColumnsFor` below both index into.\n //\n // Paragraph types have no projection: core's `buildProjections` iterates\n // content and taxonomy types only. For those, and for any type this\n // projection happens to omit, fall back to the registry's own fields —\n // nested paragraph content follows current's shape on every version, by\n // design, not by oversight.\n const fieldKeyMaps: Record<string, FieldKeyMap> = {}\n const sources: Array<Record<string, { fields: ParsedField[] }>> = [\n registry.content_types,\n registry.taxonomy_types,\n registry.paragraph_types,\n ]\n for (const source of sources) {\n for (const [typeName, type] of Object.entries(source)) {\n const projectionType = projection?.types[typeName]\n fieldKeyMaps[typeName] = projectionType\n ? createFieldKeyMapFromProjection(projectionType, type.fields)\n : createFieldKeyMap(type.fields)\n }\n }\n\n const fallbacks = projection ? fallbacksFor(projection) : undefined\n const projectors = buildProjectors(registry, fieldKeyMaps, fallbacks)\n\n // Maps SORTABLE_FIELDS' labels to THIS version's storage columns. A rename\n // moves the column a version sorts by, so this must read the per-version\n // map above, never current's.\n //\n // A label absent from this version's map is EXCLUDED, not passed through\n // as a bogus literal: `columnFor` only returns undefined for a label this\n // type has no field for at all (SORTABLE_FIELDS is shared across every\n // type), or a system field (id/slug/created_at/...), which is not part of\n // the field-key map but is still legitimately sortable — its column IS its\n // own name. Anything else unresolved has no business in the repository's\n // sortable-columns allow-set.\n const sortableColumnsFor = (typeName: string): Set<string> => {\n const map = fieldKeyMaps[typeName]!\n const type = registry.content_types[typeName] ?? registry.taxonomy_types[typeName]\n const systemFieldNames = new Set((type?.system_fields ?? []).map((f) => f.name))\n const out = new Set<string>()\n for (const label of SORTABLE_FIELDS) {\n const column = map.columnFor(label)\n if (column !== undefined) out.add(column)\n else if (systemFieldNames.has(label)) out.add(label)\n }\n return out\n }\n\n const repos: Record<string, ContentRepository<Row>> = {}\n for (const [typeName, ct] of Object.entries(registry.content_types)) {\n repos[typeName] = makeRepo(typeName, ct.db.table_name, sortableColumnsFor(typeName))\n }\n for (const [typeName, tt] of Object.entries(registry.taxonomy_types)) {\n repos[typeName] = makeRepo(typeName, tt.db.table_name, sortableColumnsFor(typeName))\n }\n\n return { paths, fieldKeyMaps, projectors, repos }\n}\n\n// ─── Deprecation headers ────────────────────────────────────────────────────\n\n/**\n * The headers a response carries, or null for none.\n *\n * Two different mistakes get two different messages: the unversioned path\n * FLOATS and will change under the consumer when a version is cut; an older\n * live version is BEHIND. A consumer on the current version's own path has\n * made neither mistake and gets nothing.\n *\n * The unversioned path stays silent while only one version is live. The\n * warning would be technically true — cutting v2 later does move it — but it\n * is not yet actionable risk, and emitting it would nag every project that\n * never opted into versioning the moment it rebuilt.\n */\nexport function deprecationHeaders(input: {\n requested: string | null // null = the unversioned path\n model: BakedVersionModel\n successor: string // the URL to point at\n}): Record<string, string> | null {\n const { requested, model, successor } = input\n const link = `<${successor}>; rel=\"successor-version\"`\n\n if (requested === null) {\n // Silent while only one version is live: the warning would be technically\n // true, since cutting v2 later does move this path, but it is not yet\n // actionable risk and would nag every project that never opted in.\n if (model.live.length <= 1) return null\n return {\n Deprecation: 'true',\n Link: link,\n Warning:\n `299 - \"Unversioned path resolves to the latest version (${model.current}) and will ` +\n `change when a new version is cut. Pin a version.\"`,\n }\n }\n\n // A consumer on the current version's own path has made no mistake.\n if (requested === model.current) return null\n\n return { Deprecation: 'true', Link: link }\n}\n","import type { ServerAdapter, CorsConfig } from '@bobbykim/manguito-cms-core'\n\nexport type NodeServerOptions = {\n port?: number\n base_url?: string\n cors?: Partial<CorsConfig>\n}\n\nexport function createServer(options: NodeServerOptions = {}): ServerAdapter {\n const port = options.port ?? Number(process.env['PORT'] ?? 3000)\n const _base_url = options.base_url ?? `http://localhost:${port}`\n\n const cors: CorsConfig = {\n origin: options.cors?.origin ?? (process.env['ALLOWED_ORIGIN'] ?? '*'),\n methods: options.cors?.methods ?? [\n 'GET',\n 'POST',\n 'PUT',\n 'PATCH',\n 'DELETE',\n ],\n credentials: options.cors?.credentials ?? true,\n }\n\n return {\n type: 'node',\n cors,\n getEntryPoint(): string {\n return 'node'\n },\n }\n}\n"],"mappings":"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","names":["crypto","import_drizzle_orm","import_graphql","import_graphql","codeError","isPublished","import_graphql","import_graphql","DataLoader","import_graphql","import_drizzle_orm","import_cookie","jwtVerify","value","parsePagination","import_drizzle_orm","quoteIdent","row","import_jwt","import_cookie","import_drizzle_orm","import_drizzle_orm","import_node_crypto","import_drizzle_orm","import_drizzle_orm","import_node_crypto","import_drizzle_orm","SORTABLE_FIELDS","quoteIdent","crypto","import_node_crypto","import_drizzle_orm","crypto","createGraphQLHandler"]}