@bobbykim/manguito-cms-api 0.8.0 → 0.8.2

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.mjs CHANGED
@@ -38,7 +38,7 @@ function createVersionedPaths(prefix, version) {
38
38
 
39
39
  // src/app.ts
40
40
  import { Hono } from "hono";
41
- import { sql as sql10 } from "drizzle-orm";
41
+ import { sql as sql11 } from "drizzle-orm";
42
42
 
43
43
  // src/middleware/cors.ts
44
44
  function createCorsMiddleware(corsConfig) {
@@ -1049,7 +1049,7 @@ function registerPublicMediaRoutes(app, mediaRepo, paths, listRateLimit) {
1049
1049
  }
1050
1050
 
1051
1051
  // src/routes/admin/content.ts
1052
- import { sql as sql3 } from "drizzle-orm";
1052
+ import { sql as sql4 } from "drizzle-orm";
1053
1053
 
1054
1054
  // src/references-in-use.ts
1055
1055
  import { sql as sql2 } from "drizzle-orm";
@@ -1134,6 +1134,7 @@ async function applyMediaReferenceDelta(delta, mediaRepo) {
1134
1134
  }
1135
1135
 
1136
1136
  // src/relation-input.ts
1137
+ import { sql as sql3 } from "drizzle-orm";
1137
1138
  import { relationCardinality as relationCardinality2 } from "@bobbykim/manguito-cms-core";
1138
1139
  var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1139
1140
  function isUuid(v) {
@@ -1178,15 +1179,91 @@ function checkRelationInput(fields, body, registry, path = "") {
1178
1179
  }
1179
1180
  return errors;
1180
1181
  }
1182
+ function paragraphItemsIn(fields, body, registry, path) {
1183
+ const out = [];
1184
+ for (const field of fields) {
1185
+ if (field.field_type !== "paragraph" || !(field.name in body)) continue;
1186
+ const comp = field.ui_component;
1187
+ const pType = comp.component === "paragraph-embed" ? registry.paragraph_types[comp.ref] : void 0;
1188
+ if (!pType) continue;
1189
+ const value = body[field.name];
1190
+ const name = `${path}${field.name}`;
1191
+ if (relationCardinality2(field) === "one") {
1192
+ if (isPlainObject(value)) out.push({ item: value, fields: pType.fields, prefix: `${name}.` });
1193
+ } else if (Array.isArray(value)) {
1194
+ value.forEach((item, i) => {
1195
+ if (isPlainObject(item)) out.push({ item, fields: pType.fields, prefix: `${name}[${i}].` });
1196
+ });
1197
+ }
1198
+ }
1199
+ return out;
1200
+ }
1201
+ function isMissing(value) {
1202
+ if (value === null || value === void 0) return true;
1203
+ if (typeof value === "string" && value.trim() === "") return true;
1204
+ if (Array.isArray(value) && value.length === 0) return true;
1205
+ return false;
1206
+ }
1207
+ function checkRequiredInParagraphItems(fields, body, registry, path = "") {
1208
+ const errors = [];
1209
+ for (const { item, fields: itemFields, prefix } of paragraphItemsIn(fields, body, registry, path)) {
1210
+ for (const f of itemFields) {
1211
+ if (!f.required || f.removed === true || !f.db_column || f.db_column.junction) continue;
1212
+ if (isMissing(item[f.name])) {
1213
+ errors.push({ field: `${prefix}${f.name}`, message: `${prefix}${f.name} is required.` });
1214
+ }
1215
+ }
1216
+ errors.push(...checkRequiredInParagraphItems(itemFields, item, registry, prefix));
1217
+ }
1218
+ return errors;
1219
+ }
1220
+ function quoteIdent2(name) {
1221
+ if (!/^[a-z][a-z0-9_-]*$/.test(name)) throw new Error(`Unsafe identifier: ${name}`);
1222
+ return `"${name}"`;
1223
+ }
1224
+ function collectReferences(fields, body, registry, path, out) {
1225
+ for (const f of fields) {
1226
+ if (!(f.name in body) || !f.db_column) continue;
1227
+ const table = f.db_column.junction ? f.db_column.junction.right_table : f.db_column.foreign_key?.table;
1228
+ if (!table) continue;
1229
+ const value = body[f.name];
1230
+ const name = `${path}${f.name}`;
1231
+ if (typeof value === "string") {
1232
+ out.push({ table, id: value, field: name });
1233
+ } else if (Array.isArray(value)) {
1234
+ value.forEach((v, i) => {
1235
+ if (typeof v === "string") out.push({ table, id: v, field: `${name}[${i}]` });
1236
+ });
1237
+ }
1238
+ }
1239
+ for (const { item, fields: itemFields, prefix } of paragraphItemsIn(fields, body, registry, path)) {
1240
+ collectReferences(itemFields, item, registry, prefix, out);
1241
+ }
1242
+ }
1243
+ async function findMissingReferences(db, fields, body, registry) {
1244
+ const uses = [];
1245
+ collectReferences(fields, body, registry, "", uses);
1246
+ if (uses.length === 0) return [];
1247
+ const lookups = [...new Set(uses.map((u) => u.table))].map(async (table) => {
1248
+ const ids = [...new Set(uses.filter((u) => u.table === table && isUuid(u.id)).map((u) => u.id.toLowerCase()))];
1249
+ if (ids.length === 0) return [table, /* @__PURE__ */ new Set()];
1250
+ const result = await db.execute(
1251
+ sql3`SELECT id FROM ${sql3.raw(quoteIdent2(table))} WHERE id IN (${sql3.join(ids.map((id) => sql3`${id}`), sql3`, `)})`
1252
+ );
1253
+ return [table, new Set(result.rows.map((r) => String(r.id).toLowerCase()))];
1254
+ });
1255
+ const found = new Map(await Promise.all(lookups));
1256
+ return uses.filter((u) => !found.get(u.table).has(u.id.toLowerCase())).map((u) => ({ field: u.field, message: `${u.field} refers to an item that does not exist.` }));
1257
+ }
1181
1258
 
1182
1259
  // src/routes/admin/content.ts
1183
- function quoteIdent2(name) {
1260
+ function quoteIdent3(name) {
1184
1261
  if (!/^[a-z][a-z0-9_-]*$/.test(name)) throw new Error(`Unsafe identifier: ${name}`);
1185
1262
  return `"${name}"`;
1186
1263
  }
1187
1264
  async function loadParagraphRows(db, registry, pType, parentId, fieldName) {
1188
1265
  const result = await db.execute(
1189
- sql3`SELECT * FROM ${sql3.raw(quoteIdent2(pType.db.table_name))} WHERE parent_id = ${parentId} AND parent_field = ${fieldName} ORDER BY "order" ASC`
1266
+ sql4`SELECT * FROM ${sql4.raw(quoteIdent3(pType.db.table_name))} WHERE parent_id = ${parentId} AND parent_field = ${fieldName} ORDER BY "order" ASC`
1190
1267
  );
1191
1268
  const rows = result.rows;
1192
1269
  for (const nf of pType.fields) {
@@ -1204,29 +1281,67 @@ async function loadParagraphRows(db, registry, pType, parentId, fieldName) {
1204
1281
  }
1205
1282
  return rows;
1206
1283
  }
1284
+ async function loadRelationsForEdit(db, registry, fields, id, row) {
1285
+ for (const f of fields) {
1286
+ if (f.db_column === null) {
1287
+ const comp = f.ui_component;
1288
+ if (comp.component !== "paragraph-embed" || !comp.ref) continue;
1289
+ const pType = registry.paragraph_types[comp.ref];
1290
+ if (!pType) continue;
1291
+ row[f.name] = oneOrMany(relationCardinality3(f) ?? "many", await loadParagraphRows(db, registry, pType, id, f.name));
1292
+ } else if (f.db_column.junction) {
1293
+ const j = f.db_column.junction;
1294
+ const r = await db.execute(
1295
+ sql4`SELECT ${sql4.raw(quoteIdent3(j.right_column))} FROM ${sql4.raw(quoteIdent3(j.table_name))} WHERE ${sql4.raw(quoteIdent3(j.left_column))} = ${id}`
1296
+ );
1297
+ row[f.name] = r.rows.map((linked) => linked[j.right_column]);
1298
+ }
1299
+ }
1300
+ }
1301
+ async function valuesForPublishCheck(db, registry, requiredFields, id, storedLabels, body) {
1302
+ const stored = { ...storedLabels };
1303
+ if (db) await loadRelationsForEdit(db, registry, requiredFields.filter((f) => !(f.name in body)), id, stored);
1304
+ return { ...stored, ...body };
1305
+ }
1306
+ async function persistRelationFields(db, registry, fields, body, itemId, ownerTable) {
1307
+ const deltas = [];
1308
+ for (const f of fields) {
1309
+ if (!(f.name in body)) continue;
1310
+ if (f.db_column === null) {
1311
+ const comp = f.ui_component;
1312
+ if (comp.component !== "paragraph-embed" || !comp.ref) continue;
1313
+ const pType = registry.paragraph_types[comp.ref];
1314
+ if (!pType) continue;
1315
+ deltas.push(await persistParagraphField(db, itemId, ownerTable, f.name, pType, paragraphItems(f, body[f.name]), registry));
1316
+ } else if (f.db_column.junction) {
1317
+ const value = body[f.name];
1318
+ const relatedIds = Array.isArray(value) ? value.filter((v) => typeof v === "string") : [];
1319
+ await persistJunctionField(db, itemId, f.db_column.junction, relatedIds);
1320
+ }
1321
+ }
1322
+ return deltas;
1323
+ }
1207
1324
  async function lookupBasePathId(db, pathOrName) {
1208
1325
  const r = await db.execute(
1209
- sql3`SELECT id FROM base_paths WHERE path = ${pathOrName} OR name = ${pathOrName} LIMIT 1`
1326
+ sql4`SELECT id FROM base_paths WHERE path = ${pathOrName} OR name = ${pathOrName} LIMIT 1`
1210
1327
  );
1211
1328
  return r.rows[0]?.id ?? null;
1212
1329
  }
1213
1330
  var SLUG_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
1214
- function isEmpty(value) {
1215
- if (value === null || value === void 0) return true;
1216
- if (typeof value === "string" && value.trim() === "") return true;
1217
- if (Array.isArray(value) && value.length === 0) return true;
1218
- return false;
1219
- }
1220
1331
  function checkRequiredFields(fields, data) {
1221
- return fields.filter((f) => f.required && isEmpty(data[f.name])).map((f) => ({ field: f.name, message: `${f.label} is required` }));
1222
- }
1223
- function relationShapeError(c, fields, body, registry) {
1224
- const details = checkRelationInput(fields, body, registry);
1225
- if (details.length === 0) return null;
1226
- return c.json(
1227
- { ok: false, error: { code: "VALIDATION_ERROR", message: "Relation fields have the wrong shape", details } },
1228
- 422
1229
- );
1332
+ return fields.filter((f) => f.required && isMissing(data[f.name])).map((f) => ({ field: f.name, message: `${f.label} is required` }));
1333
+ }
1334
+ async function relationInputError(c, fields, body, registry, db) {
1335
+ const refuse = (message, details) => c.json({ ok: false, error: { code: "VALIDATION_ERROR", message, details } }, 422);
1336
+ const shape = checkRelationInput(fields, body, registry);
1337
+ if (shape.length > 0) return refuse("Relation fields have the wrong shape", shape);
1338
+ const required = checkRequiredInParagraphItems(fields, body, registry);
1339
+ if (required.length > 0) return refuse("Required fields are missing", required);
1340
+ if (db) {
1341
+ const missing = await findMissingReferences(db, fields, body, registry);
1342
+ if (missing.length > 0) return refuse("Relation fields refer to items that do not exist", missing);
1343
+ }
1344
+ return null;
1230
1345
  }
1231
1346
  function parseListQuery(url, schemaFieldNames, relationFieldNames, columnFor) {
1232
1347
  const searchParams = new URL(url).searchParams;
@@ -1361,26 +1476,7 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
1361
1476
  404
1362
1477
  );
1363
1478
  }
1364
- if (db) {
1365
- const row = item;
1366
- for (const f of contentType.fields) {
1367
- if (f.db_column === null) {
1368
- const comp = f.ui_component;
1369
- if (comp.component !== "paragraph-embed" || !comp.ref) continue;
1370
- const pType = registry.paragraph_types[comp.ref];
1371
- if (!pType) continue;
1372
- row[f.name] = oneOrMany(relationCardinality3(f) ?? "many", await loadParagraphRows(db, registry, pType, id, f.name));
1373
- } else if (f.db_column.junction) {
1374
- const j = f.db_column.junction;
1375
- const r = await db.execute(
1376
- sql3`SELECT ${sql3.raw(quoteIdent2(j.right_column))} FROM ${sql3.raw(quoteIdent2(j.table_name))} WHERE ${sql3.raw(quoteIdent2(j.left_column))} = ${id}`
1377
- );
1378
- row[f.name] = r.rows.map(
1379
- (row2) => row2[j.right_column]
1380
- );
1381
- }
1382
- }
1383
- }
1479
+ if (db) await loadRelationsForEdit(db, registry, contentType.fields, id, item);
1384
1480
  const data = projectRow(item, typeName, projectors);
1385
1481
  return c.json({ ok: true, data });
1386
1482
  }
@@ -1449,8 +1545,8 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
1449
1545
  }
1450
1546
  );
