@bobbykim/manguito-cms-api 0.6.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-JVP33FI2.mjs → chunk-IHEIX6P4.mjs} +67 -48
- package/dist/chunk-IHEIX6P4.mjs.map +1 -0
- package/dist/{chunk-XNXM4W4K.mjs → chunk-Y5SWT7PO.mjs} +24 -26
- 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.d.mts +1 -0
- package/dist/graphql/index.d.ts +1 -0
- package/dist/graphql/index.js +40 -45
- 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 +454 -265
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +323 -150
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
- package/dist/chunk-JVP33FI2.mjs.map +0 -1
- package/dist/chunk-XNXM4W4K.mjs.map +0 -1
- package/dist/handler-XZVDLKZ2.mjs +0 -8
- /package/dist/{handler-XZVDLKZ2.mjs.map → handler-VEOWJRAL.mjs.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -38,7 +38,7 @@ function isColumnBacked(field) {
|
|
|
38
38
|
if (col.junction) return false;
|
|
39
39
|
return true;
|
|
40
40
|
}
|
|
41
|
-
function buildFieldKeyMap(pairs, allLabels,
|
|
41
|
+
function buildFieldKeyMap(pairs, allLabels, droppedColumns, droppedLabels) {
|
|
42
42
|
const labelToColumn = /* @__PURE__ */ new Map();
|
|
43
43
|
const columnToLabel = /* @__PURE__ */ new Map();
|
|
44
44
|
for (const { label, column } of pairs) {
|
|
@@ -46,7 +46,7 @@ function buildFieldKeyMap(pairs, allLabels, droppedKeys) {
|
|
|
46
46
|
columnToLabel.set(column, label);
|
|
47
47
|
}
|
|
48
48
|
for (const { label, ownColumn } of allLabels) {
|
|
49
|
-
if (ownColumn
|
|
49
|
+
if (ownColumn !== void 0) continue;
|
|
50
50
|
const columnOwner = columnToLabel.get(label);
|
|
51
51
|
if (columnOwner !== void 0 && columnOwner !== label) {
|
|
52
52
|
throw new Error(
|
|
@@ -55,23 +55,33 @@ function buildFieldKeyMap(pairs, allLabels, droppedKeys) {
|
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
for (const label of labelToColumn.keys()) {
|
|
58
|
-
if (
|
|
58
|
+
if (droppedLabels.has(label)) labelToColumn.delete(label);
|
|
59
59
|
}
|
|
60
60
|
for (const column of columnToLabel.keys()) {
|
|
61
|
-
if (
|
|
61
|
+
if (droppedColumns.has(column)) columnToLabel.delete(column);
|
|
62
62
|
}
|
|
63
63
|
const diverges = [...labelToColumn].some(([label, column]) => label !== column);
|
|
64
|
-
function remap(input, lookup) {
|
|
64
|
+
function remap(input, lookup, dropped, droppedIfUnmapped) {
|
|
65
65
|
const out = {};
|
|
66
66
|
for (const key of Object.keys(input)) {
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
const mapped = lookup.get(key);
|
|
68
|
+
if (mapped !== void 0) {
|
|
69
|
+
out[mapped] = input[key];
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (dropped.has(key) || droppedIfUnmapped?.has(key)) continue;
|
|
73
|
+
out[key] = input[key];
|
|
69
74
|
}
|
|
70
75
|
return out;
|
|
71
76
|
}
|
|
72
77
|
return {
|
|
73
|
-
toStorage
|
|
74
|
-
|
|
78
|
+
// toStorage reads LABEL-keyed input: a live label maps and writes
|
|
79
|
+
// regardless of either drop set; an unmapped key is refused if it is
|
|
80
|
+
// EITHER the tombstone's own name (droppedLabels) or a raw, unexposed
|
|
81
|
+
// column (droppedColumns) — see remap's comment.
|
|
82
|
+
// toLabels reads COLUMN-keyed input, so it consults only droppedColumns.
|
|
83
|
+
toStorage: (input) => remap(input, labelToColumn, droppedLabels, droppedColumns),
|
|
84
|
+
toLabels: (row) => remap(row, columnToLabel, droppedColumns),
|
|
75
85
|
columnFor: (label) => labelToColumn.get(label),
|
|
76
86
|
labelFor: (column) => columnToLabel.get(column),
|
|
77
87
|
labels: [...labelToColumn.keys()],
|
|
@@ -79,14 +89,15 @@ function buildFieldKeyMap(pairs, allLabels, droppedKeys) {
|
|
|
79
89
|
};
|
|
80
90
|
}
|
|
81
91
|
function createFieldKeyMap(fields) {
|
|
82
|
-
const
|
|
92
|
+
const droppedColumns = /* @__PURE__ */ new Set();
|
|
93
|
+
const droppedLabels = /* @__PURE__ */ new Set();
|
|
83
94
|
const pairs = [];
|
|
84
95
|
for (const f of fields) {
|
|
85
96
|
if (!isColumnBacked(f)) continue;
|
|
86
97
|
pairs.push({ label: f.name, column: f.db_column.column_name });
|
|
87
98
|
if (f.removed === true) {
|
|
88
|
-
|
|
89
|
-
|
|
99
|
+
droppedLabels.add(f.name);
|
|
100
|
+
droppedColumns.add(f.db_column.column_name);
|
|
90
101
|
}
|
|
91
102
|
}
|
|
92
103
|
return buildFieldKeyMap(
|
|
@@ -97,30 +108,33 @@ function createFieldKeyMap(fields) {
|
|
|
97
108
|
// forbids present-and-undefined.
|
|
98
109
|
...isColumnBacked(f) && { ownColumn: f.db_column.column_name }
|
|
99
110
|
})),
|
|
100
|
-
|
|
111
|
+
droppedColumns,
|
|
112
|
+
droppedLabels
|
|
101
113
|
);
|
|
102
114
|
}
|
|
103
115
|
function createFieldKeyMapFromProjection(projectionType, allFields) {
|
|
104
116
|
const pairs = projectionType.fields.map((f) => ({ label: f.exposed_as, column: f.column_name }));
|
|
105
|
-
const
|
|
106
|
-
const
|
|
117
|
+
const projectedColumns = new Set(pairs.map((p) => p.column));
|
|
118
|
+
const projectedLabels = new Set(pairs.map((p) => p.label));
|
|
119
|
+
const droppedColumns = /* @__PURE__ */ new Set();
|
|
120
|
+
const droppedLabels = /* @__PURE__ */ new Set();
|
|
107
121
|
for (const f of allFields) {
|
|
108
122
|
if (!isColumnBacked(f)) continue;
|
|
109
|
-
|
|
110
|
-
|
|
123
|
+
if (!projectedColumns.has(f.db_column.column_name)) droppedColumns.add(f.db_column.column_name);
|
|
124
|
+
if (!projectedLabels.has(f.name)) droppedLabels.add(f.name);
|
|
111
125
|
}
|
|
112
|
-
for (const p of pairs) dropped.delete(p.column);
|
|
113
|
-
for (const label of projected) dropped.delete(label);
|
|
114
126
|
return buildFieldKeyMap(
|
|
115
127
|
pairs,
|
|
116
128
|
allFields.map((f) => ({
|
|
117
129
|
label: f.name,
|
|
118
|
-
// See buildFieldKeyMap's collision check:
|
|
119
|
-
//
|
|
120
|
-
// label
|
|
130
|
+
// See buildFieldKeyMap's collision check: `ownColumn` marks every
|
|
131
|
+
// column-backed field so the check skips it entirely — only a
|
|
132
|
+
// paragraph/junction/programmatic label (no column of its own) can
|
|
133
|
+
// still collide.
|
|
121
134
|
...isColumnBacked(f) && { ownColumn: f.db_column.column_name }
|
|
122
135
|
})),
|
|
123
|
-
|
|
136
|
+
droppedColumns,
|
|
137
|
+
droppedLabels
|
|
124
138
|
);
|
|
125
139
|
}
|
|
126
140
|
var init_field_keys = __esm({
|
|
@@ -130,7 +144,7 @@ var init_field_keys = __esm({
|
|
|
130
144
|
});
|
|
131
145
|
|
|
132
146
|
// src/relations.ts
|
|
133
|
-
function
|
|
147
|
+
function quoteIdent2(name) {
|
|
134
148
|
if (!/^[a-z][a-z0-9_-]*$/.test(name)) throw new Error(`Unsafe identifier: ${name}`);
|
|
135
149
|
return `"${name}"`;
|
|
136
150
|
}
|
|
@@ -142,10 +156,10 @@ function paragraphMediaFieldNames(pType) {
|
|
|
142
156
|
async function fetchParagraphMediaIds(db, pType, itemId, fieldName) {
|
|
143
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);
|
|
144
158
|
if (mediaColumns.length === 0) return [];
|
|
145
|
-
const tbl =
|
|
146
|
-
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`, `);
|
|
147
161
|
const result = await db.execute(
|
|
148
|
-
|
|
162
|
+
import_drizzle_orm3.sql`SELECT ${cols} FROM ${tbl} WHERE parent_id = ${itemId} AND parent_field = ${fieldName}`
|
|
149
163
|
);
|
|
150
164
|
const ids = [];
|
|
151
165
|
for (const row of result.rows) {
|
|
@@ -164,14 +178,14 @@ function nestedParagraphFields(pType, registry) {
|
|
|
164
178
|
const comp = f.ui_component;
|
|
165
179
|
if (comp.component !== "paragraph-embed" || !comp.ref) continue;
|
|
166
180
|
const nType = registry.paragraph_types[comp.ref];
|
|
167
|
-
if (nType) out.push({ fieldName: f.name, nType });
|
|
181
|
+
if (nType) out.push({ fieldName: f.name, nType, field: f });
|
|
168
182
|
}
|
|
169
183
|
return out;
|
|
170
184
|
}
|
|
171
185
|
async function fetchParagraphRowIds(db, pType, itemId, fieldName) {
|
|
172
|
-
const tbl =
|
|
186
|
+
const tbl = import_drizzle_orm3.sql.raw(quoteIdent2(pType.db.table_name));
|
|
173
187
|
const result = await db.execute(
|
|
174
|
-
|
|
188
|
+
import_drizzle_orm3.sql`SELECT id FROM ${tbl} WHERE parent_id = ${itemId} AND parent_field = ${fieldName}`
|
|
175
189
|
);
|
|
176
190
|
return result.rows.map((r) => r["id"]).filter((id) => typeof id === "string");
|
|
177
191
|
}
|
|
@@ -187,8 +201,8 @@ async function persistParagraphField(db, itemId, parentType, fieldName, pType, i
|
|
|
187
201
|
}
|
|
188
202
|
}
|
|
189
203
|
}
|
|
190
|
-
const tbl =
|
|
191
|
-
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}`);
|
|
192
206
|
const mediaFieldNames = paragraphMediaFieldNames(pType);
|
|
193
207
|
const added = [];
|
|
194
208
|
for (let i = 0; i < items.length; i++) {
|
|
@@ -207,15 +221,15 @@ async function persistParagraphField(db, itemId, parentType, fieldName, pType, i
|
|
|
207
221
|
if (!pf.db_column || pf.db_column.junction) continue;
|
|
208
222
|
data[pf.db_column.column_name] = pItem[pf.name] ?? null;
|
|
209
223
|
}
|
|
210
|
-
const cols =
|
|
211
|
-
const vals =
|
|
212
|
-
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})`);
|
|
213
227
|
for (const name of mediaFieldNames) {
|
|
214
228
|
const val = pItem[name];
|
|
215
229
|
if (typeof val === "string" && val !== "") added.push(val);
|
|
216
230
|
}
|
|
217
231
|
for (const n of nested) {
|
|
218
|
-
const nestedItems =
|
|
232
|
+
const nestedItems = paragraphItems(n.field, pItem[n.fieldName]);
|
|
219
233
|
const d = await persistParagraphField(
|
|
220
234
|
db,
|
|
221
235
|
rowId,
|
|
@@ -243,19 +257,28 @@ async function deleteParagraphField(db, itemId, fieldName, pType, registry) {
|
|
|
243
257
|
}
|
|
244
258
|
}
|
|
245
259
|
}
|
|
246
|
-
const tbl =
|
|
247
|
-
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}`);
|
|
248
262
|
return { added: [], removed };
|
|
249
263
|
}
|
|
250
264
|
async function persistJunctionField(db, itemId, junction, relatedIds) {
|
|
251
|
-
const tbl =
|
|
252
|
-
const left =
|
|
253
|
-
const right =
|
|
254
|
-
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}`);
|
|
255
269
|
for (const rid of relatedIds) {
|
|
256
|
-
await db.execute(
|
|
270
|
+
await db.execute(import_drizzle_orm3.sql`INSERT INTO ${tbl} (${left}, ${right}) VALUES (${itemId}, ${rid})`);
|
|
257
271
|
}
|
|
258
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
|
+
}
|
|
259
282
|
function buildRelationsMap(fields, registry) {
|
|
260
283
|
const relations = {};
|
|
261
284
|
for (const field of fields) {
|
|
@@ -266,7 +289,11 @@ function buildRelationsMap(fields, registry) {
|
|
|
266
289
|
const ref = field.ui_component.ref;
|
|
267
290
|
const pType = ref ? registry.paragraph_types[ref] : void 0;
|
|
268
291
|
if (!pType) continue;
|
|
269
|
-
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
|
+
};
|
|
270
297
|
} else if (field.field_type === "reference") {
|
|
271
298
|
if (!field.db_column) continue;
|
|
272
299
|
if (field.db_column.junction) {
|
|
@@ -299,9 +326,8 @@ function groupBy(items, key) {
|
|
|
299
326
|
}
|
|
300
327
|
return result;
|
|
301
328
|
}
|
|
302
|
-
function needsFkResolution(row, fkColumn
|
|
303
|
-
|
|
304
|
-
const current = row[fieldName];
|
|
329
|
+
function needsFkResolution(row, fkColumn) {
|
|
330
|
+
const current = row[fkColumn];
|
|
305
331
|
return !(typeof current === "object" && current !== null);
|
|
306
332
|
}
|
|
307
333
|
async function resolveRelationField(db, rows, fieldName, rel, cache, publishedOnly = false) {
|
|
@@ -309,33 +335,29 @@ async function resolveRelationField(db, rows, fieldName, rel, cache, publishedOn
|
|
|
309
335
|
const batch = [...new Set(rows)];
|
|
310
336
|
if (rel.type === "paragraph") {
|
|
311
337
|
const parentIds = batch.map((r) => r["id"]);
|
|
312
|
-
const inList =
|
|
338
|
+
const inList = import_drizzle_orm3.sql.join(parentIds.map((id) => import_drizzle_orm3.sql`${id}`), import_drizzle_orm3.sql`, `);
|
|
313
339
|
const result = await db.execute(
|
|
314
|
-
|
|
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`
|
|
315
341
|
);
|
|
316
342
|
const byParent = groupBy(result.rows, "parent_id");
|
|
317
343
|
for (const row of batch) {
|
|
318
|
-
row[fieldName] = byParent[row["id"]] ?? [];
|
|
344
|
+
row[fieldName] = oneOrMany(rel.cardinality, byParent[row["id"]] ?? []);
|
|
319
345
|
}
|
|
320
346
|
} else if (rel.type === "reference") {
|
|
321
|
-
const
|
|
322
|
-
const pending = batch.filter((r) => needsFkResolution(r, rel.fk_column, fieldName));
|
|
347
|
+
const pending = batch.filter((r) => needsFkResolution(r, rel.fk_column));
|
|
323
348
|
if (pending.length === 0) return;
|
|
324
349
|
const fkValues = pending.map((r) => r[rel.fk_column]).filter(Boolean);
|
|
325
350
|
if (fkValues.length === 0) {
|
|
326
|
-
for (const row of pending)
|
|
327
|
-
row[fieldName] = null;
|
|
328
|
-
if (dropFk) delete row[rel.fk_column];
|
|
329
|
-
}
|
|
351
|
+
for (const row of pending) row[rel.fk_column] = null;
|
|
330
352
|
return;
|
|
331
353
|
}
|
|
332
354
|
const unique = [...new Set(fkValues)];
|
|
333
355
|
const uncached = unique.filter((id) => !cache.has(`${rel.table}:${id}`));
|
|
334
356
|
if (uncached.length > 0) {
|
|
335
|
-
const inList =
|
|
336
|
-
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``;
|
|
337
359
|
const result = await db.execute(
|
|
338
|
-
|
|
360
|
+
import_drizzle_orm3.sql`SELECT * FROM ${import_drizzle_orm3.sql.raw(quoteIdent2(rel.table))} WHERE id IN (${inList})${publishedCond}`
|
|
339
361
|
);
|
|
340
362
|
for (const item of result.rows) {
|
|
341
363
|
cache.set(`${rel.table}:${item["id"]}`, item);
|
|
@@ -343,24 +365,23 @@ async function resolveRelationField(db, rows, fieldName, rel, cache, publishedOn
|
|
|
343
365
|
}
|
|
344
366
|
for (const row of pending) {
|
|
345
367
|
const fkVal = row[rel.fk_column];
|
|
346
|
-
row[
|
|
347
|
-
if (dropFk) delete row[rel.fk_column];
|
|
368
|
+
row[rel.fk_column] = fkVal ? cache.get(`${rel.table}:${fkVal}`) ?? null : null;
|
|
348
369
|
}
|
|
349
370
|
} else if (rel.type === "junction") {
|
|
350
371
|
const parentIds = batch.map((r) => r["id"]);
|
|
351
|
-
const inList =
|
|
352
|
-
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``;
|
|
353
374
|
const junctionResult = await db.execute(
|
|
354
|
-
|
|
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}`
|
|
355
376
|
);
|
|
356
377
|
const jRows = junctionResult.rows;
|
|
357
378
|
const rightIds = [...new Set(jRows.map((r) => r[rel.right_column]))];
|
|
358
379
|
const uncached = rightIds.filter((id) => !cache.has(`${rel.table}:${id}`));
|
|
359
380
|
if (uncached.length > 0) {
|
|
360
|
-
const rightInList =
|
|
361
|
-
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``;
|
|
362
383
|
const entitiesResult = await db.execute(
|
|
363
|
-
|
|
384
|
+
import_drizzle_orm3.sql`SELECT * FROM ${import_drizzle_orm3.sql.raw(quoteIdent2(rel.table))} WHERE id IN (${rightInList})${publishedCond}`
|
|
364
385
|
);
|
|
365
386
|
for (const item of entitiesResult.rows) {
|
|
366
387
|
cache.set(`${rel.table}:${item["id"]}`, item);
|
|
@@ -372,30 +393,25 @@ async function resolveRelationField(db, rows, fieldName, rel, cache, publishedOn
|
|
|
372
393
|
row[fieldName] = jEntries.map((jr) => cache.get(`${rel.table}:${jr[rel.right_column]}`)).filter(Boolean);
|
|
373
394
|
}
|
|
374
395
|
} else if (rel.type === "media") {
|
|
375
|
-
const
|
|
376
|
-
const pending = batch.filter((r) => needsFkResolution(r, rel.fk_column, fieldName));
|
|
396
|
+
const pending = batch.filter((r) => needsFkResolution(r, rel.fk_column));
|
|
377
397
|
if (pending.length === 0) return;
|
|
378
398
|
const fkValues = pending.map((r) => r[rel.fk_column]).filter(Boolean);
|
|
379
399
|
if (fkValues.length === 0) {
|
|
380
|
-
for (const row of pending)
|
|
381
|
-
row[fieldName] = null;
|
|
382
|
-
if (dropFk) delete row[rel.fk_column];
|
|
383
|
-
}
|
|
400
|
+
for (const row of pending) row[rel.fk_column] = null;
|
|
384
401
|
return;
|
|
385
402
|
}
|
|
386
403
|
const unique = [...new Set(fkValues)];
|
|
387
404
|
const uncached = unique.filter((id) => !cache.has(`media:${id}`));
|
|
388
405
|
if (uncached.length > 0) {
|
|
389
|
-
const inList =
|
|
390
|
-
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})`);
|
|
391
408
|
for (const item of result.rows) {
|
|
392
409
|
cache.set(`media:${item["id"]}`, item);
|
|
393
410
|
}
|
|
394
411
|
}
|
|
395
412
|
for (const row of pending) {
|
|
396
413
|
const fkVal = row[rel.fk_column];
|
|
397
|
-
row[
|
|
398
|
-
if (dropFk) delete row[rel.fk_column];
|
|
414
|
+
row[rel.fk_column] = fkVal ? cache.get(`media:${fkVal}`) ?? null : null;
|
|
399
415
|
}
|
|
400
416
|
}
|
|
401
417
|
}
|
|
@@ -403,19 +419,19 @@ async function resolveRelationBareIds(db, rows, fieldName, rel) {
|
|
|
403
419
|
if (rows.length === 0) return;
|
|
404
420
|
if (rel.type === "paragraph") {
|
|
405
421
|
const parentIds = rows.map((r) => r["id"]);
|
|
406
|
-
const inList =
|
|
422
|
+
const inList = import_drizzle_orm3.sql.join(parentIds.map((id) => import_drizzle_orm3.sql`${id}`), import_drizzle_orm3.sql`, `);
|
|
407
423
|
const result = await db.execute(
|
|
408
|
-
|
|
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`
|
|
409
425
|
);
|
|
410
426
|
const byParent = groupBy(result.rows, "parent_id");
|
|
411
427
|
for (const row of rows) {
|
|
412
|
-
row[fieldName] = (byParent[row["id"]] ?? []).map((r) => r["id"]);
|
|
428
|
+
row[fieldName] = oneOrMany(rel.cardinality, (byParent[row["id"]] ?? []).map((r) => r["id"]));
|
|
413
429
|
}
|
|
414
430
|
} else if (rel.type === "junction") {
|
|
415
431
|
const parentIds = rows.map((r) => r["id"]);
|
|
416
|
-
const inList =
|
|
432
|
+
const inList = import_drizzle_orm3.sql.join(parentIds.map((id) => import_drizzle_orm3.sql`${id}`), import_drizzle_orm3.sql`, `);
|
|
417
433
|
const result = await db.execute(
|
|
418
|
-
|
|
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})`
|
|
419
435
|
);
|
|
420
436
|
const byLeft = groupBy(result.rows, "left_id");
|
|
421
437
|
for (const row of rows) {
|
|
@@ -423,12 +439,13 @@ async function resolveRelationBareIds(db, rows, fieldName, rel) {
|
|
|
423
439
|
}
|
|
424
440
|
}
|
|
425
441
|
}
|
|
426
|
-
var import_node_crypto,
|
|
442
|
+
var import_node_crypto, import_drizzle_orm3, import_manguito_cms_core2;
|
|
427
443
|
var init_relations = __esm({
|
|
428
444
|
"src/relations.ts"() {
|
|
429
445
|
"use strict";
|
|
430
446
|
import_node_crypto = __toESM(require("crypto"));
|
|
431
|
-
|
|
447
|
+
import_drizzle_orm3 = require("drizzle-orm");
|
|
448
|
+
import_manguito_cms_core2 = require("@bobbykim/manguito-cms-core");
|
|
432
449
|
}
|
|
433
450
|
});
|
|
434
451
|
|
|
@@ -677,29 +694,22 @@ function scalarFieldResolver(field, fallback) {
|
|
|
677
694
|
function relationFieldResolver(typeName, schemaFieldName) {
|
|
678
695
|
return (parent, _args, ctx) => ctx.loaders.load(typeName, schemaFieldName, parent);
|
|
679
696
|
}
|
|
680
|
-
function programmaticFieldResolver(typeName, schemaFieldName,
|
|
697
|
+
function programmaticFieldResolver(typeName, schemaFieldName, mediaFieldNames = [], fieldKeys) {
|
|
681
698
|
return async (parent, _args, ctx) => {
|
|
682
699
|
let p = ctx.programmaticMemo.get(parent);
|
|
683
700
|
if (!p) {
|
|
684
|
-
p = resolveProgrammaticRow(typeName, parent, ctx,
|
|
701
|
+
p = resolveProgrammaticRow(typeName, parent, ctx, mediaFieldNames, fieldKeys);
|
|
685
702
|
ctx.programmaticMemo.set(parent, p);
|
|
686
703
|
}
|
|
687
704
|
return (await p)[schemaFieldName];
|
|
688
705
|
};
|
|
689
706
|
}
|
|
690
|
-
async function resolveProgrammaticRow(typeName, parent, ctx,
|
|
707
|
+
async function resolveProgrammaticRow(typeName, parent, ctx, mediaFieldNames, fieldKeys) {
|
|
691
708
|
const toLabels = (row) => fieldKeys ? fieldKeys.toLabels(row) : row;
|
|
692
|
-
if (
|
|
709
|
+
if (mediaFieldNames.length === 0) return ctx.resolver.resolveItem(typeName, toLabels(parent));
|
|
693
710
|
const enriched = { ...parent };
|
|
694
|
-
await Promise.all(
|
|
695
|
-
return ctx.resolver.resolveItem(typeName,
|
|
696
|
-
}
|
|
697
|
-
function relabelMedia(record, enriched, mediaFields) {
|
|
698
|
-
for (const { name, exposedAs } of mediaFields) {
|
|
699
|
-
if (!(name in enriched)) continue;
|
|
700
|
-
record[exposedAs] = enriched[name];
|
|
701
|
-
}
|
|
702
|
-
return record;
|
|
711
|
+
await Promise.all(mediaFieldNames.map((name) => ctx.loaders.load(typeName, name, enriched)));
|
|
712
|
+
return ctx.resolver.resolveItem(typeName, toLabels(enriched));
|
|
703
713
|
}
|
|
704
714
|
function collectionResolver(typeName, nameMap, fieldKeys) {
|
|
705
715
|
return async (_root, args, ctx) => {
|
|
@@ -757,7 +767,7 @@ var init_resolvers = __esm({
|
|
|
757
767
|
});
|
|
758
768
|
|
|
759
769
|
// src/graphql/schema.ts
|
|
760
|
-
function buildGraphQLSchema(registry, fieldKeyMaps = {}, view) {
|
|
770
|
+
function buildGraphQLSchema(registry, fieldKeyMaps = {}, view, programmaticKeyMaps = fieldKeyMaps) {
|
|
761
771
|
const objectTypes = /* @__PURE__ */ new Map();
|
|
762
772
|
const enumTypes = /* @__PURE__ */ new Map();
|
|
763
773
|
for (const [name, enumType] of Object.entries(registry.enum_types)) {
|
|
@@ -797,7 +807,8 @@ function buildGraphQLSchema(registry, fieldKeyMaps = {}, view) {
|
|
|
797
807
|
`
|
|
798
808
|
);
|
|
799
809
|
}
|
|
800
|
-
|
|
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));
|
|
801
812
|
}
|
|
802
813
|
if (field.field_type === "reference") {
|
|
803
814
|
const ref = field.ui_component.component === "typeahead-select" ? field.ui_component.ref : void 0;
|
|
@@ -806,8 +817,7 @@ function buildGraphQLSchema(registry, fieldKeyMaps = {}, view) {
|
|
|
806
817
|
process.stderr.write(`\u26A0 reference field '${field.name}' targets unknown type '${ref}'; exposing as Media
|
|
807
818
|
`);
|
|
808
819
|
}
|
|
809
|
-
const
|
|
810
|
-
const isMany = rel === "many-to-many" || rel === "one-to-many";
|
|
820
|
+
const isMany = (0, import_manguito_cms_core6.relationCardinality)(field) === "many";
|
|
811
821
|
const t = target ?? MEDIA;
|
|
812
822
|
return isMany ? new import_graphql5.GraphQLList(new import_graphql5.GraphQLNonNull(t)) : t;
|
|
813
823
|
}
|
|
@@ -821,9 +831,9 @@ function buildGraphQLSchema(registry, fieldKeyMaps = {}, view) {
|
|
|
821
831
|
}
|
|
822
832
|
function buildObjectType(machineName, type) {
|
|
823
833
|
const visible = viewFieldsFor(machineName, type);
|
|
824
|
-
const
|
|
825
|
-
(
|
|
826
|
-
).map((
|
|
834
|
+
const mediaFieldNames = type.fields.filter(
|
|
835
|
+
(f) => f.removed !== true && (f.field_type === "image" || f.field_type === "video" || f.field_type === "file")
|
|
836
|
+
).map((f) => f.name);
|
|
827
837
|
return new import_graphql5.GraphQLObjectType({
|
|
828
838
|
name: graphqlTypeName(machineName),
|
|
829
839
|
fields: () => {
|
|
@@ -843,7 +853,7 @@ function buildGraphQLSchema(registry, fieldKeyMaps = {}, view) {
|
|
|
843
853
|
const outType = outputTypeForField(field, vf.required);
|
|
844
854
|
let resolve;
|
|
845
855
|
if (field.field_type === "programmatic") {
|
|
846
|
-
resolve = programmaticFieldResolver(machineName, field.name,
|
|
856
|
+
resolve = programmaticFieldResolver(machineName, field.name, mediaFieldNames, programmaticKeyMaps[machineName]);
|
|
847
857
|
} else if (field.field_type === "reference" || field.field_type === "paragraph" || field.field_type === "image" || field.field_type === "video" || field.field_type === "file") {
|
|
848
858
|
resolve = relationFieldResolver(machineName, field.name);
|
|
849
859
|
} else {
|
|
@@ -888,7 +898,10 @@ function buildGraphQLSchema(registry, fieldKeyMaps = {}, view) {
|
|
|
888
898
|
for (const [name, ct] of Object.entries(registry.content_types)) {
|
|
889
899
|
const objType = objectTypes.get(name);
|
|
890
900
|
const visible = viewFieldsFor(name, ct);
|
|
891
|
-
const nameMap = buildFieldNameMap(
|
|
901
|
+
const nameMap = buildFieldNameMap([
|
|
902
|
+
...visible.map((v) => v.exposedAs),
|
|
903
|
+
...ct.system_fields.map((s) => s.name)
|
|
904
|
+
]);
|
|
892
905
|
if (ct.only_one) {
|
|
893
906
|
queryFields[singleQueryName(name)] = { type: objType, resolve: singletonResolver(name) };
|
|
894
907
|
continue;
|
|
@@ -946,11 +959,12 @@ function buildGraphQLSchema(registry, fieldKeyMaps = {}, view) {
|
|
|
946
959
|
query: new import_graphql5.GraphQLObjectType({ name: "Query", fields: queryFields })
|
|
947
960
|
});
|
|
948
961
|
}
|
|
949
|
-
var import_graphql5, PAGE_META, MEDIA;
|
|
962
|
+
var import_graphql5, import_manguito_cms_core6, PAGE_META, MEDIA;
|
|
950
963
|
var init_schema = __esm({
|
|
951
964
|
"src/graphql/schema.ts"() {
|
|
952
965
|
"use strict";
|
|
953
966
|
import_graphql5 = require("graphql");
|
|
967
|
+
import_manguito_cms_core6 = require("@bobbykim/manguito-cms-core");
|
|
954
968
|
init_scalars();
|
|
955
969
|
init_type_mapping();
|
|
956
970
|
init_naming();
|
|
@@ -1076,7 +1090,8 @@ function createRelationLoaders(db, registry) {
|
|
|
1076
1090
|
async (parents) => {
|
|
1077
1091
|
const rows = parents;
|
|
1078
1092
|
await resolveRelationField(db, rows, fieldName, rel, cache, true);
|
|
1079
|
-
|
|
1093
|
+
const resultKey = rel.type === "reference" || rel.type === "media" ? rel.fk_column : fieldName;
|
|
1094
|
+
return rows.map((r) => r[resultKey]);
|
|
1080
1095
|
},
|
|
1081
1096
|
// Batch within a tick; do not memoize by parent identity (rows are mutated
|
|
1082
1097
|
// and may recur across nesting levels).
|
|
@@ -1161,7 +1176,7 @@ function createGraphQLHandler(registry, repos, fieldKeyMaps, resolver, db, optio
|
|
|
1161
1176
|
currentVersion: versioning.currentVersion
|
|
1162
1177
|
});
|
|
1163
1178
|
}
|
|
1164
|
-
const schema = buildGraphQLSchema(registry, fieldKeyMaps, view);
|
|
1179
|
+
const schema = buildGraphQLSchema(registry, fieldKeyMaps, view, versioning.currentFieldKeyMaps ?? fieldKeyMaps);
|
|
1165
1180
|
const { plugins } = buildArmorPlugin({
|
|
1166
1181
|
maxDepth: options.maxDepth,
|
|
1167
1182
|
maxComplexity: options.maxComplexity
|
|
@@ -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
|
|
@@ -1817,18 +1836,21 @@ function createProgrammaticResolver(resolvers, options) {
|
|
|
1817
1836
|
}
|
|
1818
1837
|
|
|
1819
1838
|
// src/projector.ts
|
|
1839
|
+
init_field_keys();
|
|
1820
1840
|
function isPlainRow(v) {
|
|
1821
1841
|
return typeof v === "object" && v !== null && !Array.isArray(v) && !(v instanceof Date);
|
|
1822
1842
|
}
|
|
1823
1843
|
var MEDIA_FIELD_TYPES = /* @__PURE__ */ new Set(["image", "video", "file"]);
|
|
1824
|
-
function nestedTargets(fields) {
|
|
1844
|
+
function nestedTargets(fields, map) {
|
|
1825
1845
|
const out = [];
|
|
1826
1846
|
for (const f of fields) {
|
|
1827
1847
|
if (MEDIA_FIELD_TYPES.has(f.field_type)) continue;
|
|
1828
1848
|
if (f.field_type !== "paragraph" && f.field_type !== "reference") continue;
|
|
1829
1849
|
const ref = f.ui_component.ref;
|
|
1830
1850
|
if (!ref) continue;
|
|
1831
|
-
|
|
1851
|
+
const label = isColumnBacked(f) ? map.labelFor(f.db_column.column_name) : f.name;
|
|
1852
|
+
if (label === void 0) continue;
|
|
1853
|
+
out.push({ label, target: ref });
|
|
1832
1854
|
}
|
|
1833
1855
|
return out;
|
|
1834
1856
|
}
|
|
@@ -1846,7 +1868,7 @@ function buildProjectors(registry, fieldKeyMaps, fallbacks) {
|
|
|
1846
1868
|
const typeFallbacks = fallbacks?.[typeName];
|
|
1847
1869
|
projectors[typeName] = {
|
|
1848
1870
|
map,
|
|
1849
|
-
nested: nestedTargets(type.fields),
|
|
1871
|
+
nested: nestedTargets(type.fields, map),
|
|
1850
1872
|
// Spread rather than assign: with exactOptionalPropertyTypes, an optional
|
|
1851
1873
|
// property may be ABSENT but not present-and-undefined.
|
|
1852
1874
|
...typeFallbacks !== void 0 && { fallbacks: typeFallbacks }
|
|
@@ -1933,7 +1955,28 @@ function parseFilters(url, validFields, columnFor) {
|
|
|
1933
1955
|
function isPublished(item) {
|
|
1934
1956
|
return item["published"] === true;
|
|
1935
1957
|
}
|
|
1936
|
-
function registerPublicContentRoutes(app, registry, repos, projectors, paths, listRateLimit, resolver) {
|
|
1958
|
+
function registerPublicContentRoutes(app, registry, repos, projectors, paths, listRateLimit, resolver, resolverProjectors) {
|
|
1959
|
+
const forResolver = resolverProjectors ?? projectors;
|
|
1960
|
+
function pickProgrammatic(resolved, names) {
|
|
1961
|
+
const out = {};
|
|
1962
|
+
for (const name of names) if (name in resolved) out[name] = resolved[name];
|
|
1963
|
+
return out;
|
|
1964
|
+
}
|
|
1965
|
+
async function respondItem(row, typeName, programmatic) {
|
|
1966
|
+
const data = projectRow(row, typeName, projectors);
|
|
1967
|
+
if (!resolver?.hasSchema(typeName)) return data;
|
|
1968
|
+
const resolved = await resolver.resolveItem(typeName, projectRow(row, typeName, forResolver));
|
|
1969
|
+
return { ...data, ...pickProgrammatic(resolved, programmatic) };
|
|
1970
|
+
}
|
|
1971
|
+
async function respondList(rows, typeName, programmatic) {
|
|
1972
|
+
const data = rows.map((row) => projectRow(row, typeName, projectors));
|
|
1973
|
+
if (!resolver?.hasSchema(typeName)) return data;
|
|
1974
|
+
const resolved = await resolver.resolveList(
|
|
1975
|
+
typeName,
|
|
1976
|
+
rows.map((row) => projectRow(row, typeName, forResolver))
|
|
1977
|
+
);
|
|
1978
|
+
return data.map((d, i) => ({ ...d, ...pickProgrammatic(resolved[i], programmatic) }));
|
|
1979
|
+
}
|
|
1937
1980
|
function registerListRoute(path, handler) {
|
|
1938
1981
|
if (listRateLimit) {
|
|
1939
1982
|
app.get(path, listRateLimit, handler);
|
|
@@ -1957,18 +2000,29 @@ function registerPublicContentRoutes(app, registry, repos, projectors, paths, li
|
|
|
1957
2000
|
return c.json({ ok: true, data });
|
|
1958
2001
|
});
|
|
1959
2002
|
for (const [typeName, contentType] of Object.entries(registry.content_types)) {
|
|
2003
|
+
let translateInclude2 = function(requested) {
|
|
2004
|
+
const include = [];
|
|
2005
|
+
for (const field of requested) {
|
|
2006
|
+
const registryName = includeNames.get(field);
|
|
2007
|
+
if (registryName === void 0) return { ok: false, field };
|
|
2008
|
+
include.push(registryName);
|
|
2009
|
+
}
|
|
2010
|
+
return { ok: true, include };
|
|
2011
|
+
};
|
|
2012
|
+
var translateInclude = translateInclude2;
|
|
1960
2013
|
const basePath = contentType.default_base_path;
|
|
1961
2014
|
const repo = repos[typeName];
|
|
1962
2015
|
if (!repo) continue;
|
|
1963
2016
|
const fieldKeys = projectors[typeName].map;
|
|
1964
|
-
const
|
|
2017
|
+
const programmatic = contentType.fields.filter((f) => f.field_type === "programmatic").map((f) => f.name);
|
|
2018
|
+
const includeNames = /* @__PURE__ */ new Map();
|
|
1965
2019
|
for (const f of contentType.fields) {
|
|
1966
2020
|
if (!RELATION_FIELD_TYPES.has(f.field_type)) continue;
|
|
1967
2021
|
if (isColumnBacked(f)) {
|
|
1968
2022
|
const label = fieldKeys.labelFor(f.db_column.column_name);
|
|
1969
|
-
if (label !== void 0)
|
|
2023
|
+
if (label !== void 0) includeNames.set(label, f.name);
|
|
1970
2024
|
} else {
|
|
1971
|
-
|
|
2025
|
+
includeNames.set(f.name, f.name);
|
|
1972
2026
|
}
|
|
1973
2027
|
}
|
|
1974
2028
|
const filterableFieldNames = /* @__PURE__ */ new Set([
|
|
@@ -1987,8 +2041,11 @@ function registerPublicContentRoutes(app, registry, repos, projectors, paths, li
|
|
|
1987
2041
|
404
|
|
1988
2042
|
);
|
|
1989
2043
|
}
|
|
1990
|
-
|
|
1991
|
-
|
|
2044
|
+
const data = await respondItem(
|
|
2045
|
+
result.data[0],
|
|
2046
|
+
typeName,
|
|
2047
|
+
programmatic
|
|
2048
|
+
);
|
|
1992
2049
|
return c.json({ ok: true, data });
|
|
1993
2050
|
});
|
|
1994
2051
|
} else {
|
|
@@ -2045,21 +2102,20 @@ function registerPublicContentRoutes(app, registry, repos, projectors, paths, li
|
|
|
2045
2102
|
400
|
|
2046
2103
|
);
|
|
2047
2104
|
}
|
|
2048
|
-
const
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
);
|
|
2061
|
-
}
|
|
2105
|
+
const translated = translateInclude2(parseInclude(c.req.query("include")));
|
|
2106
|
+
if (!translated.ok) {
|
|
2107
|
+
return c.json(
|
|
2108
|
+
{
|
|
2109
|
+
ok: false,
|
|
2110
|
+
error: {
|
|
2111
|
+
code: "INVALID_INCLUDE_FIELD",
|
|
2112
|
+
message: `'${translated.field}' is not a valid relation field`
|
|
2113
|
+
}
|
|
2114
|
+
},
|
|
2115
|
+
400
|
|
2116
|
+
);
|
|
2062
2117
|
}
|
|
2118
|
+
const include = translated.include;
|
|
2063
2119
|
const sortColumn = fieldKeys.labels.includes(sortBy) ? fieldKeys.columnFor(sortBy) : sortBy;
|
|
2064
2120
|
const result = await repo.findMany({
|
|
2065
2121
|
published_only: true,
|
|
@@ -2070,29 +2126,25 @@ function registerPublicContentRoutes(app, registry, repos, projectors, paths, li
|
|
|
2070
2126
|
filters: filtersResult.filters,
|
|
2071
2127
|
include
|
|
2072
2128
|
});
|
|
2073
|
-
const
|
|
2074
|
-
(row) => projectRow(row, typeName, projectors)
|
|
2075
|
-
);
|
|
2076
|
-
const data = resolver?.hasSchema(typeName) ? await resolver.resolveList(typeName, labeled) : labeled;
|
|
2129
|
+
const data = await respondList(result.data, typeName, programmatic);
|
|
2077
2130
|
return c.json({ ...result, data });
|
|
2078
2131
|
});
|
|
2079
2132
|
app.get(paths.item(basePath), async (c) => {
|
|
2080
2133
|
const slug = c.req.param("slug");
|
|
2081
|
-
const
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
);
|
|
2094
|
-
}
|
|
2134
|
+
const translated = translateInclude2(parseInclude(c.req.query("include")));
|
|
2135
|
+
if (!translated.ok) {
|
|
2136
|
+
return c.json(
|
|
2137
|
+
{
|
|
2138
|
+
ok: false,
|
|
2139
|
+
error: {
|
|
2140
|
+
code: "INVALID_INCLUDE_FIELD",
|
|
2141
|
+
message: `'${translated.field}' is not a valid relation field`
|
|
2142
|
+
}
|
|
2143
|
+
},
|
|
2144
|
+
400
|
|
2145
|
+
);
|
|
2095
2146
|
}
|
|
2147
|
+
const include = translated.include;
|
|
2096
2148
|
const item = await repo.findBySlug(slug, include);
|
|
2097
2149
|
if (!item || !isPublished(item)) {
|
|
2098
2150
|
return c.json(
|
|
@@ -2103,15 +2155,15 @@ function registerPublicContentRoutes(app, registry, repos, projectors, paths, li
|
|
|
2103
2155
|
404
|
|
2104
2156
|
);
|
|
2105
2157
|
}
|
|
2106
|
-
|
|
2107
|
-
if (resolver?.hasSchema(typeName)) data = await resolver.resolveItem(typeName, data);
|
|
2158
|
+
const data = await respondItem(item, typeName, programmatic);
|
|
2108
2159
|
return c.json({ ok: true, data });
|
|
2109
2160
|
});
|
|
2110
2161
|
}
|
|
2111
2162
|
}
|
|
2112
|
-
for (const [typeName] of Object.entries(registry.taxonomy_types)) {
|
|
2163
|
+
for (const [typeName, taxonomyType] of Object.entries(registry.taxonomy_types)) {
|
|
2113
2164
|
const repo = repos[typeName];
|
|
2114
2165
|
if (!repo) continue;
|
|
2166
|
+
const programmatic = taxonomyType.fields.filter((f) => f.field_type === "programmatic").map((f) => f.name);
|
|
2115
2167
|
registerListRoute(paths.taxonomyCollection(typeName), async (c) => {
|
|
2116
2168
|
const pagination = parsePagination(c.req.query("page"), c.req.query("per_page"));
|
|
2117
2169
|
if (!pagination.ok) {
|
|
@@ -2131,10 +2183,7 @@ function registerPublicContentRoutes(app, registry, repos, projectors, paths, li
|
|
|
2131
2183
|
page: pagination.page,
|
|
2132
2184
|
per_page: pagination.per_page
|
|
2133
2185
|
});
|
|
2134
|
-
const
|
|
2135
|
-
(row) => projectRow(row, typeName, projectors)
|
|
2136
|
-
);
|
|
2137
|
-
const data = resolver?.hasSchema(typeName) ? await resolver.resolveList(typeName, labeled) : labeled;
|
|
2186
|
+
const data = await respondList(result.data, typeName, programmatic);
|
|
2138
2187
|
return c.json({ ...result, data });
|
|
2139
2188
|
});
|
|
2140
2189
|
app.get(paths.taxonomyItem(typeName), async (c) => {
|
|
@@ -2149,8 +2198,7 @@ function registerPublicContentRoutes(app, registry, repos, projectors, paths, li
|
|
|
2149
2198
|
404
|
|
2150
2199
|
);
|
|
2151
2200
|
}
|
|
2152
|
-
|
|
2153
|
-
if (resolver?.hasSchema(typeName)) data = await resolver.resolveItem(typeName, data);
|
|
2201
|
+
const data = await respondItem(item, typeName, programmatic);
|
|
2154
2202
|
return c.json({ ok: true, data });
|
|
2155
2203
|
});
|
|
2156
2204
|
}
|
|
@@ -2222,7 +2270,55 @@ function registerPublicMediaRoutes(app, mediaRepo, paths, listRateLimit) {
|
|
|
2222
2270
|
}
|
|
2223
2271
|
|
|
2224
2272
|
// src/routes/admin/content.ts
|
|
2225
|
-
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");
|
|
2226
2322
|
|
|
2227
2323
|
// src/media-references.ts
|
|
2228
2324
|
function extractMediaId(value) {
|
|
@@ -2260,14 +2356,62 @@ async function applyMediaReferenceDelta(delta, mediaRepo) {
|
|
|
2260
2356
|
|
|
2261
2357
|
// src/routes/admin/content.ts
|
|
2262
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
|
|
2263
2407
|
init_field_keys();
|
|
2264
|
-
function
|
|
2408
|
+
function quoteIdent3(name) {
|
|
2265
2409
|
if (!/^[a-z][a-z0-9_-]*$/.test(name)) throw new Error(`Unsafe identifier: ${name}`);
|
|
2266
2410
|
return `"${name}"`;
|
|
2267
2411
|
}
|
|
2268
2412
|
async function loadParagraphRows(db, registry, pType, parentId, fieldName) {
|
|
2269
2413
|
const result = await db.execute(
|
|
2270
|
-
|
|
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`
|
|
2271
2415
|
);
|
|
2272
2416
|
const rows = result.rows;
|
|
2273
2417
|
for (const nf of pType.fields) {
|
|
@@ -2277,14 +2421,17 @@ async function loadParagraphRows(db, registry, pType, parentId, fieldName) {
|
|
|
2277
2421
|
const nType = registry.paragraph_types[comp.ref];
|
|
2278
2422
|
if (!nType) continue;
|
|
2279
2423
|
for (const row of rows) {
|
|
2280
|
-
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
|
+
);
|
|
2281
2428
|
}
|
|
2282
2429
|
}
|
|
2283
2430
|
return rows;
|
|
2284
2431
|
}
|
|
2285
2432
|
async function lookupBasePathId(db, pathOrName) {
|
|
2286
2433
|
const r = await db.execute(
|
|
2287
|
-
|
|
2434
|
+
import_drizzle_orm4.sql`SELECT id FROM base_paths WHERE path = ${pathOrName} OR name = ${pathOrName} LIMIT 1`
|
|
2288
2435
|
);
|
|
2289
2436
|
return r.rows[0]?.id ?? null;
|
|
2290
2437
|
}
|
|
@@ -2298,6 +2445,14 @@ function isEmpty(value) {
|
|
|
2298
2445
|
function checkRequiredFields(fields, data) {
|
|
2299
2446
|
return fields.filter((f) => f.required && isEmpty(data[f.name])).map((f) => ({ field: f.name, message: `${f.label} is required` }));
|
|
2300
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
|
+
}
|
|
2301
2456
|
function parseListQuery(url, schemaFieldNames, relationFieldNames, columnFor) {
|
|
2302
2457
|
const searchParams = new URL(url).searchParams;
|
|
2303
2458
|
const pagination = parsePagination(
|
|
@@ -2439,11 +2594,11 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
|
|
|
2439
2594
|
if (comp.component !== "paragraph-embed" || !comp.ref) continue;
|
|
2440
2595
|
const pType = registry.paragraph_types[comp.ref];
|
|
2441
2596
|
if (!pType) continue;
|
|
2442
|
-
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));
|
|
2443
2598
|
} else if (f.db_column.junction) {
|
|
2444
2599
|
const j = f.db_column.junction;
|
|
2445
2600
|
const r = await db.execute(
|
|
2446
|
-
|
|
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}`
|
|
2447
2602
|
);
|
|
2448
2603
|
row[f.name] = r.rows.map(
|
|
2449
2604
|
(row2) => row2[j.right_column]
|
|
@@ -2519,6 +2674,8 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
|
|
|
2519
2674
|
}
|
|
2520
2675
|
);
|
|
2521
2676
|
const writeNewItem = async (c, body) => {
|
|
2677
|
+
const shapeError = relationShapeError(c, contentType.fields, body, registry);
|
|
2678
|
+
if (shapeError) return shapeError;
|
|
2522
2679
|
const storageBody = fieldKeys.toStorage(body);
|
|
2523
2680
|
const fieldErrors = checkRequiredFields(requiredFields, body);
|
|
2524
2681
|
if (fieldErrors.length > 0) {
|
|
@@ -2582,7 +2739,7 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
|
|
|
2582
2739
|
if (comp.component !== "paragraph-embed" || !comp.ref) continue;
|
|
2583
2740
|
const pType = registry.paragraph_types[comp.ref];
|
|
2584
2741
|
if (!pType) continue;
|
|
2585
|
-
const items =
|
|
2742
|
+
const items = paragraphItems(f, body[f.name]);
|
|
2586
2743
|
mediaDeltas.push(await persistParagraphField(db, itemId, contentType.db.table_name, f.name, pType, items, registry));
|
|
2587
2744
|
}
|
|
2588
2745
|
for (const f of junctionFields) {
|
|
@@ -2653,6 +2810,8 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
|
|
|
2653
2810
|
}
|
|
2654
2811
|
);
|
|
2655
2812
|
const writeExistingItem = async (c, id, existing, body) => {
|
|
2813
|
+
const shapeError = relationShapeError(c, contentType.fields, body, registry);
|
|
2814
|
+
if (shapeError) return shapeError;
|
|
2656
2815
|
const storageBody = fieldKeys.toStorage(body);
|
|
2657
2816
|
if (body["published"] === true) {
|
|
2658
2817
|
const publishDeny = await requirePermission("content:edit")(c, async () => {
|
|
@@ -2699,14 +2858,16 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
|
|
|
2699
2858
|
];
|
|
2700
2859
|
if (db) {
|
|
2701
2860
|
for (const f of patchParagraphFields) {
|
|
2861
|
+
if (!(f.name in body)) continue;
|
|
2702
2862
|
const comp = f.ui_component;
|
|
2703
2863
|
if (comp.component !== "paragraph-embed" || !comp.ref) continue;
|
|
2704
2864
|
const pType = registry.paragraph_types[comp.ref];
|
|
2705
2865
|
if (!pType) continue;
|
|
2706
|
-
const items =
|
|
2866
|
+
const items = paragraphItems(f, body[f.name]);
|
|
2707
2867
|
mediaDeltas.push(await persistParagraphField(db, id, contentType.db.table_name, f.name, pType, items, registry));
|
|
2708
2868
|
}
|
|
2709
2869
|
for (const f of patchJunctionFields) {
|
|
2870
|
+
if (!(f.name in body)) continue;
|
|
2710
2871
|
const junction = f.db_column.junction;
|
|
2711
2872
|
const relatedIds = Array.isArray(body[f.name]) ? body[f.name].filter((v) => typeof v === "string") : [];
|
|
2712
2873
|
await persistJunctionField(db, id, junction, relatedIds);
|
|
@@ -2746,6 +2907,18 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
|
|
|
2746
2907
|
404
|
|
2747
2908
|
);
|
|
2748
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
|
+
}
|
|
2749
2922
|
const mediaDeltas = [
|
|
2750
2923
|
topLevelMediaDelta(mediaFields, item, null)
|
|
2751
2924
|
];
|
|
@@ -2759,7 +2932,6 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
|
|
|
2759
2932
|
}
|
|
2760
2933
|
}
|
|
2761
2934
|
await applyMediaReferenceDelta(mergeMediaDeltas(...mediaDeltas), mediaRepo);
|
|
2762
|
-
await repo.delete(id);
|
|
2763
2935
|
return c.json({ ok: true });
|
|
2764
2936
|
}
|
|
2765
2937
|
);
|
|
@@ -2837,6 +3009,8 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
|
|
|
2837
3009
|
requirePermission("content:create"),
|
|
2838
3010
|
async (c) => {
|
|
2839
3011
|
const body = await c.req.json();
|
|
3012
|
+
const shapeError = relationShapeError(c, taxonomyType.fields, body, registry);
|
|
3013
|
+
if (shapeError) return shapeError;
|
|
2840
3014
|
const storageBody = fieldKeys.toStorage(body);
|
|
2841
3015
|
const fieldErrors = checkRequiredFields(requiredFields, body);
|
|
2842
3016
|
if (fieldErrors.length > 0) {
|
|
@@ -2876,7 +3050,7 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
|
|
|
2876
3050
|
if (comp.component !== "paragraph-embed" || !comp.ref) continue;
|
|
2877
3051
|
const pType = registry.paragraph_types[comp.ref];
|
|
2878
3052
|
if (!pType) continue;
|
|
2879
|
-
const items =
|
|
3053
|
+
const items = paragraphItems(f, body[f.name]);
|
|
2880
3054
|
mediaDeltas.push(await persistParagraphField(db, taxItemId, taxonomyType.db.table_name, f.name, pType, items, registry));
|
|
2881
3055
|
}
|
|
2882
3056
|
}
|
|
@@ -2899,6 +3073,8 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
|
|
|
2899
3073
|
404
|
|
2900
3074
|
);
|
|
2901
3075
|
}
|
|
3076
|
+
const shapeError = relationShapeError(c, taxonomyType.fields, body, registry);
|
|
3077
|
+
if (shapeError) return shapeError;
|
|
2902
3078
|
if (body["published"] === true) {
|
|
2903
3079
|
const publishDeny = await requirePermission("content:edit")(c, async () => {
|
|
2904
3080
|
});
|
|
@@ -2940,11 +3116,12 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
|
|
|
2940
3116
|
];
|
|
2941
3117
|
if (db) {
|
|
2942
3118
|
for (const f of taxPatchParagraphFields) {
|
|
3119
|
+
if (!(f.name in body)) continue;
|
|
2943
3120
|
const comp = f.ui_component;
|
|
2944
3121
|
if (comp.component !== "paragraph-embed" || !comp.ref) continue;
|
|
2945
3122
|
const pType = registry.paragraph_types[comp.ref];
|
|
2946
3123
|
if (!pType) continue;
|
|
2947
|
-
const items =
|
|
3124
|
+
const items = paragraphItems(f, body[f.name]);
|
|
2948
3125
|
mediaDeltas.push(await persistParagraphField(db, id, taxonomyType.db.table_name, f.name, pType, items, registry));
|
|
2949
3126
|
}
|
|
2950
3127
|
}
|
|
@@ -2965,6 +3142,18 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
|
|
|
2965
3142
|
404
|
|
2966
3143
|
);
|
|
2967
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
|
+
}
|
|
2968
3157
|
const mediaDeltas = [
|
|
2969
3158
|
topLevelMediaDelta(mediaFields, item, null)
|
|
2970
3159
|
];
|
|
@@ -2978,7 +3167,6 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
|
|
|
2978
3167
|
}
|
|
2979
3168
|
}
|
|
2980
3169
|
await applyMediaReferenceDelta(mergeMediaDeltas(...mediaDeltas), mediaRepo);
|
|
2981
|
-
await repo.delete(id);
|
|
2982
3170
|
return c.json({ ok: true });
|
|
2983
3171
|
}
|
|
2984
3172
|
);
|
|
@@ -3440,10 +3628,10 @@ function registerAdminMediaRoutes(app, mediaRepo, storage, requirePermission, ma
|
|
|
3440
3628
|
|
|
3441
3629
|
// src/routes/admin/auth.ts
|
|
3442
3630
|
var import_cookie3 = require("hono/cookie");
|
|
3443
|
-
var
|
|
3631
|
+
var import_drizzle_orm5 = require("drizzle-orm");
|
|
3444
3632
|
|
|
3445
3633
|
// src/auth/password.ts
|
|
3446
|
-
var
|
|
3634
|
+
var import_manguito_cms_core5 = require("@bobbykim/manguito-cms-core");
|
|
3447
3635
|
|
|
3448
3636
|
// src/routes/admin/auth.ts
|
|
3449
3637
|
var loginAttempts = /* @__PURE__ */ new Map();
|
|
@@ -3506,20 +3694,20 @@ function registerAuthRoutes(app, db) {
|
|
|
3506
3694
|
);
|
|
3507
3695
|
}
|
|
3508
3696
|
const userResult = await db.execute(
|
|
3509
|
-
|
|
3697
|
+
import_drizzle_orm5.sql`SELECT id, email, password_hash, role_id, token_version
|
|
3510
3698
|
FROM users WHERE email = ${email} LIMIT 1`
|
|
3511
3699
|
);
|
|
3512
3700
|
const user = userResult.rows[0];
|
|
3513
3701
|
if (!user) {
|
|
3514
|
-
await (0,
|
|
3702
|
+
await (0, import_manguito_cms_core5.verifyPassword)(password, DUMMY_PASSWORD_HASH);
|
|
3515
3703
|
return c.json(invalidCredentials, 401);
|
|
3516
3704
|
}
|
|
3517
|
-
const passwordValid = await (0,
|
|
3705
|
+
const passwordValid = await (0, import_manguito_cms_core5.verifyPassword)(password, user.password_hash);
|
|
3518
3706
|
if (!passwordValid) {
|
|
3519
3707
|
return c.json(invalidCredentials, 401);
|
|
3520
3708
|
}
|
|
3521
3709
|
const roleResult = await db.execute(
|
|
3522
|
-
|
|
3710
|
+
import_drizzle_orm5.sql`SELECT r.name AS name, u.must_change_password
|
|
3523
3711
|
FROM roles r
|
|
3524
3712
|
JOIN users u ON u.role_id = r.id
|
|
3525
3713
|
WHERE r.id = ${user.role_id} AND u.id = ${user.id}
|
|
@@ -3579,7 +3767,7 @@ function registerAuthRoutes(app, db) {
|
|
|
3579
3767
|
);
|
|
3580
3768
|
}
|
|
3581
3769
|
const rowResult = await db.execute(
|
|
3582
|
-
|
|
3770
|
+
import_drizzle_orm5.sql`SELECT u.token_version, r.name AS role
|
|
3583
3771
|
FROM users u
|
|
3584
3772
|
JOIN roles r ON r.id = u.role_id
|
|
3585
3773
|
WHERE u.id = ${payload.user_id}
|
|
@@ -3603,7 +3791,7 @@ function registerAuthRoutes(app, db) {
|
|
|
3603
3791
|
app.post("/admin/api/auth/logout", authMiddleware, async (c) => {
|
|
3604
3792
|
const user = c.get("user");
|
|
3605
3793
|
await db.execute(
|
|
3606
|
-
|
|
3794
|
+
import_drizzle_orm5.sql`UPDATE users SET token_version = token_version + 1 WHERE id = ${user.id}`
|
|
3607
3795
|
);
|
|
3608
3796
|
clearAuthCookie(c, "auth_token");
|
|
3609
3797
|
clearAuthCookie(c, "refresh_token", "/admin/api/auth");
|
|
@@ -3612,7 +3800,7 @@ function registerAuthRoutes(app, db) {
|
|
|
3612
3800
|
}
|
|
3613
3801
|
|
|
3614
3802
|
// src/routes/admin/users.ts
|
|
3615
|
-
var
|
|
3803
|
+
var import_drizzle_orm6 = require("drizzle-orm");
|
|
3616
3804
|
var import_node_crypto2 = require("crypto");
|
|
3617
3805
|
function getActingUser(c) {
|
|
3618
3806
|
return c.get("user");
|
|
@@ -3647,18 +3835,18 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
3647
3835
|
);
|
|
3648
3836
|
}
|
|
3649
3837
|
const hashResult = await db.execute(
|
|
3650
|
-
|
|
3838
|
+
import_drizzle_orm6.sql`SELECT password_hash FROM users WHERE id = ${actingUser.id} LIMIT 1`
|
|
3651
3839
|
);
|
|
3652
3840
|
const hashRow = hashResult.rows[0];
|
|
3653
|
-
if (!hashRow || !await (0,
|
|
3841
|
+
if (!hashRow || !await (0, import_manguito_cms_core5.verifyPassword)(currentPassword, hashRow.password_hash)) {
|
|
3654
3842
|
return c.json(
|
|
3655
3843
|
{ ok: false, error: { code: "INVALID_CREDENTIALS", message: "Invalid credentials." } },
|
|
3656
3844
|
401
|
|
3657
3845
|
);
|
|
3658
3846
|
}
|
|
3659
|
-
const newHash = await (0,
|
|
3847
|
+
const newHash = await (0, import_manguito_cms_core5.hashPassword)(newPassword);
|
|
3660
3848
|
await db.execute(
|
|
3661
|
-
|
|
3849
|
+
import_drizzle_orm6.sql`UPDATE users
|
|
3662
3850
|
SET password_hash = ${newHash},
|
|
3663
3851
|
must_change_password = false,
|
|
3664
3852
|
token_version = token_version + 1,
|
|
@@ -3669,7 +3857,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
3669
3857
|
});
|
|
3670
3858
|
app.get("/admin/api/users", requirePermission("users:read"), async (c) => {
|
|
3671
3859
|
const result = await db.execute(
|
|
3672
|
-
|
|
3860
|
+
import_drizzle_orm6.sql`SELECT u.id, u.email, r.name AS role, u.must_change_password,
|
|
3673
3861
|
u.created_at, u.updated_at
|
|
3674
3862
|
FROM users u
|
|
3675
3863
|
JOIN roles r ON r.id = u.role_id
|
|
@@ -3680,7 +3868,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
3680
3868
|
app.get("/admin/api/users/:id", requirePermission("users:read"), async (c) => {
|
|
3681
3869
|
const id = c.req.param("id");
|
|
3682
3870
|
const result = await db.execute(
|
|
3683
|
-
|
|
3871
|
+
import_drizzle_orm6.sql`SELECT u.id, u.email, r.name AS role, u.must_change_password,
|
|
3684
3872
|
u.created_at, u.updated_at
|
|
3685
3873
|
FROM users u
|
|
3686
3874
|
JOIN roles r ON r.id = u.role_id
|
|
@@ -3725,7 +3913,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
3725
3913
|
);
|
|
3726
3914
|
}
|
|
3727
3915
|
const roleResult = await db.execute(
|
|
3728
|
-
|
|
3916
|
+
import_drizzle_orm6.sql`SELECT id FROM roles WHERE name = ${roleName} LIMIT 1`
|
|
3729
3917
|
);
|
|
3730
3918
|
const roleRow = roleResult.rows[0];
|
|
3731
3919
|
if (!roleRow) {
|
|
@@ -3738,9 +3926,9 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
3738
3926
|
);
|
|
3739
3927
|
}
|
|
3740
3928
|
const tempPassword = generateTempPassword();
|
|
3741
|
-
const passwordHash = await (0,
|
|
3929
|
+
const passwordHash = await (0, import_manguito_cms_core5.hashPassword)(tempPassword);
|
|
3742
3930
|
const insertResult = await db.execute(
|
|
3743
|
-
|
|
3931
|
+
import_drizzle_orm6.sql`INSERT INTO users
|
|
3744
3932
|
(id, email, password_hash, role_id, must_change_password, token_version,
|
|
3745
3933
|
created_at, updated_at)
|
|
3746
3934
|
VALUES
|
|
@@ -3795,7 +3983,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
3795
3983
|
);
|
|
3796
3984
|
}
|
|
3797
3985
|
const existingResult = await db.execute(
|
|
3798
|
-
|
|
3986
|
+
import_drizzle_orm6.sql`SELECT id, email, role_id FROM users WHERE id = ${id} LIMIT 1`
|
|
3799
3987
|
);
|
|
3800
3988
|
const existing = existingResult.rows[0];
|
|
3801
3989
|
if (!existing) {
|
|
@@ -3809,7 +3997,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
3809
3997
|
let changeRole = false;
|
|
3810
3998
|
if (typeof body.role === "string" && body.role) {
|
|
3811
3999
|
const roleResult = await db.execute(
|
|
3812
|
-
|
|
4000
|
+
import_drizzle_orm6.sql`SELECT id FROM roles WHERE name = ${body.role} LIMIT 1`
|
|
3813
4001
|
);
|
|
3814
4002
|
const roleRow = roleResult.rows[0];
|
|
3815
4003
|
if (!roleRow) {
|
|
@@ -3829,7 +4017,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
3829
4017
|
}
|
|
3830
4018
|
if (changeRole) {
|
|
3831
4019
|
await db.execute(
|
|
3832
|
-
|
|
4020
|
+
import_drizzle_orm6.sql`UPDATE users
|
|
3833
4021
|
SET email = ${newEmail},
|
|
3834
4022
|
role_id = ${newRoleId},
|
|
3835
4023
|
token_version = token_version + 1,
|
|
@@ -3838,7 +4026,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
3838
4026
|
);
|
|
3839
4027
|
} else {
|
|
3840
4028
|
await db.execute(
|
|
3841
|
-
|
|
4029
|
+
import_drizzle_orm6.sql`UPDATE users
|
|
3842
4030
|
SET email = ${newEmail},
|
|
3843
4031
|
role_id = ${newRoleId},
|
|
3844
4032
|
updated_at = NOW()
|
|
@@ -3846,7 +4034,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
3846
4034
|
);
|
|
3847
4035
|
}
|
|
3848
4036
|
const updatedResult = await db.execute(
|
|
3849
|
-
|
|
4037
|
+
import_drizzle_orm6.sql`SELECT u.id, u.email, r.name AS role, u.must_change_password,
|
|
3850
4038
|
u.created_at, u.updated_at
|
|
3851
4039
|
FROM users u
|
|
3852
4040
|
JOIN roles r ON r.id = u.role_id
|
|
@@ -3883,7 +4071,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
3883
4071
|
);
|
|
3884
4072
|
}
|
|
3885
4073
|
const result = await db.execute(
|
|
3886
|
-
|
|
4074
|
+
import_drizzle_orm6.sql`DELETE FROM users WHERE id = ${id} RETURNING id`
|
|
3887
4075
|
);
|
|
3888
4076
|
if (result.rows.length === 0) {
|
|
3889
4077
|
return c.json(
|
|
@@ -3914,7 +4102,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
3914
4102
|
);
|
|
3915
4103
|
}
|
|
3916
4104
|
const existingResult = await db.execute(
|
|
3917
|
-
|
|
4105
|
+
import_drizzle_orm6.sql`SELECT id FROM users WHERE id = ${id} LIMIT 1`
|
|
3918
4106
|
);
|
|
3919
4107
|
const existing = existingResult.rows[0];
|
|
3920
4108
|
if (!existing) {
|
|
@@ -3924,9 +4112,9 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
3924
4112
|
);
|
|
3925
4113
|
}
|
|
3926
4114
|
const tempPassword = generateTempPassword();
|
|
3927
|
-
const passwordHash = await (0,
|
|
4115
|
+
const passwordHash = await (0, import_manguito_cms_core5.hashPassword)(tempPassword);
|
|
3928
4116
|
await db.execute(
|
|
3929
|
-
|
|
4117
|
+
import_drizzle_orm6.sql`UPDATE users
|
|
3930
4118
|
SET password_hash = ${passwordHash},
|
|
3931
4119
|
must_change_password = true,
|
|
3932
4120
|
token_version = token_version + 1,
|
|
@@ -3939,10 +4127,10 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
3939
4127
|
}
|
|
3940
4128
|
|
|
3941
4129
|
// src/routes/admin/config.ts
|
|
3942
|
-
var
|
|
4130
|
+
var import_drizzle_orm7 = require("drizzle-orm");
|
|
3943
4131
|
var API_VERSION = (() => {
|
|
3944
4132
|
try {
|
|
3945
|
-
return "0.
|
|
4133
|
+
return "0.8.0";
|
|
3946
4134
|
} catch {
|
|
3947
4135
|
return "0.0.0";
|
|
3948
4136
|
}
|
|
@@ -3961,7 +4149,7 @@ function registerConfigRoute(app, config, registry, db) {
|
|
|
3961
4149
|
permissions: r.permissions
|
|
3962
4150
|
}));
|
|
3963
4151
|
const userResult = await db.execute(
|
|
3964
|
-
|
|
4152
|
+
import_drizzle_orm7.sql`SELECT email, must_change_password FROM users WHERE id = ${actingUser.id} LIMIT 1`
|
|
3965
4153
|
);
|
|
3966
4154
|
const userRow = userResult.rows[0];
|
|
3967
4155
|
return c.json({
|
|
@@ -3987,7 +4175,7 @@ function registerConfigRoute(app, config, registry, db) {
|
|
|
3987
4175
|
}
|
|
3988
4176
|
|
|
3989
4177
|
// src/routes/admin/schema.ts
|
|
3990
|
-
var
|
|
4178
|
+
var import_drizzle_orm8 = require("drizzle-orm");
|
|
3991
4179
|
function liveFields(fields) {
|
|
3992
4180
|
return fields.filter((f) => f.removed !== true);
|
|
3993
4181
|
}
|
|
@@ -4027,7 +4215,7 @@ function registerSchemaRoute(app, registry, db) {
|
|
|
4027
4215
|
const data = await Promise.all(
|
|
4028
4216
|
Object.values(registry.content_types).map(async (ct) => {
|
|
4029
4217
|
const result = await db.execute(
|
|
4030
|
-
|
|
4218
|
+
import_drizzle_orm8.sql`SELECT COUNT(*)::int AS count FROM ${import_drizzle_orm8.sql.raw(`"${ct.db.table_name}"`)}`
|
|
4031
4219
|
);
|
|
4032
4220
|
const count = Number(result.rows[0].count);
|
|
4033
4221
|
return { name: ct.name, label: ct.label, only_one: ct.only_one, count };
|
|
@@ -4039,7 +4227,7 @@ function registerSchemaRoute(app, registry, db) {
|
|
|
4039
4227
|
const data = await Promise.all(
|
|
4040
4228
|
Object.values(registry.taxonomy_types).map(async (tt) => {
|
|
4041
4229
|
const result = await db.execute(
|
|
4042
|
-
|
|
4230
|
+
import_drizzle_orm8.sql`SELECT COUNT(*)::int AS count FROM ${import_drizzle_orm8.sql.raw(`"${tt.db.table_name}"`)}`
|
|
4043
4231
|
);
|
|
4044
4232
|
const count = Number(result.rows[0].count);
|
|
4045
4233
|
return { name: tt.name, label: tt.label, count };
|
|
@@ -4051,7 +4239,7 @@ function registerSchemaRoute(app, registry, db) {
|
|
|
4051
4239
|
|
|
4052
4240
|
// src/repositories/content.ts
|
|
4053
4241
|
var import_node_crypto3 = __toESM(require("crypto"));
|
|
4054
|
-
var
|
|
4242
|
+
var import_drizzle_orm9 = require("drizzle-orm");
|
|
4055
4243
|
init_relations();
|
|
4056
4244
|
var SORTABLE_FIELDS2 = /* @__PURE__ */ new Set(["title", "created_at", "updated_at"]);
|
|
4057
4245
|
function codeError(code, message) {
|
|
@@ -4059,7 +4247,7 @@ function codeError(code, message) {
|
|
|
4059
4247
|
err.code = code;
|
|
4060
4248
|
return err;
|
|
4061
4249
|
}
|
|
4062
|
-
function
|
|
4250
|
+
function quoteIdent4(name) {
|
|
4063
4251
|
if (!/^[a-z][a-z0-9_-]*$/.test(name)) {
|
|
4064
4252
|
throw new Error(`Unsafe SQL identifier: ${name}`);
|
|
4065
4253
|
}
|
|
@@ -4074,43 +4262,43 @@ function quoteField(name) {
|
|
|
4074
4262
|
function buildConditions(published_only, filters, search) {
|
|
4075
4263
|
const conds = [];
|
|
4076
4264
|
if (published_only) {
|
|
4077
|
-
conds.push(
|
|
4265
|
+
conds.push(import_drizzle_orm9.sql`published = true`);
|
|
4078
4266
|
}
|
|
4079
4267
|
if (search && search.term.trim() !== "" && search.columns.length > 0) {
|
|
4080
4268
|
const pattern = `%${search.term.trim()}%`;
|
|
4081
|
-
const orConds = search.columns.map((col) =>
|
|
4082
|
-
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 `)})`);
|
|
4083
4271
|
}
|
|
4084
4272
|
if (filters) {
|
|
4085
4273
|
for (const [field, value] of Object.entries(filters)) {
|
|
4086
|
-
const col =
|
|
4274
|
+
const col = import_drizzle_orm9.sql.raw(quoteField(field));
|
|
4087
4275
|
if (Array.isArray(value)) {
|
|
4088
4276
|
if (value.length === 0) continue;
|
|
4089
|
-
const inList =
|
|
4090
|
-
value.map((v) =>
|
|
4091
|
-
|
|
4277
|
+
const inList = import_drizzle_orm9.sql.join(
|
|
4278
|
+
value.map((v) => import_drizzle_orm9.sql`${v}`),
|
|
4279
|
+
import_drizzle_orm9.sql`, `
|
|
4092
4280
|
);
|
|
4093
|
-
conds.push(
|
|
4281
|
+
conds.push(import_drizzle_orm9.sql`${col} IN (${inList})`);
|
|
4094
4282
|
} else if (typeof value === "object" && value !== null) {
|
|
4095
4283
|
const op = value;
|
|
4096
|
-
if (op.gt !== void 0) conds.push(
|
|
4097
|
-
if (op.gte !== void 0) conds.push(
|
|
4098
|
-
if (op.lt !== void 0) conds.push(
|
|
4099
|
-
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}`);
|
|
4100
4288
|
} else {
|
|
4101
|
-
conds.push(
|
|
4289
|
+
conds.push(import_drizzle_orm9.sql`${col} = ${value}`);
|
|
4102
4290
|
}
|
|
4103
4291
|
}
|
|
4104
4292
|
}
|
|
4105
4293
|
return conds;
|
|
4106
4294
|
}
|
|
4107
4295
|
function whereFragment(conds) {
|
|
4108
|
-
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``;
|
|
4109
4297
|
}
|
|
4110
4298
|
function createDrizzleContentRepository(db, tableName, options = {}) {
|
|
4111
4299
|
const { relations = {}, publishedRelations = false, sortableColumns } = options;
|
|
4112
4300
|
function tableRaw() {
|
|
4113
|
-
return
|
|
4301
|
+
return import_drizzle_orm9.sql.raw(quoteIdent4(tableName));
|
|
4114
4302
|
}
|
|
4115
4303
|
async function resolveRows(rows, include, cache) {
|
|
4116
4304
|
if (rows.length === 0) return rows;
|
|
@@ -4153,14 +4341,14 @@ function createDrizzleContentRepository(db, tableName, options = {}) {
|
|
|
4153
4341
|
const offset = (page - 1) * per_page;
|
|
4154
4342
|
const conds = buildConditions(published_only, filters, search);
|
|
4155
4343
|
const where = whereFragment(conds);
|
|
4156
|
-
const sortCol =
|
|
4157
|
-
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");
|
|
4158
4346
|
const countResult = await db.execute(
|
|
4159
|
-
|
|
4347
|
+
import_drizzle_orm9.sql`SELECT COUNT(*) AS total FROM ${tableRaw()}${where}`
|
|
4160
4348
|
);
|
|
4161
4349
|
const total = Number(countResult.rows[0]?.["total"] ?? 0);
|
|
4162
4350
|
const dataResult = await db.execute(
|
|
4163
|
-
|
|
4351
|
+
import_drizzle_orm9.sql`SELECT * FROM ${tableRaw()}${where} ORDER BY ${sortCol} ${sortDir} LIMIT ${per_page} OFFSET ${offset}`
|
|
4164
4352
|
);
|
|
4165
4353
|
const rows = dataResult.rows;
|
|
4166
4354
|
const cache = /* @__PURE__ */ new Map();
|
|
@@ -4181,7 +4369,7 @@ function createDrizzleContentRepository(db, tableName, options = {}) {
|
|
|
4181
4369
|
},
|
|
4182
4370
|
async findOne(id, include = []) {
|
|
4183
4371
|
const result = await db.execute(
|
|
4184
|
-
|
|
4372
|
+
import_drizzle_orm9.sql`SELECT * FROM ${tableRaw()} WHERE id = ${id} LIMIT 1`
|
|
4185
4373
|
);
|
|
4186
4374
|
if (result.rows.length === 0) return null;
|
|
4187
4375
|
const rows = result.rows;
|
|
@@ -4191,7 +4379,7 @@ function createDrizzleContentRepository(db, tableName, options = {}) {
|
|
|
4191
4379
|
},
|
|
4192
4380
|
async findBySlug(slug, include = []) {
|
|
4193
4381
|
const result = await db.execute(
|
|
4194
|
-
|
|
4382
|
+
import_drizzle_orm9.sql`SELECT * FROM ${tableRaw()} WHERE slug = ${slug} LIMIT 1`
|
|
4195
4383
|
);
|
|
4196
4384
|
if (result.rows.length === 0) return null;
|
|
4197
4385
|
const rows = result.rows;
|
|
@@ -4207,10 +4395,10 @@ function createDrizzleContentRepository(db, tableName, options = {}) {
|
|
|
4207
4395
|
updated_at: /* @__PURE__ */ new Date()
|
|
4208
4396
|
};
|
|
4209
4397
|
const entries = Object.entries(record).filter(([, v]) => v !== void 0);
|
|
4210
|
-
const colsSql =
|
|
4211
|
-
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`, `);
|
|
4212
4400
|
const result = await db.execute(
|
|
4213
|
-
|
|
4401
|
+
import_drizzle_orm9.sql`INSERT INTO ${tableRaw()} (${colsSql}) VALUES (${valsSql}) RETURNING *`
|
|
4214
4402
|
);
|
|
4215
4403
|
return result.rows[0];
|
|
4216
4404
|
},
|
|
@@ -4223,25 +4411,25 @@ function createDrizzleContentRepository(db, tableName, options = {}) {
|
|
|
4223
4411
|
if (entries.length === 0) {
|
|
4224
4412
|
return this.findOne(id);
|
|
4225
4413
|
}
|
|
4226
|
-
const setClauses =
|
|
4227
|
-
entries.map(([k, v]) =>
|
|
4228
|
-
|
|
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`, `
|
|
4229
4417
|
);
|
|
4230
4418
|
const result = await db.execute(
|
|
4231
|
-
|
|
4419
|
+
import_drizzle_orm9.sql`UPDATE ${tableRaw()} SET ${setClauses} WHERE id = ${id} RETURNING *`
|
|
4232
4420
|
);
|
|
4233
4421
|
if (result.rows.length === 0) return null;
|
|
4234
4422
|
return result.rows[0];
|
|
4235
4423
|
},
|
|
4236
4424
|
async delete(id) {
|
|
4237
|
-
await db.execute(
|
|
4425
|
+
await db.execute(import_drizzle_orm9.sql`DELETE FROM ${tableRaw()} WHERE id = ${id}`);
|
|
4238
4426
|
},
|
|
4239
4427
|
async findAll(opts = {}) {
|
|
4240
4428
|
const { include = [], published_only } = opts;
|
|
4241
4429
|
const conds = buildConditions(published_only, void 0, void 0);
|
|
4242
4430
|
const where = whereFragment(conds);
|
|
4243
4431
|
const result = await db.execute(
|
|
4244
|
-
|
|
4432
|
+
import_drizzle_orm9.sql`SELECT * FROM ${tableRaw()}${where} ORDER BY created_at ASC`
|
|
4245
4433
|
);
|
|
4246
4434
|
const rows = result.rows;
|
|
4247
4435
|
const cache = /* @__PURE__ */ new Map();
|
|
@@ -4256,23 +4444,23 @@ init_relations();
|
|
|
4256
4444
|
|
|
4257
4445
|
// src/repositories/media.ts
|
|
4258
4446
|
var import_node_crypto4 = __toESM(require("crypto"));
|
|
4259
|
-
var
|
|
4447
|
+
var import_drizzle_orm10 = require("drizzle-orm");
|
|
4260
4448
|
function createMediaRepository(db) {
|
|
4261
|
-
const TABLE =
|
|
4449
|
+
const TABLE = import_drizzle_orm10.sql.raw('"media"');
|
|
4262
4450
|
return {
|
|
4263
4451
|
async findMany(opts = {}) {
|
|
4264
4452
|
const { page = 1, per_page = 10, type, orphaned } = opts;
|
|
4265
4453
|
const offset = (page - 1) * per_page;
|
|
4266
4454
|
const conds = [];
|
|
4267
|
-
if (type) conds.push(
|
|
4268
|
-
if (orphaned) conds.push(
|
|
4269
|
-
const where = conds.length > 0 ?
|
|
4270
|
-
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}`);
|
|
4271
4459
|
const total = Number(
|
|
4272
4460
|
countResult.rows[0]?.["total"] ?? 0
|
|
4273
4461
|
);
|
|
4274
4462
|
const dataResult = await db.execute(
|
|
4275
|
-
|
|
4463
|
+
import_drizzle_orm10.sql`SELECT * FROM ${TABLE}${where} ORDER BY created_at DESC LIMIT ${per_page} OFFSET ${offset}`
|
|
4276
4464
|
);
|
|
4277
4465
|
const total_pages = Math.ceil(total / per_page);
|
|
4278
4466
|
return {
|
|
@@ -4290,7 +4478,7 @@ function createMediaRepository(db) {
|
|
|
4290
4478
|
},
|
|
4291
4479
|
async findOne(id) {
|
|
4292
4480
|
const result = await db.execute(
|
|
4293
|
-
|
|
4481
|
+
import_drizzle_orm10.sql`SELECT * FROM ${TABLE} WHERE id = ${id} LIMIT 1`
|
|
4294
4482
|
);
|
|
4295
4483
|
if (result.rows.length === 0) return null;
|
|
4296
4484
|
return result.rows[0];
|
|
@@ -4304,10 +4492,10 @@ function createMediaRepository(db) {
|
|
|
4304
4492
|
updated_at: /* @__PURE__ */ new Date()
|
|
4305
4493
|
};
|
|
4306
4494
|
const entries = Object.entries(record).filter(([, v]) => v !== void 0);
|
|
4307
|
-
const colsSql =
|
|
4308
|
-
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`, `);
|
|
4309
4497
|
const result = await db.execute(
|
|
4310
|
-
|
|
4498
|
+
import_drizzle_orm10.sql`INSERT INTO ${TABLE} (${colsSql}) VALUES (${valsSql}) RETURNING *`
|
|
4311
4499
|
);
|
|
4312
4500
|
return result.rows[0];
|
|
4313
4501
|
},
|
|
@@ -4317,31 +4505,31 @@ function createMediaRepository(db) {
|
|
|
4317
4505
|
([k, v]) => k !== "id" && v !== void 0
|
|
4318
4506
|
);
|
|
4319
4507
|
if (entries.length === 0) return this.findOne(id);
|
|
4320
|
-
const setClauses =
|
|
4321
|
-
entries.map(([k, v]) =>
|
|
4322
|
-
|
|
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`, `
|
|
4323
4511
|
);
|
|
4324
4512
|
const result = await db.execute(
|
|
4325
|
-
|
|
4513
|
+
import_drizzle_orm10.sql`UPDATE ${TABLE} SET ${setClauses} WHERE id = ${id} RETURNING *`
|
|
4326
4514
|
);
|
|
4327
4515
|
if (result.rows.length === 0) return null;
|
|
4328
4516
|
return result.rows[0];
|
|
4329
4517
|
},
|
|
4330
4518
|
async delete(id) {
|
|
4331
|
-
await db.execute(
|
|
4519
|
+
await db.execute(import_drizzle_orm10.sql`DELETE FROM ${TABLE} WHERE id = ${id}`);
|
|
4332
4520
|
},
|
|
4333
4521
|
async incrementReferenceCount(ids) {
|
|
4334
4522
|
if (ids.length === 0) return;
|
|
4335
|
-
const inList =
|
|
4523
|
+
const inList = import_drizzle_orm10.sql.join(ids.map((id) => import_drizzle_orm10.sql`${id}`), import_drizzle_orm10.sql`, `);
|
|
4336
4524
|
await db.execute(
|
|
4337
|
-
|
|
4525
|
+
import_drizzle_orm10.sql`UPDATE ${TABLE} SET reference_count = reference_count + 1, updated_at = NOW() WHERE id IN (${inList})`
|
|
4338
4526
|
);
|
|
4339
4527
|
},
|
|
4340
4528
|
async decrementReferenceCount(ids) {
|
|
4341
4529
|
if (ids.length === 0) return;
|
|
4342
|
-
const inList =
|
|
4530
|
+
const inList = import_drizzle_orm10.sql.join(ids.map((id) => import_drizzle_orm10.sql`${id}`), import_drizzle_orm10.sql`, `);
|
|
4343
4531
|
await db.execute(
|
|
4344
|
-
|
|
4532
|
+
import_drizzle_orm10.sql`UPDATE ${TABLE} SET reference_count = GREATEST(reference_count - 1, 0), updated_at = NOW() WHERE id IN (${inList})`
|
|
4345
4533
|
);
|
|
4346
4534
|
}
|
|
4347
4535
|
};
|
|
@@ -4478,7 +4666,7 @@ function createCmsApp(options) {
|
|
|
4478
4666
|
const requirePermission = createPermissionMiddleware(rolesRegistry);
|
|
4479
4667
|
const getUserRole = async (userId) => {
|
|
4480
4668
|
const result = await db.execute(
|
|
4481
|
-
|
|
4669
|
+
import_drizzle_orm11.sql`SELECT r.name AS role
|
|
4482
4670
|
FROM users u
|
|
4483
4671
|
JOIN roles r ON r.id = u.role_id
|
|
4484
4672
|
WHERE u.id = ${userId}
|
|
@@ -4606,7 +4794,7 @@ function createCmsApp(options) {
|
|
|
4606
4794
|
app.use(path, attachDeprecationHeaders);
|
|
4607
4795
|
}
|
|
4608
4796
|
}
|
|
4609
|
-
registerPublicContentRoutes(app, registry, surface.repos, surface.projectors, surface.paths, listRateLimit, programmaticResolver);
|
|
4797
|
+
registerPublicContentRoutes(app, registry, surface.repos, surface.projectors, surface.paths, listRateLimit, programmaticResolver, projectors);
|
|
4610
4798
|
}
|
|
4611
4799
|
if (options.versions !== void 0) {
|
|
4612
4800
|
app.all(`${prefix}/:version/*`, async (c, next) => {
|
|
@@ -4672,7 +4860,8 @@ function createCmsApp(options) {
|
|
|
4672
4860
|
endpoint: mount.path,
|
|
4673
4861
|
currentVersion: model.current,
|
|
4674
4862
|
...projection !== void 0 && { projection },
|
|
4675
|
-
...currentProjection !== void 0 && { currentProjection }
|
|
4863
|
+
...currentProjection !== void 0 && { currentProjection },
|
|
4864
|
+
currentFieldKeyMaps: fieldKeyMaps
|
|
4676
4865
|
}
|
|
4677
4866
|
);
|
|
4678
4867
|
} catch (err) {
|