@bobbykim/manguito-cms-api 0.7.0 → 0.8.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.mjs CHANGED
@@ -4,11 +4,13 @@ import {
4
4
  createFieldKeyMapFromProjection,
5
5
  deleteParagraphField,
6
6
  isColumnBacked,
7
+ oneOrMany,
8
+ paragraphItems,
7
9
  persistJunctionField,
8
10
  persistParagraphField,
9
11
  resolveRelationBareIds,
10
12
  resolveRelationField
11
- } from "./chunk-FXPKGPYF.mjs";
13
+ } from "./chunk-IHEIX6P4.mjs";
12
14
 
13
15
  // src/paths.ts
14
16
  function normalizePrefix(prefix) {
@@ -36,7 +38,7 @@ function createVersionedPaths(prefix, version) {
36
38
 
37
39
  // src/app.ts
38
40
  import { Hono } from "hono";
39
- import { sql as sql9 } from "drizzle-orm";
41
+ import { sql as sql10 } from "drizzle-orm";
40
42
 
41
43
  // src/middleware/cors.ts
42
44
  function createCorsMiddleware(corsConfig) {
@@ -130,16 +132,20 @@ var ERROR_STATUS_MAP = {
130
132
  UNSUPPORTED_MIME_TYPE: 415,
131
133
  STORAGE_ERROR: 502,
132
134
  MEDIA_IN_USE: 409,
135
+ ITEM_IN_USE: 409,
133
136
  PRESIGNED_URL_EXPIRED: 410,
134
137
  RATE_LIMITED: 429,
135
138
  INTERNAL_ERROR: 500
136
139
  };
137
140
  var errorHandler = (err, c) => {
138
141
  console.error(err.stack ?? err.message);
139
- const apiErr = err;
140
- const code = apiErr.code ?? "INTERNAL_ERROR";
141
- const status = ERROR_STATUS_MAP[code] ?? 500;
142
- return c.json({ ok: false, error: { code, message: err.message } }, status);
142
+ const raw = err.code;
143
+ const deliberate = raw !== void 0 && raw !== "INTERNAL_ERROR" && raw in ERROR_STATUS_MAP;
144
+ if (!deliberate) {
145
+ return c.json({ ok: false, error: { code: "INTERNAL_ERROR", message: "Internal server error" } }, 500);
146
+ }
147
+ const status = ERROR_STATUS_MAP[raw] ?? 500;
148
+ return c.json({ ok: false, error: { code: raw, message: err.message } }, status);
143
149
  };
144
150
 
145
151
  // src/middleware/auth.ts
@@ -1043,7 +1049,55 @@ function registerPublicMediaRoutes(app, mediaRepo, paths, listRateLimit) {
1043
1049
  }
1044
1050
 
1045
1051
  // src/routes/admin/content.ts
1052
+ import { sql as sql3 } from "drizzle-orm";
1053
+
1054
+ // src/references-in-use.ts
1046
1055
  import { sql as sql2 } from "drizzle-orm";
1056
+ import { relationCardinality } from "@bobbykim/manguito-cms-core";
1057
+ function quoteIdent(name) {
1058
+ if (!/^[a-z][a-z0-9_-]*$/.test(name)) throw new Error(`Unsafe identifier: ${name}`);
1059
+ return `"${name}"`;
1060
+ }
1061
+ function isRequiredSingleReferenceTo(f, targetType) {
1062
+ return f.field_type === "reference" && f.required && relationCardinality(f) === "one" && f.db_column?.foreign_key !== void 0 && f.ui_component.component === "typeahead-select" && f.ui_component.ref === targetType;
1063
+ }
1064
+ async function findRequiredReferrers(db, registry, targetType, id) {
1065
+ const owners = [
1066
+ ...Object.values(registry.content_types),
1067
+ ...Object.values(registry.taxonomy_types),
1068
+ ...Object.values(registry.paragraph_types)
1069
+ ];
1070
+ const out = [];
1071
+ for (const owner of owners) {
1072
+ for (const f of owner.fields) {
1073
+ if (!isRequiredSingleReferenceTo(f, targetType)) continue;
1074
+ const notSelf = owner.name === targetType ? sql2` AND id <> ${id}` : sql2``;
1075
+ const r = await db.execute(
1076
+ sql2`SELECT count(*)::int AS n FROM ${sql2.raw(quoteIdent(owner.db.table_name))} WHERE ${sql2.raw(quoteIdent(f.db_column.column_name))} = ${id}${notSelf}`
1077
+ );
1078
+ const n = r.rows[0].n;
1079
+ if (n > 0) out.push({ type_label: owner.label, field_label: f.label, count: n });
1080
+ }
1081
+ }
1082
+ return out;
1083
+ }
1084
+ function inUseMessage(referrers) {
1085
+ const total = referrers.reduce((sum, r) => sum + r.count, 0);
1086
+ const where = referrers.map((r) => `${r.count} in ${r.type_label} (${r.field_label})`).join(", ");
1087
+ return `This item is still used by ${total} item${total === 1 ? "" : "s"}: ${where}. Remove it from those first.`;
1088
+ }
1089
+ var IN_USE_RACE_MESSAGE = "This item is still in use. Remove it from the items that use it first.";
1090
+ function isForeignKeyViolation(err) {
1091
+ let e = err;
1092
+ for (let depth = 0; depth < 3 && typeof e === "object" && e !== null; depth++) {
1093
+ if (e.code === "23503") return true;
1094
+ e = e.cause;
1095
+ }
1096
+ return false;
1097
+ }
1098
+
1099
+ // src/routes/admin/content.ts
1100
+ import { relationCardinality as relationCardinality3 } from "@bobbykim/manguito-cms-core";
1047
1101
 
1048
1102
  // src/media-references.ts
1049
1103
  function extractMediaId(value) {
@@ -1079,14 +1133,60 @@ async function applyMediaReferenceDelta(delta, mediaRepo) {
1079
1133
  if (netRemove.length > 0) await mediaRepo.decrementReferenceCount(netRemove);
1080
1134
  }
1081
1135
 
1136
+ // src/relation-input.ts
1137
+ import { relationCardinality as relationCardinality2 } from "@bobbykim/manguito-cms-core";
1138
+ 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
+ function isUuid(v) {
1140
+ return typeof v === "string" && UUID_RE.test(v);
1141
+ }
1142
+ function isPlainObject(v) {
1143
+ return typeof v === "object" && v !== null && !Array.isArray(v);
1144
+ }
1145
+ function checkRelationInput(fields, body, registry, path = "") {
1146
+ const errors = [];
1147
+ for (const field of fields) {
1148
+ if (!(field.name in body)) continue;
1149
+ const cardinality = relationCardinality2(field);
1150
+ if (cardinality === null) continue;
1151
+ const value = body[field.name];
1152
+ const name = `${path}${field.name}`;
1153
+ if (field.field_type === "reference") {
1154
+ const ok = cardinality === "one" ? value === null || isUuid(value) : Array.isArray(value) && value.every(isUuid);
1155
+ if (!ok) {
1156
+ errors.push({
1157
+ field: name,
1158
+ message: cardinality === "one" ? `${name} holds one item: send a UUID string or null.` : `${name} holds a list: send an array of UUID strings.`
1159
+ });
1160
+ }
1161
+ continue;
1162
+ }
1163
+ const items = cardinality === "one" ? value === null ? [] : isPlainObject(value) ? [value] : void 0 : Array.isArray(value) && value.every(isPlainObject) ? value : void 0;
1164
+ if (items === void 0) {
1165
+ errors.push({
1166
+ field: name,
1167
+ message: cardinality === "one" ? `${name} holds one item: send an object or null.` : `${name} holds a list: send an array of objects.`
1168
+ });
1169
+ continue;
1170
+ }
1171
+ const comp = field.ui_component;
1172
+ const pType = comp.component === "paragraph-embed" ? registry.paragraph_types[comp.ref] : void 0;
1173
+ if (!pType) continue;
1174
+ items.forEach((item, i) => {
1175
+ const prefix = cardinality === "one" ? `${name}.` : `${name}[${i}].`;
1176
+ errors.push(...checkRelationInput(pType.fields, item, registry, prefix));
1177
+ });
1178
+ }
1179
+ return errors;
1180
+ }
1181
+
1082
1182
  // src/routes/admin/content.ts
1083
- function quoteIdent(name) {
1183
+ function quoteIdent2(name) {
1084
1184
  if (!/^[a-z][a-z0-9_-]*$/.test(name)) throw new Error(`Unsafe identifier: ${name}`);
1085
1185
  return `"${name}"`;
1086
1186
  }
1087
1187
  async function loadParagraphRows(db, registry, pType, parentId, fieldName) {
1088
1188
  const result = await db.execute(
1089
- sql2`SELECT * FROM ${sql2.raw(quoteIdent(pType.db.table_name))} WHERE parent_id = ${parentId} AND parent_field = ${fieldName} ORDER BY "order" ASC`
1189
+ sql3`SELECT * FROM ${sql3.raw(quoteIdent2(pType.db.table_name))} WHERE parent_id = ${parentId} AND parent_field = ${fieldName} ORDER BY "order" ASC`
1090
1190
  );
1091
1191
  const rows = result.rows;
1092
1192
  for (const nf of pType.fields) {
@@ -1096,14 +1196,17 @@ async function loadParagraphRows(db, registry, pType, parentId, fieldName) {
1096
1196
  const nType = registry.paragraph_types[comp.ref];
1097
1197
  if (!nType) continue;
1098
1198
  for (const row of rows) {
1099
- row[nf.name] = await loadParagraphRows(db, registry, nType, row["id"], nf.name);
1199
+ row[nf.name] = oneOrMany(
1200
+ relationCardinality3(nf) ?? "many",
1201
+ await loadParagraphRows(db, registry, nType, row["id"], nf.name)
1202
+ );
1100
1203
  }
1101
1204
  }
1102
1205
  return rows;
1103
1206
  }
1104
1207
  async function lookupBasePathId(db, pathOrName) {
1105
1208
  const r = await db.execute(
1106
- sql2`SELECT id FROM base_paths WHERE path = ${pathOrName} OR name = ${pathOrName} LIMIT 1`
1209
+ sql3`SELECT id FROM base_paths WHERE path = ${pathOrName} OR name = ${pathOrName} LIMIT 1`
1107
1210
  );
1108
1211
  return r.rows[0]?.id ?? null;
1109
1212
  }
@@ -1117,6 +1220,14 @@ function isEmpty(value) {
1117
1220
  function checkRequiredFields(fields, data) {
1118
1221
  return fields.filter((f) => f.required && isEmpty(data[f.name])).map((f) => ({ field: f.name, message: `${f.label} is required` }));
1119
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
+ );
1230
+ }
1120
1231
  function parseListQuery(url, schemaFieldNames, relationFieldNames, columnFor) {
1121
1232
  const searchParams = new URL(url).searchParams;
1122
1233
  const pagination = parsePagination(
@@ -1258,11 +1369,11 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
1258
1369
  if (comp.component !== "paragraph-embed" || !comp.ref) continue;
1259
1370
  const pType = registry.paragraph_types[comp.ref];
1260
1371
  if (!pType) continue;
1261
- row[f.name] = await loadParagraphRows(db, registry, pType, id, f.name);
1372
+ row[f.name] = oneOrMany(relationCardinality3(f) ?? "many", await loadParagraphRows(db, registry, pType, id, f.name));
1262
1373
  } else if (f.db_column.junction) {
1263
1374
  const j = f.db_column.junction;
1264
1375
  const r = await db.execute(
1265
- sql2`SELECT ${sql2.raw(quoteIdent(j.right_column))} FROM ${sql2.raw(quoteIdent(j.table_name))} WHERE ${sql2.raw(quoteIdent(j.left_column))} = ${id}`
1376
+ sql3`SELECT ${sql3.raw(quoteIdent2(j.right_column))} FROM ${sql3.raw(quoteIdent2(j.table_name))} WHERE ${sql3.raw(quoteIdent2(j.left_column))} = ${id}`
1266
1377
  );
1267
1378
  row[f.name] = r.rows.map(
1268
1379
  (row2) => row2[j.right_column]
@@ -1338,6 +1449,8 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
1338
1449
  }
1339
1450
  );
1340
1451
  const writeNewItem = async (c, body) => {
1452
+ const shapeError = relationShapeError(c, contentType.fields, body, registry);
1453
+ if (shapeError) return shapeError;
1341
1454
  const storageBody = fieldKeys.toStorage(body);
1342
1455
  const fieldErrors = checkRequiredFields(requiredFields, body);
1343
1456
  if (fieldErrors.length > 0) {
@@ -1401,7 +1514,7 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
1401
1514
  if (comp.component !== "paragraph-embed" || !comp.ref) continue;
1402
1515
  const pType = registry.paragraph_types[comp.ref];
1403
1516
  if (!pType) continue;
1404
- const items = Array.isArray(body[f.name]) ? body[f.name] : [];
1517
+ const items = paragraphItems(f, body[f.name]);
1405
1518
  mediaDeltas.push(await persistParagraphField(db, itemId, contentType.db.table_name, f.name, pType, items, registry));
1406
1519
  }
1407
1520
  for (const f of junctionFields) {
@@ -1472,6 +1585,8 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
1472
1585
  }
1473
1586
  );
1474
1587
  const writeExistingItem = async (c, id, existing, body) => {
1588
+ const shapeError = relationShapeError(c, contentType.fields, body, registry);
1589
+ if (shapeError) return shapeError;
1475
1590
  const storageBody = fieldKeys.toStorage(body);
1476
1591
  if (body["published"] === true) {
1477
1592
  const publishDeny = await requirePermission("content:edit")(c, async () => {
@@ -1518,14 +1633,16 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
1518
1633
  ];
1519
1634
  if (db) {
1520
1635
  for (const f of patchParagraphFields) {
1636
+ if (!(f.name in body)) continue;
1521
1637
  const comp = f.ui_component;
1522
1638
  if (comp.component !== "paragraph-embed" || !comp.ref) continue;
1523
1639
  const pType = registry.paragraph_types[comp.ref];
1524
1640
  if (!pType) continue;
1525
- const items = Array.isArray(body[f.name]) ? body[f.name] : [];
1641
+ const items = paragraphItems(f, body[f.name]);
1526
1642
  mediaDeltas.push(await persistParagraphField(db, id, contentType.db.table_name, f.name, pType, items, registry));
1527
1643
  }
1528
1644
  for (const f of patchJunctionFields) {
1645
+ if (!(f.name in body)) continue;
1529
1646
  const junction = f.db_column.junction;
1530
1647
  const relatedIds = Array.isArray(body[f.name]) ? body[f.name].filter((v) => typeof v === "string") : [];
1531
1648
  await persistJunctionField(db, id, junction, relatedIds);
@@ -1565,6 +1682,18 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
1565
1682
  404
1566
1683
  );
1567
1684
  }
1685
+ const referrers = db ? await findRequiredReferrers(db, registry, typeName, id) : [];
1686
+ if (referrers.length > 0) {
1687
+ return c.json({ ok: false, error: { code: "ITEM_IN_USE", message: inUseMessage(referrers) } }, 409);
1688
+ }
1689
+ try {
1690
+ await repo.delete(id);
1691
+ } catch (err) {
1692
+ if (isForeignKeyViolation(err)) {
1693
+ return c.json({ ok: false, error: { code: "ITEM_IN_USE", message: IN_USE_RACE_MESSAGE } }, 409);
1694
+ }
1695
+ throw err;
1696
+ }
1568
1697
  const mediaDeltas = [
1569
1698
  topLevelMediaDelta(mediaFields, item, null)
1570
1699
  ];
@@ -1578,7 +1707,6 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
1578
1707
  }
1579
1708
  }
1580
1709
  await applyMediaReferenceDelta(mergeMediaDeltas(...mediaDeltas), mediaRepo);
1581
- await repo.delete(id);
1582
1710
  return c.json({ ok: true });
1583
1711
  }
1584
1712
  );
@@ -1656,6 +1784,8 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
1656
1784
  requirePermission("content:create"),
1657
1785
  async (c) => {
1658
1786
  const body = await c.req.json();
1787
+ const shapeError = relationShapeError(c, taxonomyType.fields, body, registry);
1788
+ if (shapeError) return shapeError;
1659
1789
  const storageBody = fieldKeys.toStorage(body);
1660
1790
  const fieldErrors = checkRequiredFields(requiredFields, body);
1661
1791
  if (fieldErrors.length > 0) {
@@ -1695,7 +1825,7 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
1695
1825
  if (comp.component !== "paragraph-embed" || !comp.ref) continue;
1696
1826
  const pType = registry.paragraph_types[comp.ref];
1697
1827
  if (!pType) continue;
1698
- const items = Array.isArray(body[f.name]) ? body[f.name] : [];
1828
+ const items = paragraphItems(f, body[f.name]);
1699
1829
  mediaDeltas.push(await persistParagraphField(db, taxItemId, taxonomyType.db.table_name, f.name, pType, items, registry));
1700
1830
  }
1701
1831
  }
@@ -1718,6 +1848,8 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
1718
1848
  404
1719
1849
  );
1720
1850
  }
1851
+ const shapeError = relationShapeError(c, taxonomyType.fields, body, registry);
1852
+ if (shapeError) return shapeError;
1721
1853
  if (body["published"] === true) {
1722
1854
  const publishDeny = await requirePermission("content:edit")(c, async () => {
1723
1855
  });
@@ -1759,11 +1891,12 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
1759
1891
  ];
1760
1892
  if (db) {
1761
1893
  for (const f of taxPatchParagraphFields) {
1894
+ if (!(f.name in body)) continue;
1762
1895
  const comp = f.ui_component;
1763
1896
  if (comp.component !== "paragraph-embed" || !comp.ref) continue;
1764
1897
  const pType = registry.paragraph_types[comp.ref];
1765
1898
  if (!pType) continue;
1766
- const items = Array.isArray(body[f.name]) ? body[f.name] : [];
1899
+ const items = paragraphItems(f, body[f.name]);
1767
1900
  mediaDeltas.push(await persistParagraphField(db, id, taxonomyType.db.table_name, f.name, pType, items, registry));
1768
1901
  }
1769
1902
  }
@@ -1784,6 +1917,18 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
1784
1917
  404
1785
1918
  );
1786
1919
  }
1920
+ const referrers = db ? await findRequiredReferrers(db, registry, typeName, id) : [];
1921
+ if (referrers.length > 0) {
1922
+ return c.json({ ok: false, error: { code: "ITEM_IN_USE", message: inUseMessage(referrers) } }, 409);
1923
+ }
1924
+ try {
1925
+ await repo.delete(id);
1926
+ } catch (err) {
1927
+ if (isForeignKeyViolation(err)) {
1928
+ return c.json({ ok: false, error: { code: "ITEM_IN_USE", message: IN_USE_RACE_MESSAGE } }, 409);
1929
+ }
1930
+ throw err;
1931
+ }
1787
1932
  const mediaDeltas = [
1788
1933
  topLevelMediaDelta(mediaFields, item, null)
1789
1934
  ];
@@ -1797,7 +1942,6 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
1797
1942
  }
1798
1943
  }