1451
1547
  const writeNewItem = async (c, body) => {
1452
- const shapeError = relationShapeError(c, contentType.fields, body, registry);
1453
- if (shapeError) return shapeError;
1548
+ const inputError = await relationInputError(c, contentType.fields, body, registry, db);
1549
+ if (inputError) return inputError;
1454
1550
  const storageBody = fieldKeys.toStorage(body);
1455
1551
  const fieldErrors = checkRequiredFields(requiredFields, body);
1456
1552
  if (fieldErrors.length > 0) {
@@ -1471,10 +1567,6 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
1471
1567
  });
1472
1568
  if (publishDeny) return publishDeny;
1473
1569
  }
1474
- const paragraphFields = contentType.fields.filter((f) => f.db_column === null);
1475
- const junctionFields = contentType.fields.filter(
1476
- (f) => f.db_column !== null && f.db_column.junction !== void 0
1477
- );
1478
1570
  const columnBackedFields = contentType.fields.filter(isColumnBacked);
1479
1571
  const columnData = {
1480
1572
  published: body["published"] ?? false
@@ -1509,19 +1601,7 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
1509
1601
  const itemId = item["id"];
1510
1602
  const mediaDeltas = [topLevelMediaDelta(mediaFields, null, storageBody)];
1511
1603
  if (db) {
1512
- for (const f of paragraphFields) {
1513
- const comp = f.ui_component;
1514
- if (comp.component !== "paragraph-embed" || !comp.ref) continue;
1515
- const pType = registry.paragraph_types[comp.ref];
1516
- if (!pType) continue;
1517
- const items = paragraphItems(f, body[f.name]);
1518
- mediaDeltas.push(await persistParagraphField(db, itemId, contentType.db.table_name, f.name, pType, items, registry));
1519
- }
1520
- for (const f of junctionFields) {
1521
- const junction = f.db_column.junction;
1522
- const relatedIds = Array.isArray(body[f.name]) ? body[f.name].filter((v) => typeof v === "string") : [];
1523
- await persistJunctionField(db, itemId, junction, relatedIds);
1524
- }
1604
+ mediaDeltas.push(...await persistRelationFields(db, registry, contentType.fields, body, itemId, contentType.db.table_name));
1525
1605
  }
1526
1606
  await applyMediaReferenceDelta(mergeMediaDeltas(...mediaDeltas), mediaRepo);
1527
1607
  const data = projectRow(item, typeName, projectors);
@@ -1585,14 +1665,14 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
1585
1665
  }
