@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/{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 +309 -150
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +242 -98
- 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,18 +257,27 @@ 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
|
+
}
|
|
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] : [];
|
|
271
279
|
}
|
|
280
|
+
return Array.isArray(value) ? value : [];
|
|
272
281
|
}
|
|
273
282
|
function buildRelationsMap(fields, registry) {
|
|
274
283
|
const relations = {};
|
|
@@ -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_orm11 = 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_orm4 = 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,62 @@ 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_manguito_cms_core3 = require("@bobbykim/manguito-cms-core");
|
|
2362
|
+
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
2363
|
+
function isUuid(v) {
|
|
2364
|
+
return typeof v === "string" && UUID_RE.test(v);
|
|
2365
|
+
}
|
|
2366
|
+
function isPlainObject(v) {
|
|
2367
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
2368
|
+
}
|
|
2369
|
+
function checkRelationInput(fields, body, registry, path = "") {
|
|
2370
|
+
const errors = [];
|
|
2371
|
+
for (const field of fields) {
|
|
2372
|
+
if (!(field.name in body)) continue;
|
|
2373
|
+
const cardinality = (0, import_manguito_cms_core3.relationCardinality)(field);
|
|
2374
|
+
if (cardinality === null) continue;
|
|
2375
|
+
const value = body[field.name];
|
|
2376
|
+
const name = `${path}${field.name}`;
|
|
2377
|
+
if (field.field_type === "reference") {
|
|
2378
|
+
const ok = cardinality === "one" ? value === null || isUuid(value) : Array.isArray(value) && value.every(isUuid);
|
|
2379
|
+
if (!ok) {
|
|
2380
|
+
errors.push({
|
|
2381
|
+
field: name,
|
|
2382
|
+
message: cardinality === "one" ? `${name} holds one item: send a UUID string or null.` : `${name} holds a list: send an array of UUID strings.`
|
|
2383
|
+
});
|
|
2384
|
+
}
|
|
2385
|
+
continue;
|
|
2386
|
+
}
|
|
2387
|
+
const items = cardinality === "one" ? value === null ? [] : isPlainObject(value) ? [value] : void 0 : Array.isArray(value) && value.every(isPlainObject) ? value : void 0;
|
|
2388
|
+
if (items === void 0) {
|
|
2389
|
+
errors.push({
|
|
2390
|
+
field: name,
|
|
2391
|
+
message: cardinality === "one" ? `${name} holds one item: send an object or null.` : `${name} holds a list: send an array of objects.`
|
|
2392
|
+
});
|
|
2393
|
+
continue;
|
|
2394
|
+
}
|
|
2395
|
+
const comp = field.ui_component;
|
|
2396
|
+
const pType = comp.component === "paragraph-embed" ? registry.paragraph_types[comp.ref] : void 0;
|
|
2397
|
+
if (!pType) continue;
|
|
2398
|
+
items.forEach((item, i) => {
|
|
2399
|
+
const prefix = cardinality === "one" ? `${name}.` : `${name}[${i}].`;
|
|
2400
|
+
errors.push(...checkRelationInput(pType.fields, item, registry, prefix));
|
|
2401
|
+
});
|
|
2402
|
+
}
|
|
2403
|
+
return errors;
|
|
2404
|
+
}
|
|
2405
|
+
|
|
2406
|
+
// src/routes/admin/content.ts
|
|
2292
2407
|
init_field_keys();
|
|
2293
|
-
function
|
|
2408
|
+
function quoteIdent3(name) {
|
|
2294
2409
|
if (!/^[a-z][a-z0-9_-]*$/.test(name)) throw new Error(`Unsafe identifier: ${name}`);
|
|
2295
2410
|
return `"${name}"`;
|
|
2296
2411
|
}
|
|
2297
2412
|
async function loadParagraphRows(db, registry, pType, parentId, fieldName) {
|
|
2298
2413
|
const result = await db.execute(
|
|
2299
|
-
|
|
2414
|
+
import_drizzle_orm4.sql`SELECT * FROM ${import_drizzle_orm4.sql.raw(quoteIdent3(pType.db.table_name))} WHERE parent_id = ${parentId} AND parent_field = ${fieldName} ORDER BY "order" ASC`
|
|
2300
2415
|
);
|
|
2301
2416
|
const rows = result.rows;
|
|
2302
2417
|
for (const nf of pType.fields) {
|
|
@@ -2306,14 +2421,17 @@ async function loadParagraphRows(db, registry, pType, parentId, fieldName) {
|
|
|
2306
2421
|
const nType = registry.paragraph_types[comp.ref];
|
|
2307
2422
|
if (!nType) continue;
|
|
2308
2423
|
for (const row of rows) {
|
|
2309
|
-
row[nf.name] =
|
|
2424
|
+
row[nf.name] = oneOrMany(
|
|
2425
|
+
(0, import_manguito_cms_core4.relationCardinality)(nf) ?? "many",
|
|
2426
|
+
await loadParagraphRows(db, registry, nType, row["id"], nf.name)
|
|
2427
|
+
);
|
|
2310
2428
|
}
|
|
2311
2429
|
}
|
|
2312
2430
|
return rows;
|
|
2313
2431
|
}
|
|
2314
2432
|
async function lookupBasePathId(db, pathOrName) {
|
|
2315
2433
|
const r = await db.execute(
|
|
2316
|
-
|
|
2434
|
+
import_drizzle_orm4.sql`SELECT id FROM base_paths WHERE path = ${pathOrName} OR name = ${pathOrName} LIMIT 1`
|
|
2317
2435
|
);
|
|
2318
2436
|
return r.rows[0]?.id ?? null;
|
|
2319
2437
|
}
|
|
@@ -2327,6 +2445,14 @@ function isEmpty(value) {
|
|
|
2327
2445
|
function checkRequiredFields(fields, data) {
|
|
2328
2446
|
return fields.filter((f) => f.required && isEmpty(data[f.name])).map((f) => ({ field: f.name, message: `${f.label} is required` }));
|
|
2329
2447
|
}
|
|
2448
|
+
function relationShapeError(c, fields, body, registry) {
|
|
2449
|
+
const details = checkRelationInput(fields, body, registry);
|
|
2450
|
+
if (details.length === 0) return null;
|
|
2451
|
+
return c.json(
|
|
2452
|
+
{ ok: false, error: { code: "VALIDATION_ERROR", message: "Relation fields have the wrong shape", details } },
|
|
2453
|
+
422
|
|
2454
|
+
);
|
|
2455
|
+
}
|
|
2330
2456
|
function parseListQuery(url, schemaFieldNames, relationFieldNames, columnFor) {
|
|
2331
2457
|
const searchParams = new URL(url).searchParams;
|
|
2332
2458
|
const pagination = parsePagination(
|
|
@@ -2468,11 +2594,11 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
|
|
|
2468
2594
|
if (comp.component !== "paragraph-embed" || !comp.ref) continue;
|
|
2469
2595
|
const pType = registry.paragraph_types[comp.ref];
|
|
2470
2596
|
if (!pType) continue;
|
|
2471
|
-
row[f.name] = await loadParagraphRows(db, registry, pType, id, f.name);
|
|
2597
|
+
row[f.name] = oneOrMany((0, import_manguito_cms_core4.relationCardinality)(f) ?? "many", await loadParagraphRows(db, registry, pType, id, f.name));
|
|
2472
2598
|
} else if (f.db_column.junction) {
|
|
2473
2599
|
const j = f.db_column.junction;
|
|
2474
2600
|
const r = await db.execute(
|
|
2475
|
-
|
|
2601
|
+
import_drizzle_orm4.sql`SELECT ${import_drizzle_orm4.sql.raw(quoteIdent3(j.right_column))} FROM ${import_drizzle_orm4.sql.raw(quoteIdent3(j.table_name))} WHERE ${import_drizzle_orm4.sql.raw(quoteIdent3(j.left_column))} = ${id}`
|
|
2476
2602
|
);
|
|
2477
2603
|
row[f.name] = r.rows.map(
|
|
2478
2604
|
(row2) => row2[j.right_column]
|
|
@@ -2548,6 +2674,8 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
|
|
|
2548
2674
|
}
|
|
2549
2675
|
);
|
|
2550
2676
|
const writeNewItem = async (c, body) => {
|
|
2677
|
+
const shapeError = relationShapeError(c, contentType.fields, body, registry);
|
|
2678
|
+
if (shapeError) return shapeError;
|
|
2551
2679
|
const storageBody = fieldKeys.toStorage(body);
|
|
2552
2680
|
const fieldErrors = checkRequiredFields(requiredFields, body);
|
|
2553
2681
|
if (fieldErrors.length > 0) {
|
|
@@ -2611,7 +2739,7 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
|
|
|
2611
2739
|
if (comp.component !== "paragraph-embed" || !comp.ref) continue;
|
|
2612
2740
|
const pType = registry.paragraph_types[comp.ref];
|
|
2613
2741
|
if (!pType) continue;
|
|
2614
|
-
const items =
|
|
2742
|
+
const items = paragraphItems(f, body[f.name]);
|
|
2615
2743
|
mediaDeltas.push(await persistParagraphField(db, itemId, contentType.db.table_name, f.name, pType, items, registry));
|
|
2616
2744
|
}
|
|
2617
2745
|
for (const f of junctionFields) {
|
|
@@ -2682,6 +2810,8 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
|
|
|
2682
2810
|
}
|
|
2683
2811
|
);
|
|
2684
2812
|
const writeExistingItem = async (c, id, existing, body) => {
|
|
2813
|
+
const shapeError = relationShapeError(c, contentType.fields, body, registry);
|
|
2814
|
+
if (shapeError) return shapeError;
|
|
2685
2815
|
const storageBody = fieldKeys.toStorage(body);
|
|
2686
2816
|
if (body["published"] === true) {
|
|
2687
2817
|
const publishDeny = await requirePermission("content:edit")(c, async () => {
|
|
@@ -2728,14 +2858,16 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
|
|
|
2728
2858
|
];
|
|
2729
2859
|
if (db) {
|
|
2730
2860
|
for (const f of patchParagraphFields) {
|
|
2861
|
+
if (!(f.name in body)) continue;
|
|
2731
2862
|
const comp = f.ui_component;
|
|
2732
2863
|
if (comp.component !== "paragraph-embed" || !comp.ref) continue;
|
|
2733
2864
|
const pType = registry.paragraph_types[comp.ref];
|
|
2734
2865
|
if (!pType) continue;
|
|
2735
|
-
const items =
|
|
2866
|
+
const items = paragraphItems(f, body[f.name]);
|
|
2736
2867
|
mediaDeltas.push(await persistParagraphField(db, id, contentType.db.table_name, f.name, pType, items, registry));
|
|
2737
2868
|
}
|
|
2738
2869
|
for (const f of patchJunctionFields) {
|
|
2870
|
+
if (!(f.name in body)) continue;
|
|
2739
2871
|
const junction = f.db_column.junction;
|
|
2740
2872
|
const relatedIds = Array.isArray(body[f.name]) ? body[f.name].filter((v) => typeof v === "string") : [];
|
|
2741
2873
|
await persistJunctionField(db, id, junction, relatedIds);
|
|
@@ -2775,6 +2907,18 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
|
|
|
2775
2907
|
404
|
|
2776
2908
|
);
|
|
2777
2909
|
}
|
|
2910
|
+
const referrers = db ? await findRequiredReferrers(db, registry, typeName, id) : [];
|
|
2911
|
+
if (referrers.length > 0) {
|
|
2912
|
+
return c.json({ ok: false, error: { code: "ITEM_IN_USE", message: inUseMessage(referrers) } }, 409);
|
|
2913
|
+
}
|
|
2914
|
+
try {
|
|
2915
|
+
await repo.delete(id);
|
|
2916
|
+
} catch (err) {
|
|
2917
|
+
if (isForeignKeyViolation(err)) {
|
|
2918
|
+
return c.json({ ok: false, error: { code: "ITEM_IN_USE", message: IN_USE_RACE_MESSAGE } }, 409);
|
|
2919
|
+
}
|
|
2920
|
+
throw err;
|
|
2921
|
+
}
|
|
2778
2922
|
const mediaDeltas = [
|
|
2779
2923
|
topLevelMediaDelta(mediaFields, item, null)
|
|
2780
2924
|
];
|
|
@@ -2788,7 +2932,6 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
|
|
|
2788
2932
|
}
|
|
2789
2933
|
}
|
|
2790
2934
|
await applyMediaReferenceDelta(mergeMediaDeltas(...mediaDeltas), mediaRepo);
|
|
2791
|
-
await repo.delete(id);
|
|
2792
2935
|
return c.json({ ok: true });
|
|
2793
2936
|
}
|
|
2794
2937
|
);
|
|
@@ -2866,6 +3009,8 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
|
|
|
2866
3009
|
requirePermission("content:create"),
|
|
2867
3010
|
async (c) => {
|
|
2868
3011
|
const body = await c.req.json();
|
|
3012
|
+
const shapeError = relationShapeError(c, taxonomyType.fields, body, registry);
|
|
3013
|
+
if (shapeError) return shapeError;
|
|
2869
3014
|
const storageBody = fieldKeys.toStorage(body);
|
|
2870
3015
|
const fieldErrors = checkRequiredFields(requiredFields, body);
|
|
2871
3016
|
if (fieldErrors.length > 0) {
|
|
@@ -2905,7 +3050,7 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
|
|
|
2905
3050
|
if (comp.component !== "paragraph-embed" || !comp.ref) continue;
|
|
2906
3051
|
const pType = registry.paragraph_types[comp.ref];
|
|
2907
3052
|
if (!pType) continue;
|
|
2908
|
-
const items =
|
|
3053
|
+
const items = paragraphItems(f, body[f.name]);
|
|
2909
3054
|
mediaDeltas.push(await persistParagraphField(db, taxItemId, taxonomyType.db.table_name, f.name, pType, items, registry));
|
|
2910
3055
|
}
|
|
2911
3056
|
}
|
|
@@ -2928,6 +3073,8 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
|
|
|
2928
3073
|
404
|
|
2929
3074
|
);
|
|
2930
3075
|
}
|
|
3076
|
+
const shapeError = relationShapeError(c, taxonomyType.fields, body, registry);
|
|
3077
|
+
if (shapeError) return shapeError;
|
|
2931
3078
|
if (body["published"] === true) {
|
|
2932
3079
|
const publishDeny = await requirePermission("content:edit")(c, async () => {
|
|
2933
3080
|
});
|
|
@@ -2969,11 +3116,12 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
|
|
|
2969
3116
|
];
|
|
2970
3117
|
if (db) {
|
|
2971
3118
|
for (const f of taxPatchParagraphFields) {
|
|
3119
|
+
if (!(f.name in body)) continue;
|
|
2972
3120
|
const comp = f.ui_component;
|
|
2973
3121
|
if (comp.component !== "paragraph-embed" || !comp.ref) continue;
|
|
2974
3122
|
const pType = registry.paragraph_types[comp.ref];
|
|
2975
3123
|
if (!pType) continue;
|
|
2976
|
-
const items =
|
|
3124
|
+
const items = paragraphItems(f, body[f.name]);
|
|
2977
3125
|
mediaDeltas.push(await persistParagraphField(db, id, taxonomyType.db.table_name, f.name, pType, items, registry));
|
|
2978
3126
|
}
|
|
2979
3127
|
}
|
|
@@ -2994,6 +3142,18 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
|
|
|
2994
3142
|
404
|
|
2995
3143
|
);
|
|
2996
3144
|
}
|
|
3145
|
+
const referrers = db ? await findRequiredReferrers(db, registry, typeName, id) : [];
|
|
3146
|
+
if (referrers.length > 0) {
|
|
3147
|
+
return c.json({ ok: false, error: { code: "ITEM_IN_USE", message: inUseMessage(referrers) } }, 409);
|
|
3148
|
+
}
|
|
3149
|
+
try {
|
|
3150
|
+
await repo.delete(id);
|
|
3151
|
+
} catch (err) {
|
|
3152
|
+
if (isForeignKeyViolation(err)) {
|
|
3153
|
+
return c.json({ ok: false, error: { code: "ITEM_IN_USE", message: IN_USE_RACE_MESSAGE } }, 409);
|
|
3154
|
+
}
|
|
3155
|
+
throw err;
|
|
3156
|
+
}
|
|
2997
3157
|
const mediaDeltas = [
|
|
2998
3158
|
topLevelMediaDelta(mediaFields, item, null)
|
|
2999
3159
|
];
|
|
@@ -3007,7 +3167,6 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
|
|
|
3007
3167
|
}
|
|
3008
3168
|
}
|
|
3009
3169
|
await applyMediaReferenceDelta(mergeMediaDeltas(...mediaDeltas), mediaRepo);
|
|
3010
|
-
await repo.delete(id);
|
|
3011
3170
|
return c.json({ ok: true });
|
|
3012
3171
|
}
|
|
3013
3172
|
);
|
|
@@ -3469,10 +3628,10 @@ function registerAdminMediaRoutes(app, mediaRepo, storage, requirePermission, ma
|
|
|
3469
3628
|
|
|
3470
3629
|
// src/routes/admin/auth.ts
|
|
3471
3630
|
var import_cookie3 = require("hono/cookie");
|
|
3472
|
-
var
|
|
3631
|
+
var import_drizzle_orm5 = require("drizzle-orm");
|
|
3473
3632
|
|
|
3474
3633
|
// src/auth/password.ts
|
|
3475
|
-
var
|
|
3634
|
+
var import_manguito_cms_core5 = require("@bobbykim/manguito-cms-core");
|
|
3476
3635
|
|
|
3477
3636
|
// src/routes/admin/auth.ts
|
|
3478
3637
|
var loginAttempts = /* @__PURE__ */ new Map();
|
|
@@ -3535,20 +3694,20 @@ function registerAuthRoutes(app, db) {
|
|
|
3535
3694
|
);
|
|
3536
3695
|
}
|
|
3537
3696
|
const userResult = await db.execute(
|
|
3538
|
-
|
|
3697
|
+
import_drizzle_orm5.sql`SELECT id, email, password_hash, role_id, token_version
|
|
3539
3698
|
FROM users WHERE email = ${email} LIMIT 1`
|
|
3540
3699
|
);
|
|
3541
3700
|
const user = userResult.rows[0];
|
|
3542
3701
|
if (!user) {
|
|
3543
|
-
await (0,
|
|
3702
|
+
await (0, import_manguito_cms_core5.verifyPassword)(password, DUMMY_PASSWORD_HASH);
|
|
3544
3703
|
return c.json(invalidCredentials, 401);
|
|
3545
3704
|
}
|
|
3546
|
-
const passwordValid = await (0,
|
|
3705
|
+
const passwordValid = await (0, import_manguito_cms_core5.verifyPassword)(password, user.password_hash);
|
|
3547
3706
|
if (!passwordValid) {
|
|
3548
3707
|
return c.json(invalidCredentials, 401);
|
|
3549
3708
|
}
|
|
3550
3709
|
const roleResult = await db.execute(
|
|
3551
|
-
|
|
3710
|
+
import_drizzle_orm5.sql`SELECT r.name AS name, u.must_change_password
|
|
3552
3711
|
FROM roles r
|
|
3553
3712
|
JOIN users u ON u.role_id = r.id
|
|
3554
3713
|
WHERE r.id = ${user.role_id} AND u.id = ${user.id}
|
|
@@ -3608,7 +3767,7 @@ function registerAuthRoutes(app, db) {
|
|
|
3608
3767
|
);
|
|
3609
3768
|
}
|
|
3610
3769
|
const rowResult = await db.execute(
|
|
3611
|
-
|
|
3770
|
+
import_drizzle_orm5.sql`SELECT u.token_version, r.name AS role
|
|
3612
3771
|
FROM users u
|
|
3613
3772
|
JOIN roles r ON r.id = u.role_id
|
|
3614
3773
|
WHERE u.id = ${payload.user_id}
|
|
@@ -3632,7 +3791,7 @@ function registerAuthRoutes(app, db) {
|
|
|
3632
3791
|
app.post("/admin/api/auth/logout", authMiddleware, async (c) => {
|
|
3633
3792
|
const user = c.get("user");
|
|
3634
3793
|
await db.execute(
|
|
3635
|
-
|
|
3794
|
+
import_drizzle_orm5.sql`UPDATE users SET token_version = token_version + 1 WHERE id = ${user.id}`
|
|
3636
3795
|
);
|
|
3637
3796
|
clearAuthCookie(c, "auth_token");
|
|
3638
3797
|
clearAuthCookie(c, "refresh_token", "/admin/api/auth");
|
|
@@ -3641,7 +3800,7 @@ function registerAuthRoutes(app, db) {
|
|
|
3641
3800
|
}
|
|
3642
3801
|
|
|
3643
3802
|
// src/routes/admin/users.ts
|
|
3644
|
-
var
|
|
3803
|
+
var import_drizzle_orm6 = require("drizzle-orm");
|
|
3645
3804
|
var import_node_crypto2 = require("crypto");
|
|
3646
3805
|
function getActingUser(c) {
|
|
3647
3806
|
return c.get("user");
|
|
@@ -3676,18 +3835,18 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
3676
3835
|
);
|
|
3677
3836
|
}
|
|
3678
3837
|
const hashResult = await db.execute(
|
|
3679
|
-
|
|
3838
|
+
import_drizzle_orm6.sql`SELECT password_hash FROM users WHERE id = ${actingUser.id} LIMIT 1`
|
|
3680
3839
|
);
|
|
3681
3840
|
const hashRow = hashResult.rows[0];
|
|
3682
|
-
if (!hashRow || !await (0,
|
|
3841
|
+
if (!hashRow || !await (0, import_manguito_cms_core5.verifyPassword)(currentPassword, hashRow.password_hash)) {
|
|
3683
3842
|
return c.json(
|
|
3684
3843
|
{ ok: false, error: { code: "INVALID_CREDENTIALS", message: "Invalid credentials." } },
|
|
3685
3844
|
401
|
|
3686
3845
|
);
|
|
3687
3846
|
}
|
|
3688
|
-
const newHash = await (0,
|
|
3847
|
+
const newHash = await (0, import_manguito_cms_core5.hashPassword)(newPassword);
|
|
3689
3848
|
await db.execute(
|
|
3690
|
-
|
|
3849
|
+
import_drizzle_orm6.sql`UPDATE users
|
|
3691
3850
|
SET password_hash = ${newHash},
|
|
3692
3851
|
must_change_password = false,
|
|
3693
3852
|
token_version = token_version + 1,
|
|
@@ -3698,7 +3857,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
3698
3857
|
});
|
|
3699
3858
|
app.get("/admin/api/users", requirePermission("users:read"), async (c) => {
|
|
3700
3859
|
const result = await db.execute(
|
|
3701
|
-
|
|
3860
|
+
import_drizzle_orm6.sql`SELECT u.id, u.email, r.name AS role, u.must_change_password,
|
|
3702
3861
|
u.created_at, u.updated_at
|
|
3703
3862
|
FROM users u
|
|
3704
3863
|
JOIN roles r ON r.id = u.role_id
|
|
@@ -3709,7 +3868,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
3709
3868
|
app.get("/admin/api/users/:id", requirePermission("users:read"), async (c) => {
|
|
3710
3869
|
const id = c.req.param("id");
|
|
3711
3870
|
const result = await db.execute(
|
|
3712
|
-
|
|
3871
|
+
import_drizzle_orm6.sql`SELECT u.id, u.email, r.name AS role, u.must_change_password,
|
|
3713
3872
|
u.created_at, u.updated_at
|
|
3714
3873
|
FROM users u
|
|
3715
3874
|
JOIN roles r ON r.id = u.role_id
|
|
@@ -3754,7 +3913,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
3754
3913
|
);
|
|
3755
3914
|
}
|
|
3756
3915
|
const roleResult = await db.execute(
|
|
3757
|
-
|
|
3916
|
+
import_drizzle_orm6.sql`SELECT id FROM roles WHERE name = ${roleName} LIMIT 1`
|
|
3758
3917
|
);
|
|
3759
3918
|
const roleRow = roleResult.rows[0];
|
|
3760
3919
|
if (!roleRow) {
|
|
@@ -3767,9 +3926,9 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
3767
3926
|
);
|
|
3768
3927
|
}
|
|
3769
3928
|
const tempPassword = generateTempPassword();
|
|
3770
|
-
const passwordHash = await (0,
|
|
3929
|
+
const passwordHash = await (0, import_manguito_cms_core5.hashPassword)(tempPassword);
|
|
3771
3930
|
const insertResult = await db.execute(
|
|
3772
|
-
|
|
3931
|
+
import_drizzle_orm6.sql`INSERT INTO users
|
|
3773
3932
|
(id, email, password_hash, role_id, must_change_password, token_version,
|
|
3774
3933
|
created_at, updated_at)
|
|
3775
3934
|
VALUES
|
|
@@ -3824,7 +3983,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
3824
3983
|
);
|
|
3825
3984
|
}
|
|
3826
3985
|
const existingResult = await db.execute(
|
|
3827
|
-
|
|
3986
|
+
import_drizzle_orm6.sql`SELECT id, email, role_id FROM users WHERE id = ${id} LIMIT 1`
|
|
3828
3987
|
);
|
|
3829
3988
|
const existing = existingResult.rows[0];
|
|
3830
3989
|
if (!existing) {
|
|
@@ -3838,7 +3997,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
3838
3997
|
let changeRole = false;
|
|
3839
3998
|
if (typeof body.role === "string" && body.role) {
|
|
3840
3999
|
const roleResult = await db.execute(
|
|
3841
|
-
|
|
4000
|
+
import_drizzle_orm6.sql`SELECT id FROM roles WHERE name = ${body.role} LIMIT 1`
|
|
3842
4001
|
);
|
|
3843
4002
|
const roleRow = roleResult.rows[0];
|
|
3844
4003
|
if (!roleRow) {
|
|
@@ -3858,7 +4017,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
3858
4017
|
}
|
|
3859
4018
|
if (changeRole) {
|
|
3860
4019
|
await db.execute(
|
|
3861
|
-
|
|
4020
|
+
import_drizzle_orm6.sql`UPDATE users
|
|
3862
4021
|
SET email = ${newEmail},
|
|
3863
4022
|
role_id = ${newRoleId},
|
|
3864
4023
|
token_version = token_version + 1,
|
|
@@ -3867,7 +4026,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
3867
4026
|
);
|
|
3868
4027
|
} else {
|
|
3869
4028
|
await db.execute(
|
|
3870
|
-
|
|
4029
|
+
import_drizzle_orm6.sql`UPDATE users
|
|
3871
4030
|
SET email = ${newEmail},
|
|
3872
4031
|
role_id = ${newRoleId},
|
|
3873
4032
|
updated_at = NOW()
|
|
@@ -3875,7 +4034,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
3875
4034
|
);
|
|
3876
4035
|
}
|
|
3877
4036
|
const updatedResult = await db.execute(
|
|
3878
|
-
|
|
4037
|
+
import_drizzle_orm6.sql`SELECT u.id, u.email, r.name AS role, u.must_change_password,
|
|
3879
4038
|
u.created_at, u.updated_at
|
|
3880
4039
|
FROM users u
|
|
3881
4040
|
JOIN roles r ON r.id = u.role_id
|
|
@@ -3912,7 +4071,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
3912
4071
|
);
|
|
3913
4072
|
}
|
|
3914
4073
|
const result = await db.execute(
|
|
3915
|
-
|
|
4074
|
+
import_drizzle_orm6.sql`DELETE FROM users WHERE id = ${id} RETURNING id`
|
|
3916
4075
|
);
|
|
3917
4076
|
if (result.rows.length === 0) {
|
|
3918
4077
|
return c.json(
|
|
@@ -3943,7 +4102,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
3943
4102
|
);
|
|
3944
4103
|
}
|
|
3945
4104
|
const existingResult = await db.execute(
|
|
3946
|
-
|
|
4105
|
+
import_drizzle_orm6.sql`SELECT id FROM users WHERE id = ${id} LIMIT 1`
|
|
3947
4106
|
);
|
|
3948
4107
|
const existing = existingResult.rows[0];
|
|
3949
4108
|
if (!existing) {
|
|
@@ -3953,9 +4112,9 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
3953
4112
|
);
|
|
3954
4113
|
}
|
|
3955
4114
|
const tempPassword = generateTempPassword();
|
|
3956
|
-
const passwordHash = await (0,
|
|
4115
|
+
const passwordHash = await (0, import_manguito_cms_core5.hashPassword)(tempPassword);
|
|
3957
4116
|
await db.execute(
|
|
3958
|
-
|
|
4117
|
+
import_drizzle_orm6.sql`UPDATE users
|
|
3959
4118
|
SET password_hash = ${passwordHash},
|
|
3960
4119
|
must_change_password = true,
|
|
3961
4120
|
token_version = token_version + 1,
|
|
@@ -3968,10 +4127,10 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
3968
4127
|
}
|
|
3969
4128
|
|
|
3970
4129
|
// src/routes/admin/config.ts
|
|
3971
|
-
var
|
|
4130
|
+
var import_drizzle_orm7 = require("drizzle-orm");
|
|
3972
4131
|
var API_VERSION = (() => {
|
|
3973
4132
|
try {
|
|
3974
|
-
return "0.
|
|
4133
|
+
return "0.8.0";
|
|
3975
4134
|
} catch {
|
|
3976
4135
|
return "0.0.0";
|
|
3977
4136
|
}
|
|
@@ -3990,7 +4149,7 @@ function registerConfigRoute(app, config, registry, db) {
|
|
|
3990
4149
|
permissions: r.permissions
|
|
3991
4150
|
}));
|
|
3992
4151
|
const userResult = await db.execute(
|
|
3993
|
-
|
|
4152
|
+
import_drizzle_orm7.sql`SELECT email, must_change_password FROM users WHERE id = ${actingUser.id} LIMIT 1`
|
|
3994
4153
|
);
|
|
3995
4154
|
const userRow = userResult.rows[0];
|
|
3996
4155
|
return c.json({
|
|
@@ -4016,7 +4175,7 @@ function registerConfigRoute(app, config, registry, db) {
|
|
|
4016
4175
|
}
|
|
4017
4176
|
|
|
4018
4177
|
// src/routes/admin/schema.ts
|
|
4019
|
-
var
|
|
4178
|
+
var import_drizzle_orm8 = require("drizzle-orm");
|
|
4020
4179
|
function liveFields(fields) {
|
|
4021
4180
|
return fields.filter((f) => f.removed !== true);
|
|
4022
4181
|
}
|
|
@@ -4056,7 +4215,7 @@ function registerSchemaRoute(app, registry, db) {
|
|
|
4056
4215
|
const data = await Promise.all(
|
|
4057
4216
|
Object.values(registry.content_types).map(async (ct) => {
|
|
4058
4217
|
const result = await db.execute(
|
|
4059
|
-
|
|
4218
|
+
import_drizzle_orm8.sql`SELECT COUNT(*)::int AS count FROM ${import_drizzle_orm8.sql.raw(`"${ct.db.table_name}"`)}`
|
|
4060
4219
|
);
|
|
4061
4220
|
const count = Number(result.rows[0].count);
|
|
4062
4221
|
return { name: ct.name, label: ct.label, only_one: ct.only_one, count };
|
|
@@ -4068,7 +4227,7 @@ function registerSchemaRoute(app, registry, db) {
|
|
|
4068
4227
|
const data = await Promise.all(
|
|
4069
4228
|
Object.values(registry.taxonomy_types).map(async (tt) => {
|
|
4070
4229
|
const result = await db.execute(
|
|
4071
|
-
|
|
4230
|
+
import_drizzle_orm8.sql`SELECT COUNT(*)::int AS count FROM ${import_drizzle_orm8.sql.raw(`"${tt.db.table_name}"`)}`
|
|
4072
4231
|
);
|
|
4073
4232
|
const count = Number(result.rows[0].count);
|
|
4074
4233
|
return { name: tt.name, label: tt.label, count };
|
|
@@ -4080,7 +4239,7 @@ function registerSchemaRoute(app, registry, db) {
|
|
|
4080
4239
|
|
|
4081
4240
|
// src/repositories/content.ts
|
|
4082
4241
|
var import_node_crypto3 = __toESM(require("crypto"));
|
|
4083
|
-
var
|
|
4242
|
+
var import_drizzle_orm9 = require("drizzle-orm");
|
|
4084
4243
|
init_relations();
|
|
4085
4244
|
var SORTABLE_FIELDS2 = /* @__PURE__ */ new Set(["title", "created_at", "updated_at"]);
|
|
4086
4245
|
function codeError(code, message) {
|
|
@@ -4088,7 +4247,7 @@ function codeError(code, message) {
|
|
|
4088
4247
|
err.code = code;
|
|
4089
4248
|
return err;
|
|
4090
4249
|
}
|
|
4091
|
-
function
|
|
4250
|
+
function quoteIdent4(name) {
|
|
4092
4251
|
if (!/^[a-z][a-z0-9_-]*$/.test(name)) {
|
|
4093
4252
|
throw new Error(`Unsafe SQL identifier: ${name}`);
|
|
4094
4253
|
}
|
|
@@ -4103,43 +4262,43 @@ function quoteField(name) {
|
|
|
4103
4262
|
function buildConditions(published_only, filters, search) {
|
|
4104
4263
|
const conds = [];
|
|
4105
4264
|
if (published_only) {
|
|
4106
|
-
conds.push(
|
|
4265
|
+
conds.push(import_drizzle_orm9.sql`published = true`);
|
|
4107
4266
|
}
|
|
4108
4267
|
if (search && search.term.trim() !== "" && search.columns.length > 0) {
|
|
4109
4268
|
const pattern = `%${search.term.trim()}%`;
|
|
4110
|
-
const orConds = search.columns.map((col) =>
|
|
4111
|
-
conds.push(
|
|
4269
|
+
const orConds = search.columns.map((col) => import_drizzle_orm9.sql`${import_drizzle_orm9.sql.raw(quoteField(col))} ILIKE ${pattern}`);
|
|
4270
|
+
conds.push(import_drizzle_orm9.sql`(${import_drizzle_orm9.sql.join(orConds, import_drizzle_orm9.sql` OR `)})`);
|
|
4112
4271
|
}
|
|
4113
4272
|
if (filters) {
|
|
4114
4273
|
for (const [field, value] of Object.entries(filters)) {
|
|
4115
|
-
const col =
|
|
4274
|
+
const col = import_drizzle_orm9.sql.raw(quoteField(field));
|
|
4116
4275
|
if (Array.isArray(value)) {
|
|
4117
4276
|
if (value.length === 0) continue;
|
|
4118
|
-
const inList =
|
|
4119
|
-
value.map((v) =>
|
|
4120
|
-
|
|
4277
|
+
const inList = import_drizzle_orm9.sql.join(
|
|
4278
|
+
value.map((v) => import_drizzle_orm9.sql`${v}`),
|
|
4279
|
+
import_drizzle_orm9.sql`, `
|
|
4121
4280
|
);
|
|
4122
|
-
conds.push(
|
|
4281
|
+
conds.push(import_drizzle_orm9.sql`${col} IN (${inList})`);
|
|
4123
4282
|
} else if (typeof value === "object" && value !== null) {
|
|
4124
4283
|
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(
|
|
4284
|
+
if (op.gt !== void 0) conds.push(import_drizzle_orm9.sql`${col} > ${op.gt}`);
|
|
4285
|
+
if (op.gte !== void 0) conds.push(import_drizzle_orm9.sql`${col} >= ${op.gte}`);
|
|
4286
|
+
if (op.lt !== void 0) conds.push(import_drizzle_orm9.sql`${col} < ${op.lt}`);
|
|
4287
|
+
if (op.lte !== void 0) conds.push(import_drizzle_orm9.sql`${col} <= ${op.lte}`);
|
|
4129
4288
|
} else {
|
|
4130
|
-
conds.push(
|
|
4289
|
+
conds.push(import_drizzle_orm9.sql`${col} = ${value}`);
|
|
4131
4290
|
}
|
|
4132
4291
|
}
|
|
4133
4292
|
}
|
|
4134
4293
|
return conds;
|
|
4135
4294
|
}
|
|
4136
4295
|
function whereFragment(conds) {
|
|
4137
|
-
return conds.length > 0 ?
|
|
4296
|
+
return conds.length > 0 ? import_drizzle_orm9.sql` WHERE ${import_drizzle_orm9.sql.join(conds, import_drizzle_orm9.sql` AND `)}` : import_drizzle_orm9.sql``;
|
|
4138
4297
|
}
|
|
4139
4298
|
function createDrizzleContentRepository(db, tableName, options = {}) {
|
|
4140
4299
|
const { relations = {}, publishedRelations = false, sortableColumns } = options;
|
|
4141
4300
|
function tableRaw() {
|
|
4142
|
-
return
|
|
4301
|
+
return import_drizzle_orm9.sql.raw(quoteIdent4(tableName));
|
|
4143
4302
|
}
|
|
4144
4303
|
async function resolveRows(rows, include, cache) {
|
|
4145
4304
|
if (rows.length === 0) return rows;
|
|
@@ -4182,14 +4341,14 @@ function createDrizzleContentRepository(db, tableName, options = {}) {
|
|
|
4182
4341
|
const offset = (page - 1) * per_page;
|
|
4183
4342
|
const conds = buildConditions(published_only, filters, search);
|
|
4184
4343
|
const where = whereFragment(conds);
|
|
4185
|
-
const sortCol =
|
|
4186
|
-
const sortDir =
|
|
4344
|
+
const sortCol = import_drizzle_orm9.sql.raw(quoteIdent4(sort_by));
|
|
4345
|
+
const sortDir = import_drizzle_orm9.sql.raw(sort_order === "desc" ? "DESC" : "ASC");
|
|
4187
4346
|
const countResult = await db.execute(
|
|
4188
|
-
|
|
4347
|
+
import_drizzle_orm9.sql`SELECT COUNT(*) AS total FROM ${tableRaw()}${where}`
|
|
4189
4348
|
);
|
|
4190
4349
|
const total = Number(countResult.rows[0]?.["total"] ?? 0);
|
|
4191
4350
|
const dataResult = await db.execute(
|
|
4192
|
-
|
|
4351
|
+
import_drizzle_orm9.sql`SELECT * FROM ${tableRaw()}${where} ORDER BY ${sortCol} ${sortDir} LIMIT ${per_page} OFFSET ${offset}`
|
|
4193
4352
|
);
|
|
4194
4353
|
const rows = dataResult.rows;
|
|
4195
4354
|
const cache = /* @__PURE__ */ new Map();
|
|
@@ -4210,7 +4369,7 @@ function createDrizzleContentRepository(db, tableName, options = {}) {
|
|
|
4210
4369
|
},
|
|
4211
4370
|
async findOne(id, include = []) {
|
|
4212
4371
|
const result = await db.execute(
|
|
4213
|
-
|
|
4372
|
+
import_drizzle_orm9.sql`SELECT * FROM ${tableRaw()} WHERE id = ${id} LIMIT 1`
|
|
4214
4373
|
);
|
|
4215
4374
|
if (result.rows.length === 0) return null;
|
|
4216
4375
|
const rows = result.rows;
|
|
@@ -4220,7 +4379,7 @@ function createDrizzleContentRepository(db, tableName, options = {}) {
|
|
|
4220
4379
|
},
|
|
4221
4380
|
async findBySlug(slug, include = []) {
|
|
4222
4381
|
const result = await db.execute(
|
|
4223
|
-
|
|
4382
|
+
import_drizzle_orm9.sql`SELECT * FROM ${tableRaw()} WHERE slug = ${slug} LIMIT 1`
|
|
4224
4383
|
);
|
|
4225
4384
|
if (result.rows.length === 0) return null;
|
|
4226
4385
|
const rows = result.rows;
|
|
@@ -4236,10 +4395,10 @@ function createDrizzleContentRepository(db, tableName, options = {}) {
|
|
|
4236
4395
|
updated_at: /* @__PURE__ */ new Date()
|
|
4237
4396
|
};
|
|
4238
4397
|
const entries = Object.entries(record).filter(([, v]) => v !== void 0);
|
|
4239
|
-
const colsSql =
|
|
4240
|
-
const valsSql =
|
|
4398
|
+
const colsSql = import_drizzle_orm9.sql.join(entries.map(([k]) => import_drizzle_orm9.sql.raw(quoteIdent4(k))), import_drizzle_orm9.sql`, `);
|
|
4399
|
+
const valsSql = import_drizzle_orm9.sql.join(entries.map(([, v]) => import_drizzle_orm9.sql`${v}`), import_drizzle_orm9.sql`, `);
|
|
4241
4400
|
const result = await db.execute(
|
|
4242
|
-
|
|
4401
|
+
import_drizzle_orm9.sql`INSERT INTO ${tableRaw()} (${colsSql}) VALUES (${valsSql}) RETURNING *`
|
|
4243
4402
|
);
|
|
4244
4403
|
return result.rows[0];
|
|
4245
4404
|
},
|
|
@@ -4252,25 +4411,25 @@ function createDrizzleContentRepository(db, tableName, options = {}) {
|
|
|
4252
4411
|
if (entries.length === 0) {
|
|
4253
4412
|
return this.findOne(id);
|
|
4254
4413
|
}
|
|
4255
|
-
const setClauses =
|
|
4256
|
-
entries.map(([k, v]) =>
|
|
4257
|
-
|
|
4414
|
+
const setClauses = import_drizzle_orm9.sql.join(
|
|
4415
|
+
entries.map(([k, v]) => import_drizzle_orm9.sql`${import_drizzle_orm9.sql.raw(quoteIdent4(k))} = ${v}`),
|
|
4416
|
+
import_drizzle_orm9.sql`, `
|
|
4258
4417
|
);
|
|
4259
4418
|
const result = await db.execute(
|
|
4260
|
-
|
|
4419
|
+
import_drizzle_orm9.sql`UPDATE ${tableRaw()} SET ${setClauses} WHERE id = ${id} RETURNING *`
|
|
4261
4420
|
);
|
|
4262
4421
|
if (result.rows.length === 0) return null;
|
|
4263
4422
|
return result.rows[0];
|
|
4264
4423
|
},
|
|
4265
4424
|
async delete(id) {
|
|
4266
|
-
await db.execute(
|
|
4425
|
+
await db.execute(import_drizzle_orm9.sql`DELETE FROM ${tableRaw()} WHERE id = ${id}`);
|
|
4267
4426
|
},
|
|
4268
4427
|
async findAll(opts = {}) {
|
|
4269
4428
|
const { include = [], published_only } = opts;
|
|
4270
4429
|
const conds = buildConditions(published_only, void 0, void 0);
|
|
4271
4430
|
const where = whereFragment(conds);
|
|
4272
4431
|
const result = await db.execute(
|
|
4273
|
-
|
|
4432
|
+
import_drizzle_orm9.sql`SELECT * FROM ${tableRaw()}${where} ORDER BY created_at ASC`
|
|
4274
4433
|
);
|
|
4275
4434
|
const rows = result.rows;
|
|
4276
4435
|
const cache = /* @__PURE__ */ new Map();
|
|
@@ -4285,23 +4444,23 @@ init_relations();
|
|
|
4285
4444
|
|
|
4286
4445
|
// src/repositories/media.ts
|
|
4287
4446
|
var import_node_crypto4 = __toESM(require("crypto"));
|
|
4288
|
-
var
|
|
4447
|
+
var import_drizzle_orm10 = require("drizzle-orm");
|
|
4289
4448
|
function createMediaRepository(db) {
|
|
4290
|
-
const TABLE =
|
|
4449
|
+
const TABLE = import_drizzle_orm10.sql.raw('"media"');
|
|
4291
4450
|
return {
|
|
4292
4451
|
async findMany(opts = {}) {
|
|
4293
4452
|
const { page = 1, per_page = 10, type, orphaned } = opts;
|
|
4294
4453
|
const offset = (page - 1) * per_page;
|
|
4295
4454
|
const conds = [];
|
|
4296
|
-
if (type) conds.push(
|
|
4297
|
-
if (orphaned) conds.push(
|
|
4298
|
-
const where = conds.length > 0 ?
|
|
4299
|
-
const countResult = await db.execute(
|
|
4455
|
+
if (type) conds.push(import_drizzle_orm10.sql`type = ${type}`);
|
|
4456
|
+
if (orphaned) conds.push(import_drizzle_orm10.sql`reference_count = 0`);
|
|
4457
|
+
const where = conds.length > 0 ? import_drizzle_orm10.sql` WHERE ${import_drizzle_orm10.sql.join(conds, import_drizzle_orm10.sql` AND `)}` : import_drizzle_orm10.sql``;
|
|
4458
|
+
const countResult = await db.execute(import_drizzle_orm10.sql`SELECT COUNT(*) AS total FROM ${TABLE}${where}`);
|
|
4300
4459
|
const total = Number(
|
|
4301
4460
|
countResult.rows[0]?.["total"] ?? 0
|
|
4302
4461
|
);
|
|
4303
4462
|
const dataResult = await db.execute(
|
|
4304
|
-
|
|
4463
|
+
import_drizzle_orm10.sql`SELECT * FROM ${TABLE}${where} ORDER BY created_at DESC LIMIT ${per_page} OFFSET ${offset}`
|
|
4305
4464
|
);
|
|
4306
4465
|
const total_pages = Math.ceil(total / per_page);
|
|
4307
4466
|
return {
|
|
@@ -4319,7 +4478,7 @@ function createMediaRepository(db) {
|
|
|
4319
4478
|
},
|
|
4320
4479
|
async findOne(id) {
|
|
4321
4480
|
const result = await db.execute(
|
|
4322
|
-
|
|
4481
|
+
import_drizzle_orm10.sql`SELECT * FROM ${TABLE} WHERE id = ${id} LIMIT 1`
|
|
4323
4482
|
);
|
|
4324
4483
|
if (result.rows.length === 0) return null;
|
|
4325
4484
|
return result.rows[0];
|
|
@@ -4333,10 +4492,10 @@ function createMediaRepository(db) {
|
|
|
4333
4492
|
updated_at: /* @__PURE__ */ new Date()
|
|
4334
4493
|
};
|
|
4335
4494
|
const entries = Object.entries(record).filter(([, v]) => v !== void 0);
|
|
4336
|
-
const colsSql =
|
|
4337
|
-
const valsSql =
|
|
4495
|
+
const colsSql = import_drizzle_orm10.sql.join(entries.map(([k]) => import_drizzle_orm10.sql.raw(`"${k}"`)), import_drizzle_orm10.sql`, `);
|
|
4496
|
+
const valsSql = import_drizzle_orm10.sql.join(entries.map(([, v]) => import_drizzle_orm10.sql`${v}`), import_drizzle_orm10.sql`, `);
|
|
4338
4497
|
const result = await db.execute(
|
|
4339
|
-
|
|
4498
|
+
import_drizzle_orm10.sql`INSERT INTO ${TABLE} (${colsSql}) VALUES (${valsSql}) RETURNING *`
|
|
4340
4499
|
);
|
|
4341
4500
|
return result.rows[0];
|
|
4342
4501
|
},
|
|
@@ -4346,31 +4505,31 @@ function createMediaRepository(db) {
|
|
|
4346
4505
|
([k, v]) => k !== "id" && v !== void 0
|
|
4347
4506
|
);
|
|
4348
4507
|
if (entries.length === 0) return this.findOne(id);
|
|
4349
|
-
const setClauses =
|
|
4350
|
-
entries.map(([k, v]) =>
|
|
4351
|
-
|
|
4508
|
+
const setClauses = import_drizzle_orm10.sql.join(
|
|
4509
|
+
entries.map(([k, v]) => import_drizzle_orm10.sql`${import_drizzle_orm10.sql.raw(`"${k}"`)} = ${v}`),
|
|
4510
|
+
import_drizzle_orm10.sql`, `
|
|
4352
4511
|
);
|
|
4353
4512
|
const result = await db.execute(
|
|
4354
|
-
|
|
4513
|
+
import_drizzle_orm10.sql`UPDATE ${TABLE} SET ${setClauses} WHERE id = ${id} RETURNING *`
|
|
4355
4514
|
);
|
|
4356
4515
|
if (result.rows.length === 0) return null;
|
|
4357
4516
|
return result.rows[0];
|
|
4358
4517
|
},
|
|
4359
4518
|
async delete(id) {
|
|
4360
|
-
await db.execute(
|
|
4519
|
+
await db.execute(import_drizzle_orm10.sql`DELETE FROM ${TABLE} WHERE id = ${id}`);
|
|
4361
4520
|
},
|
|
4362
4521
|
async incrementReferenceCount(ids) {
|
|
4363
4522
|
if (ids.length === 0) return;
|
|
4364
|
-
const inList =
|
|
4523
|
+
const inList = import_drizzle_orm10.sql.join(ids.map((id) => import_drizzle_orm10.sql`${id}`), import_drizzle_orm10.sql`, `);
|
|
4365
4524
|
await db.execute(
|
|
4366
|
-
|
|
4525
|
+
import_drizzle_orm10.sql`UPDATE ${TABLE} SET reference_count = reference_count + 1, updated_at = NOW() WHERE id IN (${inList})`
|
|
4367
4526
|
);
|
|
4368
4527
|
},
|
|
4369
4528
|
async decrementReferenceCount(ids) {
|
|
4370
4529
|
if (ids.length === 0) return;
|
|
4371
|
-
const inList =
|
|
4530
|
+
const inList = import_drizzle_orm10.sql.join(ids.map((id) => import_drizzle_orm10.sql`${id}`), import_drizzle_orm10.sql`, `);
|
|
4372
4531
|
await db.execute(
|
|
4373
|
-
|
|
4532
|
+
import_drizzle_orm10.sql`UPDATE ${TABLE} SET reference_count = GREATEST(reference_count - 1, 0), updated_at = NOW() WHERE id IN (${inList})`
|
|
4374
4533
|
);
|
|
4375
4534
|
}
|
|
4376
4535
|
};
|
|
@@ -4507,7 +4666,7 @@ function createCmsApp(options) {
|
|
|
4507
4666
|
const requirePermission = createPermissionMiddleware(rolesRegistry);
|
|
4508
4667
|
const getUserRole = async (userId) => {
|
|
4509
4668
|
const result = await db.execute(
|
|
4510
|
-
|
|
4669
|
+
import_drizzle_orm11.sql`SELECT r.name AS role
|
|
4511
4670
|
FROM users u
|
|
4512
4671
|
JOIN roles r ON r.id = u.role_id
|
|
4513
4672
|
WHERE u.id = ${userId}
|