1799
1944
  await applyMediaReferenceDelta(mergeMediaDeltas(...mediaDeltas), mediaRepo);
1800
- await repo.delete(id);
1801
1945
  return c.json({ ok: true });
1802
1946
  }
1803
1947
  );
@@ -2259,7 +2403,7 @@ function registerAdminMediaRoutes(app, mediaRepo, storage, requirePermission, ma
2259
2403
 
2260
2404
  // src/routes/admin/auth.ts
2261
2405
  import { getCookie as getCookie2 } from "hono/cookie";
2262
- import { sql as sql3 } from "drizzle-orm";
2406
+ import { sql as sql4 } from "drizzle-orm";
2263
2407
 
2264
2408
  // src/auth/password.ts
2265
2409
  import { hashPassword, verifyPassword } from "@bobbykim/manguito-cms-core";
@@ -2325,7 +2469,7 @@ function registerAuthRoutes(app, db) {
2325
2469
  );
2326
2470
  }
2327
2471
  const userResult = await db.execute(
2328
- sql3`SELECT id, email, password_hash, role_id, token_version
2472
+ sql4`SELECT id, email, password_hash, role_id, token_version
2329
2473
  FROM users WHERE email = ${email} LIMIT 1`
2330
2474
  );
2331
2475
  const user = userResult.rows[0];
@@ -2338,7 +2482,7 @@ function registerAuthRoutes(app, db) {
2338
2482
  return c.json(invalidCredentials, 401);
2339
2483
  }
2340
2484
  const roleResult = await db.execute(
2341
- sql3`SELECT r.name AS name, u.must_change_password
2485
+ sql4`SELECT r.name AS name, u.must_change_password
2342
2486
  FROM roles r
2343
2487
  JOIN users u ON u.role_id = r.id
2344
2488
  WHERE r.id = ${user.role_id} AND u.id = ${user.id}
@@ -2398,7 +2542,7 @@ function registerAuthRoutes(app, db) {
2398
2542
  );
2399
2543
  }
2400
2544
  const rowResult = await db.execute(
2401
- sql3`SELECT u.token_version, r.name AS role
2545
+ sql4`SELECT u.token_version, r.name AS role
2402
2546
  FROM users u
2403
2547
  JOIN roles r ON r.id = u.role_id
2404
2548
  WHERE u.id = ${payload.user_id}
@@ -2422,7 +2566,7 @@ function registerAuthRoutes(app, db) {
2422
2566
  app.post("/admin/api/auth/logout", authMiddleware, async (c) => {
2423
2567
  const user = c.get("user");
2424
2568
  await db.execute(
2425
- sql3`UPDATE users SET token_version = token_version + 1 WHERE id = ${user.id}`
2569
+ sql4`UPDATE users SET token_version = token_version + 1 WHERE id = ${user.id}`
2426
2570
  );
2427
2571
  clearAuthCookie(c, "auth_token");
2428
2572
  clearAuthCookie(c, "refresh_token", "/admin/api/auth");
@@ -2431,7 +2575,7 @@ function registerAuthRoutes(app, db) {
2431
2575
  }
2432
2576
 
2433
2577
  // src/routes/admin/users.ts
2434
- import { sql as sql4 } from "drizzle-orm";
2578
+ import { sql as sql5 } from "drizzle-orm";
2435
2579
  import { randomBytes } from "crypto";
2436
2580
  function getActingUser(c) {
2437
2581
  return c.get("user");
@@ -2466,7 +2610,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
2466
2610
  );
2467
2611
  }
2468
2612
  const hashResult = await db.execute(
2469
- sql4`SELECT password_hash FROM users WHERE id = ${actingUser.id} LIMIT 1`
2613
+ sql5`SELECT password_hash FROM users WHERE id = ${actingUser.id} LIMIT 1`
2470
2614
  );
2471
2615
  const hashRow = hashResult.rows[0];
2472
2616
  if (!hashRow || !await verifyPassword(currentPassword, hashRow.password_hash)) {
@@ -2477,7 +2621,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
2477
2621
  }
2478
2622
  const newHash = await hashPassword(newPassword);
2479
2623
  await db.execute(
2480
- sql4`UPDATE users
2624
+ sql5`UPDATE users
2481
2625
  SET password_hash = ${newHash},
2482
2626
  must_change_password = false,
2483
2627
  token_version = token_version + 1,
@@ -2488,7 +2632,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
2488
2632
  });
2489
2633
  app.get("/admin/api/users", requirePermission("users:read"), async (c) => {
2490
2634
  const result = await db.execute(
2491
- sql4`SELECT u.id, u.email, r.name AS role, u.must_change_password,
2635
+ sql5`SELECT u.id, u.email, r.name AS role, u.must_change_password,
2492
2636
  u.created_at, u.updated_at
2493
2637
  FROM users u
2494
2638
  JOIN roles r ON r.id = u.role_id
@@ -2499,7 +2643,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
2499
2643
  app.get("/admin/api/users/:id", requirePermission("users:read"), async (c) => {
2500
2644
  const id = c.req.param("id");
2501
2645
  const result = await db.execute(
2502
- sql4`SELECT u.id, u.email, r.name AS role, u.must_change_password,
2646
+ sql5`SELECT u.id, u.email, r.name AS role, u.must_change_password,
2503
2647
  u.created_at, u.updated_at
2504
2648
  FROM users u
2505
2649
  JOIN roles r ON r.id = u.role_id
@@ -2544,7 +2688,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
2544
2688
  );
2545
2689
  }
2546
2690
  const roleResult = await db.execute(
2547
- sql4`SELECT id FROM roles WHERE name = ${roleName} LIMIT 1`
2691
+ sql5`SELECT id FROM roles WHERE name = ${roleName} LIMIT 1`
2548
2692
  );
2549
2693
  const roleRow = roleResult.rows[0];
2550
2694
  if (!roleRow) {
@@ -2559,7 +2703,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
2559
2703
  const tempPassword = generateTempPassword();
2560
2704
  const passwordHash = await hashPassword(tempPassword);
2561
2705
  const insertResult = await db.execute(
2562
- sql4`INSERT INTO users
2706
+ sql5`INSERT INTO users
2563
2707
  (id, email, password_hash, role_id, must_change_password, token_version,
2564
2708
  created_at, updated_at)
2565
2709
  VALUES
@@ -2614,7 +2758,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
2614
2758
  );
2615
2759
  }
2616
2760
  const existingResult = await db.execute(
2617
- sql4`SELECT id, email, role_id FROM users WHERE id = ${id} LIMIT 1`
2761
+ sql5`SELECT id, email, role_id FROM users WHERE id = ${id} LIMIT 1`
2618
2762
  );
2619
2763
  const existing = existingResult.rows[0];
2620
2764
  if (!existing) {
@@ -2628,7 +2772,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
2628
2772
  let changeRole = false;
2629
2773
  if (typeof body.role === "string" && body.role) {
2630
2774
  const roleResult = await db.execute(
2631
- sql4`SELECT id FROM roles WHERE name = ${body.role} LIMIT 1`
2775
+ sql5`SELECT id FROM roles WHERE name = ${body.role} LIMIT 1`
2632
2776
  );
2633
2777
  const roleRow = roleResult.rows[0];
2634
2778
  if (!roleRow) {
@@ -2648,7 +2792,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
2648
2792
  }
2649
2793
  if (changeRole) {
2650
2794
  await db.execute(
2651
- sql4`UPDATE users
2795
+ sql5`UPDATE users
2652
2796
  SET email = ${newEmail},
2653
2797
  role_id = ${newRoleId},
2654
2798
  token_version = token_version + 1,
@@ -2657,7 +2801,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
2657
2801
  );
2658
2802
  } else {
2659
2803
  await db.execute(
2660
- sql4`UPDATE users
2804
+ sql5`UPDATE users
2661
2805
  SET email = ${newEmail},
2662
2806
  role_id = ${newRoleId},
2663
2807
  updated_at = NOW()
@@ -2665,7 +2809,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
2665
2809
  );
2666
2810
  }
2667
2811
  const updatedResult = await db.execute(
2668
- sql4`SELECT u.id, u.email, r.name AS role, u.must_change_password,
2812
+ sql5`SELECT u.id, u.email, r.name AS role, u.must_change_password,
2669
2813
  u.created_at, u.updated_at
2670
2814
  FROM users u
2671
2815
  JOIN roles r ON r.id = u.role_id
@@ -2702,7 +2846,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
2702
2846
  );
2703
2847
  }
2704
2848
  const result = await db.execute(
2705
- sql4`DELETE FROM users WHERE id = ${id} RETURNING id`
2849
+ sql5`DELETE FROM users WHERE id = ${id} RETURNING id`
2706
2850
  );
2707
2851
  if (result.rows.length === 0) {
2708
2852
  return c.json(
@@ -2733,7 +2877,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
2733
2877
  );
2734
2878
  }
2735
2879
  const existingResult = await db.execute(
2736
- sql4`SELECT id FROM users WHERE id = ${id} LIMIT 1`
2880
+ sql5`SELECT id FROM users WHERE id = ${id} LIMIT 1`
2737
2881
  );
2738
2882
  const existing = existingResult.rows[0];
2739
2883
  if (!existing) {
@@ -2745,7 +2889,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
2745
2889
  const tempPassword = generateTempPassword();
2746
2890
  const passwordHash = await hashPassword(tempPassword);
2747
2891
  await db.execute(
2748
- sql4`UPDATE users
2892
+ sql5`UPDATE users
2749
2893
  SET password_hash = ${passwordHash},
2750
2894
  must_change_password = true,
2751
2895
  token_version = token_version + 1,
@@ -2758,10 +2902,10 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
2758
2902
  }
2759
2903
 
2760
2904
  // src/routes/admin/config.ts
2761
- import { sql as sql5 } from "drizzle-orm";
2905
+ import { sql as sql6 } from "drizzle-orm";
2762
2906
  var API_VERSION = (() => {
2763
2907
  try {
2764
- return "0.7.0";
2908
+ return "0.8.0";
2765
2909
  } catch {
2766
2910
  return "0.0.0";
2767
2911
  }
@@ -2780,7 +2924,7 @@ function registerConfigRoute(app, config, registry, db) {
2780
2924
  permissions: r.permissions
2781
2925
  }));
2782
2926
  const userResult = await db.execute(
2783
- sql5`SELECT email, must_change_password FROM users WHERE id = ${actingUser.id} LIMIT 1`
2927
+ sql6`SELECT email, must_change_password FROM users WHERE id = ${actingUser.id} LIMIT 1`
2784
2928
  );
2785
2929
  const userRow = userResult.rows[0];
2786
2930
  return c.json({
@@ -2806,7 +2950,7 @@ function registerConfigRoute(app, config, registry, db) {
2806
2950
  }
2807
2951
 
2808
2952
  // src/routes/admin/schema.ts
2809
- import { sql as sql6 } from "drizzle-orm";
2953
+ import { sql as sql7 } from "drizzle-orm";
2810
2954
  function liveFields(fields) {
2811
2955
  return fields.filter((f) => f.removed !== true);
2812
2956
  }
@@ -2846,7 +2990,7 @@ function registerSchemaRoute(app, registry, db) {
2846
2990
  const data = await Promise.all(
2847
2991
  Object.values(registry.content_types).map(async (ct) => {
2848
2992
  const result = await db.execute(
2849
- sql6`SELECT COUNT(*)::int AS count FROM ${sql6.raw(`"${ct.db.table_name}"`)}`
2993
+ sql7`SELECT COUNT(*)::int AS count FROM ${sql7.raw(`"${ct.db.table_name}"`)}`
2850
2994
  );
2851
2995
  const count = Number(result.rows[0].count);
2852
2996
  return { name: ct.name, label: ct.label, only_one: ct.only_one, count };
@@ -2858,7 +3002,7 @@ function registerSchemaRoute(app, registry, db) {
2858
3002
  const data = await Promise.all(
2859
3003
  Object.values(registry.taxonomy_types).map(async (tt) => {
2860
3004
  const result = await db.execute(
2861
- sql6`SELECT COUNT(*)::int AS count FROM ${sql6.raw(`"${tt.db.table_name}"`)}`
3005
+ sql7`SELECT COUNT(*)::int AS count FROM ${sql7.raw(`"${tt.db.table_name}"`)}`
2862
3006
  );
2863
3007
  const count = Number(result.rows[0].count);
2864
3008
  return { name: tt.name, label: tt.label, count };
@@ -2870,14 +3014,14 @@ function registerSchemaRoute(app, registry, db) {
2870
3014
 
2871
3015
  // src/repositories/content.ts
2872
3016
  import crypto from "crypto";
2873
- import { sql as sql7 } from "drizzle-orm";
3017
+ import { sql as sql8 } from "drizzle-orm";
2874
3018
  var SORTABLE_FIELDS2 = /* @__PURE__ */ new Set(["title", "created_at", "updated_at"]);
2875
3019
  function codeError(code, message) {
2876
3020
  const err = new Error(message);
2877
3021
  err.code = code;
2878
3022
  return err;
2879
3023
  }
2880
- function quoteIdent2(name) {
3024
+ function quoteIdent3(name) {
2881
3025
  if (!/^[a-z][a-z0-9_-]*$/.test(name)) {
2882
3026
  throw new Error(`Unsafe SQL identifier: ${name}`);
2883
3027
  }
@@ -2892,43 +3036,43 @@ function quoteField(name) {
2892
3036
  function buildConditions(published_only, filters, search) {
2893
3037
  const conds = [];
2894
3038
  if (published_only) {
2895
- conds.push(sql7`published = true`);
3039
+ conds.push(sql8`published = true`);
2896
3040
  }
2897
3041
  if (search && search.term.trim() !== "" && search.columns.length > 0) {
2898
3042
  const pattern = `%${search.term.trim()}%`;
2899
- const orConds = search.columns.map((col) => sql7`${sql7.raw(quoteField(col))} ILIKE ${pattern}`);
2900
- conds.push(sql7`(${sql7.join(orConds, sql7` OR `)})`);
3043
+ const orConds = search.columns.map((col) => sql8`${sql8.raw(quoteField(col))} ILIKE ${pattern}`);
3044
+ conds.push(sql8`(${sql8.join(orConds, sql8` OR `)})`);
2901
3045
  }
2902
3046
  if (filters) {
2903
3047
  for (const [field, value] of Object.entries(filters)) {
2904
- const col = sql7.raw(quoteField(field));
3048
+ const col = sql8.raw(quoteField(field));
2905
3049
  if (Array.isArray(value)) {
2906
3050
  if (value.length === 0) continue;
2907
- const inList = sql7.join(
2908
- value.map((v) => sql7`${v}`),
2909
- sql7`, `
3051
+ const inList = sql8.join(
3052
+ value.map((v) => sql8`${v}`),
3053
+ sql8`, `
2910
3054
  );
2911
- conds.push(sql7`${col} IN (${inList})`);
3055
+ conds.push(sql8`${col} IN (${inList})`);
2912
3056
  } else if (typeof value === "object" && value !== null) {
2913
3057
  const op = value;
2914
- if (op.gt !== void 0) conds.push(sql7`${col} > ${op.gt}`);
2915
- if (op.gte !== void 0) conds.push(sql7`${col} >= ${op.gte}`);
2916
- if (op.lt !== void 0) conds.push(sql7`${col} < ${op.lt}`);
2917
- if (op.lte !== void 0) conds.push(sql7`${col} <= ${op.lte}`);
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}`);
2918
3062
  } else {
2919
- conds.push(sql7`${col} = ${value}`);
3063
+ conds.push(sql8`${col} = ${value}`);
2920
3064
  }