1586
1666
  );
1587
1667
  const writeExistingItem = async (c, id, existing, body) => {
1588
- const shapeError = relationShapeError(c, contentType.fields, body, registry);
1589
- if (shapeError) return shapeError;
1668
+ const inputError = await relationInputError(c, contentType.fields, body, registry, db);
1669
+ if (inputError) return inputError;
1590
1670
  const storageBody = fieldKeys.toStorage(body);
1591
1671
  if (body["published"] === true) {
1592
1672
  const publishDeny = await requirePermission("content:edit")(c, async () => {
1593
1673
  });
1594
1674
  if (publishDeny) return publishDeny;
1595
- const merged = { ...fieldKeys.toLabels(existing), ...body };
1675
+ const merged = await valuesForPublishCheck(db, registry, requiredFields, id, fieldKeys.toLabels(existing), body);
1596
1676
  const fieldErrors = checkRequiredFields(requiredFields, merged);
1597
1677
  if (fieldErrors.length > 0) {
1598
1678
  return c.json(
@@ -1608,10 +1688,6 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
1608
1688
  );
1609
1689
  }
1610
1690
  }
1611
- const patchParagraphFields = contentType.fields.filter((f) => f.db_column === null);
1612
- const patchJunctionFields = contentType.fields.filter(
1613
- (f) => f.db_column !== null && f.db_column.junction !== void 0
1614
- );
1615
1691
  const patchColumnBackedFields = contentType.fields.filter(isColumnBacked);
1616
1692
  const patchData = {};
1617
1693
  if (!contentType.only_one && "slug" in body) patchData["slug"] = body["slug"];
@@ -1632,21 +1708,7 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
1632
1708
  topLevelMediaDelta(mediaFields, existing, storageBody)
1633
1709
  ];
1634
1710
  if (db) {
1635
- for (const f of patchParagraphFields) {
1636
- if (!(f.name in body)) continue;
1637
- const comp = f.ui_component;
1638
- if (comp.component !== "paragraph-embed" || !comp.ref) continue;
1639
- const pType = registry.paragraph_types[comp.ref];
1640
- if (!pType) continue;
1641
- const items = paragraphItems(f, body[f.name]);
1642
- mediaDeltas.push(await persistParagraphField(db, id, contentType.db.table_name, f.name, pType, items, registry));
1643
- }
1644
- for (const f of patchJunctionFields) {
1645
- if (!(f.name in body)) continue;
1646
- const junction = f.db_column.junction;
1647
- const relatedIds = Array.isArray(body[f.name]) ? body[f.name].filter((v) => typeof v === "string") : [];
1648
- await persistJunctionField(db, id, junction, relatedIds);
1649
- }
1711
+ mediaDeltas.push(...await persistRelationFields(db, registry, contentType.fields, body, id, contentType.db.table_name));
1650
1712
  }
1651
1713
  await applyMediaReferenceDelta(mergeMediaDeltas(...mediaDeltas), mediaRepo);
1652
1714
  const data = projectRow(updated, typeName, projectors);
@@ -1775,6 +1837,7 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
1775
1837
  404
1776
1838
  );
1777
1839
  }
1840
+ if (db) await loadRelationsForEdit(db, registry, taxonomyType.fields, id, item);
1778
1841
  const data = projectRow(item, typeName, projectors);
1779
1842
  return c.json({ ok: true, data });
1780
1843
  }
@@ -1784,8 +1847,8 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
1784
1847
  requirePermission("content:create"),
1785
1848
  async (c) => {
1786
1849
  const body = await c.req.json();
1787
- const shapeError = relationShapeError(c, taxonomyType.fields, body, registry);
1788
- if (shapeError) return shapeError;
1850
+ const inputError = await relationInputError(c, taxonomyType.fields, body, registry, db);
1851
+ if (inputError) return inputError;
1789
1852
  const storageBody = fieldKeys.toStorage(body);
1790
1853
  const fieldErrors = checkRequiredFields(requiredFields, body);
1791
1854
  if (fieldErrors.length > 0) {
@@ -1806,7 +1869,6 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
1806
1869
  });
1807
1870
  if (publishDeny) return publishDeny;
