@bobbykim/manguito-cms-api 0.7.0 → 0.8.1
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/{chunk-FXPKGPYF.mjs → chunk-IHEIX6P4.mjs} +22 -6
- package/dist/chunk-IHEIX6P4.mjs.map +1 -0
- package/dist/{chunk-4LLELFAZ.mjs → chunk-Y5SWT7PO.mjs} +6 -5
- package/dist/chunk-Y5SWT7PO.mjs.map +1 -0
- package/dist/codegen/index.js +3 -2
- package/dist/codegen/index.js.map +1 -1
- package/dist/codegen/index.mjs +3 -2
- package/dist/codegen/index.mjs.map +1 -1
- package/dist/graphql/index.js +14 -5
- package/dist/graphql/index.js.map +1 -1
- package/dist/graphql/index.mjs +2 -2
- package/dist/handler-VEOWJRAL.mjs +8 -0
- package/dist/index.js +391 -157
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +324 -105
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/dist/chunk-4LLELFAZ.mjs.map +0 -1
- package/dist/chunk-FXPKGPYF.mjs.map +0 -1
- package/dist/handler-GED3I7Z2.mjs +0 -8
- /package/dist/{handler-GED3I7Z2.mjs.map → handler-VEOWJRAL.mjs.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -144,7 +144,7 @@ var init_field_keys = __esm({
|
|
|
144
144
|
});
|
|
145
145
|
|
|
146
146
|
// src/relations.ts
|
|
147
|
-
function
|
|
147
|
+
function quoteIdent2(name) {
|
|
148
148
|
if (!/^[a-z][a-z0-9_-]*$/.test(name)) throw new Error(`Unsafe identifier: ${name}`);
|
|
149
149
|
return `"${name}"`;
|
|
150
150
|
}
|
|
@@ -156,10 +156,10 @@ function paragraphMediaFieldNames(pType) {
|
|
|
156
156
|
async function fetchParagraphMediaIds(db, pType, itemId, fieldName) {
|
|
157
157
|
const mediaColumns = pType.fields.filter((f) => f.field_type === "image" || f.field_type === "video" || f.field_type === "file").map((f) => f.db_column.column_name);
|
|
158
158
|
if (mediaColumns.length === 0) return [];
|
|
159
|
-
const tbl =
|
|
160
|
-
const cols =
|
|
159
|
+
const tbl = import_drizzle_orm3.sql.raw(quoteIdent2(pType.db.table_name));
|
|
160
|
+
const cols = import_drizzle_orm3.sql.join(mediaColumns.map((c) => import_drizzle_orm3.sql.raw(quoteIdent2(c))), import_drizzle_orm3.sql`, `);
|
|
161
161
|
const result = await db.execute(
|
|
162
|
-
|
|
162
|
+
import_drizzle_orm3.sql`SELECT ${cols} FROM ${tbl} WHERE parent_id = ${itemId} AND parent_field = ${fieldName}`
|
|
163
163
|
);
|
|
164
164
|
const ids = [];
|
|
165
165
|
for (const row of result.rows) {
|
|
@@ -178,14 +178,14 @@ function nestedParagraphFields(pType, registry) {
|
|
|
178
178
|
const comp = f.ui_component;
|
|
179
179
|
if (comp.component !== "paragraph-embed" || !comp.ref) continue;
|
|
180
180
|
const nType = registry.paragraph_types[comp.ref];
|
|
181
|
-
if (nType) out.push({ fieldName: f.name, nType });
|
|
181
|
+
if (nType) out.push({ fieldName: f.name, nType, field: f });
|
|
182
182
|
}
|
|
183
183
|
return out;
|
|
184
184
|
}
|
|
185
185
|
async function fetchParagraphRowIds(db, pType, itemId, fieldName) {
|
|
186
|
-
const tbl =
|
|
186
|
+
const tbl = import_drizzle_orm3.sql.raw(quoteIdent2(pType.db.table_name));
|
|
187
187
|
const result = await db.execute(
|
|
188
|
-
|
|
188
|
+
import_drizzle_orm3.sql`SELECT id FROM ${tbl} WHERE parent_id = ${itemId} AND parent_field = ${fieldName}`
|
|
189
189
|
);
|
|
190
190
|
return result.rows.map((r) => r["id"]).filter((id) => typeof id === "string");
|
|
191
191
|
}
|
|
@@ -201,8 +201,8 @@ async function persistParagraphField(db, itemId, parentType, fieldName, pType, i
|
|
|
201
201
|
}
|
|
202
202
|
}
|
|
203
203
|
}
|
|
204
|
-
const tbl =
|
|
205
|
-
await db.execute(
|
|
204
|
+
const tbl = import_drizzle_orm3.sql.raw(quoteIdent2(pType.db.table_name));
|
|
205
|
+
await db.execute(import_drizzle_orm3.sql`DELETE FROM ${tbl} WHERE parent_id = ${itemId} AND parent_field = ${fieldName}`);
|
|
206
206
|
const mediaFieldNames = paragraphMediaFieldNames(pType);
|
|
207
207
|
const added = [];
|
|
208
208
|
for (let i = 0; i < items.length; i++) {
|
|
@@ -221,15 +221,15 @@ async function persistParagraphField(db, itemId, parentType, fieldName, pType, i
|
|
|
221
221
|
if (!pf.db_column || pf.db_column.junction) continue;
|
|
222
222
|
data[pf.db_column.column_name] = pItem[pf.name] ?? null;
|
|
223
223
|
}
|
|
224
|
-
const cols =
|
|
225
|
-
const vals =
|
|
226
|
-
await db.execute(
|
|
224
|
+
const cols = import_drizzle_orm3.sql.join(Object.keys(data).map((k) => import_drizzle_orm3.sql.raw(quoteIdent2(k))), import_drizzle_orm3.sql`, `);
|
|
225
|
+
const vals = import_drizzle_orm3.sql.join(Object.values(data).map((v) => import_drizzle_orm3.sql`${v}`), import_drizzle_orm3.sql`, `);
|
|
226
|
+
await db.execute(import_drizzle_orm3.sql`INSERT INTO ${tbl} (${cols}) VALUES (${vals})`);
|
|
227
227
|
for (const name of mediaFieldNames) {
|
|
228
228
|
const val = pItem[name];
|
|
229
229
|
if (typeof val === "string" && val !== "") added.push(val);
|
|
230
230
|
}
|
|
231
231
|
for (const n of nested) {
|
|
232
|
-
const nestedItems =
|
|
232
|
+
const nestedItems = paragraphItems(n.field, pItem[n.fieldName]);
|
|
233
233
|
const d = await persistParagraphField(
|
|
234
234
|
db,
|
|
235
235
|
rowId,
|
|
@@ -257,19 +257,28 @@ async function deleteParagraphField(db, itemId, fieldName, pType, registry) {
|
|
|
257
257
|
}
|
|
258
258
|
}
|
|
259
259
|
}
|
|
260
|
-
const tbl =
|
|
261
|
-
await db.execute(
|
|
260
|
+
const tbl = import_drizzle_orm3.sql.raw(quoteIdent2(pType.db.table_name));
|
|
261
|
+
await db.execute(import_drizzle_orm3.sql`DELETE FROM ${tbl} WHERE parent_id = ${itemId} AND parent_field = ${fieldName}`);
|
|
262
262
|
return { added: [], removed };
|
|
263
263
|
}
|
|
264
264
|
async function persistJunctionField(db, itemId, junction, relatedIds) {
|
|
265
|
-
const tbl =
|
|
266
|
-
const left =
|
|
267
|
-
const right =
|
|
268
|
-
await db.execute(
|
|
265
|
+
const tbl = import_drizzle_orm3.sql.raw(quoteIdent2(junction.table_name));
|
|
266
|
+
const left = import_drizzle_orm3.sql.raw(quoteIdent2(junction.left_column));
|
|
267
|
+
const right = import_drizzle_orm3.sql.raw(quoteIdent2(junction.right_column));
|
|
268
|
+
await db.execute(import_drizzle_orm3.sql`DELETE FROM ${tbl} WHERE ${left} = ${itemId}`);
|
|
269
269
|
for (const rid of relatedIds) {
|
|
270
|
-
await db.execute(
|
|
270
|
+
await db.execute(import_drizzle_orm3.sql`INSERT INTO ${tbl} (${left}, ${right}) VALUES (${itemId}, ${rid})`);
|
|
271
271
|
}
|
|
272
272
|
}
|
|
273
|
+
function oneOrMany(cardinality, list) {
|
|
274
|
+
return cardinality === "one" ? list[0] ?? null : list;
|
|
275
|
+
}
|
|
276
|
+
function paragraphItems(field, value) {
|
|
277
|
+
if ((0, import_manguito_cms_core2.relationCardinality)(field) === "one") {
|
|
278
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? [value] : [];
|
|
279
|
+
}
|
|
280
|
+
return Array.isArray(value) ? value : [];
|
|
281
|
+
}
|
|
273
282
|
function buildRelationsMap(fields, registry) {
|
|
274
283
|
const relations = {};
|
|
275
284
|
for (const field of fields) {
|
|
@@ -280,7 +289,11 @@ function buildRelationsMap(fields, registry) {
|
|
|
280
289
|
const ref = field.ui_component.ref;
|
|
281
290
|
const pType = ref ? registry.paragraph_types[ref] : void 0;
|
|
282
291
|
if (!pType) continue;
|
|
283
|
-
relations[field.name] = {
|
|
292
|
+
relations[field.name] = {
|
|
293
|
+
type: "paragraph",
|
|
294
|
+
table: pType.db.table_name,
|
|
295
|
+
cardinality: (0, import_manguito_cms_core2.relationCardinality)(field) ?? "many"
|
|
296
|
+
};
|
|
284
297
|
} else if (field.field_type === "reference") {
|
|
285
298
|
if (!field.db_column) continue;
|
|
286
299
|
if (field.db_column.junction) {
|
|
@@ -322,13 +335,13 @@ async function resolveRelationField(db, rows, fieldName, rel, cache, publishedOn
|
|
|
322
335
|
const batch = [...new Set(rows)];
|
|
323
336
|
if (rel.type === "paragraph") {
|
|
324
337
|
const parentIds = batch.map((r) => r["id"]);
|
|
325
|
-
const inList =
|
|
338
|
+
const inList = import_drizzle_orm3.sql.join(parentIds.map((id) => import_drizzle_orm3.sql`${id}`), import_drizzle_orm3.sql`, `);
|
|
326
339
|
const result = await db.execute(
|
|
327
|
-
|
|
340
|
+
import_drizzle_orm3.sql`SELECT * FROM ${import_drizzle_orm3.sql.raw(quoteIdent2(rel.table))} WHERE parent_id IN (${inList}) AND parent_field = ${fieldName} ORDER BY "order" ASC`
|
|
328
341
|
);
|
|
329
342
|
const byParent = groupBy(result.rows, "parent_id");
|
|
330
343
|
for (const row of batch) {
|
|
331
|
-
row[fieldName] = byParent[row["id"]] ?? [];
|
|
344
|
+
row[fieldName] = oneOrMany(rel.cardinality, byParent[row["id"]] ?? []);
|
|
332
345
|
}
|
|
333
346
|
} else if (rel.type === "reference") {
|
|
334
347
|
const pending = batch.filter((r) => needsFkResolution(r, rel.fk_column));
|
|
@@ -341,10 +354,10 @@ async function resolveRelationField(db, rows, fieldName, rel, cache, publishedOn
|
|
|
341
354
|
const unique = [...new Set(fkValues)];
|
|
342
355
|
const uncached = unique.filter((id) => !cache.has(`${rel.table}:${id}`));
|
|
343
356
|
if (uncached.length > 0) {
|
|
344
|
-
const inList =
|
|
345
|
-
const publishedCond = publishedOnly ?
|
|
357
|
+
const inList = import_drizzle_orm3.sql.join(uncached.map((id) => import_drizzle_orm3.sql`${id}`), import_drizzle_orm3.sql`, `);
|
|
358
|
+
const publishedCond = publishedOnly ? import_drizzle_orm3.sql` AND published = true` : import_drizzle_orm3.sql``;
|
|
346
359
|
const result = await db.execute(
|
|
347
|
-
|
|
360
|
+
import_drizzle_orm3.sql`SELECT * FROM ${import_drizzle_orm3.sql.raw(quoteIdent2(rel.table))} WHERE id IN (${inList})${publishedCond}`
|
|
348
361
|
);
|
|
349
362
|
for (const item of result.rows) {
|
|
350
363
|
cache.set(`${rel.table}:${item["id"]}`, item);
|
|
@@ -356,19 +369,19 @@ async function resolveRelationField(db, rows, fieldName, rel, cache, publishedOn
|
|
|
356
369
|
}
|
|
357
370
|
} else if (rel.type === "junction") {
|
|
358
371
|
const parentIds = batch.map((r) => r["id"]);
|
|
359
|
-
const inList =
|
|
360
|
-
const orderBy = rel.order_column ?
|
|
372
|
+
const inList = import_drizzle_orm3.sql.join(parentIds.map((id) => import_drizzle_orm3.sql`${id}`), import_drizzle_orm3.sql`, `);
|
|
373
|
+
const orderBy = rel.order_column ? import_drizzle_orm3.sql` ORDER BY "order" ASC` : import_drizzle_orm3.sql``;
|
|
361
374
|
const junctionResult = await db.execute(
|
|
362
|
-
|
|
375
|
+
import_drizzle_orm3.sql`SELECT * FROM ${import_drizzle_orm3.sql.raw(quoteIdent2(rel.junction_table))} WHERE ${import_drizzle_orm3.sql.raw(quoteIdent2(rel.left_column))} IN (${inList})${orderBy}`
|
|
363
376
|
);
|
|
364
377
|
const jRows = junctionResult.rows;
|
|
365
378
|
const rightIds = [...new Set(jRows.map((r) => r[rel.right_column]))];
|
|
366
379
|
const uncached = rightIds.filter((id) => !cache.has(`${rel.table}:${id}`));
|
|
367
380
|
if (uncached.length > 0) {
|
|
368
|
-
const rightInList =
|
|
369
|
-
const publishedCond = publishedOnly ?
|
|
381
|
+
const rightInList = import_drizzle_orm3.sql.join(uncached.map((id) => import_drizzle_orm3.sql`${id}`), import_drizzle_orm3.sql`, `);
|
|
382
|
+
const publishedCond = publishedOnly ? import_drizzle_orm3.sql` AND published = true` : import_drizzle_orm3.sql``;
|
|
370
383
|
const entitiesResult = await db.execute(
|
|
371
|
-
|
|
384
|
+
import_drizzle_orm3.sql`SELECT * FROM ${import_drizzle_orm3.sql.raw(quoteIdent2(rel.table))} WHERE id IN (${rightInList})${publishedCond}`
|
|
372
385
|
);
|
|
373
386
|
for (const item of entitiesResult.rows) {
|
|
374
387
|
cache.set(`${rel.table}:${item["id"]}`, item);
|
|
@@ -390,8 +403,8 @@ async function resolveRelationField(db, rows, fieldName, rel, cache, publishedOn
|
|
|
390
403
|
const unique = [...new Set(fkValues)];
|
|
391
404
|
const uncached = unique.filter((id) => !cache.has(`media:${id}`));
|
|
392
405
|
if (uncached.length > 0) {
|
|
393
|
-
const inList =
|
|
394
|
-
const result = await db.execute(
|
|
406
|
+
const inList = import_drizzle_orm3.sql.join(uncached.map((id) => import_drizzle_orm3.sql`${id}`), import_drizzle_orm3.sql`, `);
|
|
407
|
+
const result = await db.execute(import_drizzle_orm3.sql`SELECT * FROM "media" WHERE id IN (${inList})`);
|
|
395
408
|
for (const item of result.rows) {
|
|
396
409
|
cache.set(`media:${item["id"]}`, item);
|
|
397
410
|
}
|
|
@@ -406,19 +419,19 @@ async function resolveRelationBareIds(db, rows, fieldName, rel) {
|
|
|
406
419
|
if (rows.length === 0) return;
|
|
407
420
|
if (rel.type === "paragraph") {
|
|
408
421
|
const parentIds = rows.map((r) => r["id"]);
|
|
409
|
-
const inList =
|
|
422
|
+
const inList = import_drizzle_orm3.sql.join(parentIds.map((id) => import_drizzle_orm3.sql`${id}`), import_drizzle_orm3.sql`, `);
|
|
410
423
|
const result = await db.execute(
|
|
411
|
-
|
|
424
|
+
import_drizzle_orm3.sql`SELECT id, parent_id FROM ${import_drizzle_orm3.sql.raw(quoteIdent2(rel.table))} WHERE parent_id IN (${inList}) AND parent_field = ${fieldName} ORDER BY "order" ASC`
|
|
412
425
|
);
|
|
413
426
|
const byParent = groupBy(result.rows, "parent_id");
|
|
414
427
|
for (const row of rows) {
|
|
415
|
-
row[fieldName] = (byParent[row["id"]] ?? []).map((r) => r["id"]);
|
|
428
|
+
row[fieldName] = oneOrMany(rel.cardinality, (byParent[row["id"]] ?? []).map((r) => r["id"]));
|
|
416
429
|
}
|
|
417
430
|
} else if (rel.type === "junction") {
|
|
418
431
|
const parentIds = rows.map((r) => r["id"]);
|
|
419
|
-
const inList =
|
|
432
|
+
const inList = import_drizzle_orm3.sql.join(parentIds.map((id) => import_drizzle_orm3.sql`${id}`), import_drizzle_orm3.sql`, `);
|
|
420
433
|
const result = await db.execute(
|
|
421
|
-
|
|
434
|
+
import_drizzle_orm3.sql`SELECT ${import_drizzle_orm3.sql.raw(quoteIdent2(rel.left_column))} AS left_id, ${import_drizzle_orm3.sql.raw(quoteIdent2(rel.right_column))} AS right_id FROM ${import_drizzle_orm3.sql.raw(quoteIdent2(rel.junction_table))} WHERE ${import_drizzle_orm3.sql.raw(quoteIdent2(rel.left_column))} IN (${inList})`
|
|
422
435
|
);
|
|
423
436
|
const byLeft = groupBy(result.rows, "left_id");
|
|
424
437
|
for (const row of rows) {
|
|
@@ -426,12 +439,13 @@ async function resolveRelationBareIds(db, rows, fieldName, rel) {
|
|
|
426
439
|
}
|
|
427
440
|
}
|
|
428
441
|
}
|
|
429
|
-
var import_node_crypto,
|
|
442
|
+
var import_node_crypto, import_drizzle_orm3, import_manguito_cms_core2;
|
|
430
443
|
var init_relations = __esm({
|
|
431
444
|
"src/relations.ts"() {
|
|
432
445
|
"use strict";
|
|
433
446
|
import_node_crypto = __toESM(require("crypto"));
|
|
434
|
-
|
|
447
|
+
import_drizzle_orm3 = require("drizzle-orm");
|
|
448
|
+
import_manguito_cms_core2 = require("@bobbykim/manguito-cms-core");
|
|
435
449
|
}
|
|
436
450
|
});
|
|
437
451
|
|
|
@@ -793,7 +807,8 @@ function buildGraphQLSchema(registry, fieldKeyMaps = {}, view, programmaticKeyMa
|
|
|
793
807
|
`
|
|
794
808
|
);
|
|
795
809
|
}
|
|
796
|
-
|
|
810
|
+
const t = target ?? MEDIA;
|
|
811
|
+
return (0, import_manguito_cms_core6.relationCardinality)(field) === "one" ? t : new import_graphql5.GraphQLList(new import_graphql5.GraphQLNonNull(t));
|
|
797
812
|
}
|
|
798
813
|
if (field.field_type === "reference") {
|
|
799
814
|
const ref = field.ui_component.component === "typeahead-select" ? field.ui_component.ref : void 0;
|
|
@@ -802,8 +817,7 @@ function buildGraphQLSchema(registry, fieldKeyMaps = {}, view, programmaticKeyMa
|
|
|
802
817
|
process.stderr.write(`\u26A0 reference field '${field.name}' targets unknown type '${ref}'; exposing as Media
|
|
803
818
|
`);
|
|
804
819
|
}
|
|
805
|
-
const
|
|
806
|
-
const isMany = rel === "many-to-many" || rel === "one-to-many";
|
|
820
|
+
const isMany = (0, import_manguito_cms_core6.relationCardinality)(field) === "many";
|
|
807
821
|
const t = target ?? MEDIA;
|
|
808
822
|
return isMany ? new import_graphql5.GraphQLList(new import_graphql5.GraphQLNonNull(t)) : t;
|
|
809
823
|
}
|
|
@@ -945,11 +959,12 @@ function buildGraphQLSchema(registry, fieldKeyMaps = {}, view, programmaticKeyMa
|
|
|
945
959
|
query: new import_graphql5.GraphQLObjectType({ name: "Query", fields: queryFields })
|
|
946
960
|
});
|
|
947
961
|
}
|
|
948
|
-
var import_graphql5, PAGE_META, MEDIA;
|
|
962
|
+
var import_graphql5, import_manguito_cms_core6, PAGE_META, MEDIA;
|
|
949
963
|
var init_schema = __esm({
|
|
950
964
|
"src/graphql/schema.ts"() {
|
|
951
965
|
"use strict";
|
|
952
966
|
import_graphql5 = require("graphql");
|
|
967
|
+
import_manguito_cms_core6 = require("@bobbykim/manguito-cms-core");
|
|
953
968
|
init_scalars();
|
|
954
969
|
init_type_mapping();
|
|
955
970
|
init_naming();
|
|
@@ -1240,7 +1255,7 @@ function createVersionedPaths(prefix, version) {
|
|
|
1240
1255
|
|
|
1241
1256
|
// src/app.ts
|
|
1242
1257
|
var import_hono = require("hono");
|
|
1243
|
-
var
|
|
1258
|
+
var import_drizzle_orm12 = require("drizzle-orm");
|
|
1244
1259
|
|
|
1245
1260
|
// src/middleware/cors.ts
|
|
1246
1261
|
function createCorsMiddleware(corsConfig) {
|
|
@@ -1334,16 +1349,20 @@ var ERROR_STATUS_MAP = {
|
|
|
1334
1349
|
UNSUPPORTED_MIME_TYPE: 415,
|
|
1335
1350
|
STORAGE_ERROR: 502,
|
|
1336
1351
|
MEDIA_IN_USE: 409,
|
|
1352
|
+
ITEM_IN_USE: 409,
|
|
1337
1353
|
PRESIGNED_URL_EXPIRED: 410,
|
|
1338
1354
|
RATE_LIMITED: 429,
|
|
1339
1355
|
INTERNAL_ERROR: 500
|
|
1340
1356
|
};
|
|
1341
1357
|
var errorHandler = (err, c) => {
|
|
1342
1358
|
console.error(err.stack ?? err.message);
|
|
1343
|
-
const
|
|
1344
|
-
const
|
|
1345
|
-
|
|
1346
|
-
|
|
1359
|
+
const raw = err.code;
|
|
1360
|
+
const deliberate = raw !== void 0 && raw !== "INTERNAL_ERROR" && raw in ERROR_STATUS_MAP;
|
|
1361
|
+
if (!deliberate) {
|
|
1362
|
+
return c.json({ ok: false, error: { code: "INTERNAL_ERROR", message: "Internal server error" } }, 500);
|
|
1363
|
+
}
|
|
1364
|
+
const status = ERROR_STATUS_MAP[raw] ?? 500;
|
|
1365
|
+
return c.json({ ok: false, error: { code: raw, message: err.message } }, status);
|
|
1347
1366
|
};
|
|
1348
1367
|
|
|
1349
1368
|
// src/middleware/auth.ts
|
|
@@ -2251,7 +2270,55 @@ function registerPublicMediaRoutes(app, mediaRepo, paths, listRateLimit) {
|
|
|
2251
2270
|
}
|
|
2252
2271
|
|
|
2253
2272
|
// src/routes/admin/content.ts
|
|
2254
|
-
var
|
|
2273
|
+
var import_drizzle_orm5 = require("drizzle-orm");
|
|
2274
|
+
|
|
2275
|
+
// src/references-in-use.ts
|
|
2276
|
+
var import_drizzle_orm2 = require("drizzle-orm");
|
|
2277
|
+
var import_manguito_cms_core = require("@bobbykim/manguito-cms-core");
|
|
2278
|
+
function quoteIdent(name) {
|
|
2279
|
+
if (!/^[a-z][a-z0-9_-]*$/.test(name)) throw new Error(`Unsafe identifier: ${name}`);
|
|
2280
|
+
return `"${name}"`;
|
|
2281
|
+
}
|
|
2282
|
+
function isRequiredSingleReferenceTo(f, targetType) {
|
|
2283
|
+
return f.field_type === "reference" && f.required && (0, import_manguito_cms_core.relationCardinality)(f) === "one" && f.db_column?.foreign_key !== void 0 && f.ui_component.component === "typeahead-select" && f.ui_component.ref === targetType;
|
|
2284
|
+
}
|
|
2285
|
+
async function findRequiredReferrers(db, registry, targetType, id) {
|
|
2286
|
+
const owners = [
|
|
2287
|
+
...Object.values(registry.content_types),
|
|
2288
|
+
...Object.values(registry.taxonomy_types),
|
|
2289
|
+
...Object.values(registry.paragraph_types)
|
|
2290
|
+
];
|
|
2291
|
+
const out = [];
|
|
2292
|
+
for (const owner of owners) {
|
|
2293
|
+
for (const f of owner.fields) {
|
|
2294
|
+
if (!isRequiredSingleReferenceTo(f, targetType)) continue;
|
|
2295
|
+
const notSelf = owner.name === targetType ? import_drizzle_orm2.sql` AND id <> ${id}` : import_drizzle_orm2.sql``;
|
|
2296
|
+
const r = await db.execute(
|
|
2297
|
+
import_drizzle_orm2.sql`SELECT count(*)::int AS n FROM ${import_drizzle_orm2.sql.raw(quoteIdent(owner.db.table_name))} WHERE ${import_drizzle_orm2.sql.raw(quoteIdent(f.db_column.column_name))} = ${id}${notSelf}`
|
|
2298
|
+
);
|
|
2299
|
+
const n = r.rows[0].n;
|
|
2300
|
+
if (n > 0) out.push({ type_label: owner.label, field_label: f.label, count: n });
|
|
2301
|
+
}
|
|
2302
|
+
}
|
|
2303
|
+
return out;
|
|
2304
|
+
}
|
|
2305
|
+
function inUseMessage(referrers) {
|
|
2306
|
+
const total = referrers.reduce((sum, r) => sum + r.count, 0);
|
|
2307
|
+
const where = referrers.map((r) => `${r.count} in ${r.type_label} (${r.field_label})`).join(", ");
|
|
2308
|
+
return `This item is still used by ${total} item${total === 1 ? "" : "s"}: ${where}. Remove it from those first.`;
|
|
2309
|
+
}
|
|
2310
|
+
var IN_USE_RACE_MESSAGE = "This item is still in use. Remove it from the items that use it first.";
|
|
2311
|
+
function isForeignKeyViolation(err) {
|
|
2312
|
+
let e = err;
|
|
2313
|
+
for (let depth = 0; depth < 3 && typeof e === "object" && e !== null; depth++) {
|
|
2314
|
+
if (e.code === "23503") return true;
|
|
2315
|
+
e = e.cause;
|
|
2316
|
+
}
|
|
2317
|
+
return false;
|
|
2318
|
+
}
|
|
2319
|
+
|
|
2320
|
+
// src/routes/admin/content.ts
|
|
2321
|
+
var import_manguito_cms_core4 = require("@bobbykim/manguito-cms-core");
|
|
2255
2322
|
|
|
2256
2323
|
// src/media-references.ts
|
|
2257
2324
|
function extractMediaId(value) {
|
|
@@ -2289,14 +2356,139 @@ async function applyMediaReferenceDelta(delta, mediaRepo) {
|
|
|
2289
2356
|
|
|
2290
2357
|
// src/routes/admin/content.ts
|
|
2291
2358
|
init_relations();
|
|
2359
|
+
|
|
2360
|
+
// src/relation-input.ts
|
|
2361
|
+
var import_drizzle_orm4 = require("drizzle-orm");
|
|
2362
|
+
var import_manguito_cms_core3 = require("@bobbykim/manguito-cms-core");
|
|
2363
|
+
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
2364
|
+
function isUuid(v) {
|
|
2365
|
+
return typeof v === "string" && UUID_RE.test(v);
|
|
2366
|
+
}
|
|
2367
|
+
function isPlainObject(v) {
|
|
2368
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
2369
|
+
}
|
|
2370
|
+
function checkRelationInput(fields, body, registry, path = "") {
|
|
2371
|
+
const errors = [];
|
|
2372
|
+
for (const field of fields) {
|
|
2373
|
+
if (!(field.name in body)) continue;
|
|
2374
|
+
const cardinality = (0, import_manguito_cms_core3.relationCardinality)(field);
|
|
2375
|
+
if (cardinality === null) continue;
|
|
2376
|
+
const value = body[field.name];
|
|
2377
|
+
const name = `${path}${field.name}`;
|
|
2378
|
+
if (field.field_type === "reference") {
|
|
2379
|
+
const ok = cardinality === "one" ? value === null || isUuid(value) : Array.isArray(value) && value.every(isUuid);
|
|
2380
|
+
if (!ok) {
|
|
2381
|
+
errors.push({
|
|
2382
|
+
field: name,
|
|
2383
|
+
message: cardinality === "one" ? `${name} holds one item: send a UUID string or null.` : `${name} holds a list: send an array of UUID strings.`
|
|
2384
|
+
});
|
|
2385
|
+
}
|
|
2386
|
+
continue;
|
|
2387
|
+
}
|
|
2388
|
+
const items = cardinality === "one" ? value === null ? [] : isPlainObject(value) ? [value] : void 0 : Array.isArray(value) && value.every(isPlainObject) ? value : void 0;
|
|
2389
|
+
if (items === void 0) {
|
|
2390
|
+
errors.push({
|
|
2391
|
+
field: name,
|
|
2392
|
+
message: cardinality === "one" ? `${name} holds one item: send an object or null.` : `${name} holds a list: send an array of objects.`
|
|
2393
|
+
});
|
|
2394
|
+
continue;
|
|
2395
|
+
}
|
|
2396
|
+
const comp = field.ui_component;
|
|
2397
|
+
const pType = comp.component === "paragraph-embed" ? registry.paragraph_types[comp.ref] : void 0;
|
|
2398
|
+
if (!pType) continue;
|
|
2399
|
+
items.forEach((item, i) => {
|
|
2400
|
+
const prefix = cardinality === "one" ? `${name}.` : `${name}[${i}].`;
|
|
2401
|
+
errors.push(...checkRelationInput(pType.fields, item, registry, prefix));
|
|
2402
|
+
});
|
|
2403
|
+
}
|
|
2404
|
+
return errors;
|
|
2405
|
+
}
|
|
2406
|
+
function paragraphItemsIn(fields, body, registry, path) {
|
|
2407
|
+
const out = [];
|
|
2408
|
+
for (const field of fields) {
|
|
2409
|
+
if (field.field_type !== "paragraph" || !(field.name in body)) continue;
|
|
2410
|
+
const comp = field.ui_component;
|
|
2411
|
+
const pType = comp.component === "paragraph-embed" ? registry.paragraph_types[comp.ref] : void 0;
|
|
2412
|
+
if (!pType) continue;
|
|
2413
|
+
const value = body[field.name];
|
|
2414
|
+
const name = `${path}${field.name}`;
|
|
2415
|
+
if ((0, import_manguito_cms_core3.relationCardinality)(field) === "one") {
|
|
2416
|
+
if (isPlainObject(value)) out.push({ item: value, fields: pType.fields, prefix: `${name}.` });
|
|
2417
|
+
} else if (Array.isArray(value)) {
|
|
2418
|
+
value.forEach((item, i) => {
|
|
2419
|
+
if (isPlainObject(item)) out.push({ item, fields: pType.fields, prefix: `${name}[${i}].` });
|
|
2420
|
+
});
|
|
2421
|
+
}
|
|
2422
|
+
}
|
|
2423
|
+
return out;
|
|
2424
|
+
}
|
|
2425
|
+
function isMissing(value) {
|
|
2426
|
+
if (value === null || value === void 0) return true;
|
|
2427
|
+
if (typeof value === "string" && value.trim() === "") return true;
|
|
2428
|
+
if (Array.isArray(value) && value.length === 0) return true;
|
|
2429
|
+
return false;
|
|
2430
|
+
}
|
|
2431
|
+
function checkRequiredInParagraphItems(fields, body, registry, path = "") {
|
|
2432
|
+
const errors = [];
|
|
2433
|
+
for (const { item, fields: itemFields, prefix } of paragraphItemsIn(fields, body, registry, path)) {
|
|
2434
|
+
for (const f of itemFields) {
|
|
2435
|
+
if (!f.required || f.removed === true || !f.db_column || f.db_column.junction) continue;
|
|
2436
|
+
if (isMissing(item[f.name])) {
|
|
2437
|
+
errors.push({ field: `${prefix}${f.name}`, message: `${prefix}${f.name} is required.` });
|
|
2438
|
+
}
|
|
2439
|
+
}
|
|
2440
|
+
errors.push(...checkRequiredInParagraphItems(itemFields, item, registry, prefix));
|
|
2441
|
+
}
|
|
2442
|
+
return errors;
|
|
2443
|
+
}
|
|
2444
|
+
function quoteIdent3(name) {
|
|
2445
|
+
if (!/^[a-z][a-z0-9_-]*$/.test(name)) throw new Error(`Unsafe identifier: ${name}`);
|
|
2446
|
+
return `"${name}"`;
|
|
2447
|
+
}
|
|
2448
|
+
function collectReferences(fields, body, registry, path, out) {
|
|
2449
|
+
for (const f of fields) {
|
|
2450
|
+
if (!(f.name in body) || !f.db_column) continue;
|
|
2451
|
+
const table = f.db_column.junction ? f.db_column.junction.right_table : f.db_column.foreign_key?.table;
|
|
2452
|
+
if (!table) continue;
|
|
2453
|
+
const value = body[f.name];
|
|
2454
|
+
const name = `${path}${f.name}`;
|
|
2455
|
+
if (typeof value === "string") {
|
|
2456
|
+
out.push({ table, id: value, field: name });
|
|
2457
|
+
} else if (Array.isArray(value)) {
|
|
2458
|
+
value.forEach((v, i) => {
|
|
2459
|
+
if (typeof v === "string") out.push({ table, id: v, field: `${name}[${i}]` });
|
|
2460
|
+
});
|
|
2461
|
+
}
|
|
2462
|
+
}
|
|
2463
|
+
for (const { item, fields: itemFields, prefix } of paragraphItemsIn(fields, body, registry, path)) {
|
|
2464
|
+
collectReferences(itemFields, item, registry, prefix, out);
|
|
2465
|
+
}
|
|
2466
|
+
}
|
|
2467
|
+
async function findMissingReferences(db, fields, body, registry) {
|
|
2468
|
+
const uses = [];
|
|
2469
|
+
collectReferences(fields, body, registry, "", uses);
|
|
2470
|
+
if (uses.length === 0) return [];
|
|
2471
|
+
const lookups = [...new Set(uses.map((u) => u.table))].map(async (table) => {
|
|
2472
|
+
const ids = [...new Set(uses.filter((u) => u.table === table && isUuid(u.id)).map((u) => u.id.toLowerCase()))];
|
|
2473
|
+
if (ids.length === 0) return [table, /* @__PURE__ */ new Set()];
|
|
2474
|
+
const result = await db.execute(
|
|
2475
|
+
import_drizzle_orm4.sql`SELECT id FROM ${import_drizzle_orm4.sql.raw(quoteIdent3(table))} WHERE id IN (${import_drizzle_orm4.sql.join(ids.map((id) => import_drizzle_orm4.sql`${id}`), import_drizzle_orm4.sql`, `)})`
|
|
2476
|
+
);
|
|
2477
|
+
return [table, new Set(result.rows.map((r) => String(r.id).toLowerCase()))];
|
|
2478
|
+
});
|
|
2479
|
+
const found = new Map(await Promise.all(lookups));
|
|
2480
|
+
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.` }));
|
|
2481
|
+
}
|
|
2482
|
+
|
|
2483
|
+
// src/routes/admin/content.ts
|
|
2292
2484
|
init_field_keys();
|
|
2293
|
-
function
|
|
2485
|
+
function quoteIdent4(name) {
|
|
2294
2486
|
if (!/^[a-z][a-z0-9_-]*$/.test(name)) throw new Error(`Unsafe identifier: ${name}`);
|
|
2295
2487
|
return `"${name}"`;
|
|
2296
2488
|
}
|
|
2297
2489
|
async function loadParagraphRows(db, registry, pType, parentId, fieldName) {
|
|
2298
2490
|
const result = await db.execute(
|
|
2299
|
-
|
|
2491
|
+
import_drizzle_orm5.sql`SELECT * FROM ${import_drizzle_orm5.sql.raw(quoteIdent4(pType.db.table_name))} WHERE parent_id = ${parentId} AND parent_field = ${fieldName} ORDER BY "order" ASC`
|
|
2300
2492
|
);
|
|
2301
2493
|
const rows = result.rows;
|
|
2302
2494
|
for (const nf of pType.fields) {
|
|
@@ -2306,26 +2498,35 @@ async function loadParagraphRows(db, registry, pType, parentId, fieldName) {
|
|
|
2306
2498
|
const nType = registry.paragraph_types[comp.ref];
|
|
2307
2499
|
if (!nType) continue;
|
|
2308
2500
|
for (const row of rows) {
|
|
2309
|
-
row[nf.name] =
|
|
2501
|
+
row[nf.name] = oneOrMany(
|
|
2502
|
+
(0, import_manguito_cms_core4.relationCardinality)(nf) ?? "many",
|
|
2503
|
+
await loadParagraphRows(db, registry, nType, row["id"], nf.name)
|
|
2504
|
+
);
|
|
2310
2505
|
}
|
|
2311
2506
|
}
|
|
2312
2507
|
return rows;
|
|
2313
2508
|
}
|
|
2314
2509
|
async function lookupBasePathId(db, pathOrName) {
|
|
2315
2510
|
const r = await db.execute(
|
|
2316
|
-
|
|
2511
|
+
import_drizzle_orm5.sql`SELECT id FROM base_paths WHERE path = ${pathOrName} OR name = ${pathOrName} LIMIT 1`
|
|
2317
2512
|
);
|
|
2318
2513
|
return r.rows[0]?.id ?? null;
|
|
2319
2514
|
}
|
|
2320
2515
|
var SLUG_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
2321
|
-
function isEmpty(value) {
|
|
2322
|
-
if (value === null || value === void 0) return true;
|
|
2323
|
-
if (typeof value === "string" && value.trim() === "") return true;
|
|
2324
|
-
if (Array.isArray(value) && value.length === 0) return true;
|
|
2325
|
-
return false;
|
|
2326
|
-
}
|
|
2327
2516
|
function checkRequiredFields(fields, data) {
|
|
2328
|
-
return fields.filter((f) => f.required &&
|
|
2517
|
+
return fields.filter((f) => f.required && isMissing(data[f.name])).map((f) => ({ field: f.name, message: `${f.label} is required` }));
|
|
2518
|
+
}
|
|
2519
|
+
async function relationInputError(c, fields, body, registry, db) {
|
|
2520
|
+
const refuse = (message, details) => c.json({ ok: false, error: { code: "VALIDATION_ERROR", message, details } }, 422);
|
|
2521
|
+
const shape = checkRelationInput(fields, body, registry);
|
|
2522
|
+
if (shape.length > 0) return refuse("Relation fields have the wrong shape", shape);
|
|
2523
|
+
const required = checkRequiredInParagraphItems(fields, body, registry);
|
|
2524
|
+
if (required.length > 0) return refuse("Required fields are missing", required);
|
|
2525
|
+
if (db) {
|
|
2526
|
+
const missing = await findMissingReferences(db, fields, body, registry);
|
|
2527
|
+
if (missing.length > 0) return refuse("Relation fields refer to items that do not exist", missing);
|
|
2528
|
+
}
|
|
2529
|
+
return null;
|
|
2329
2530
|
}
|
|
2330
2531
|
function parseListQuery(url, schemaFieldNames, relationFieldNames, columnFor) {
|
|
2331
2532
|
const searchParams = new URL(url).searchParams;
|
|
@@ -2468,11 +2669,11 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
|
|
|
2468
2669
|
if (comp.component !== "paragraph-embed" || !comp.ref) continue;
|
|
2469
2670
|
const pType = registry.paragraph_types[comp.ref];
|
|
2470
2671
|
if (!pType) continue;
|
|
2471
|
-
row[f.name] = await loadParagraphRows(db, registry, pType, id, f.name);
|
|
2672
|
+
row[f.name] = oneOrMany((0, import_manguito_cms_core4.relationCardinality)(f) ?? "many", await loadParagraphRows(db, registry, pType, id, f.name));
|
|
2472
2673
|
} else if (f.db_column.junction) {
|
|
2473
2674
|
const j = f.db_column.junction;
|
|
2474
2675
|
const r = await db.execute(
|
|
2475
|
-
|
|
2676
|
+
import_drizzle_orm5.sql`SELECT ${import_drizzle_orm5.sql.raw(quoteIdent4(j.right_column))} FROM ${import_drizzle_orm5.sql.raw(quoteIdent4(j.table_name))} WHERE ${import_drizzle_orm5.sql.raw(quoteIdent4(j.left_column))} = ${id}`
|
|
2476
2677
|
);
|
|
2477
2678
|
row[f.name] = r.rows.map(
|
|
2478
2679
|
(row2) => row2[j.right_column]
|
|
@@ -2548,6 +2749,8 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
|
|
|
2548
2749
|
}
|
|
2549
2750
|
);
|
|
2550
2751
|
const writeNewItem = async (c, body) => {
|
|
2752
|
+
const inputError = await relationInputError(c, contentType.fields, body, registry, db);
|
|
2753
|
+
if (inputError) return inputError;
|
|
2551
2754
|
const storageBody = fieldKeys.toStorage(body);
|
|
2552
2755
|
const fieldErrors = checkRequiredFields(requiredFields, body);
|
|
2553
2756
|
if (fieldErrors.length > 0) {
|
|
@@ -2611,7 +2814,7 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
|
|
|
2611
2814
|
if (comp.component !== "paragraph-embed" || !comp.ref) continue;
|
|
2612
2815
|
const pType = registry.paragraph_types[comp.ref];
|
|
2613
2816
|
if (!pType) continue;
|
|
2614
|
-
const items =
|
|
2817
|
+
const items = paragraphItems(f, body[f.name]);
|
|
2615
2818
|
mediaDeltas.push(await persistParagraphField(db, itemId, contentType.db.table_name, f.name, pType, items, registry));
|
|
2616
2819
|
}
|
|
2617
2820
|
for (const f of junctionFields) {
|
|
@@ -2682,6 +2885,8 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
|
|
|
2682
2885
|
}
|
|
2683
2886
|
);
|
|
2684
2887
|
const writeExistingItem = async (c, id, existing, body) => {
|
|
2888
|
+
const inputError = await relationInputError(c, contentType.fields, body, registry, db);
|
|
2889
|
+
if (inputError) return inputError;
|
|
2685
2890
|
const storageBody = fieldKeys.toStorage(body);
|
|
2686
2891
|
if (body["published"] === true) {
|
|
2687
2892
|
const publishDeny = await requirePermission("content:edit")(c, async () => {
|
|
@@ -2728,14 +2933,16 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
|
|
|
2728
2933
|
];
|
|
2729
2934
|
if (db) {
|
|
2730
2935
|
for (const f of patchParagraphFields) {
|
|
2936
|
+
if (!(f.name in body)) continue;
|
|
2731
2937
|
const comp = f.ui_component;
|
|
2732
2938
|
if (comp.component !== "paragraph-embed" || !comp.ref) continue;
|
|
2733
2939
|
const pType = registry.paragraph_types[comp.ref];
|
|
2734
2940
|
if (!pType) continue;
|
|
2735
|
-
const items =
|
|
2941
|
+
const items = paragraphItems(f, body[f.name]);
|
|
2736
2942
|
mediaDeltas.push(await persistParagraphField(db, id, contentType.db.table_name, f.name, pType, items, registry));
|
|
2737
2943
|
}
|
|
2738
2944
|
for (const f of patchJunctionFields) {
|
|
2945
|
+
if (!(f.name in body)) continue;
|
|
2739
2946
|
const junction = f.db_column.junction;
|
|
2740
2947
|
const relatedIds = Array.isArray(body[f.name]) ? body[f.name].filter((v) => typeof v === "string") : [];
|
|
2741
2948
|
await persistJunctionField(db, id, junction, relatedIds);
|
|
@@ -2775,6 +2982,18 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
|
|
|
2775
2982
|
404
|
|
2776
2983
|
);
|
|
2777
2984
|
}
|
|
2985
|
+
const referrers = db ? await findRequiredReferrers(db, registry, typeName, id) : [];
|
|
2986
|
+
if (referrers.length > 0) {
|
|
2987
|
+
return c.json({ ok: false, error: { code: "ITEM_IN_USE", message: inUseMessage(referrers) } }, 409);
|
|
2988
|
+
}
|
|
2989
|
+
try {
|
|
2990
|
+
await repo.delete(id);
|
|
2991
|
+
} catch (err) {
|
|
2992
|
+
if (isForeignKeyViolation(err)) {
|
|
2993
|
+
return c.json({ ok: false, error: { code: "ITEM_IN_USE", message: IN_USE_RACE_MESSAGE } }, 409);
|
|
2994
|
+
}
|
|
2995
|
+
throw err;
|
|
2996
|
+
}
|
|
2778
2997
|
const mediaDeltas = [
|
|
2779
2998
|
topLevelMediaDelta(mediaFields, item, null)
|
|
2780
2999
|
];
|
|
@@ -2788,7 +3007,6 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
|
|
|
2788
3007
|
}
|
|
2789
3008
|
}
|
|
2790
3009
|
await applyMediaReferenceDelta(mergeMediaDeltas(...mediaDeltas), mediaRepo);
|
|
2791
|
-
await repo.delete(id);
|
|
2792
3010
|
return c.json({ ok: true });
|
|
2793
3011
|
}
|
|
2794
3012
|
);
|
|
@@ -2866,6 +3084,8 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
|
|
|
2866
3084
|
requirePermission("content:create"),
|
|
2867
3085
|
async (c) => {
|
|
2868
3086
|
const body = await c.req.json();
|
|
3087
|
+
const inputError = await relationInputError(c, taxonomyType.fields, body, registry, db);
|
|
3088
|
+
if (inputError) return inputError;
|
|
2869
3089
|
const storageBody = fieldKeys.toStorage(body);
|
|
2870
3090
|
const fieldErrors = checkRequiredFields(requiredFields, body);
|
|
2871
3091
|
if (fieldErrors.length > 0) {
|
|
@@ -2905,7 +3125,7 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
|
|
|
2905
3125
|
if (comp.component !== "paragraph-embed" || !comp.ref) continue;
|
|
2906
3126
|
const pType = registry.paragraph_types[comp.ref];
|
|
2907
3127
|
if (!pType) continue;
|
|
2908
|
-
const items =
|
|
3128
|
+
const items = paragraphItems(f, body[f.name]);
|
|
2909
3129
|
mediaDeltas.push(await persistParagraphField(db, taxItemId, taxonomyType.db.table_name, f.name, pType, items, registry));
|
|
2910
3130
|
}
|
|
2911
3131
|
}
|
|
@@ -2928,6 +3148,8 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
|
|
|
2928
3148
|
404
|
|
2929
3149
|
);
|
|
2930
3150
|
}
|
|
3151
|
+
const inputError = await relationInputError(c, taxonomyType.fields, body, registry, db);
|
|
3152
|
+
if (inputError) return inputError;
|
|
2931
3153
|
if (body["published"] === true) {
|
|
2932
3154
|
const publishDeny = await requirePermission("content:edit")(c, async () => {
|
|
2933
3155
|
});
|
|
@@ -2969,11 +3191,12 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
|
|
|
2969
3191
|
];
|
|
2970
3192
|
if (db) {
|
|
2971
3193
|
for (const f of taxPatchParagraphFields) {
|
|
3194
|
+
if (!(f.name in body)) continue;
|
|
2972
3195
|
const comp = f.ui_component;
|
|
2973
3196
|
if (comp.component !== "paragraph-embed" || !comp.ref) continue;
|
|
2974
3197
|
const pType = registry.paragraph_types[comp.ref];
|
|
2975
3198
|
if (!pType) continue;
|
|
2976
|
-
const items =
|
|
3199
|
+
const items = paragraphItems(f, body[f.name]);
|
|
2977
3200
|
mediaDeltas.push(await persistParagraphField(db, id, taxonomyType.db.table_name, f.name, pType, items, registry));
|
|
2978
3201
|
}
|
|
2979
3202
|
}
|
|
@@ -2994,6 +3217,18 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
|
|
|
2994
3217
|
404
|
|
2995
3218
|
);
|
|
2996
3219
|
}
|
|
3220
|
+
const referrers = db ? await findRequiredReferrers(db, registry, typeName, id) : [];
|
|
3221
|
+
if (referrers.length > 0) {
|
|
3222
|
+
return c.json({ ok: false, error: { code: "ITEM_IN_USE", message: inUseMessage(referrers) } }, 409);
|
|
3223
|
+
}
|
|
3224
|
+
try {
|
|
3225
|
+
await repo.delete(id);
|
|
3226
|
+
} catch (err) {
|
|
3227
|
+
if (isForeignKeyViolation(err)) {
|
|
3228
|
+
return c.json({ ok: false, error: { code: "ITEM_IN_USE", message: IN_USE_RACE_MESSAGE } }, 409);
|
|
3229
|
+
}
|
|
3230
|
+
throw err;
|
|
3231
|
+
}
|
|
2997
3232
|
const mediaDeltas = [
|
|
2998
3233
|
topLevelMediaDelta(mediaFields, item, null)
|
|
2999
3234
|
];
|
|
@@ -3007,7 +3242,6 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
|
|
|
3007
3242
|
}
|
|
3008
3243
|
}
|
|
3009
3244
|
await applyMediaReferenceDelta(mergeMediaDeltas(...mediaDeltas), mediaRepo);
|
|
3010
|
-
await repo.delete(id);
|
|
3011
3245
|
return c.json({ ok: true });
|
|
3012
3246
|
}
|
|
3013
3247
|
);
|
|
@@ -3469,10 +3703,10 @@ function registerAdminMediaRoutes(app, mediaRepo, storage, requirePermission, ma
|
|
|
3469
3703
|
|
|
3470
3704
|
// src/routes/admin/auth.ts
|
|
3471
3705
|
var import_cookie3 = require("hono/cookie");
|
|
3472
|
-
var
|
|
3706
|
+
var import_drizzle_orm6 = require("drizzle-orm");
|
|
3473
3707
|
|
|
3474
3708
|
// src/auth/password.ts
|
|
3475
|
-
var
|
|
3709
|
+
var import_manguito_cms_core5 = require("@bobbykim/manguito-cms-core");
|
|
3476
3710
|
|
|
3477
3711
|
// src/routes/admin/auth.ts
|
|
3478
3712
|
var loginAttempts = /* @__PURE__ */ new Map();
|
|
@@ -3535,20 +3769,20 @@ function registerAuthRoutes(app, db) {
|
|
|
3535
3769
|
);
|
|
3536
3770
|
}
|
|
3537
3771
|
const userResult = await db.execute(
|
|
3538
|
-
|
|
3772
|
+
import_drizzle_orm6.sql`SELECT id, email, password_hash, role_id, token_version
|
|
3539
3773
|
FROM users WHERE email = ${email} LIMIT 1`
|
|
3540
3774
|
);
|
|
3541
3775
|
const user = userResult.rows[0];
|
|
3542
3776
|
if (!user) {
|
|
3543
|
-
await (0,
|
|
3777
|
+
await (0, import_manguito_cms_core5.verifyPassword)(password, DUMMY_PASSWORD_HASH);
|
|
3544
3778
|
return c.json(invalidCredentials, 401);
|
|
3545
3779
|
}
|
|
3546
|
-
const passwordValid = await (0,
|
|
3780
|
+
const passwordValid = await (0, import_manguito_cms_core5.verifyPassword)(password, user.password_hash);
|
|
3547
3781
|
if (!passwordValid) {
|
|
3548
3782
|
return c.json(invalidCredentials, 401);
|
|
3549
3783
|
}
|
|
3550
3784
|
const roleResult = await db.execute(
|
|
3551
|
-
|
|
3785
|
+
import_drizzle_orm6.sql`SELECT r.name AS name, u.must_change_password
|
|
3552
3786
|
FROM roles r
|
|
3553
3787
|
JOIN users u ON u.role_id = r.id
|
|
3554
3788
|
WHERE r.id = ${user.role_id} AND u.id = ${user.id}
|
|
@@ -3608,7 +3842,7 @@ function registerAuthRoutes(app, db) {
|
|
|
3608
3842
|
);
|
|
3609
3843
|
}
|
|
3610
3844
|
const rowResult = await db.execute(
|
|
3611
|
-
|
|
3845
|
+
import_drizzle_orm6.sql`SELECT u.token_version, r.name AS role
|
|
3612
3846
|
FROM users u
|
|
3613
3847
|
JOIN roles r ON r.id = u.role_id
|
|
3614
3848
|
WHERE u.id = ${payload.user_id}
|
|
@@ -3632,7 +3866,7 @@ function registerAuthRoutes(app, db) {
|
|
|
3632
3866
|
app.post("/admin/api/auth/logout", authMiddleware, async (c) => {
|
|
3633
3867
|
const user = c.get("user");
|
|
3634
3868
|
await db.execute(
|
|
3635
|
-
|
|
3869
|
+
import_drizzle_orm6.sql`UPDATE users SET token_version = token_version + 1 WHERE id = ${user.id}`
|
|
3636
3870
|
);
|
|
3637
3871
|
clearAuthCookie(c, "auth_token");
|
|
3638
3872
|
clearAuthCookie(c, "refresh_token", "/admin/api/auth");
|
|
@@ -3641,7 +3875,7 @@ function registerAuthRoutes(app, db) {
|
|
|
3641
3875
|
}
|
|
3642
3876
|
|
|
3643
3877
|
// src/routes/admin/users.ts
|
|
3644
|
-
var
|
|
3878
|
+
var import_drizzle_orm7 = require("drizzle-orm");
|
|
3645
3879
|
var import_node_crypto2 = require("crypto");
|
|
3646
3880
|
function getActingUser(c) {
|
|
3647
3881
|
return c.get("user");
|
|
@@ -3676,18 +3910,18 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
3676
3910
|
);
|
|
3677
3911
|
}
|
|
3678
3912
|
const hashResult = await db.execute(
|
|
3679
|
-
|
|
3913
|
+
import_drizzle_orm7.sql`SELECT password_hash FROM users WHERE id = ${actingUser.id} LIMIT 1`
|
|
3680
3914
|
);
|
|
3681
3915
|
const hashRow = hashResult.rows[0];
|
|
3682
|
-
if (!hashRow || !await (0,
|
|
3916
|
+
if (!hashRow || !await (0, import_manguito_cms_core5.verifyPassword)(currentPassword, hashRow.password_hash)) {
|
|
3683
3917
|
return c.json(
|
|
3684
3918
|
{ ok: false, error: { code: "INVALID_CREDENTIALS", message: "Invalid credentials." } },
|
|
3685
3919
|
401
|
|
3686
3920
|
);
|
|
3687
3921
|
}
|
|
3688
|
-
const newHash = await (0,
|
|
3922
|
+
const newHash = await (0, import_manguito_cms_core5.hashPassword)(newPassword);
|
|
3689
3923
|
await db.execute(
|
|
3690
|
-
|
|
3924
|
+
import_drizzle_orm7.sql`UPDATE users
|
|
3691
3925
|
SET password_hash = ${newHash},
|
|
3692
3926
|
must_change_password = false,
|
|
3693
3927
|
token_version = token_version + 1,
|
|
@@ -3698,7 +3932,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
3698
3932
|
});
|
|
3699
3933
|
app.get("/admin/api/users", requirePermission("users:read"), async (c) => {
|
|
3700
3934
|
const result = await db.execute(
|
|
3701
|
-
|
|
3935
|
+
import_drizzle_orm7.sql`SELECT u.id, u.email, r.name AS role, u.must_change_password,
|
|
3702
3936
|
u.created_at, u.updated_at
|
|
3703
3937
|
FROM users u
|
|
3704
3938
|
JOIN roles r ON r.id = u.role_id
|
|
@@ -3709,7 +3943,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
3709
3943
|
app.get("/admin/api/users/:id", requirePermission("users:read"), async (c) => {
|
|
3710
3944
|
const id = c.req.param("id");
|
|
3711
3945
|
const result = await db.execute(
|
|
3712
|
-
|
|
3946
|
+
import_drizzle_orm7.sql`SELECT u.id, u.email, r.name AS role, u.must_change_password,
|
|
3713
3947
|
u.created_at, u.updated_at
|
|
3714
3948
|
FROM users u
|
|
3715
3949
|
JOIN roles r ON r.id = u.role_id
|
|
@@ -3754,7 +3988,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
3754
3988
|
);
|
|
3755
3989
|
}
|
|
3756
3990
|
const roleResult = await db.execute(
|
|
3757
|
-
|
|
3991
|
+
import_drizzle_orm7.sql`SELECT id FROM roles WHERE name = ${roleName} LIMIT 1`
|
|
3758
3992
|
);
|
|
3759
3993
|
const roleRow = roleResult.rows[0];
|
|
3760
3994
|
if (!roleRow) {
|
|
@@ -3767,9 +4001,9 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
3767
4001
|
);
|
|
3768
4002
|
}
|
|
3769
4003
|
const tempPassword = generateTempPassword();
|
|
3770
|
-
const passwordHash = await (0,
|
|
4004
|
+
const passwordHash = await (0, import_manguito_cms_core5.hashPassword)(tempPassword);
|
|
3771
4005
|
const insertResult = await db.execute(
|
|
3772
|
-
|
|
4006
|
+
import_drizzle_orm7.sql`INSERT INTO users
|
|
3773
4007
|
(id, email, password_hash, role_id, must_change_password, token_version,
|
|
3774
4008
|
created_at, updated_at)
|
|
3775
4009
|
VALUES
|
|
@@ -3824,7 +4058,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
3824
4058
|
);
|
|
3825
4059
|
}
|
|
3826
4060
|
const existingResult = await db.execute(
|
|
3827
|
-
|
|
4061
|
+
import_drizzle_orm7.sql`SELECT id, email, role_id FROM users WHERE id = ${id} LIMIT 1`
|
|
3828
4062
|
);
|
|
3829
4063
|
const existing = existingResult.rows[0];
|
|
3830
4064
|
if (!existing) {
|
|
@@ -3838,7 +4072,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
3838
4072
|
let changeRole = false;
|
|
3839
4073
|
if (typeof body.role === "string" && body.role) {
|
|
3840
4074
|
const roleResult = await db.execute(
|
|
3841
|
-
|
|
4075
|
+
import_drizzle_orm7.sql`SELECT id FROM roles WHERE name = ${body.role} LIMIT 1`
|
|
3842
4076
|
);
|
|
3843
4077
|
const roleRow = roleResult.rows[0];
|
|
3844
4078
|
if (!roleRow) {
|
|
@@ -3858,7 +4092,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
3858
4092
|
}
|
|
3859
4093
|
if (changeRole) {
|
|
3860
4094
|
await db.execute(
|
|
3861
|
-
|
|
4095
|
+
import_drizzle_orm7.sql`UPDATE users
|
|
3862
4096
|
SET email = ${newEmail},
|
|
3863
4097
|
role_id = ${newRoleId},
|
|
3864
4098
|
token_version = token_version + 1,
|
|
@@ -3867,7 +4101,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
3867
4101
|
);
|
|
3868
4102
|
} else {
|
|
3869
4103
|
await db.execute(
|
|
3870
|
-
|
|
4104
|
+
import_drizzle_orm7.sql`UPDATE users
|
|
3871
4105
|
SET email = ${newEmail},
|
|
3872
4106
|
role_id = ${newRoleId},
|
|
3873
4107
|
updated_at = NOW()
|
|
@@ -3875,7 +4109,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
3875
4109
|
);
|
|
3876
4110
|
}
|
|
3877
4111
|
const updatedResult = await db.execute(
|
|
3878
|
-
|
|
4112
|
+
import_drizzle_orm7.sql`SELECT u.id, u.email, r.name AS role, u.must_change_password,
|
|
3879
4113
|
u.created_at, u.updated_at
|
|
3880
4114
|
FROM users u
|
|
3881
4115
|
JOIN roles r ON r.id = u.role_id
|
|
@@ -3912,7 +4146,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
3912
4146
|
);
|
|
3913
4147
|
}
|
|
3914
4148
|
const result = await db.execute(
|
|
3915
|
-
|
|
4149
|
+
import_drizzle_orm7.sql`DELETE FROM users WHERE id = ${id} RETURNING id`
|
|
3916
4150
|
);
|
|
3917
4151
|
if (result.rows.length === 0) {
|
|
3918
4152
|
return c.json(
|
|
@@ -3943,7 +4177,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
3943
4177
|
);
|
|
3944
4178
|
}
|
|
3945
4179
|
const existingResult = await db.execute(
|
|
3946
|
-
|
|
4180
|
+
import_drizzle_orm7.sql`SELECT id FROM users WHERE id = ${id} LIMIT 1`
|
|
3947
4181
|
);
|
|
3948
4182
|
const existing = existingResult.rows[0];
|
|
3949
4183
|
if (!existing) {
|
|
@@ -3953,9 +4187,9 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
3953
4187
|
);
|
|
3954
4188
|
}
|
|
3955
4189
|
const tempPassword = generateTempPassword();
|
|
3956
|
-
const passwordHash = await (0,
|
|
4190
|
+
const passwordHash = await (0, import_manguito_cms_core5.hashPassword)(tempPassword);
|
|
3957
4191
|
await db.execute(
|
|
3958
|
-
|
|
4192
|
+
import_drizzle_orm7.sql`UPDATE users
|
|
3959
4193
|
SET password_hash = ${passwordHash},
|
|
3960
4194
|
must_change_password = true,
|
|
3961
4195
|
token_version = token_version + 1,
|
|
@@ -3968,10 +4202,10 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
3968
4202
|
}
|
|
3969
4203
|
|
|
3970
4204
|
// src/routes/admin/config.ts
|
|
3971
|
-
var
|
|
4205
|
+
var import_drizzle_orm8 = require("drizzle-orm");
|
|
3972
4206
|
var API_VERSION = (() => {
|
|
3973
4207
|
try {
|
|
3974
|
-
return "0.
|
|
4208
|
+
return "0.8.1";
|
|
3975
4209
|
} catch {
|
|
3976
4210
|
return "0.0.0";
|
|
3977
4211
|
}
|
|
@@ -3990,7 +4224,7 @@ function registerConfigRoute(app, config, registry, db) {
|
|
|
3990
4224
|
permissions: r.permissions
|
|
3991
4225
|
}));
|
|
3992
4226
|
const userResult = await db.execute(
|
|
3993
|
-
|
|
4227
|
+
import_drizzle_orm8.sql`SELECT email, must_change_password FROM users WHERE id = ${actingUser.id} LIMIT 1`
|
|
3994
4228
|
);
|
|
3995
4229
|
const userRow = userResult.rows[0];
|
|
3996
4230
|
return c.json({
|
|
@@ -4016,7 +4250,7 @@ function registerConfigRoute(app, config, registry, db) {
|
|
|
4016
4250
|
}
|
|
4017
4251
|
|
|
4018
4252
|
// src/routes/admin/schema.ts
|
|
4019
|
-
var
|
|
4253
|
+
var import_drizzle_orm9 = require("drizzle-orm");
|
|
4020
4254
|
function liveFields(fields) {
|
|
4021
4255
|
return fields.filter((f) => f.removed !== true);
|
|
4022
4256
|
}
|
|
@@ -4056,7 +4290,7 @@ function registerSchemaRoute(app, registry, db) {
|
|
|
4056
4290
|
const data = await Promise.all(
|
|
4057
4291
|
Object.values(registry.content_types).map(async (ct) => {
|
|
4058
4292
|
const result = await db.execute(
|
|
4059
|
-
|
|
4293
|
+
import_drizzle_orm9.sql`SELECT COUNT(*)::int AS count FROM ${import_drizzle_orm9.sql.raw(`"${ct.db.table_name}"`)}`
|
|
4060
4294
|
);
|
|
4061
4295
|
const count = Number(result.rows[0].count);
|
|
4062
4296
|
return { name: ct.name, label: ct.label, only_one: ct.only_one, count };
|
|
@@ -4068,7 +4302,7 @@ function registerSchemaRoute(app, registry, db) {
|
|
|
4068
4302
|
const data = await Promise.all(
|
|
4069
4303
|
Object.values(registry.taxonomy_types).map(async (tt) => {
|
|
4070
4304
|
const result = await db.execute(
|
|
4071
|
-
|
|
4305
|
+
import_drizzle_orm9.sql`SELECT COUNT(*)::int AS count FROM ${import_drizzle_orm9.sql.raw(`"${tt.db.table_name}"`)}`
|
|
4072
4306
|
);
|
|
4073
4307
|
const count = Number(result.rows[0].count);
|
|
4074
4308
|
return { name: tt.name, label: tt.label, count };
|
|
@@ -4080,7 +4314,7 @@ function registerSchemaRoute(app, registry, db) {
|
|
|
4080
4314
|
|
|
4081
4315
|
// src/repositories/content.ts
|
|
4082
4316
|
var import_node_crypto3 = __toESM(require("crypto"));
|
|
4083
|
-
var
|
|
4317
|
+
var import_drizzle_orm10 = require("drizzle-orm");
|
|
4084
4318
|
init_relations();
|
|
4085
4319
|
var SORTABLE_FIELDS2 = /* @__PURE__ */ new Set(["title", "created_at", "updated_at"]);
|
|
4086
4320
|
function codeError(code, message) {
|
|
@@ -4088,7 +4322,7 @@ function codeError(code, message) {
|
|
|
4088
4322
|
err.code = code;
|
|
4089
4323
|
return err;
|
|
4090
4324
|
}
|
|
4091
|
-
function
|
|
4325
|
+
function quoteIdent5(name) {
|
|
4092
4326
|
if (!/^[a-z][a-z0-9_-]*$/.test(name)) {
|
|
4093
4327
|
throw new Error(`Unsafe SQL identifier: ${name}`);
|
|
4094
4328
|
}
|
|
@@ -4103,43 +4337,43 @@ function quoteField(name) {
|
|
|
4103
4337
|
function buildConditions(published_only, filters, search) {
|
|
4104
4338
|
const conds = [];
|
|
4105
4339
|
if (published_only) {
|
|
4106
|
-
conds.push(
|
|
4340
|
+
conds.push(import_drizzle_orm10.sql`published = true`);
|
|
4107
4341
|
}
|
|
4108
4342
|
if (search && search.term.trim() !== "" && search.columns.length > 0) {
|
|
4109
4343
|
const pattern = `%${search.term.trim()}%`;
|
|
4110
|
-
const orConds = search.columns.map((col) =>
|
|
4111
|
-
conds.push(
|
|
4344
|
+
const orConds = search.columns.map((col) => import_drizzle_orm10.sql`${import_drizzle_orm10.sql.raw(quoteField(col))} ILIKE ${pattern}`);
|
|
4345
|
+
conds.push(import_drizzle_orm10.sql`(${import_drizzle_orm10.sql.join(orConds, import_drizzle_orm10.sql` OR `)})`);
|
|
4112
4346
|
}
|
|
4113
4347
|
if (filters) {
|
|
4114
4348
|
for (const [field, value] of Object.entries(filters)) {
|
|
4115
|
-
const col =
|
|
4349
|
+
const col = import_drizzle_orm10.sql.raw(quoteField(field));
|
|
4116
4350
|
if (Array.isArray(value)) {
|
|
4117
4351
|
if (value.length === 0) continue;
|
|
4118
|
-
const inList =
|
|
4119
|
-
value.map((v) =>
|
|
4120
|
-
|
|
4352
|
+
const inList = import_drizzle_orm10.sql.join(
|
|
4353
|
+
value.map((v) => import_drizzle_orm10.sql`${v}`),
|
|
4354
|
+
import_drizzle_orm10.sql`, `
|
|
4121
4355
|
);
|
|
4122
|
-
conds.push(
|
|
4356
|
+
conds.push(import_drizzle_orm10.sql`${col} IN (${inList})`);
|
|
4123
4357
|
} else if (typeof value === "object" && value !== null) {
|
|
4124
4358
|
const op = value;
|
|
4125
|
-
if (op.gt !== void 0) conds.push(
|
|
4126
|
-
if (op.gte !== void 0) conds.push(
|
|
4127
|
-
if (op.lt !== void 0) conds.push(
|
|
4128
|
-
if (op.lte !== void 0) conds.push(
|
|
4359
|
+
if (op.gt !== void 0) conds.push(import_drizzle_orm10.sql`${col} > ${op.gt}`);
|
|
4360
|
+
if (op.gte !== void 0) conds.push(import_drizzle_orm10.sql`${col} >= ${op.gte}`);
|
|
4361
|
+
if (op.lt !== void 0) conds.push(import_drizzle_orm10.sql`${col} < ${op.lt}`);
|
|
4362
|
+
if (op.lte !== void 0) conds.push(import_drizzle_orm10.sql`${col} <= ${op.lte}`);
|
|
4129
4363
|
} else {
|
|
4130
|
-
conds.push(
|
|
4364
|
+
conds.push(import_drizzle_orm10.sql`${col} = ${value}`);
|
|
4131
4365
|
}
|
|
4132
4366
|
}
|
|
4133
4367
|
}
|
|
4134
4368
|
return conds;
|
|
4135
4369
|
}
|
|
4136
4370
|
function whereFragment(conds) {
|
|
4137
|
-
return conds.length > 0 ?
|
|
4371
|
+
return conds.length > 0 ? import_drizzle_orm10.sql` WHERE ${import_drizzle_orm10.sql.join(conds, import_drizzle_orm10.sql` AND `)}` : import_drizzle_orm10.sql``;
|
|
4138
4372
|
}
|
|
4139
4373
|
function createDrizzleContentRepository(db, tableName, options = {}) {
|
|
4140
4374
|
const { relations = {}, publishedRelations = false, sortableColumns } = options;
|
|
4141
4375
|
function tableRaw() {
|
|
4142
|
-
return
|
|
4376
|
+
return import_drizzle_orm10.sql.raw(quoteIdent5(tableName));
|
|
4143
4377
|
}
|
|
4144
4378
|
async function resolveRows(rows, include, cache) {
|
|
4145
4379
|
if (rows.length === 0) return rows;
|
|
@@ -4182,14 +4416,14 @@ function createDrizzleContentRepository(db, tableName, options = {}) {
|
|
|
4182
4416
|
const offset = (page - 1) * per_page;
|
|
4183
4417
|
const conds = buildConditions(published_only, filters, search);
|
|
4184
4418
|
const where = whereFragment(conds);
|
|
4185
|
-
const sortCol =
|
|
4186
|
-
const sortDir =
|
|
4419
|
+
const sortCol = import_drizzle_orm10.sql.raw(quoteIdent5(sort_by));
|
|
4420
|
+
const sortDir = import_drizzle_orm10.sql.raw(sort_order === "desc" ? "DESC" : "ASC");
|
|
4187
4421
|
const countResult = await db.execute(
|
|
4188
|
-
|
|
4422
|
+
import_drizzle_orm10.sql`SELECT COUNT(*) AS total FROM ${tableRaw()}${where}`
|
|
4189
4423
|
);
|
|
4190
4424
|
const total = Number(countResult.rows[0]?.["total"] ?? 0);
|
|
4191
4425
|
const dataResult = await db.execute(
|
|
4192
|
-
|
|
4426
|
+
import_drizzle_orm10.sql`SELECT * FROM ${tableRaw()}${where} ORDER BY ${sortCol} ${sortDir} LIMIT ${per_page} OFFSET ${offset}`
|
|
4193
4427
|
);
|
|
4194
4428
|
const rows = dataResult.rows;
|
|
4195
4429
|
const cache = /* @__PURE__ */ new Map();
|
|
@@ -4210,7 +4444,7 @@ function createDrizzleContentRepository(db, tableName, options = {}) {
|
|
|
4210
4444
|
},
|
|
4211
4445
|
async findOne(id, include = []) {
|
|
4212
4446
|
const result = await db.execute(
|
|
4213
|
-
|
|
4447
|
+
import_drizzle_orm10.sql`SELECT * FROM ${tableRaw()} WHERE id = ${id} LIMIT 1`
|
|
4214
4448
|
);
|
|
4215
4449
|
if (result.rows.length === 0) return null;
|
|
4216
4450
|
const rows = result.rows;
|
|
@@ -4220,7 +4454,7 @@ function createDrizzleContentRepository(db, tableName, options = {}) {
|
|
|
4220
4454
|
},
|
|
4221
4455
|
async findBySlug(slug, include = []) {
|
|
4222
4456
|
const result = await db.execute(
|
|
4223
|
-
|
|
4457
|
+
import_drizzle_orm10.sql`SELECT * FROM ${tableRaw()} WHERE slug = ${slug} LIMIT 1`
|
|
4224
4458
|
);
|
|
4225
4459
|
if (result.rows.length === 0) return null;
|
|
4226
4460
|
const rows = result.rows;
|
|
@@ -4236,10 +4470,10 @@ function createDrizzleContentRepository(db, tableName, options = {}) {
|
|
|
4236
4470
|
updated_at: /* @__PURE__ */ new Date()
|
|
4237
4471
|
};
|
|
4238
4472
|
const entries = Object.entries(record).filter(([, v]) => v !== void 0);
|
|
4239
|
-
const colsSql =
|
|
4240
|
-
const valsSql =
|
|
4473
|
+
const colsSql = import_drizzle_orm10.sql.join(entries.map(([k]) => import_drizzle_orm10.sql.raw(quoteIdent5(k))), import_drizzle_orm10.sql`, `);
|
|
4474
|
+
const valsSql = import_drizzle_orm10.sql.join(entries.map(([, v]) => import_drizzle_orm10.sql`${v}`), import_drizzle_orm10.sql`, `);
|
|
4241
4475
|
const result = await db.execute(
|
|
4242
|
-
|
|
4476
|
+
import_drizzle_orm10.sql`INSERT INTO ${tableRaw()} (${colsSql}) VALUES (${valsSql}) RETURNING *`
|
|
4243
4477
|
);
|
|
4244
4478
|
return result.rows[0];
|
|
4245
4479
|
},
|
|
@@ -4252,25 +4486,25 @@ function createDrizzleContentRepository(db, tableName, options = {}) {
|
|
|
4252
4486
|
if (entries.length === 0) {
|
|
4253
4487
|
return this.findOne(id);
|
|
4254
4488
|
}
|
|
4255
|
-
const setClauses =
|
|
4256
|
-
entries.map(([k, v]) =>
|
|
4257
|
-
|
|
4489
|
+
const setClauses = import_drizzle_orm10.sql.join(
|
|
4490
|
+
entries.map(([k, v]) => import_drizzle_orm10.sql`${import_drizzle_orm10.sql.raw(quoteIdent5(k))} = ${v}`),
|
|
4491
|
+
import_drizzle_orm10.sql`, `
|
|
4258
4492
|
);
|
|
4259
4493
|
const result = await db.execute(
|
|
4260
|
-
|
|
4494
|
+
import_drizzle_orm10.sql`UPDATE ${tableRaw()} SET ${setClauses} WHERE id = ${id} RETURNING *`
|
|
4261
4495
|
);
|
|
4262
4496
|
if (result.rows.length === 0) return null;
|
|
4263
4497
|
return result.rows[0];
|
|
4264
4498
|
},
|
|
4265
4499
|
async delete(id) {
|
|
4266
|
-
await db.execute(
|
|
4500
|
+
await db.execute(import_drizzle_orm10.sql`DELETE FROM ${tableRaw()} WHERE id = ${id}`);
|
|
4267
4501
|
},
|
|
4268
4502
|
async findAll(opts = {}) {
|
|
4269
4503
|
const { include = [], published_only } = opts;
|
|
4270
4504
|
const conds = buildConditions(published_only, void 0, void 0);
|
|
4271
4505
|
const where = whereFragment(conds);
|
|
4272
4506
|
const result = await db.execute(
|
|
4273
|
-
|
|
4507
|
+
import_drizzle_orm10.sql`SELECT * FROM ${tableRaw()}${where} ORDER BY created_at ASC`
|
|
4274
4508
|
);
|
|
4275
4509
|
const rows = result.rows;
|
|
4276
4510
|
const cache = /* @__PURE__ */ new Map();
|
|
@@ -4285,23 +4519,23 @@ init_relations();
|
|
|
4285
4519
|
|
|
4286
4520
|
// src/repositories/media.ts
|
|
4287
4521
|
var import_node_crypto4 = __toESM(require("crypto"));
|
|
4288
|
-
var
|
|
4522
|
+
var import_drizzle_orm11 = require("drizzle-orm");
|
|
4289
4523
|
function createMediaRepository(db) {
|
|
4290
|
-
const TABLE =
|
|
4524
|
+
const TABLE = import_drizzle_orm11.sql.raw('"media"');
|
|
4291
4525
|
return {
|
|
4292
4526
|
async findMany(opts = {}) {
|
|
4293
4527
|
const { page = 1, per_page = 10, type, orphaned } = opts;
|
|
4294
4528
|
const offset = (page - 1) * per_page;
|
|
4295
4529
|
const conds = [];
|
|
4296
|
-
if (type) conds.push(
|
|
4297
|
-
if (orphaned) conds.push(
|
|
4298
|
-
const where = conds.length > 0 ?
|
|
4299
|
-
const countResult = await db.execute(
|
|
4530
|
+
if (type) conds.push(import_drizzle_orm11.sql`type = ${type}`);
|
|
4531
|
+
if (orphaned) conds.push(import_drizzle_orm11.sql`reference_count = 0`);
|
|
4532
|
+
const where = conds.length > 0 ? import_drizzle_orm11.sql` WHERE ${import_drizzle_orm11.sql.join(conds, import_drizzle_orm11.sql` AND `)}` : import_drizzle_orm11.sql``;
|
|
4533
|
+
const countResult = await db.execute(import_drizzle_orm11.sql`SELECT COUNT(*) AS total FROM ${TABLE}${where}`);
|
|
4300
4534
|
const total = Number(
|
|
4301
4535
|
countResult.rows[0]?.["total"] ?? 0
|
|
4302
4536
|
);
|
|
4303
4537
|
const dataResult = await db.execute(
|
|
4304
|
-
|
|
4538
|
+
import_drizzle_orm11.sql`SELECT * FROM ${TABLE}${where} ORDER BY created_at DESC LIMIT ${per_page} OFFSET ${offset}`
|
|
4305
4539
|
);
|
|
4306
4540
|
const total_pages = Math.ceil(total / per_page);
|
|
4307
4541
|
return {
|
|
@@ -4319,7 +4553,7 @@ function createMediaRepository(db) {
|
|
|
4319
4553
|
},
|
|
4320
4554
|
async findOne(id) {
|
|
4321
4555
|
const result = await db.execute(
|
|
4322
|
-
|
|
4556
|
+
import_drizzle_orm11.sql`SELECT * FROM ${TABLE} WHERE id = ${id} LIMIT 1`
|
|
4323
4557
|
);
|
|
4324
4558
|
if (result.rows.length === 0) return null;
|
|
4325
4559
|
return result.rows[0];
|
|
@@ -4333,10 +4567,10 @@ function createMediaRepository(db) {
|
|
|
4333
4567
|
updated_at: /* @__PURE__ */ new Date()
|
|
4334
4568
|
};
|
|
4335
4569
|
const entries = Object.entries(record).filter(([, v]) => v !== void 0);
|
|
4336
|
-
const colsSql =
|
|
4337
|
-
const valsSql =
|
|
4570
|
+
const colsSql = import_drizzle_orm11.sql.join(entries.map(([k]) => import_drizzle_orm11.sql.raw(`"${k}"`)), import_drizzle_orm11.sql`, `);
|
|
4571
|
+
const valsSql = import_drizzle_orm11.sql.join(entries.map(([, v]) => import_drizzle_orm11.sql`${v}`), import_drizzle_orm11.sql`, `);
|
|
4338
4572
|
const result = await db.execute(
|
|
4339
|
-
|
|
4573
|
+
import_drizzle_orm11.sql`INSERT INTO ${TABLE} (${colsSql}) VALUES (${valsSql}) RETURNING *`
|
|
4340
4574
|
);
|
|
4341
4575
|
return result.rows[0];
|
|
4342
4576
|
},
|
|
@@ -4346,31 +4580,31 @@ function createMediaRepository(db) {
|
|
|
4346
4580
|
([k, v]) => k !== "id" && v !== void 0
|
|
4347
4581
|
);
|
|
4348
4582
|
if (entries.length === 0) return this.findOne(id);
|
|
4349
|
-
const setClauses =
|
|
4350
|
-
entries.map(([k, v]) =>
|
|
4351
|
-
|
|
4583
|
+
const setClauses = import_drizzle_orm11.sql.join(
|
|
4584
|
+
entries.map(([k, v]) => import_drizzle_orm11.sql`${import_drizzle_orm11.sql.raw(`"${k}"`)} = ${v}`),
|
|
4585
|
+
import_drizzle_orm11.sql`, `
|
|
4352
4586
|
);
|
|
4353
4587
|
const result = await db.execute(
|
|
4354
|
-
|
|
4588
|
+
import_drizzle_orm11.sql`UPDATE ${TABLE} SET ${setClauses} WHERE id = ${id} RETURNING *`
|
|
4355
4589
|
);
|
|
4356
4590
|
if (result.rows.length === 0) return null;
|
|
4357
4591
|
return result.rows[0];
|
|
4358
4592
|
},
|
|
4359
4593
|
async delete(id) {
|
|
4360
|
-
await db.execute(
|
|
4594
|
+
await db.execute(import_drizzle_orm11.sql`DELETE FROM ${TABLE} WHERE id = ${id}`);
|
|
4361
4595
|
},
|
|
4362
4596
|
async incrementReferenceCount(ids) {
|
|
4363
4597
|
if (ids.length === 0) return;
|
|
4364
|
-
const inList =
|
|
4598
|
+
const inList = import_drizzle_orm11.sql.join(ids.map((id) => import_drizzle_orm11.sql`${id}`), import_drizzle_orm11.sql`, `);
|
|
4365
4599
|
await db.execute(
|
|
4366
|
-
|
|
4600
|
+
import_drizzle_orm11.sql`UPDATE ${TABLE} SET reference_count = reference_count + 1, updated_at = NOW() WHERE id IN (${inList})`
|
|
4367
4601
|
);
|
|
4368
4602
|
},
|
|
4369
4603
|
async decrementReferenceCount(ids) {
|
|
4370
4604
|
if (ids.length === 0) return;
|
|
4371
|
-
const inList =
|
|
4605
|
+
const inList = import_drizzle_orm11.sql.join(ids.map((id) => import_drizzle_orm11.sql`${id}`), import_drizzle_orm11.sql`, `);
|
|
4372
4606
|
await db.execute(
|
|
4373
|
-
|
|
4607
|
+
import_drizzle_orm11.sql`UPDATE ${TABLE} SET reference_count = GREATEST(reference_count - 1, 0), updated_at = NOW() WHERE id IN (${inList})`
|
|
4374
4608
|
);
|
|
4375
4609
|
}
|
|
4376
4610
|
};
|
|
@@ -4507,7 +4741,7 @@ function createCmsApp(options) {
|
|
|
4507
4741
|
const requirePermission = createPermissionMiddleware(rolesRegistry);
|
|
4508
4742
|
const getUserRole = async (userId) => {
|
|
4509
4743
|
const result = await db.execute(
|
|
4510
|
-
|
|
4744
|
+
import_drizzle_orm12.sql`SELECT r.name AS role
|
|
4511
4745
|
FROM users u
|
|
4512
4746
|
JOIN roles r ON r.id = u.role_id
|
|
4513
4747
|
WHERE u.id = ${userId}
|