2921
3065
  }
2922
3066
  }
2923
3067
  return conds;
2924
3068
  }
2925
3069
  function whereFragment(conds) {
2926
- return conds.length > 0 ? sql7` WHERE ${sql7.join(conds, sql7` AND `)}` : sql7``;
3070
+ return conds.length > 0 ? sql8` WHERE ${sql8.join(conds, sql8` AND `)}` : sql8``;
2927
3071
  }
2928
3072
  function createDrizzleContentRepository(db, tableName, options = {}) {
2929
3073
  const { relations = {}, publishedRelations = false, sortableColumns } = options;
2930
3074
  function tableRaw() {
2931
- return sql7.raw(quoteIdent2(tableName));
3075
+ return sql8.raw(quoteIdent3(tableName));
2932
3076
  }
2933
3077
  async function resolveRows(rows, include, cache) {
2934
3078
  if (rows.length === 0) return rows;
@@ -2971,14 +3115,14 @@ function createDrizzleContentRepository(db, tableName, options = {}) {
2971
3115
  const offset = (page - 1) * per_page;
2972
3116
  const conds = buildConditions(published_only, filters, search);
2973
3117
  const where = whereFragment(conds);
2974
- const sortCol = sql7.raw(quoteIdent2(sort_by));
2975
- const sortDir = sql7.raw(sort_order === "desc" ? "DESC" : "ASC");
3118
+ const sortCol = sql8.raw(quoteIdent3(sort_by));
3119
+ const sortDir = sql8.raw(sort_order === "desc" ? "DESC" : "ASC");
2976
3120
  const countResult = await db.execute(
2977
- sql7`SELECT COUNT(*) AS total FROM ${tableRaw()}${where}`
3121
+ sql8`SELECT COUNT(*) AS total FROM ${tableRaw()}${where}`
2978
3122
  );
2979
3123
  const total = Number(countResult.rows[0]?.["total"] ?? 0);
2980
3124
  const dataResult = await db.execute(
2981
- sql7`SELECT * FROM ${tableRaw()}${where} ORDER BY ${sortCol} ${sortDir} LIMIT ${per_page} OFFSET ${offset}`
3125
+ sql8`SELECT * FROM ${tableRaw()}${where} ORDER BY ${sortCol} ${sortDir} LIMIT ${per_page} OFFSET ${offset}`
2982
3126
  );
2983
3127
  const rows = dataResult.rows;
2984
3128
  const cache = /* @__PURE__ */ new Map();
@@ -2999,7 +3143,7 @@ function createDrizzleContentRepository(db, tableName, options = {}) {
2999
3143
  },
3000
3144
  async findOne(id, include = []) {
3001
3145
  const result = await db.execute(
3002
- sql7`SELECT * FROM ${tableRaw()} WHERE id = ${id} LIMIT 1`
3146
+ sql8`SELECT * FROM ${tableRaw()} WHERE id = ${id} LIMIT 1`
3003
3147
  );
3004
3148
  if (result.rows.length === 0) return null;
3005
3149
  const rows = result.rows;
@@ -3009,7 +3153,7 @@ function createDrizzleContentRepository(db, tableName, options = {}) {
3009
3153
  },
3010
3154
  async findBySlug(slug, include = []) {
3011
3155
  const result = await db.execute(
3012
- sql7`SELECT * FROM ${tableRaw()} WHERE slug = ${slug} LIMIT 1`
3156
+ sql8`SELECT * FROM ${tableRaw()} WHERE slug = ${slug} LIMIT 1`
3013
3157
  );
3014
3158
  if (result.rows.length === 0) return null;
3015
3159
  const rows = result.rows;
@@ -3025,10 +3169,10 @@ function createDrizzleContentRepository(db, tableName, options = {}) {
3025
3169
  updated_at: /* @__PURE__ */ new Date()
3026
3170
  };
3027
3171
  const entries = Object.entries(record).filter(([, v]) => v !== void 0);
3028
- const colsSql = sql7.join(entries.map(([k]) => sql7.raw(quoteIdent2(k))), sql7`, `);
3029
- const valsSql = sql7.join(entries.map(([, v]) => sql7`${v}`), sql7`, `);
3172
+ const colsSql = sql8.join(entries.map(([k]) => sql8.raw(quoteIdent3(k))), sql8`, `);
3173
+ const valsSql = sql8.join(entries.map(([, v]) => sql8`${v}`), sql8`, `);
3030
3174
  const result = await db.execute(
3031
- sql7`INSERT INTO ${tableRaw()} (${colsSql}) VALUES (${valsSql}) RETURNING *`
3175
+ sql8`INSERT INTO ${tableRaw()} (${colsSql}) VALUES (${valsSql}) RETURNING *`
3032
3176
  );
3033
3177
  return result.rows[0];
3034
3178
  },
@@ -3041,25 +3185,25 @@ function createDrizzleContentRepository(db, tableName, options = {}) {
3041
3185
  if (entries.length === 0) {
3042
3186
  return this.findOne(id);
3043
3187
  }
3044
- const setClauses = sql7.join(
3045
- entries.map(([k, v]) => sql7`${sql7.raw(quoteIdent2(k))} = ${v}`),
3046
- sql7`, `
3188
+ const setClauses = sql8.join(
3189
+ entries.map(([k, v]) => sql8`${sql8.raw(quoteIdent3(k))} = ${v}`),
3190
+ sql8`, `
3047
3191
  );
3048
3192
  const result = await db.execute(
3049
- sql7`UPDATE ${tableRaw()} SET ${setClauses} WHERE id = ${id} RETURNING *`
3193
+ sql8`UPDATE ${tableRaw()} SET ${setClauses} WHERE id = ${id} RETURNING *`
3050
3194
  );
3051
3195
  if (result.rows.length === 0) return null;
3052
3196
  return result.rows[0];
3053
3197
  },
3054
3198
  async delete(id) {
3055
- await db.execute(sql7`DELETE FROM ${tableRaw()} WHERE id = ${id}`);
3199
+ await db.execute(sql8`DELETE FROM ${tableRaw()} WHERE id = ${id}`);
3056
3200
  },
3057
3201
  async findAll(opts = {}) {
3058
3202
  const { include = [], published_only } = opts;
3059
3203
  const conds = buildConditions(published_only, void 0, void 0);
3060
3204
  const where = whereFragment(conds);
3061
3205
  const result = await db.execute(
3062
- sql7`SELECT * FROM ${tableRaw()}${where} ORDER BY created_at ASC`
3206
+ sql8`SELECT * FROM ${tableRaw()}${where} ORDER BY created_at ASC`
3063
3207
  );
3064
3208
  const rows = result.rows;
3065
3209
  const cache = /* @__PURE__ */ new Map();
@@ -3071,23 +3215,23 @@ function createDrizzleContentRepository(db, tableName, options = {}) {
3071
3215
 
3072
3216
  // src/repositories/media.ts
3073
3217
  import crypto2 from "crypto";
3074
- import { sql as sql8 } from "drizzle-orm";
3218
+ import { sql as sql9 } from "drizzle-orm";
3075
3219
  function createMediaRepository(db) {
3076
- const TABLE = sql8.raw('"media"');
3220
+ const TABLE = sql9.raw('"media"');
3077
3221
  return {
3078
3222
  async findMany(opts = {}) {
3079
3223
  const { page = 1, per_page = 10, type, orphaned } = opts;
3080
3224
  const offset = (page - 1) * per_page;
3081
3225
  const conds = [];
3082
- if (type) conds.push(sql8`type = ${type}`);
3083
- if (orphaned) conds.push(sql8`reference_count = 0`);
3084
- const where = conds.length > 0 ? sql8` WHERE ${sql8.join(conds, sql8` AND `)}` : sql8``;
3085
- const countResult = await db.execute(sql8`SELECT COUNT(*) AS total FROM ${TABLE}${where}`);
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}`);
3086
3230
  const total = Number(
3087
3231
  countResult.rows[0]?.["total"] ?? 0
3088
3232
  );
3089
3233
  const dataResult = await db.execute(
3090
- sql8`SELECT * FROM ${TABLE}${where} ORDER BY created_at DESC LIMIT ${per_page} OFFSET ${offset}`
3234
+ sql9`SELECT * FROM ${TABLE}${where} ORDER BY created_at DESC LIMIT ${per_page} OFFSET ${offset}`
3091
3235
  );
3092
3236
  const total_pages = Math.ceil(total / per_page);
3093
3237
  return {
@@ -3105,7 +3249,7 @@ function createMediaRepository(db) {
3105
3249
  },
3106
3250
  async findOne(id) {
3107
3251
  const result = await db.execute(
3108
- sql8`SELECT * FROM ${TABLE} WHERE id = ${id} LIMIT 1`
3252
+ sql9`SELECT * FROM ${TABLE} WHERE id = ${id} LIMIT 1`
3109
3253
  );
3110
3254
  if (result.rows.length === 0) return null;
3111
3255
  return result.rows[0];
@@ -3119,10 +3263,10 @@ function createMediaRepository(db) {
3119
3263
  updated_at: /* @__PURE__ */ new Date()
3120
3264
  };
3121
3265
  const entries = Object.entries(record).filter(([, v]) => v !== void 0);
3122
- const colsSql = sql8.join(entries.map(([k]) => sql8.raw(`"${k}"`)), sql8`, `);
3123
- const valsSql = sql8.join(entries.map(([, v]) => sql8`${v}`), sql8`, `);
3266
+ const colsSql = sql9.join(entries.map(([k]) => sql9.raw(`"${k}"`)), sql9`, `);
3267
+ const valsSql = sql9.join(entries.map(([, v]) => sql9`${v}`), sql9`, `);
3124
3268
  const result = await db.execute(
3125
- sql8`INSERT INTO ${TABLE} (${colsSql}) VALUES (${valsSql}) RETURNING *`
3269
+ sql9`INSERT INTO ${TABLE} (${colsSql}) VALUES (${valsSql}) RETURNING *`
3126
3270
  );
3127
3271
  return result.rows[0];
3128
3272
  },
@@ -3132,31 +3276,31 @@ function createMediaRepository(db) {
3132
3276
  ([k, v]) => k !== "id" && v !== void 0
3133
3277
  );
3134
3278
  if (entries.length === 0) return this.findOne(id);
3135
- const setClauses = sql8.join(
3136
- entries.map(([k, v]) => sql8`${sql8.raw(`"${k}"`)} = ${v}`),
3137
- sql8`, `
3279
+ const setClauses = sql9.join(
3280
+ entries.map(([k, v]) => sql9`${sql9.raw(`"${k}"`)} = ${v}`),
3281
+ sql9`, `
3138
3282
  );
3139
3283
  const result = await db.execute(
3140
- sql8`UPDATE ${TABLE} SET ${setClauses} WHERE id = ${id} RETURNING *`
3284
+ sql9`UPDATE ${TABLE} SET ${setClauses} WHERE id = ${id} RETURNING *`
3141
3285
  );
3142
3286
  if (result.rows.length === 0) return null;
3143
3287
  return result.rows[0];
3144
3288
  },
3145
3289
  async delete(id) {
3146
- await db.execute(sql8`DELETE FROM ${TABLE} WHERE id = ${id}`);
3290
+ await db.execute(sql9`DELETE FROM ${TABLE} WHERE id = ${id}`);
3147
3291
  },
3148
3292
  async incrementReferenceCount(ids) {
3149
3293
  if (ids.length === 0) return;
3150
- const inList = sql8.join(ids.map((id) => sql8`${id}`), sql8`, `);
3294
+ const inList = sql9.join(ids.map((id) => sql9`${id}`), sql9`, `);
3151
3295
  await db.execute(
3152
- sql8`UPDATE ${TABLE} SET reference_count = reference_count + 1, updated_at = NOW() WHERE id IN (${inList})`
3296
+ sql9`UPDATE ${TABLE} SET reference_count = reference_count + 1, updated_at = NOW() WHERE id IN (${inList})`
3153
3297
  );
3154
3298
  },
3155
3299
  async decrementReferenceCount(ids) {
3156
3300
  if (ids.length === 0) return;
3157
- const inList = sql8.join(ids.map((id) => sql8`${id}`), sql8`, `);
3301
+ const inList = sql9.join(ids.map((id) => sql9`${id}`), sql9`, `);
3158
3302
  await db.execute(
3159
- sql8`UPDATE ${TABLE} SET reference_count = GREATEST(reference_count - 1, 0), updated_at = NOW() WHERE id IN (${inList})`
3303
+ sql9`UPDATE ${TABLE} SET reference_count = GREATEST(reference_count - 1, 0), updated_at = NOW() WHERE id IN (${inList})`
3160
3304
  );
3161
3305
  }
3162
3306
  };
@@ -3289,7 +3433,7 @@ function createCmsApp(options) {
3289
3433
  const requirePermission = createPermissionMiddleware(rolesRegistry);
3290
3434
  const getUserRole = async (userId) => {
3291
3435
  const result = await db.execute(
3292
- sql9`SELECT r.name AS role
3436
+ sql10`SELECT r.name AS role
3293
3437
  FROM users u
3294
3438
  JOIN roles r ON r.id = u.role_id
3295
3439
  WHERE u.id = ${userId}
@@ -3459,7 +3603,7 @@ function createCmsApp(options) {
3459
3603
  handler: null,
3460
3604
  error: null
3461
3605
  }));
3462
- const ready = import("./handler-GED3I7Z2.mjs").then(({ createGraphQLHandler }) => {
3606
+ const ready = import("./handler-VEOWJRAL.mjs").then(({ createGraphQLHandler }) => {
3463
3607
  gqlPasses.forEach((pass, i) => {
3464
3608
  const mount = mounts[i];
3465
3609
  try {