1808
1871
  }
1809
- const taxParagraphFields = taxonomyType.fields.filter((f) => f.db_column === null);
1810
1872
  const taxColumnBackedFields = taxonomyType.fields.filter(isColumnBacked);
1811
1873
  const taxColumnData = {
1812
1874
  published: body["published"] ?? false
@@ -1820,14 +1882,7 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
1820
1882
  const taxItemId = item["id"];
1821
1883
  const mediaDeltas = [topLevelMediaDelta(mediaFields, null, storageBody)];
1822
1884
  if (db) {
1823
- for (const f of taxParagraphFields) {
1824
- const comp = f.ui_component;
1825
- if (comp.component !== "paragraph-embed" || !comp.ref) continue;
1826
- const pType = registry.paragraph_types[comp.ref];
1827
- if (!pType) continue;
1828
- const items = paragraphItems(f, body[f.name]);
1829
- mediaDeltas.push(await persistParagraphField(db, taxItemId, taxonomyType.db.table_name, f.name, pType, items, registry));
1830
- }
1885
+ mediaDeltas.push(...await persistRelationFields(db, registry, taxonomyType.fields, body, taxItemId, taxonomyType.db.table_name));
1831
1886
  }
1832
1887
  await applyMediaReferenceDelta(mergeMediaDeltas(...mediaDeltas), mediaRepo);
1833
1888
  const data = projectRow(item, typeName, projectors);
@@ -1848,13 +1903,13 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
1848
1903
  404
1849
1904
  );
1850
1905
  }
1851
- const shapeError = relationShapeError(c, taxonomyType.fields, body, registry);
1852
- if (shapeError) return shapeError;
1906
+ const inputError = await relationInputError(c, taxonomyType.fields, body, registry, db);
1907
+ if (inputError) return inputError;
1853
1908
  if (body["published"] === true) {
1854
1909
  const publishDeny = await requirePermission("content:edit")(c, async () => {
1855
1910
  });
1856
1911
  if (publishDeny) return publishDeny;
1857
- const merged = { ...fieldKeys.toLabels(existing), ...body };
1912
+ const merged = await valuesForPublishCheck(db, registry, requiredFields, id, fieldKeys.toLabels(existing), body);
1858
1913
  const fieldErrors = checkRequiredFields(requiredFields, merged);
1859
1914
  if (fieldErrors.length > 0) {
1860
1915
  return c.json(
@@ -1870,7 +1925,6 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
1870
1925
  );
1871
1926
  }
1872
1927
  }
1873
- const taxPatchParagraphFields = taxonomyType.fields.filter((f) => f.db_column === null);
1874
1928
  const taxPatchColumnBackedFields = taxonomyType.fields.filter(isColumnBacked);
1875
1929
  const taxPatchData = {};
1876
1930
  if ("published" in body) taxPatchData["published"] = body["published"];
@@ -1890,15 +1944,7 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
1890
1944
  topLevelMediaDelta(mediaFields, existing, storageBody)
1891
1945
  ];
1892
1946
  if (db) {
1893
- for (const f of taxPatchParagraphFields) {
1894
- if (!(f.name in body)) continue;
1895
- const comp = f.ui_component;
1896
- if (comp.component !== "paragraph-embed" || !comp.ref) continue;
1897
- const pType = registry.paragraph_types[comp.ref];
1898
- if (!pType) continue;
1899
- const items = paragraphItems(f, body[f.name]);
1900
- mediaDeltas.push(await persistParagraphField(db, id, taxonomyType.db.table_name, f.name, pType, items, registry));
1901
- }
1947
+ mediaDeltas.push(...await persistRelationFields(db, registry, taxonomyType.fields, body, id, taxonomyType.db.table_name));
1902
1948
  }
1903
1949
  await applyMediaReferenceDelta(mergeMediaDeltas(...mediaDeltas), mediaRepo);
1904
1950
  const data = projectRow(updated, typeName, projectors);
@@ -2403,7 +2449,7 @@ function registerAdminMediaRoutes(app, mediaRepo, storage, requirePermission, ma
2403
2449
 
2404
2450
  // src/routes/admin/auth.ts
2405
2451
  import { getCookie as getCookie2 } from "hono/cookie";
2406
- import { sql as sql4 } from "drizzle-orm";
2452
+ import { sql as sql5 } from "drizzle-orm";
2407
2453
 
2408
2454
  // src/auth/password.ts
2409
2455
  import { hashPassword, verifyPassword } from "@bobbykim/manguito-cms-core";
@@ -2469,7 +2515,7 @@ function registerAuthRoutes(app, db) {
2469
2515
  );
2470
2516
  }
2471
2517
  const userResult = await db.execute(
2472
- sql4`SELECT id, email, password_hash, role_id, token_version
2518
+ sql5`SELECT id, email, password_hash, role_id, token_version
2473
2519
  FROM users WHERE email = ${email} LIMIT 1`
2474
2520
  );
2475
2521
  const user = userResult.rows[0];
@@ -2482,7 +2528,7 @@ function registerAuthRoutes(app, db) {
2482
2528
  return c.json(invalidCredentials, 401);
2483
2529
  }
2484
2530
  const roleResult = await db.execute(
2485
- sql4`SELECT r.name AS name, u.must_change_password
2531
+ sql5`SELECT r.name AS name, u.must_change_password
2486
2532
  FROM roles r
2487
2533
  JOIN users u ON u.role_id = r.id
2488
2534
  WHERE r.id = ${user.role_id} AND u.id = ${user.id}
@@ -2542,7 +2588,7 @@ function registerAuthRoutes(app, db) {
2542
2588
  );
2543
2589
  }
2544
2590
  const rowResult = await db.execute(
2545
- sql4`SELECT u.token_version, r.name AS role
2591
+ sql5`SELECT u.token_version, r.name AS role
2546
2592
  FROM users u
2547
2593
  JOIN roles r ON r.id = u.role_id
2548
2594
  WHERE u.id = ${payload.user_id}
@@ -2566,7 +2612,7 @@ function registerAuthRoutes(app, db) {
2566
2612
  app.post("/admin/api/auth/logout", authMiddleware, async (c) => {
2567
2613
  const user = c.get("user");
2568
2614
  await db.execute(
2569
- sql4`UPDATE users SET token_version = token_version + 1 WHERE id = ${user.id}`
2615
+ sql5`UPDATE users SET token_version = token_version + 1 WHERE id = ${user.id}`
2570
2616
  );
2571
2617
  clearAuthCookie(c, "auth_token");
2572
2618
  clearAuthCookie(c, "refresh_token", "/admin/api/auth");
@@ -2575,7 +2621,7 @@ function registerAuthRoutes(app, db) {
2575
2621
  }
2576
2622
 
2577
2623
  // src/routes/admin/users.ts
2578
- import { sql as sql5 } from "drizzle-orm";
2624
+ import { sql as sql6 } from "drizzle-orm";
2579
2625
  import { randomBytes } from "crypto";
2580
2626
  function getActingUser(c) {
2581
2627
  return c.get("user");
@@ -2610,7 +2656,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
2610
2656
  );
2611
2657
  }
2612
2658
  const hashResult = await db.execute(
2613
- sql5`SELECT password_hash FROM users WHERE id = ${actingUser.id} LIMIT 1`
2659
+ sql6`SELECT password_hash FROM users WHERE id = ${actingUser.id} LIMIT 1`
2614
2660
  );
2615
2661
  const hashRow = hashResult.rows[0];
2616
2662
  if (!hashRow || !await verifyPassword(currentPassword, hashRow.password_hash)) {
@@ -2621,7 +2667,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
2621
2667
  }
2622
2668
  const newHash = await hashPassword(newPassword);
2623
2669
  await db.execute(
2624
- sql5`UPDATE users
2670
+ sql6`UPDATE users
2625
2671
  SET password_hash = ${newHash},
2626
2672
  must_change_password = false,
2627
2673
  token_version = token_version + 1,
@@ -2632,7 +2678,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
2632
2678
  });
2633
2679
  app.get("/admin/api/users", requirePermission("users:read"), async (c) => {
2634
2680
  const result = await db.execute(
2635
- sql5`SELECT u.id, u.email, r.name AS role, u.must_change_password,
2681
+ sql6`SELECT u.id, u.email, r.name AS role, u.must_change_password,
2636
2682
  u.created_at, u.updated_at
2637
2683
  FROM users u
2638
2684
  JOIN roles r ON r.id = u.role_id
@@ -2643,7 +2689,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
2643
2689
  app.get("/admin/api/users/:id", requirePermission("users:read"), async (c) => {
2644
2690
  const id = c.req.param("id");
2645
2691
  const result = await db.execute(
2646
- sql5`SELECT u.id, u.email, r.name AS role, u.must_change_password,
2692
+ sql6`SELECT u.id, u.email, r.name AS role, u.must_change_password,
2647
2693
  u.created_at, u.updated_at
2648
2694
  FROM users u
2649
2695
  JOIN roles r ON r.id = u.role_id
@@ -2688,7 +2734,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
2688
2734
  );
2689
2735
  }
2690
2736
  const roleResult = await db.execute(
2691
- sql5`SELECT id FROM roles WHERE name = ${roleName} LIMIT 1`
2737
+ sql6`SELECT id FROM roles WHERE name = ${roleName} LIMIT 1`
2692
2738
  );
2693
2739
  const roleRow = roleResult.rows[0];
2694
2740
  if (!roleRow) {
@@ -2703,7 +2749,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
2703
2749
  const tempPassword = generateTempPassword();
2704
2750
  const passwordHash = await hashPassword(tempPassword);
2705
2751
  const insertResult = await db.execute(
2706
- sql5`INSERT INTO users
2752
+ sql6`INSERT INTO users
2707
2753
  (id, email, password_hash, role_id, must_change_password, token_version,
2708
2754
  created_at, updated_at)
2709
2755
  VALUES
@@ -2758,7 +2804,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
2758
2804
  );
2759
2805
  }
2760
2806
  const existingResult = await db.execute(
2761
- sql5`SELECT id, email, role_id FROM users WHERE id = ${id} LIMIT 1`
2807
+ sql6`SELECT id, email, role_id FROM users WHERE id = ${id} LIMIT 1`
2762
2808
  );
2763
2809
  const existing = existingResult.rows[0];
2764
2810
  if (!existing) {
@@ -2772,7 +2818,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
2772
2818
  let changeRole = false;
2773
2819
  if (typeof body.role === "string" && body.role) {
2774
2820
  const roleResult = await db.execute(
2775
- sql5`SELECT id FROM roles WHERE name = ${body.role} LIMIT 1`
2821
+ sql6`SELECT id FROM roles WHERE name = ${body.role} LIMIT 1`
2776
2822
  );
2777
2823
  const roleRow = roleResult.rows[0];
2778
2824
  if (!roleRow) {
@@ -2792,7 +2838,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
2792
2838
  }
2793
2839
  if (changeRole) {
2794
2840
  await db.execute(
2795
- sql5`UPDATE users
2841
+ sql6`UPDATE users
2796
2842
  SET email = ${newEmail},
2797
2843
  role_id = ${newRoleId},
2798
2844
  token_version = token_version + 1,
@@ -2801,7 +2847,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
2801
2847
  );
2802
2848
  } else {
2803
2849
  await db.execute(
2804
- sql5`UPDATE users
2850
+ sql6`UPDATE users
2805
2851
  SET email = ${newEmail},
2806
2852
  role_id = ${newRoleId},
2807
2853
  updated_at = NOW()
@@ -2809,7 +2855,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
2809
2855
  );
2810
2856
  }
2811
2857
  const updatedResult = await db.execute(
2812
- sql5`SELECT u.id, u.email, r.name AS role, u.must_change_password,
2858
+ sql6`SELECT u.id, u.email, r.name AS role, u.must_change_password,
2813
2859
  u.created_at, u.updated_at
2814
2860
  FROM users u
2815
2861
  JOIN roles r ON r.id = u.role_id
@@ -2846,7 +2892,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
2846
2892
  );
2847
2893
  }
2848
2894
  const result = await db.execute(
2849
- sql5`DELETE FROM users WHERE id = ${id} RETURNING id`
2895
+ sql6`DELETE FROM users WHERE id = ${id} RETURNING id`
2850
2896
  );
2851
2897
  if (result.rows.length === 0) {
2852
2898
  return c.json(
@@ -2877,7 +2923,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
2877
2923
  );
2878
2924
  }
2879
2925
  const existingResult = await db.execute(
2880
- sql5`SELECT id FROM users WHERE id = ${id} LIMIT 1`
2926
+ sql6`SELECT id FROM users WHERE id = ${id} LIMIT 1`
2881
2927
  );
2882
2928
  const existing = existingResult.rows[0];
2883
2929
  if (!existing) {
@@ -2889,7 +2935,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
2889
2935
  const tempPassword = generateTempPassword();
2890
2936
  const passwordHash = await hashPassword(tempPassword);
2891
2937
  await db.execute(
2892
- sql5`UPDATE users
2938
+ sql6`UPDATE users
2893
2939
  SET password_hash = ${passwordHash},
2894
2940
  must_change_password = true,
2895
2941
  token_version = token_version + 1,
@@ -2902,10 +2948,10 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
2902
2948
  }
2903
2949
 
2904
2950
  // src/routes/admin/config.ts
2905
- import { sql as sql6 } from "drizzle-orm";
2951
+ import { sql as sql7 } from "drizzle-orm";
2906
2952
  var API_VERSION = (() => {
2907
2953
  try {
2908
- return "0.8.0";
2954
+ return "0.8.2";
2909
2955
  } catch {
2910
2956
  return "0.0.0";
2911
2957
  }
@@ -2924,7 +2970,7 @@ function registerConfigRoute(app, config, registry, db) {
2924
2970
  permissions: r.permissions
2925
2971
  }));
2926
2972
  const userResult = await db.execute(
2927
- sql6`SELECT email, must_change_password FROM users WHERE id = ${actingUser.id} LIMIT 1`
2973
+ sql7`SELECT email, must_change_password FROM users WHERE id = ${actingUser.id} LIMIT 1`
2928
2974
  );
2929
2975
  const userRow = userResult.rows[0];
2930
2976
  return c.json({
@@ -2950,7 +2996,7 @@ function registerConfigRoute(app, config, registry, db) {
2950
2996
  }
2951
2997
 
2952
2998
  // src/routes/admin/schema.ts
2953
- import { sql as sql7 } from "drizzle-orm";
2999
+ import { sql as sql8 } from "drizzle-orm";
2954
3000
  function liveFields(fields) {
2955
3001
  return fields.filter((f) => f.removed !== true);
2956
3002
  }
@@ -2990,7 +3036,7 @@ function registerSchemaRoute(app, registry, db) {
2990
3036
  const data = await Promise.all(
2991
3037
  Object.values(registry.content_types).map(async (ct) => {
2992
3038
  const result = await db.execute(
2993
- sql7`SELECT COUNT(*)::int AS count FROM ${sql7.raw(`"${ct.db.table_name}"`)}`
3039
+ sql8`SELECT COUNT(*)::int AS count FROM ${sql8.raw(`"${ct.db.table_name}"`)}`
2994
3040
  );
2995
3041
  const count = Number(result.rows[0].count);
2996
3042
  return { name: ct.name, label: ct.label, only_one: ct.only_one, count };
@@ -3002,7 +3048,7 @@ function registerSchemaRoute(app, registry, db) {
3002
3048
  const data = await Promise.all(
3003
3049
  Object.values(registry.taxonomy_types).map(async (tt) => {
3004
3050
  const result = await db.execute(
3005
- sql7`SELECT COUNT(*)::int AS count FROM ${sql7.raw(`"${tt.db.table_name}"`)}`
3051
+ sql8`SELECT COUNT(*)::int AS count FROM ${sql8.raw(`"${tt.db.table_name}"`)}`
3006
3052
  );
3007
3053
  const count = Number(result.rows[0].count);
3008
3054
  return { name: tt.name, label: tt.label, count };
@@ -3014,14 +3060,14 @@ function registerSchemaRoute(app, registry, db) {
3014
3060
 
3015
3061
  // src/repositories/content.ts
3016
3062
  import crypto from "crypto";
3017
- import { sql as sql8 } from "drizzle-orm";
3063
+ import { sql as sql9 } from "drizzle-orm";
3018
3064
  var SORTABLE_FIELDS2 = /* @__PURE__ */ new Set(["title", "created_at", "updated_at"]);
3019
3065
  function codeError(code, message) {
3020
3066
  const err = new Error(message);
3021
3067
  err.code = code;
3022
3068
  return err;
3023
3069
  }
3024
- function quoteIdent3(name) {
3070
+ function quoteIdent4(name) {
3025
3071
  if (!/^[a-z][a-z0-9_-]*$/.test(name)) {
3026
3072
  throw new Error(`Unsafe SQL identifier: ${name}`);
3027
3073
  }
@@ -3036,43 +3082,43 @@ function quoteField(name) {
3036
3082
  function buildConditions(published_only, filters, search) {
3037
3083
  const conds = [];
3038
3084
  if (published_only) {
3039
- conds.push(sql8`published = true`);
3085
+ conds.push(sql9`published = true`);
3040
3086
  }
3041
3087
  if (search && search.term.trim() !== "" && search.columns.length > 0) {
3042
3088
  const pattern = `%${search.term.trim()}%`;
3043
- const orConds = search.columns.map((col) => sql8`${sql8.raw(quoteField(col))} ILIKE ${pattern}`);
3044
- conds.push(sql8`(${sql8.join(orConds, sql8` OR `)})`);
3089
+ const orConds = search.columns.map((col) => sql9`${sql9.raw(quoteField(col))} ILIKE ${pattern}`);
3090
+ conds.push(sql9`(${sql9.join(orConds, sql9` OR `)})`);
3045
3091
  }
3046
3092
  if (filters) {
3047
3093
  for (const [field, value] of Object.entries(filters)) {
3048
- const col = sql8.raw(quoteField(field));
3094
+ const col = sql9.raw(quoteField(field));
3049
3095
  if (Array.isArray(value)) {
3050
3096
  if (value.length === 0) continue;
3051
- const inList = sql8.join(
3052
- value.map((v) => sql8`${v}`),
3053
- sql8`, `
3097
+ const inList = sql9.join(
3098
+ value.map((v) => sql9`${v}`),
3099
+ sql9`, `
3054
3100
  );
3055
- conds.push(sql8`${col} IN (${inList})`);
3101
+ conds.push(sql9`${col} IN (${inList})`);
3056
3102
  } else if (typeof value === "object" && value !== null) {
3057
3103
  const op = value;
3058
- if (op.gt !== void 0) conds.push(sql8`${col} > ${op.gt}`);
3059
- if (op.gte !== void 0) conds.push(sql8`${col} >= ${op.gte}`);
3060
- if (op.lt !== void 0) conds.push(sql8`${col} < ${op.lt}`);
3061
- if (op.lte !== void 0) conds.push(sql8`${col} <= ${op.lte}`);
3104
+ if (op.gt !== void 0) conds.push(sql9`${col} > ${op.gt}`);
3105
+ if (op.gte !== void 0) conds.push(sql9`${col} >= ${op.gte}`);
3106
+ if (op.lt !== void 0) conds.push(sql9`${col} < ${op.lt}`);
3107
+ if (op.lte !== void 0) conds.push(sql9`${col} <= ${op.lte}`);
3062
3108
  } else {
3063
- conds.push(sql8`${col} = ${value}`);
3109
+ conds.push(sql9`${col} = ${value}`);
3064
3110
  }
3065
3111
  }
3066
3112
  }
3067
3113
  return conds;
3068
3114
  }
3069
3115
  function whereFragment(conds) {
3070
- return conds.length > 0 ? sql8` WHERE ${sql8.join(conds, sql8` AND `)}` : sql8``;
3116
+ return conds.length > 0 ? sql9` WHERE ${sql9.join(conds, sql9` AND `)}` : sql9``;
3071
3117
  }
3072
3118
  function createDrizzleContentRepository(db, tableName, options = {}) {
3073
3119
  const { relations = {}, publishedRelations = false, sortableColumns } = options;
3074
3120
  function tableRaw() {
3075
- return sql8.raw(quoteIdent3(tableName));
3121
+ return sql9.raw(quoteIdent4(tableName));
3076
3122
  }
3077
3123
  async function resolveRows(rows, include, cache) {
3078
3124
  if (rows.length === 0) return rows;
@@ -3115,14 +3161,14 @@ function createDrizzleContentRepository(db, tableName, options = {}) {
3115
3161
  const offset = (page - 1) * per_page;
3116
3162
  const conds = buildConditions(published_only, filters, search);
3117
3163
  const where = whereFragment(conds);
3118
- const sortCol = sql8.raw(quoteIdent3(sort_by));
3119
- const sortDir = sql8.raw(sort_order === "desc" ? "DESC" : "ASC");
3164
+ const sortCol = sql9.raw(quoteIdent4(sort_by));
3165
+ const sortDir = sql9.raw(sort_order === "desc" ? "DESC" : "ASC");
3120
3166
  const countResult = await db.execute(
3121
- sql8`SELECT COUNT(*) AS total FROM ${tableRaw()}${where}`
3167
+ sql9`SELECT COUNT(*) AS total FROM ${tableRaw()}${where}`
3122
3168
  );
3123
3169
  const total = Number(countResult.rows[0]?.["total"] ?? 0);
3124
3170
  const dataResult = await db.execute(
3125
- sql8`SELECT * FROM ${tableRaw()}${where} ORDER BY ${sortCol} ${sortDir} LIMIT ${per_page} OFFSET ${offset}`
3171
+ sql9`SELECT * FROM ${tableRaw()}${where} ORDER BY ${sortCol} ${sortDir} LIMIT ${per_page} OFFSET ${offset}`
3126
3172
  );
3127
3173
  const rows = dataResult.rows;
3128
3174
  const cache = /* @__PURE__ */ new Map();
@@ -3143,7 +3189,7 @@ function createDrizzleContentRepository(db, tableName, options = {}) {
3143
3189
  },
3144
3190
  async findOne(id, include = []) {
3145
3191
  const result = await db.execute(
3146
- sql8`SELECT * FROM ${tableRaw()} WHERE id = ${id} LIMIT 1`
3192
+ sql9`SELECT * FROM ${tableRaw()} WHERE id = ${id} LIMIT 1`
3147
3193
  );
3148
3194
  if (result.rows.length === 0) return null;
3149
3195
  const rows = result.rows;
@@ -3153,7 +3199,7 @@ function createDrizzleContentRepository(db, tableName, options = {}) {
3153
3199
  },
3154
3200
  async findBySlug(slug, include = []) {
3155
3201
  const result = await db.execute(
3156
- sql8`SELECT * FROM ${tableRaw()} WHERE slug = ${slug} LIMIT 1`
3202
+ sql9`SELECT * FROM ${tableRaw()} WHERE slug = ${slug} LIMIT 1`
3157
3203
  );
3158
3204
  if (result.rows.length === 0) return null;
3159
3205
  const rows = result.rows;
@@ -3169,10 +3215,10 @@ function createDrizzleContentRepository(db, tableName, options = {}) {
3169
3215
  updated_at: /* @__PURE__ */ new Date()
3170
3216
  };
3171
3217
  const entries = Object.entries(record).filter(([, v]) => v !== void 0);
3172
- const colsSql = sql8.join(entries.map(([k]) => sql8.raw(quoteIdent3(k))), sql8`, `);
3173
- const valsSql = sql8.join(entries.map(([, v]) => sql8`${v}`), sql8`, `);
3218
+ const colsSql = sql9.join(entries.map(([k]) => sql9.raw(quoteIdent4(k))), sql9`, `);
3219
+ const valsSql = sql9.join(entries.map(([, v]) => sql9`${v}`), sql9`, `);
3174
3220
  const result = await db.execute(
3175
- sql8`INSERT INTO ${tableRaw()} (${colsSql}) VALUES (${valsSql}) RETURNING *`
3221
+ sql9`INSERT INTO ${tableRaw()} (${colsSql}) VALUES (${valsSql}) RETURNING *`
3176
3222
  );
3177
3223
  return result.rows[0];
3178
3224
  },
@@ -3185,25 +3231,25 @@ function createDrizzleContentRepository(db, tableName, options = {}) {
3185
3231
  if (entries.length === 0) {
3186
3232
  return this.findOne(id);
3187
3233
  }
3188
- const setClauses = sql8.join(
3189
- entries.map(([k, v]) => sql8`${sql8.raw(quoteIdent3(k))} = ${v}`),
3190
- sql8`, `
3234
+ const setClauses = sql9.join(
3235
+ entries.map(([k, v]) => sql9`${sql9.raw(quoteIdent4(k))} = ${v}`),
3236
+ sql9`, `
3191
3237
  );
3192
3238
  const result = await db.execute(
3193
- sql8`UPDATE ${tableRaw()} SET ${setClauses} WHERE id = ${id} RETURNING *`
3239
+ sql9`UPDATE ${tableRaw()} SET ${setClauses} WHERE id = ${id} RETURNING *`
3194
3240
  );
3195
3241
  if (result.rows.length === 0) return null;
3196
3242
  return result.rows[0];
3197
3243
  },
3198
3244
  async delete(id) {
3199
- await db.execute(sql8`DELETE FROM ${tableRaw()} WHERE id = ${id}`);
3245
+ await db.execute(sql9`DELETE FROM ${tableRaw()} WHERE id = ${id}`);
3200
3246
  },
3201
3247
  async findAll(opts = {}) {
3202
3248
  const { include = [], published_only } = opts;
3203
3249
  const conds = buildConditions(published_only, void 0, void 0);
3204
3250
  const where = whereFragment(conds);
3205
3251
  const result = await db.execute(
3206
- sql8`SELECT * FROM ${tableRaw()}${where} ORDER BY created_at ASC`
3252
+ sql9`SELECT * FROM ${tableRaw()}${where} ORDER BY created_at ASC`
3207
3253
  );
3208
3254
  const rows = result.rows;
3209
3255
  const cache = /* @__PURE__ */ new Map();
@@ -3215,23 +3261,23 @@ function createDrizzleContentRepository(db, tableName, options = {}) {
3215
3261
 
3216
3262
  // src/repositories/media.ts
3217
3263
  import crypto2 from "crypto";
3218
- import { sql as sql9 } from "drizzle-orm";
3264
+ import { sql as sql10 } from "drizzle-orm";
3219
3265
  function createMediaRepository(db) {
3220
- const TABLE = sql9.raw('"media"');
3266
+ const TABLE = sql10.raw('"media"');
3221
3267
  return {
3222
3268
  async findMany(opts = {}) {
3223
3269
  const { page = 1, per_page = 10, type, orphaned } = opts;
3224
3270
  const offset = (page - 1) * per_page;
3225
3271
  const conds = [];
3226
- if (type) conds.push(sql9`type = ${type}`);
3227
- if (orphaned) conds.push(sql9`reference_count = 0`);
3228
- const where = conds.length > 0 ? sql9` WHERE ${sql9.join(conds, sql9` AND `)}` : sql9``;
3229
- const countResult = await db.execute(sql9`SELECT COUNT(*) AS total FROM ${TABLE}${where}`);
3272
+ if (type) conds.push(sql10`type = ${type}`);
3273
+ if (orphaned) conds.push(sql10`reference_count = 0`);
3274
+ const where = conds.length > 0 ? sql10` WHERE ${sql10.join(conds, sql10` AND `)}` : sql10``;
3275
+ const countResult = await db.execute(sql10`SELECT COUNT(*) AS total FROM ${TABLE}${where}`);
3230
3276
  const total = Number(
3231
3277
  countResult.rows[0]?.["total"] ?? 0
3232
3278
  );
3233
3279
  const dataResult = await db.execute(
3234
- sql9`SELECT * FROM ${TABLE}${where} ORDER BY created_at DESC LIMIT ${per_page} OFFSET ${offset}`
3280
+ sql10`SELECT * FROM ${TABLE}${where} ORDER BY created_at DESC LIMIT ${per_page} OFFSET ${offset}`
3235
3281
  );
3236
3282
  const total_pages = Math.ceil(total / per_page);
3237
3283
  return {
@@ -3249,7 +3295,7 @@ function createMediaRepository(db) {
3249
3295
  },
3250
3296
  async findOne(id) {
3251
3297
  const result = await db.execute(
3252
- sql9`SELECT * FROM ${TABLE} WHERE id = ${id} LIMIT 1`
3298
+ sql10`SELECT * FROM ${TABLE} WHERE id = ${id} LIMIT 1`
3253
3299
  );
3254
3300
  if (result.rows.length === 0) return null;
3255
3301
  return result.rows[0];
@@ -3263,10 +3309,10 @@ function createMediaRepository(db) {
3263
3309
  updated_at: /* @__PURE__ */ new Date()
3264
3310
  };
3265
3311
  const entries = Object.entries(record).filter(([, v]) => v !== void 0);
3266
- const colsSql = sql9.join(entries.map(([k]) => sql9.raw(`"${k}"`)), sql9`, `);
3267
- const valsSql = sql9.join(entries.map(([, v]) => sql9`${v}`), sql9`, `);
3312
+ const colsSql = sql10.join(entries.map(([k]) => sql10.raw(`"${k}"`)), sql10`, `);
3313
+ const valsSql = sql10.join(entries.map(([, v]) => sql10`${v}`), sql10`, `);
3268
3314
  const result = await db.execute(
3269
- sql9`INSERT INTO ${TABLE} (${colsSql}) VALUES (${valsSql}) RETURNING *`
3315
+ sql10`INSERT INTO ${TABLE} (${colsSql}) VALUES (${valsSql}) RETURNING *`
3270
3316
  );
3271
3317
  return result.rows[0];
3272
3318
  },
@@ -3276,31 +3322,31 @@ function createMediaRepository(db) {
3276
3322
  ([k, v]) => k !== "id" && v !== void 0
3277
3323
  );
3278
3324
  if (entries.length === 0) return this.findOne(id);
3279
- const setClauses = sql9.join(
3280
- entries.map(([k, v]) => sql9`${sql9.raw(`"${k}"`)} = ${v}`),
3281
- sql9`, `
3325
+ const setClauses = sql10.join(
3326
+ entries.map(([k, v]) => sql10`${sql10.raw(`"${k}"`)} = ${v}`),
3327
+ sql10`, `
3282
3328
  );
3283
3329
  const result = await db.execute(
3284
- sql9`UPDATE ${TABLE} SET ${setClauses} WHERE id = ${id} RETURNING *`
3330
+ sql10`UPDATE ${TABLE} SET ${setClauses} WHERE id = ${id} RETURNING *`
3285
3331
  );
3286
3332
  if (result.rows.length === 0) return null;
3287
3333
  return result.rows[0];
3288
3334
  },
3289
3335
  async delete(id) {
3290
- await db.execute(sql9`DELETE FROM ${TABLE} WHERE id = ${id}`);
3336
+ await db.execute(sql10`DELETE FROM ${TABLE} WHERE id = ${id}`);
3291
3337
  },
3292
3338
  async incrementReferenceCount(ids) {
3293
3339
  if (ids.length === 0) return;
3294
- const inList = sql9.join(ids.map((id) => sql9`${id}`), sql9`, `);
3340
+ const inList = sql10.join(ids.map((id) => sql10`${id}`), sql10`, `);
3295
3341
  await db.execute(
3296
- sql9`UPDATE ${TABLE} SET reference_count = reference_count + 1, updated_at = NOW() WHERE id IN (${inList})`
3342
+ sql10`UPDATE ${TABLE} SET reference_count = reference_count + 1, updated_at = NOW() WHERE id IN (${inList})`
3297
3343
  );
3298
3344
  },
3299
3345
  async decrementReferenceCount(ids) {
3300
3346
  if (ids.length === 0) return;
3301
- const inList = sql9.join(ids.map((id) => sql9`${id}`), sql9`, `);
3347
+ const inList = sql10.join(ids.map((id) => sql10`${id}`), sql10`, `);
3302
3348
  await db.execute(
3303
- sql9`UPDATE ${TABLE} SET reference_count = GREATEST(reference_count - 1, 0), updated_at = NOW() WHERE id IN (${inList})`
3349
+ sql10`UPDATE ${TABLE} SET reference_count = GREATEST(reference_count - 1, 0), updated_at = NOW() WHERE id IN (${inList})`
3304
3350
  );
3305
3351
  }
3306
3352
  };
@@ -3433,7 +3479,7 @@ function createCmsApp(options) {
3433
3479
  const requirePermission = createPermissionMiddleware(rolesRegistry);
3434
3480
  const getUserRole = async (userId) => {
3435
3481
  const result = await db.execute(
3436
- sql10`SELECT r.name AS role
3482
+ sql11`SELECT r.name AS role
3437
3483
  FROM users u
3438
3484
  JOIN roles r ON r.id = u.role_id
3439
3485
  WHERE u.id = ${userId}