@bobbykim/manguito-cms-cli 0.4.3 → 0.4.4
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.
|
@@ -49,7 +49,7 @@ var init_esm_shims = __esm({
|
|
|
49
49
|
}
|
|
50
50
|
});
|
|
51
51
|
|
|
52
|
-
// ../api/dist/chunk-
|
|
52
|
+
// ../api/dist/chunk-4F2CRXPG.mjs
|
|
53
53
|
init_esm_shims();
|
|
54
54
|
import crypto from "crypto";
|
|
55
55
|
|
|
@@ -1212,7 +1212,7 @@ Subquery.prototype.getSQL = function() {
|
|
|
1212
1212
|
return new SQL([this]);
|
|
1213
1213
|
};
|
|
1214
1214
|
|
|
1215
|
-
// ../api/dist/chunk-
|
|
1215
|
+
// ../api/dist/chunk-4F2CRXPG.mjs
|
|
1216
1216
|
function quoteIdent(name) {
|
|
1217
1217
|
if (!/^[a-z][a-z0-9_-]*$/.test(name)) throw new Error(`Unsafe identifier: ${name}`);
|
|
1218
1218
|
return `"${name}"`;
|
|
@@ -1382,22 +1382,34 @@ function groupBy(items, key) {
|
|
|
1382
1382
|
}
|
|
1383
1383
|
return result;
|
|
1384
1384
|
}
|
|
1385
|
+
function needsFkResolution(row, fkColumn, fieldName) {
|
|
1386
|
+
if (fkColumn !== fieldName && !(fkColumn in row)) return false;
|
|
1387
|
+
const current = row[fieldName];
|
|
1388
|
+
return !(typeof current === "object" && current !== null);
|
|
1389
|
+
}
|
|
1385
1390
|
async function resolveRelationField(db, rows, fieldName, rel, cache, publishedOnly = false) {
|
|
1386
1391
|
if (rows.length === 0) return;
|
|
1392
|
+
const batch = [...new Set(rows)];
|
|
1387
1393
|
if (rel.type === "paragraph") {
|
|
1388
|
-
const parentIds =
|
|
1394
|
+
const parentIds = batch.map((r) => r["id"]);
|
|
1389
1395
|
const inList = sql.join(parentIds.map((id) => sql`${id}`), sql`, `);
|
|
1390
1396
|
const result = await db.execute(
|
|
1391
1397
|
sql`SELECT * FROM ${sql.raw(quoteIdent(rel.table))} WHERE parent_id IN (${inList}) AND parent_field = ${fieldName} ORDER BY "order" ASC`
|
|
1392
1398
|
);
|
|
1393
1399
|
const byParent = groupBy(result.rows, "parent_id");
|
|
1394
|
-
for (const row of
|
|
1400
|
+
for (const row of batch) {
|
|
1395
1401
|
row[fieldName] = byParent[row["id"]] ?? [];
|
|
1396
1402
|
}
|
|
1397
1403
|
} else if (rel.type === "reference") {
|
|
1398
|
-
const
|
|
1404
|
+
const dropFk = rel.fk_column !== fieldName;
|
|
1405
|
+
const pending = batch.filter((r) => needsFkResolution(r, rel.fk_column, fieldName));
|
|
1406
|
+
if (pending.length === 0) return;
|
|
1407
|
+
const fkValues = pending.map((r) => r[rel.fk_column]).filter(Boolean);
|
|
1399
1408
|
if (fkValues.length === 0) {
|
|
1400
|
-
for (const row of
|
|
1409
|
+
for (const row of pending) {
|
|
1410
|
+
row[fieldName] = null;
|
|
1411
|
+
if (dropFk) delete row[rel.fk_column];
|
|
1412
|
+
}
|
|
1401
1413
|
return;
|
|
1402
1414
|
}
|
|
1403
1415
|
const unique = [...new Set(fkValues)];
|
|
@@ -1412,12 +1424,13 @@ async function resolveRelationField(db, rows, fieldName, rel, cache, publishedOn
|
|
|
1412
1424
|
cache.set(`${rel.table}:${item["id"]}`, item);
|
|
1413
1425
|
}
|
|
1414
1426
|
}
|
|
1415
|
-
for (const row of
|
|
1427
|
+
for (const row of pending) {
|
|
1416
1428
|
const fkVal = row[rel.fk_column];
|
|
1417
1429
|
row[fieldName] = fkVal ? cache.get(`${rel.table}:${fkVal}`) ?? null : null;
|
|
1430
|
+
if (dropFk) delete row[rel.fk_column];
|
|
1418
1431
|
}
|
|
1419
1432
|
} else if (rel.type === "junction") {
|
|
1420
|
-
const parentIds =
|
|
1433
|
+
const parentIds = batch.map((r) => r["id"]);
|
|
1421
1434
|
const inList = sql.join(parentIds.map((id) => sql`${id}`), sql`, `);
|
|
1422
1435
|
const orderBy = rel.order_column ? sql` ORDER BY "order" ASC` : sql``;
|
|
1423
1436
|
const junctionResult = await db.execute(
|
|
@@ -1437,14 +1450,20 @@ async function resolveRelationField(db, rows, fieldName, rel, cache, publishedOn
|
|
|
1437
1450
|
}
|
|
1438
1451
|
}
|
|
1439
1452
|
const byLeft = groupBy(jRows, rel.left_column);
|
|
1440
|
-
for (const row of
|
|
1453
|
+
for (const row of batch) {
|
|
1441
1454
|
const jEntries = byLeft[row["id"]] ?? [];
|
|
1442
1455
|
row[fieldName] = jEntries.map((jr) => cache.get(`${rel.table}:${jr[rel.right_column]}`)).filter(Boolean);
|
|
1443
1456
|
}
|
|
1444
1457
|
} else if (rel.type === "media") {
|
|
1445
|
-
const
|
|
1458
|
+
const dropFk = rel.fk_column !== fieldName;
|
|
1459
|
+
const pending = batch.filter((r) => needsFkResolution(r, rel.fk_column, fieldName));
|
|
1460
|
+
if (pending.length === 0) return;
|
|
1461
|
+
const fkValues = pending.map((r) => r[rel.fk_column]).filter(Boolean);
|
|
1446
1462
|
if (fkValues.length === 0) {
|
|
1447
|
-
for (const row of
|
|
1463
|
+
for (const row of pending) {
|
|
1464
|
+
row[fieldName] = null;
|
|
1465
|
+
if (dropFk) delete row[rel.fk_column];
|
|
1466
|
+
}
|
|
1448
1467
|
return;
|
|
1449
1468
|
}
|
|
1450
1469
|
const unique = [...new Set(fkValues)];
|
|
@@ -1456,9 +1475,10 @@ async function resolveRelationField(db, rows, fieldName, rel, cache, publishedOn
|
|
|
1456
1475
|
cache.set(`media:${item["id"]}`, item);
|
|
1457
1476
|
}
|
|
1458
1477
|
}
|
|
1459
|
-
for (const row of
|
|
1478
|
+
for (const row of pending) {
|
|
1460
1479
|
const fkVal = row[rel.fk_column];
|
|
1461
1480
|
row[fieldName] = fkVal ? cache.get(`media:${fkVal}`) ?? null : null;
|
|
1481
|
+
if (dropFk) delete row[rel.fk_column];
|
|
1462
1482
|
}
|
|
1463
1483
|
}
|
|
1464
1484
|
}
|
|
@@ -1486,6 +1506,46 @@ async function resolveRelationBareIds(db, rows, fieldName, rel) {
|
|
|
1486
1506
|
}
|
|
1487
1507
|
}
|
|
1488
1508
|
}
|
|
1509
|
+
function isColumnBacked(field) {
|
|
1510
|
+
const col = field.db_column;
|
|
1511
|
+
if (col === null) return false;
|
|
1512
|
+
if (col.column_name === "") return false;
|
|
1513
|
+
if (col.junction) return false;
|
|
1514
|
+
return true;
|
|
1515
|
+
}
|
|
1516
|
+
function createFieldKeyMap(fields) {
|
|
1517
|
+
const labelToColumn = /* @__PURE__ */ new Map();
|
|
1518
|
+
const columnToLabel = /* @__PURE__ */ new Map();
|
|
1519
|
+
for (const f of fields) {
|
|
1520
|
+
if (!isColumnBacked(f)) continue;
|
|
1521
|
+
labelToColumn.set(f.name, f.db_column.column_name);
|
|
1522
|
+
columnToLabel.set(f.db_column.column_name, f.name);
|
|
1523
|
+
}
|
|
1524
|
+
for (const f of fields) {
|
|
1525
|
+
const columnOwner = columnToLabel.get(f.name);
|
|
1526
|
+
if (columnOwner !== void 0 && columnOwner !== f.name) {
|
|
1527
|
+
throw new Error(
|
|
1528
|
+
`Fatal: field key map failed to build \u2014 field label "${f.name}" collides with the storage column of field "${columnOwner}". A field label may not reuse another field's column name. Rename the field, then run \`manguito validate\` to check your schema.`
|
|
1529
|
+
);
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
const diverges = [...labelToColumn].some(([label, column]) => label !== column);
|
|
1533
|
+
function remap(input, lookup) {
|
|
1534
|
+
const out = {};
|
|
1535
|
+
for (const key of Object.keys(input)) {
|
|
1536
|
+
out[lookup.get(key) ?? key] = input[key];
|
|
1537
|
+
}
|
|
1538
|
+
return out;
|
|
1539
|
+
}
|
|
1540
|
+
return {
|
|
1541
|
+
toStorage: (input) => remap(input, labelToColumn),
|
|
1542
|
+
toLabels: (row) => remap(row, columnToLabel),
|
|
1543
|
+
columnFor: (label) => labelToColumn.get(label),
|
|
1544
|
+
labelFor: (column) => columnToLabel.get(column),
|
|
1545
|
+
labels: [...labelToColumn.keys()],
|
|
1546
|
+
diverges
|
|
1547
|
+
};
|
|
1548
|
+
}
|
|
1489
1549
|
|
|
1490
1550
|
export {
|
|
1491
1551
|
__require,
|
|
@@ -1523,5 +1583,7 @@ export {
|
|
|
1523
1583
|
persistJunctionField,
|
|
1524
1584
|
buildRelationsMap,
|
|
1525
1585
|
resolveRelationField,
|
|
1526
|
-
resolveRelationBareIds
|
|
1586
|
+
resolveRelationBareIds,
|
|
1587
|
+
isColumnBacked,
|
|
1588
|
+
createFieldKeyMap
|
|
1527
1589
|
};
|
|
@@ -9,8 +9,9 @@ import {
|
|
|
9
9
|
__toESM,
|
|
10
10
|
buildRelationsMap,
|
|
11
11
|
init_esm_shims,
|
|
12
|
+
isColumnBacked,
|
|
12
13
|
resolveRelationField
|
|
13
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-UNBMPHIT.js";
|
|
14
15
|
|
|
15
16
|
// ../../node_modules/.pnpm/@whatwg-node+fetch@0.10.13/node_modules/@whatwg-node/fetch/dist/shouldSkipPonyfill.js
|
|
16
17
|
var require_shouldSkipPonyfill = __commonJS({
|
|
@@ -10785,10 +10786,10 @@ var require_parser = __commonJS({
|
|
|
10785
10786
|
}
|
|
10786
10787
|
});
|
|
10787
10788
|
|
|
10788
|
-
// ../api/dist/handler-
|
|
10789
|
+
// ../api/dist/handler-TPEULQ5W.mjs
|
|
10789
10790
|
init_esm_shims();
|
|
10790
10791
|
|
|
10791
|
-
// ../api/dist/chunk-
|
|
10792
|
+
// ../api/dist/chunk-B5LWIR4U.mjs
|
|
10792
10793
|
init_esm_shims();
|
|
10793
10794
|
|
|
10794
10795
|
// ../../node_modules/.pnpm/graphql-yoga@5.21.2_graphql@16.14.2/node_modules/graphql-yoga/esm/index.js
|
|
@@ -30121,7 +30122,7 @@ function createYoga(options) {
|
|
|
30121
30122
|
});
|
|
30122
30123
|
}
|
|
30123
30124
|
|
|
30124
|
-
// ../api/dist/chunk-
|
|
30125
|
+
// ../api/dist/chunk-B5LWIR4U.mjs
|
|
30125
30126
|
var import_dataloader = __toESM(require_dataloader(), 1);
|
|
30126
30127
|
|
|
30127
30128
|
// ../../node_modules/.pnpm/@escape.tech+graphql-armor@3.2.0_@envelop+core@5.5.1_@escape.tech+graphql-armor-types@0.7.0/node_modules/@escape.tech/graphql-armor/dist/graphql-armor.esm.js
|
|
@@ -30879,7 +30880,7 @@ var EnvelopArmor = class {
|
|
|
30879
30880
|
}
|
|
30880
30881
|
};
|
|
30881
30882
|
|
|
30882
|
-
// ../api/dist/chunk-
|
|
30883
|
+
// ../api/dist/chunk-B5LWIR4U.mjs
|
|
30883
30884
|
var DateTimeScalar = new GraphQLScalarType({
|
|
30884
30885
|
name: "DateTime",
|
|
30885
30886
|
description: "ISO-8601 date-time string",
|
|
@@ -31041,12 +31042,13 @@ function buildFilterInputType(type) {
|
|
|
31041
31042
|
if (Object.keys(fields).length === 0) return null;
|
|
31042
31043
|
return new GraphQLInputObjectType({ name: `${graphqlTypeName(type.name)}Filter`, fields });
|
|
31043
31044
|
}
|
|
31044
|
-
function translateFilters(input, nameMap) {
|
|
31045
|
+
function translateFilters(input, nameMap, columnFor) {
|
|
31045
31046
|
const out = {};
|
|
31046
31047
|
if (!input) return out;
|
|
31047
31048
|
for (const [gqlField, raw] of Object.entries(input)) {
|
|
31048
31049
|
if (raw === null || typeof raw !== "object") continue;
|
|
31049
|
-
const
|
|
31050
|
+
const label = nameMap.toSchema(gqlField);
|
|
31051
|
+
const column = columnFor?.(label) ?? label;
|
|
31050
31052
|
const spec = raw;
|
|
31051
31053
|
if (spec["eq"] !== void 0) {
|
|
31052
31054
|
out[column] = spec["eq"];
|
|
@@ -31068,29 +31070,34 @@ function codeError(code, message) {
|
|
|
31068
31070
|
function isPublished(item) {
|
|
31069
31071
|
return !!item && item["published"] === true;
|
|
31070
31072
|
}
|
|
31071
|
-
function
|
|
31072
|
-
|
|
31073
|
+
function resolveFieldValue(field, row) {
|
|
31074
|
+
const key = isColumnBacked(field) ? field.db_column.column_name : field.name;
|
|
31075
|
+
return row[key] ?? null;
|
|
31076
|
+
}
|
|
31077
|
+
function scalarFieldResolver(field) {
|
|
31078
|
+
return (parent) => resolveFieldValue(field, parent);
|
|
31073
31079
|
}
|
|
31074
31080
|
function relationFieldResolver(typeName, schemaFieldName) {
|
|
31075
31081
|
return (parent, _args, ctx) => ctx.loaders.load(typeName, schemaFieldName, parent);
|
|
31076
31082
|
}
|
|
31077
|
-
function programmaticFieldResolver(typeName, schemaFieldName, mediaFieldNames = []) {
|
|
31083
|
+
function programmaticFieldResolver(typeName, schemaFieldName, mediaFieldNames = [], fieldKeys) {
|
|
31078
31084
|
return async (parent, _args, ctx) => {
|
|
31079
31085
|
let p = ctx.programmaticMemo.get(parent);
|
|
31080
31086
|
if (!p) {
|
|
31081
|
-
p = resolveProgrammaticRow(typeName, parent, ctx, mediaFieldNames);
|
|
31087
|
+
p = resolveProgrammaticRow(typeName, parent, ctx, mediaFieldNames, fieldKeys);
|
|
31082
31088
|
ctx.programmaticMemo.set(parent, p);
|
|
31083
31089
|
}
|
|
31084
31090
|
return (await p)[schemaFieldName];
|
|
31085
31091
|
};
|
|
31086
31092
|
}
|
|
31087
|
-
async function resolveProgrammaticRow(typeName, parent, ctx, mediaFieldNames) {
|
|
31088
|
-
|
|
31093
|
+
async function resolveProgrammaticRow(typeName, parent, ctx, mediaFieldNames, fieldKeys) {
|
|
31094
|
+
const toLabels = (row) => fieldKeys ? fieldKeys.toLabels(row) : row;
|
|
31095
|
+
if (mediaFieldNames.length === 0) return ctx.resolver.resolveItem(typeName, toLabels(parent));
|
|
31089
31096
|
const enriched = { ...parent };
|
|
31090
31097
|
await Promise.all(mediaFieldNames.map((name) => ctx.loaders.load(typeName, name, enriched)));
|
|
31091
|
-
return ctx.resolver.resolveItem(typeName, enriched);
|
|
31098
|
+
return ctx.resolver.resolveItem(typeName, toLabels(enriched));
|
|
31092
31099
|
}
|
|
31093
|
-
function collectionResolver(typeName, nameMap) {
|
|
31100
|
+
function collectionResolver(typeName, nameMap, fieldKeys) {
|
|
31094
31101
|
return async (_root, args, ctx) => {
|
|
31095
31102
|
const page = args.page ?? 1;
|
|
31096
31103
|
const perPage = args.perPage ?? 10;
|
|
@@ -31107,7 +31114,7 @@ function collectionResolver(typeName, nameMap) {
|
|
|
31107
31114
|
per_page: perPage,
|
|
31108
31115
|
sort_by: args.sortBy ?? "created_at",
|
|
31109
31116
|
sort_order: args.sortOrder ?? "asc",
|
|
31110
|
-
filters: translateFilters(args.filter, nameMap)
|
|
31117
|
+
filters: translateFilters(args.filter, nameMap, fieldKeys?.columnFor)
|
|
31111
31118
|
});
|
|
31112
31119
|
return { data: result.data, meta: result.meta };
|
|
31113
31120
|
};
|
|
@@ -31158,7 +31165,7 @@ var MEDIA = new GraphQLObjectType({
|
|
|
31158
31165
|
duration: { type: GraphQLInt, resolve: (m) => m.duration }
|
|
31159
31166
|
}
|
|
31160
31167
|
});
|
|
31161
|
-
function buildGraphQLSchema(registry) {
|
|
31168
|
+
function buildGraphQLSchema(registry, fieldKeyMaps = {}) {
|
|
31162
31169
|
const objectTypes = /* @__PURE__ */ new Map();
|
|
31163
31170
|
const enumTypes = /* @__PURE__ */ new Map();
|
|
31164
31171
|
for (const [name, enumType] of Object.entries(registry.enum_types)) {
|
|
@@ -31234,11 +31241,16 @@ function buildGraphQLSchema(registry) {
|
|
|
31234
31241
|
const outType = outputTypeForField(field);
|
|
31235
31242
|
let resolve;
|
|
31236
31243
|
if (field.field_type === "programmatic") {
|
|
31237
|
-
resolve = programmaticFieldResolver(
|
|
31244
|
+
resolve = programmaticFieldResolver(
|
|
31245
|
+
machineName,
|
|
31246
|
+
field.name,
|
|
31247
|
+
mediaFieldNames,
|
|
31248
|
+
fieldKeyMaps[machineName]
|
|
31249
|
+
);
|
|
31238
31250
|
} else if (field.field_type === "reference" || field.field_type === "paragraph" || field.field_type === "image" || field.field_type === "video" || field.field_type === "file") {
|
|
31239
31251
|
resolve = relationFieldResolver(machineName, field.name);
|
|
31240
31252
|
} else {
|
|
31241
|
-
resolve = scalarFieldResolver(field
|
|
31253
|
+
resolve = scalarFieldResolver(field);
|
|
31242
31254
|
}
|
|
31243
31255
|
fields[gqlName] = { type: outType, resolve };
|
|
31244
31256
|
}
|
|
@@ -31294,7 +31306,7 @@ function buildGraphQLSchema(registry) {
|
|
|
31294
31306
|
sortOrder: { type: SortOrderEnum },
|
|
31295
31307
|
...filterType ? { filter: { type: filterType } } : {}
|
|
31296
31308
|
},
|
|
31297
|
-
resolve: collectionResolver(name, nameMap)
|
|
31309
|
+
resolve: collectionResolver(name, nameMap, fieldKeyMaps[name])
|
|
31298
31310
|
};
|
|
31299
31311
|
queryFields[singleQueryName(name)] = {
|
|
31300
31312
|
type: objType,
|
|
@@ -31314,7 +31326,7 @@ function buildGraphQLSchema(registry) {
|
|
|
31314
31326
|
queryFields[collectionQueryName(name)] = {
|
|
31315
31327
|
type: new GraphQLNonNull(listType),
|
|
31316
31328
|
args: { page: { type: GraphQLInt }, perPage: { type: GraphQLInt } },
|
|
31317
|
-
resolve: collectionResolver(name, buildFieldNameMap(tt.fields.map((f) => f.name)))
|
|
31329
|
+
resolve: collectionResolver(name, buildFieldNameMap(tt.fields.map((f) => f.name)), fieldKeyMaps[name])
|
|
31318
31330
|
};
|
|
31319
31331
|
queryFields[singleQueryName(name)] = {
|
|
31320
31332
|
type: objType,
|
|
@@ -31393,8 +31405,8 @@ function isClientSafeGraphQLError(error) {
|
|
|
31393
31405
|
}
|
|
31394
31406
|
return false;
|
|
31395
31407
|
}
|
|
31396
|
-
function createGraphQLHandler(registry, repos, resolver, db, options) {
|
|
31397
|
-
const schema = buildGraphQLSchema(registry);
|
|
31408
|
+
function createGraphQLHandler(registry, repos, fieldKeyMaps, resolver, db, options) {
|
|
31409
|
+
const schema = buildGraphQLSchema(registry, fieldKeyMaps);
|
|
31398
31410
|
const { plugins } = buildArmorPlugin({
|
|
31399
31411
|
maxDepth: options.maxDepth,
|
|
31400
31412
|
maxComplexity: options.maxComplexity
|
package/dist/index.js
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
__require,
|
|
22
22
|
__toESM,
|
|
23
23
|
buildRelationsMap,
|
|
24
|
+
createFieldKeyMap,
|
|
24
25
|
deleteParagraphField,
|
|
25
26
|
entityKind,
|
|
26
27
|
fillPlaceholders,
|
|
@@ -28,6 +29,7 @@ import {
|
|
|
28
29
|
getTableUniqueName,
|
|
29
30
|
init_esm_shims,
|
|
30
31
|
is,
|
|
32
|
+
isColumnBacked,
|
|
31
33
|
isDriverValueEncoder,
|
|
32
34
|
isSQLWrapper,
|
|
33
35
|
persistJunctionField,
|
|
@@ -36,7 +38,7 @@ import {
|
|
|
36
38
|
resolveRelationField,
|
|
37
39
|
sql,
|
|
38
40
|
tracer
|
|
39
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-UNBMPHIT.js";
|
|
40
42
|
|
|
41
43
|
// ../../node_modules/.pnpm/yaml@2.9.0/node_modules/yaml/dist/nodes/identity.js
|
|
42
44
|
var require_identity = __commonJS({
|
|
@@ -46557,6 +46559,23 @@ var sign2 = Jwt.sign;
|
|
|
46557
46559
|
import { randomBytes } from "crypto";
|
|
46558
46560
|
import crypto3 from "crypto";
|
|
46559
46561
|
import crypto22 from "crypto";
|
|
46562
|
+
function normalizePrefix(prefix) {
|
|
46563
|
+
if (prefix === void 0) return "/api";
|
|
46564
|
+
const withSlash = prefix.startsWith("/") ? prefix : `/${prefix}`;
|
|
46565
|
+
const trimmed = withSlash.replace(/\/+$/, "");
|
|
46566
|
+
return trimmed === "" ? "/api" : trimmed;
|
|
46567
|
+
}
|
|
46568
|
+
function createPublicPaths(prefix) {
|
|
46569
|
+
return {
|
|
46570
|
+
collection: (basePath) => `${prefix}/${basePath}`,
|
|
46571
|
+
item: (basePath) => `${prefix}/${basePath}/:slug`,
|
|
46572
|
+
taxonomyCollection: (typeName) => `${prefix}/taxonomy/${typeName}`,
|
|
46573
|
+
taxonomyItem: (typeName) => `${prefix}/taxonomy/${typeName}/:id`,
|
|
46574
|
+
mediaCollection: () => `${prefix}/media`,
|
|
46575
|
+
mediaItem: () => `${prefix}/media/:id`,
|
|
46576
|
+
openapi: () => `${prefix}/openapi.json`
|
|
46577
|
+
};
|
|
46578
|
+
}
|
|
46560
46579
|
function createCorsMiddleware(corsConfig) {
|
|
46561
46580
|
const allowList = Array.isArray(corsConfig.origin) ? corsConfig.origin : [corsConfig.origin];
|
|
46562
46581
|
const wildcard = allowList.includes("*");
|
|
@@ -47122,28 +47141,31 @@ function parseInclude(includeParam) {
|
|
|
47122
47141
|
if (!includeParam) return [];
|
|
47123
47142
|
return includeParam.split(",").map((s) => s.trim()).filter(Boolean);
|
|
47124
47143
|
}
|
|
47125
|
-
function parseFilters(url2, validFields) {
|
|
47144
|
+
function parseFilters(url2, validFields, columnFor) {
|
|
47126
47145
|
const { searchParams } = new URL(url2);
|
|
47127
47146
|
const filters = {};
|
|
47147
|
+
const toKey = (label) => columnFor?.(label) ?? label;
|
|
47128
47148
|
for (const [key, value] of searchParams.entries()) {
|
|
47129
47149
|
const simpleMatch = /^filter\[([^\]]+)\]$/.exec(key);
|
|
47130
47150
|
const opMatch = /^filter\[([^\]]+)\]\[([^\]]+)\]$/.exec(key);
|
|
47131
47151
|
if (simpleMatch) {
|
|
47132
47152
|
const field = simpleMatch[1];
|
|
47133
47153
|
if (!validFields.has(field)) return { ok: false, invalidField: field };
|
|
47134
|
-
const
|
|
47154
|
+
const storageKey = toKey(field);
|
|
47155
|
+
const existing = filters[storageKey];
|
|
47135
47156
|
if (existing !== void 0) {
|
|
47136
|
-
filters[
|
|
47157
|
+
filters[storageKey] = Array.isArray(existing) ? [...existing, value] : [existing, value];
|
|
47137
47158
|
} else {
|
|
47138
|
-
filters[
|
|
47159
|
+
filters[storageKey] = value;
|
|
47139
47160
|
}
|
|
47140
47161
|
} else if (opMatch) {
|
|
47141
47162
|
const field = opMatch[1];
|
|
47142
47163
|
const operator = opMatch[2];
|
|
47143
47164
|
if (!validFields.has(field)) return { ok: false, invalidField: field };
|
|
47144
47165
|
if (!["gt", "gte", "lt", "lte"].includes(operator)) continue;
|
|
47145
|
-
const
|
|
47146
|
-
|
|
47166
|
+
const storageKey = toKey(field);
|
|
47167
|
+
const existing = filters[storageKey] ?? {};
|
|
47168
|
+
filters[storageKey] = { ...existing, [operator]: value };
|
|
47147
47169
|
}
|
|
47148
47170
|
}
|
|
47149
47171
|
return { ok: true, filters };
|
|
@@ -47151,7 +47173,7 @@ function parseFilters(url2, validFields) {
|
|
|
47151
47173
|
function isPublished(item) {
|
|
47152
47174
|
return item["published"] === true;
|
|
47153
47175
|
}
|
|
47154
|
-
function registerPublicContentRoutes(app, registry2, repos, listRateLimit, resolver) {
|
|
47176
|
+
function registerPublicContentRoutes(app, registry2, repos, fieldKeyMaps, paths, listRateLimit, resolver) {
|
|
47155
47177
|
function registerListRoute(path3, handler) {
|
|
47156
47178
|
if (listRateLimit) {
|
|
47157
47179
|
app.get(path3, listRateLimit, handler);
|
|
@@ -47159,7 +47181,7 @@ function registerPublicContentRoutes(app, registry2, repos, listRateLimit, resol
|
|
|
47159
47181
|
app.get(path3, handler);
|
|
47160
47182
|
}
|
|
47161
47183
|
}
|
|
47162
|
-
registerListRoute("
|
|
47184
|
+
registerListRoute(paths.collection("content"), (c) => {
|
|
47163
47185
|
const data = Object.values(registry2.content_types).map((ct2) => ({
|
|
47164
47186
|
name: ct2.name,
|
|
47165
47187
|
label: ct2.label,
|
|
@@ -47167,7 +47189,7 @@ function registerPublicContentRoutes(app, registry2, repos, listRateLimit, resol
|
|
|
47167
47189
|
}));
|
|
47168
47190
|
return c.json({ ok: true, data });
|
|
47169
47191
|
});
|
|
47170
|
-
registerListRoute("
|
|
47192
|
+
registerListRoute(paths.collection("taxonomy"), (c) => {
|
|
47171
47193
|
const data = Object.values(registry2.taxonomy_types).map((tt2) => ({
|
|
47172
47194
|
name: tt2.name,
|
|
47173
47195
|
label: tt2.label
|
|
@@ -47186,7 +47208,7 @@ function registerPublicContentRoutes(app, registry2, repos, listRateLimit, resol
|
|
|
47186
47208
|
...contentType.system_fields.map((f) => f.name)
|
|
47187
47209
|
]);
|
|
47188
47210
|
if (contentType.only_one) {
|
|
47189
|
-
app.get(
|
|
47211
|
+
app.get(paths.collection(basePath), async (c) => {
|
|
47190
47212
|
const result = await repo.findMany({ published_only: true, page: 1, per_page: 1 });
|
|
47191
47213
|
if (result.data.length === 0) {
|
|
47192
47214
|
return c.json(
|
|
@@ -47194,12 +47216,13 @@ function registerPublicContentRoutes(app, registry2, repos, listRateLimit, resol
|
|
|
47194
47216
|
404
|
|
47195
47217
|
);
|
|
47196
47218
|
}
|
|
47197
|
-
let data = result.data[0];
|
|
47219
|
+
let data = fieldKeyMaps[typeName].toLabels(result.data[0]);
|
|
47198
47220
|
if (resolver?.hasSchema(typeName)) data = await resolver.resolveItem(typeName, data);
|
|
47199
47221
|
return c.json({ ok: true, data });
|
|
47200
47222
|
});
|
|
47201
47223
|
} else {
|
|
47202
|
-
registerListRoute(
|
|
47224
|
+
registerListRoute(paths.collection(basePath), async (c) => {
|
|
47225
|
+
const fieldKeys = fieldKeyMaps[typeName];
|
|
47203
47226
|
const pagination = parsePagination(c.req.query("page"), c.req.query("per_page"));
|
|
47204
47227
|
if (!pagination.ok) {
|
|
47205
47228
|
return c.json(
|
|
@@ -47239,7 +47262,7 @@ function registerPublicContentRoutes(app, registry2, repos, listRateLimit, resol
|
|
|
47239
47262
|
400
|
|
47240
47263
|
);
|
|
47241
47264
|
}
|
|
47242
|
-
const filtersResult = parseFilters(c.req.url, filterableFieldNames);
|
|
47265
|
+
const filtersResult = parseFilters(c.req.url, filterableFieldNames, fieldKeys.columnFor);
|
|
47243
47266
|
if (!filtersResult.ok) {
|
|
47244
47267
|
return c.json(
|
|
47245
47268
|
{
|
|
@@ -47276,13 +47299,13 @@ function registerPublicContentRoutes(app, registry2, repos, listRateLimit, resol
|
|
|
47276
47299
|
filters: filtersResult.filters,
|
|
47277
47300
|
include
|
|
47278
47301
|
});
|
|
47279
|
-
|
|
47280
|
-
|
|
47281
|
-
|
|
47282
|
-
|
|
47283
|
-
return c.json(result);
|
|
47302
|
+
const labeled = result.data.map(
|
|
47303
|
+
(row) => fieldKeys.toLabels(row)
|
|
47304
|
+
);
|
|
47305
|
+
const data = resolver?.hasSchema(typeName) ? await resolver.resolveList(typeName, labeled) : labeled;
|
|
47306
|
+
return c.json({ ...result, data });
|
|
47284
47307
|
});
|
|
47285
|
-
app.get(
|
|
47308
|
+
app.get(paths.item(basePath), async (c) => {
|
|
47286
47309
|
const slug = c.req.param("slug");
|
|
47287
47310
|
const include = parseInclude(c.req.query("include"));
|
|
47288
47311
|
for (const field of include) {
|
|
@@ -47309,7 +47332,7 @@ function registerPublicContentRoutes(app, registry2, repos, listRateLimit, resol
|
|
|
47309
47332
|
404
|
|
47310
47333
|
);
|
|
47311
47334
|
}
|
|
47312
|
-
let data = item;
|
|
47335
|
+
let data = fieldKeyMaps[typeName].toLabels(item);
|
|
47313
47336
|
if (resolver?.hasSchema(typeName)) data = await resolver.resolveItem(typeName, data);
|
|
47314
47337
|
return c.json({ ok: true, data });
|
|
47315
47338
|
});
|
|
@@ -47318,7 +47341,7 @@ function registerPublicContentRoutes(app, registry2, repos, listRateLimit, resol
|
|
|
47318
47341
|
for (const [typeName] of Object.entries(registry2.taxonomy_types)) {
|
|
47319
47342
|
const repo = repos[typeName];
|
|
47320
47343
|
if (!repo) continue;
|
|
47321
|
-
registerListRoute(
|
|
47344
|
+
registerListRoute(paths.taxonomyCollection(typeName), async (c) => {
|
|
47322
47345
|
const pagination = parsePagination(c.req.query("page"), c.req.query("per_page"));
|
|
47323
47346
|
if (!pagination.ok) {
|
|
47324
47347
|
return c.json(
|
|
@@ -47337,13 +47360,14 @@ function registerPublicContentRoutes(app, registry2, repos, listRateLimit, resol
|
|
|
47337
47360
|
page: pagination.page,
|
|
47338
47361
|
per_page: pagination.per_page
|
|
47339
47362
|
});
|
|
47340
|
-
|
|
47341
|
-
|
|
47342
|
-
|
|
47343
|
-
|
|
47344
|
-
|
|
47363
|
+
const fieldKeys = fieldKeyMaps[typeName];
|
|
47364
|
+
const labeled = result.data.map(
|
|
47365
|
+
(row) => fieldKeys.toLabels(row)
|
|
47366
|
+
);
|
|
47367
|
+
const data = resolver?.hasSchema(typeName) ? await resolver.resolveList(typeName, labeled) : labeled;
|
|
47368
|
+
return c.json({ ...result, data });
|
|
47345
47369
|
});
|
|
47346
|
-
app.get(
|
|
47370
|
+
app.get(paths.taxonomyItem(typeName), async (c) => {
|
|
47347
47371
|
const id = c.req.param("id");
|
|
47348
47372
|
const item = await repo.findOne(id);
|
|
47349
47373
|
if (!item || !isPublished(item)) {
|
|
@@ -47355,7 +47379,7 @@ function registerPublicContentRoutes(app, registry2, repos, listRateLimit, resol
|
|
|
47355
47379
|
404
|
|
47356
47380
|
);
|
|
47357
47381
|
}
|
|
47358
|
-
let data = item;
|
|
47382
|
+
let data = fieldKeyMaps[typeName].toLabels(item);
|
|
47359
47383
|
if (resolver?.hasSchema(typeName)) data = await resolver.resolveItem(typeName, data);
|
|
47360
47384
|
return c.json({ ok: true, data });
|
|
47361
47385
|
});
|
|
@@ -47369,7 +47393,7 @@ function parsePagination2(pageStr, perPageStr) {
|
|
|
47369
47393
|
if (!Number.isInteger(per_page) || per_page < 1 || per_page > 100) return { ok: false };
|
|
47370
47394
|
return { ok: true, page, per_page };
|
|
47371
47395
|
}
|
|
47372
|
-
function registerPublicMediaRoutes(app, mediaRepo, listRateLimit) {
|
|
47396
|
+
function registerPublicMediaRoutes(app, mediaRepo, paths, listRateLimit) {
|
|
47373
47397
|
const mediaListHandler = async (c) => {
|
|
47374
47398
|
const pagination = parsePagination2(c.req.query("page"), c.req.query("per_page"));
|
|
47375
47399
|
if (!pagination.ok) {
|
|
@@ -47408,11 +47432,11 @@ function registerPublicMediaRoutes(app, mediaRepo, listRateLimit) {
|
|
|
47408
47432
|
return c.json(result);
|
|
47409
47433
|
};
|
|
47410
47434
|
if (listRateLimit) {
|
|
47411
|
-
app.get(
|
|
47435
|
+
app.get(paths.mediaCollection(), listRateLimit, mediaListHandler);
|
|
47412
47436
|
} else {
|
|
47413
|
-
app.get(
|
|
47437
|
+
app.get(paths.mediaCollection(), mediaListHandler);
|
|
47414
47438
|
}
|
|
47415
|
-
app.get(
|
|
47439
|
+
app.get(paths.mediaItem(), async (c) => {
|
|
47416
47440
|
const id = c.req.param("id");
|
|
47417
47441
|
const item = await mediaRepo.findOne(id);
|
|
47418
47442
|
if (!item) {
|
|
@@ -47431,9 +47455,11 @@ function topLevelMediaDelta(mediaFields, before, after) {
|
|
|
47431
47455
|
const added = [];
|
|
47432
47456
|
const removed = [];
|
|
47433
47457
|
for (const f of mediaFields) {
|
|
47434
|
-
|
|
47435
|
-
|
|
47436
|
-
|
|
47458
|
+
const key = f.db_column?.column_name;
|
|
47459
|
+
if (key === void 0 || key === "") continue;
|
|
47460
|
+
if (after !== null && !(key in after)) continue;
|
|
47461
|
+
const oldId = before ? extractMediaId(before[key]) : null;
|
|
47462
|
+
const newId = after ? extractMediaId(after[key]) : null;
|
|
47437
47463
|
if (newId === oldId) continue;
|
|
47438
47464
|
if (oldId) removed.push(oldId);
|
|
47439
47465
|
if (newId) added.push(newId);
|
|
@@ -47491,7 +47517,7 @@ function isEmpty(value) {
|
|
|
47491
47517
|
function checkRequiredFields(fields, data) {
|
|
47492
47518
|
return fields.filter((f) => f.required && isEmpty(data[f.name])).map((f) => ({ field: f.name, message: `${f.label} is required` }));
|
|
47493
47519
|
}
|
|
47494
|
-
function parseListQuery(url2, schemaFieldNames, relationFieldNames) {
|
|
47520
|
+
function parseListQuery(url2, schemaFieldNames, relationFieldNames, columnFor) {
|
|
47495
47521
|
const searchParams = new URL(url2).searchParams;
|
|
47496
47522
|
const pagination = parsePagination(
|
|
47497
47523
|
searchParams.get("page") ?? void 0,
|
|
@@ -47526,7 +47552,7 @@ function parseListQuery(url2, schemaFieldNames, relationFieldNames) {
|
|
|
47526
47552
|
status: 400
|
|
47527
47553
|
};
|
|
47528
47554
|
}
|
|
47529
|
-
const filtersResult = parseFilters(url2, schemaFieldNames);
|
|
47555
|
+
const filtersResult = parseFilters(url2, schemaFieldNames, columnFor);
|
|
47530
47556
|
if (!filtersResult.ok) {
|
|
47531
47557
|
return {
|
|
47532
47558
|
ok: false,
|
|
@@ -47558,7 +47584,7 @@ function parseListQuery(url2, schemaFieldNames, relationFieldNames) {
|
|
|
47558
47584
|
search
|
|
47559
47585
|
};
|
|
47560
47586
|
}
|
|
47561
|
-
function registerAdminContentRoutes(app, registry2, repos, mediaRepo, requirePermission, db) {
|
|
47587
|
+
function registerAdminContentRoutes(app, registry2, repos, fieldKeyMaps, mediaRepo, requirePermission, db) {
|
|
47562
47588
|
for (const [typeName, contentType] of Object.entries(registry2.content_types)) {
|
|
47563
47589
|
const basePath = `content/${typeName}`;
|
|
47564
47590
|
const repo = repos[typeName];
|
|
@@ -47570,6 +47596,7 @@ function registerAdminContentRoutes(app, registry2, repos, mediaRepo, requirePer
|
|
|
47570
47596
|
const relationFieldNames = new Set(
|
|
47571
47597
|
contentType.fields.filter((f) => RELATION_FIELD_TYPES.has(f.field_type)).map((f) => f.name)
|
|
47572
47598
|
);
|
|
47599
|
+
const fieldKeys = fieldKeyMaps[typeName];
|
|
47573
47600
|
const requiredFields = contentType.fields.filter((f) => f.required);
|
|
47574
47601
|
const mediaFields = contentType.fields.filter(
|
|
47575
47602
|
(f) => f.field_type === "image" || f.field_type === "video" || f.field_type === "file"
|
|
@@ -47583,7 +47610,7 @@ function registerAdminContentRoutes(app, registry2, repos, mediaRepo, requirePer
|
|
|
47583
47610
|
`/admin/api/${basePath}`,
|
|
47584
47611
|
requirePermission("content:read"),
|
|
47585
47612
|
async (c) => {
|
|
47586
|
-
const parsed = parseListQuery(c.req.url, schemaFieldNames, relationFieldNames);
|
|
47613
|
+
const parsed = parseListQuery(c.req.url, schemaFieldNames, relationFieldNames, fieldKeys.columnFor);
|
|
47587
47614
|
if (!parsed.ok) {
|
|
47588
47615
|
return c.json({ ok: false, error: parsed.response }, parsed.status);
|
|
47589
47616
|
}
|
|
@@ -47603,7 +47630,8 @@ function registerAdminContentRoutes(app, registry2, repos, mediaRepo, requirePer
|
|
|
47603
47630
|
findOpts.search = { term: parsed.search, columns: searchableColumns };
|
|
47604
47631
|
}
|
|
47605
47632
|
const result = await repo.findMany(findOpts);
|
|
47606
|
-
|
|
47633
|
+
const data = result.data.map((row) => fieldKeys.toLabels(row));
|
|
47634
|
+
return c.json({ ...result, data });
|
|
47607
47635
|
}
|
|
47608
47636
|
);
|
|
47609
47637
|
app.get(
|
|
@@ -47638,7 +47666,8 @@ function registerAdminContentRoutes(app, registry2, repos, mediaRepo, requirePer
|
|
|
47638
47666
|
}
|
|
47639
47667
|
}
|
|
47640
47668
|
}
|
|
47641
|
-
|
|
47669
|
+
const data = fieldKeys.toLabels(item);
|
|
47670
|
+
return c.json({ ok: true, data });
|
|
47642
47671
|
}
|
|
47643
47672
|
);
|
|
47644
47673
|
app.post(
|
|
@@ -47705,6 +47734,7 @@ function registerAdminContentRoutes(app, registry2, repos, mediaRepo, requirePer
|
|
|
47705
47734
|
}
|
|
47706
47735
|
);
|
|
47707
47736
|
const writeNewItem = async (c, body) => {
|
|
47737
|
+
const storageBody = fieldKeys.toStorage(body);
|
|
47708
47738
|
const fieldErrors = checkRequiredFields(requiredFields, body);
|
|
47709
47739
|
if (fieldErrors.length > 0) {
|
|
47710
47740
|
return c.json(
|
|
@@ -47728,9 +47758,7 @@ function registerAdminContentRoutes(app, registry2, repos, mediaRepo, requirePer
|
|
|
47728
47758
|
const junctionFields = contentType.fields.filter(
|
|
47729
47759
|
(f) => f.db_column !== null && f.db_column.junction !== void 0
|
|
47730
47760
|
);
|
|
47731
|
-
const
|
|
47732
|
-
(f) => f.db_column !== null && f.db_column.junction === void 0
|
|
47733
|
-
);
|
|
47761
|
+
const columnBackedFields = contentType.fields.filter(isColumnBacked);
|
|
47734
47762
|
const columnData = {
|
|
47735
47763
|
published: body["published"] ?? false
|
|
47736
47764
|
};
|
|
@@ -47739,9 +47767,10 @@ function registerAdminContentRoutes(app, registry2, repos, mediaRepo, requirePer
|
|
|
47739
47767
|
} else {
|
|
47740
47768
|
columnData["slug"] = body["slug"];
|
|
47741
47769
|
}
|
|
47742
|
-
for (const f of
|
|
47743
|
-
const
|
|
47744
|
-
|
|
47770
|
+
for (const f of columnBackedFields) {
|
|
47771
|
+
const key = f.db_column.column_name;
|
|
47772
|
+
if (!(key in storageBody)) continue;
|
|
47773
|
+
columnData[key] = storageBody[key];
|
|
47745
47774
|
}
|
|
47746
47775
|
if (db) {
|
|
47747
47776
|
const basePathId = await lookupBasePathId(db, contentType.default_base_path);
|
|
@@ -47761,7 +47790,7 @@ function registerAdminContentRoutes(app, registry2, repos, mediaRepo, requirePer
|
|
|
47761
47790
|
}
|
|
47762
47791
|
const item = await repo.create(columnData);
|
|
47763
47792
|
const itemId = item["id"];
|
|
47764
|
-
const mediaDeltas = [topLevelMediaDelta(mediaFields, null,
|
|
47793
|
+
const mediaDeltas = [topLevelMediaDelta(mediaFields, null, storageBody)];
|
|
47765
47794
|
if (db) {
|
|
47766
47795
|
for (const f of paragraphFields) {
|
|
47767
47796
|
const comp = f.ui_component;
|
|
@@ -47778,7 +47807,8 @@ function registerAdminContentRoutes(app, registry2, repos, mediaRepo, requirePer
|
|
|
47778
47807
|
}
|
|
47779
47808
|
}
|
|
47780
47809
|
await applyMediaReferenceDelta(mergeMediaDeltas(...mediaDeltas), mediaRepo);
|
|
47781
|
-
|
|
47810
|
+
const data = fieldKeys.toLabels(item);
|
|
47811
|
+
return c.json({ ok: true, data }, 201);
|
|
47782
47812
|
};
|
|
47783
47813
|
app.patch(
|
|
47784
47814
|
`/admin/api/${basePath}/:id`,
|
|
@@ -47838,11 +47868,12 @@ function registerAdminContentRoutes(app, registry2, repos, mediaRepo, requirePer
|
|
|
47838
47868
|
}
|
|
47839
47869
|
);
|
|
47840
47870
|
const writeExistingItem = async (c, id, existing, body) => {
|
|
47871
|
+
const storageBody = fieldKeys.toStorage(body);
|
|
47841
47872
|
if (body["published"] === true) {
|
|
47842
47873
|
const publishDeny = await requirePermission("content:edit")(c, async () => {
|
|
47843
47874
|
});
|
|
47844
47875
|
if (publishDeny) return publishDeny;
|
|
47845
|
-
const merged = { ...existing, ...body };
|
|
47876
|
+
const merged = { ...fieldKeys.toLabels(existing), ...body };
|
|
47846
47877
|
const fieldErrors = checkRequiredFields(requiredFields, merged);
|
|
47847
47878
|
if (fieldErrors.length > 0) {
|
|
47848
47879
|
return c.json(
|
|
@@ -47862,17 +47893,14 @@ function registerAdminContentRoutes(app, registry2, repos, mediaRepo, requirePer
|
|
|
47862
47893
|
const patchJunctionFields = contentType.fields.filter(
|
|
47863
47894
|
(f) => f.db_column !== null && f.db_column.junction !== void 0
|
|
47864
47895
|
);
|
|
47865
|
-
const
|
|
47866
|
-
(f) => f.db_column !== null && f.db_column.junction === void 0
|
|
47867
|
-
);
|
|
47896
|
+
const patchColumnBackedFields = contentType.fields.filter(isColumnBacked);
|
|
47868
47897
|
const patchData = {};
|
|
47869
47898
|
if (!contentType.only_one && "slug" in body) patchData["slug"] = body["slug"];
|
|
47870
47899
|
if ("published" in body) patchData["published"] = body["published"];
|
|
47871
|
-
for (const f of
|
|
47872
|
-
|
|
47873
|
-
|
|
47874
|
-
|
|
47875
|
-
}
|
|
47900
|
+
for (const f of patchColumnBackedFields) {
|
|
47901
|
+
const key = f.db_column.column_name;
|
|
47902
|
+
if (!(key in storageBody)) continue;
|
|
47903
|
+
patchData[key] = storageBody[key];
|
|
47876
47904
|
}
|
|
47877
47905
|
const updated = await repo.update(id, patchData);
|
|
47878
47906
|
if (!updated) {
|
|
@@ -47882,7 +47910,7 @@ function registerAdminContentRoutes(app, registry2, repos, mediaRepo, requirePer
|
|
|
47882
47910
|
);
|
|
47883
47911
|
}
|
|
47884
47912
|
const mediaDeltas = [
|
|
47885
|
-
topLevelMediaDelta(mediaFields, existing,
|
|
47913
|
+
topLevelMediaDelta(mediaFields, existing, storageBody)
|
|
47886
47914
|
];
|
|
47887
47915
|
if (db) {
|
|
47888
47916
|
for (const f of patchParagraphFields) {
|
|
@@ -47900,7 +47928,8 @@ function registerAdminContentRoutes(app, registry2, repos, mediaRepo, requirePer
|
|
|
47900
47928
|
}
|
|
47901
47929
|
}
|
|
47902
47930
|
await applyMediaReferenceDelta(mergeMediaDeltas(...mediaDeltas), mediaRepo);
|
|
47903
|
-
|
|
47931
|
+
const data = fieldKeys.toLabels(updated);
|
|
47932
|
+
return c.json({ ok: true, data });
|
|
47904
47933
|
};
|
|
47905
47934
|
if (contentType.only_one) {
|
|
47906
47935
|
app.put(
|
|
@@ -47960,6 +47989,7 @@ function registerAdminContentRoutes(app, registry2, repos, mediaRepo, requirePer
|
|
|
47960
47989
|
const relationFieldNames = new Set(
|
|
47961
47990
|
taxonomyType.fields.filter((f) => RELATION_FIELD_TYPES.has(f.field_type)).map((f) => f.name)
|
|
47962
47991
|
);
|
|
47992
|
+
const fieldKeys = fieldKeyMaps[typeName];
|
|
47963
47993
|
const requiredFields = taxonomyType.fields.filter((f) => f.required);
|
|
47964
47994
|
const mediaFields = taxonomyType.fields.filter(
|
|
47965
47995
|
(f) => f.field_type === "image" || f.field_type === "video" || f.field_type === "file"
|
|
@@ -47973,7 +48003,7 @@ function registerAdminContentRoutes(app, registry2, repos, mediaRepo, requirePer
|
|
|
47973
48003
|
`/admin/api/taxonomy/${typeName}`,
|
|
47974
48004
|
requirePermission("content:read"),
|
|
47975
48005
|
async (c) => {
|
|
47976
|
-
const parsed = parseListQuery(c.req.url, schemaFieldNames, relationFieldNames);
|
|
48006
|
+
const parsed = parseListQuery(c.req.url, schemaFieldNames, relationFieldNames, fieldKeys.columnFor);
|
|
47977
48007
|
if (!parsed.ok) {
|
|
47978
48008
|
return c.json({ ok: false, error: parsed.response }, parsed.status);
|
|
47979
48009
|
}
|
|
@@ -47993,7 +48023,8 @@ function registerAdminContentRoutes(app, registry2, repos, mediaRepo, requirePer
|
|
|
47993
48023
|
findOpts.search = { term: parsed.search, columns: searchableColumns };
|
|
47994
48024
|
}
|
|
47995
48025
|
const result = await repo.findMany(findOpts);
|
|
47996
|
-
|
|
48026
|
+
const data = result.data.map((row) => fieldKeys.toLabels(row));
|
|
48027
|
+
return c.json({ ...result, data });
|
|
47997
48028
|
}
|
|
47998
48029
|
);
|
|
47999
48030
|
app.get(
|
|
@@ -48008,7 +48039,8 @@ function registerAdminContentRoutes(app, registry2, repos, mediaRepo, requirePer
|
|
|
48008
48039
|
404
|
|
48009
48040
|
);
|
|
48010
48041
|
}
|
|
48011
|
-
|
|
48042
|
+
const data = fieldKeys.toLabels(item);
|
|
48043
|
+
return c.json({ ok: true, data });
|
|
48012
48044
|
}
|
|
48013
48045
|
);
|
|
48014
48046
|
app.post(
|
|
@@ -48016,6 +48048,7 @@ function registerAdminContentRoutes(app, registry2, repos, mediaRepo, requirePer
|
|
|
48016
48048
|
requirePermission("content:create"),
|
|
48017
48049
|
async (c) => {
|
|
48018
48050
|
const body = await c.req.json();
|
|
48051
|
+
const storageBody = fieldKeys.toStorage(body);
|
|
48019
48052
|
const fieldErrors = checkRequiredFields(requiredFields, body);
|
|
48020
48053
|
if (fieldErrors.length > 0) {
|
|
48021
48054
|
return c.json(
|
|
@@ -48035,20 +48068,19 @@ function registerAdminContentRoutes(app, registry2, repos, mediaRepo, requirePer
|
|
|
48035
48068
|
});
|
|
48036
48069
|
if (publishDeny) return publishDeny;
|
|
48037
48070
|
}
|
|
48038
|
-
const taxColumnFields = taxonomyType.fields.filter(
|
|
48039
|
-
(f) => f.db_column !== null && f.db_column.junction === void 0
|
|
48040
|
-
);
|
|
48041
48071
|
const taxParagraphFields = taxonomyType.fields.filter((f) => f.db_column === null);
|
|
48072
|
+
const taxColumnBackedFields = taxonomyType.fields.filter(isColumnBacked);
|
|
48042
48073
|
const taxColumnData = {
|
|
48043
48074
|
published: body["published"] ?? false
|
|
48044
48075
|
};
|
|
48045
|
-
for (const f of
|
|
48046
|
-
const
|
|
48047
|
-
|
|
48076
|
+
for (const f of taxColumnBackedFields) {
|
|
48077
|
+
const key = f.db_column.column_name;
|
|
48078
|
+
if (!(key in storageBody)) continue;
|
|
48079
|
+
taxColumnData[key] = storageBody[key];
|
|
48048
48080
|
}
|
|
48049
48081
|
const item = await repo.create(taxColumnData);
|
|
48050
48082
|
const taxItemId = item["id"];
|
|
48051
|
-
const mediaDeltas = [topLevelMediaDelta(mediaFields, null,
|
|
48083
|
+
const mediaDeltas = [topLevelMediaDelta(mediaFields, null, storageBody)];
|
|
48052
48084
|
if (db) {
|
|
48053
48085
|
for (const f of taxParagraphFields) {
|
|
48054
48086
|
const comp = f.ui_component;
|
|
@@ -48060,7 +48092,8 @@ function registerAdminContentRoutes(app, registry2, repos, mediaRepo, requirePer
|
|
|
48060
48092
|
}
|
|
48061
48093
|
}
|
|
48062
48094
|
await applyMediaReferenceDelta(mergeMediaDeltas(...mediaDeltas), mediaRepo);
|
|
48063
|
-
|
|
48095
|
+
const data = fieldKeys.toLabels(item);
|
|
48096
|
+
return c.json({ ok: true, data }, 201);
|
|
48064
48097
|
}
|
|
48065
48098
|
);
|
|
48066
48099
|
app.patch(
|
|
@@ -48069,6 +48102,7 @@ function registerAdminContentRoutes(app, registry2, repos, mediaRepo, requirePer
|
|
|
48069
48102
|
async (c) => {
|
|
48070
48103
|
const id = c.req.param("id");
|
|
48071
48104
|
const body = await c.req.json();
|
|
48105
|
+
const storageBody = fieldKeys.toStorage(body);
|
|
48072
48106
|
const existing = await repo.findOne(id);
|
|
48073
48107
|
if (!existing) {
|
|
48074
48108
|
return c.json(
|
|
@@ -48080,7 +48114,7 @@ function registerAdminContentRoutes(app, registry2, repos, mediaRepo, requirePer
|
|
|
48080
48114
|
const publishDeny = await requirePermission("content:edit")(c, async () => {
|
|
48081
48115
|
});
|
|
48082
48116
|
if (publishDeny) return publishDeny;
|
|
48083
|
-
const merged = { ...existing, ...body };
|
|
48117
|
+
const merged = { ...fieldKeys.toLabels(existing), ...body };
|
|
48084
48118
|
const fieldErrors = checkRequiredFields(requiredFields, merged);
|
|
48085
48119
|
if (fieldErrors.length > 0) {
|
|
48086
48120
|
return c.json(
|
|
@@ -48096,17 +48130,14 @@ function registerAdminContentRoutes(app, registry2, repos, mediaRepo, requirePer
|
|
|
48096
48130
|
);
|
|
48097
48131
|
}
|
|
48098
48132
|
}
|
|
48099
|
-
const taxPatchColumnFields = taxonomyType.fields.filter(
|
|
48100
|
-
(f) => f.db_column !== null && f.db_column.junction === void 0
|
|
48101
|
-
);
|
|
48102
48133
|
const taxPatchParagraphFields = taxonomyType.fields.filter((f) => f.db_column === null);
|
|
48134
|
+
const taxPatchColumnBackedFields = taxonomyType.fields.filter(isColumnBacked);
|
|
48103
48135
|
const taxPatchData = {};
|
|
48104
48136
|
if ("published" in body) taxPatchData["published"] = body["published"];
|
|
48105
|
-
for (const f of
|
|
48106
|
-
|
|
48107
|
-
|
|
48108
|
-
|
|
48109
|
-
}
|
|
48137
|
+
for (const f of taxPatchColumnBackedFields) {
|
|
48138
|
+
const key = f.db_column.column_name;
|
|
48139
|
+
if (!(key in storageBody)) continue;
|
|
48140
|
+
taxPatchData[key] = storageBody[key];
|
|
48110
48141
|
}
|
|
48111
48142
|
const updated = await repo.update(id, taxPatchData);
|
|
48112
48143
|
if (!updated) {
|
|
@@ -48116,7 +48147,7 @@ function registerAdminContentRoutes(app, registry2, repos, mediaRepo, requirePer
|
|
|
48116
48147
|
);
|
|
48117
48148
|
}
|
|
48118
48149
|
const mediaDeltas = [
|
|
48119
|
-
topLevelMediaDelta(mediaFields, existing,
|
|
48150
|
+
topLevelMediaDelta(mediaFields, existing, storageBody)
|
|
48120
48151
|
];
|
|
48121
48152
|
if (db) {
|
|
48122
48153
|
for (const f of taxPatchParagraphFields) {
|
|
@@ -48129,7 +48160,8 @@ function registerAdminContentRoutes(app, registry2, repos, mediaRepo, requirePer
|
|
|
48129
48160
|
}
|
|
48130
48161
|
}
|
|
48131
48162
|
await applyMediaReferenceDelta(mergeMediaDeltas(...mediaDeltas), mediaRepo);
|
|
48132
|
-
|
|
48163
|
+
const data = fieldKeys.toLabels(updated);
|
|
48164
|
+
return c.json({ ok: true, data });
|
|
48133
48165
|
}
|
|
48134
48166
|
);
|
|
48135
48167
|
app.delete(
|
|
@@ -49102,7 +49134,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
49102
49134
|
}
|
|
49103
49135
|
var API_VERSION = (() => {
|
|
49104
49136
|
try {
|
|
49105
|
-
return "0.
|
|
49137
|
+
return "0.4.0";
|
|
49106
49138
|
} catch {
|
|
49107
49139
|
return "0.0.0";
|
|
49108
49140
|
}
|
|
@@ -49500,7 +49532,8 @@ function createCmsApp(options) {
|
|
|
49500
49532
|
if (!options.storage) {
|
|
49501
49533
|
throw new Error(MISSING_STORAGE_ERROR);
|
|
49502
49534
|
}
|
|
49503
|
-
const prefix = options.prefix
|
|
49535
|
+
const prefix = normalizePrefix(options.prefix);
|
|
49536
|
+
const publicPaths = createPublicPaths(prefix);
|
|
49504
49537
|
const { storage, registry: registry2, db, rateLimit, media, cors } = options;
|
|
49505
49538
|
const cmsName = options.name ?? "Manguito CMS";
|
|
49506
49539
|
const maxFileSize = media?.max_file_size;
|
|
@@ -49580,23 +49613,27 @@ function createCmsApp(options) {
|
|
|
49580
49613
|
registerAuthRoutes(app, db);
|
|
49581
49614
|
app.use("/admin/api/*", authMiddleware);
|
|
49582
49615
|
app.use("/admin/api/*", mustChangePasswordCheck);
|
|
49583
|
-
app.get(
|
|
49616
|
+
app.get(publicPaths.openapi(), (c) => {
|
|
49584
49617
|
const paths = {};
|
|
49585
49618
|
for (const ct2 of Object.values(registry2.content_types)) {
|
|
49586
49619
|
const base = ct2.default_base_path;
|
|
49587
49620
|
if (ct2.only_one) {
|
|
49588
|
-
paths[
|
|
49621
|
+
paths[publicPaths.collection(base)] = { get: { summary: `Get ${ct2.label}`, tags: [ct2.label] } };
|
|
49589
49622
|
} else {
|
|
49590
|
-
paths[
|
|
49591
|
-
paths[
|
|
49623
|
+
paths[publicPaths.collection(base)] = { get: { summary: `List published ${ct2.label}`, tags: [ct2.label] } };
|
|
49624
|
+
paths[publicPaths.item(base).replace(":slug", "{slug}")] = {
|
|
49625
|
+
get: { summary: `Get ${ct2.label} by slug`, tags: [ct2.label] }
|
|
49626
|
+
};
|
|
49592
49627
|
}
|
|
49593
49628
|
}
|
|
49594
49629
|
for (const tt2 of Object.values(registry2.taxonomy_types)) {
|
|
49595
|
-
paths[
|
|
49596
|
-
paths[
|
|
49630
|
+
paths[publicPaths.taxonomyCollection(tt2.name)] = { get: { summary: `List published ${tt2.label}`, tags: [tt2.label] } };
|
|
49631
|
+
paths[publicPaths.taxonomyItem(tt2.name).replace(":id", "{id}")] = {
|
|
49632
|
+
get: { summary: `Get ${tt2.label} by id`, tags: [tt2.label] }
|
|
49633
|
+
};
|
|
49597
49634
|
}
|
|
49598
|
-
paths[
|
|
49599
|
-
paths["
|
|
49635
|
+
paths[publicPaths.mediaCollection()] = { get: { summary: "List media items", tags: ["Media"] } };
|
|
49636
|
+
paths[publicPaths.mediaItem().replace(":id", "{id}")] = { get: { summary: "Get media item by id", tags: ["Media"] } };
|
|
49600
49637
|
return c.json({
|
|
49601
49638
|
openapi: "3.0.3",
|
|
49602
49639
|
info: { title: "Manguito CMS Public API", version: "1.0.0" },
|
|
@@ -49611,14 +49648,24 @@ function createCmsApp(options) {
|
|
|
49611
49648
|
paths: {}
|
|
49612
49649
|
})
|
|
49613
49650
|
);
|
|
49614
|
-
|
|
49615
|
-
|
|
49651
|
+
const fieldKeyMaps = Object.fromEntries([
|
|
49652
|
+
...Object.entries(registry2.content_types).map(([typeName, ct2]) => [
|
|
49653
|
+
typeName,
|
|
49654
|
+
createFieldKeyMap(ct2.fields)
|
|
49655
|
+
]),
|
|
49656
|
+
...Object.entries(registry2.taxonomy_types).map(([typeName, tt2]) => [
|
|
49657
|
+
typeName,
|
|
49658
|
+
createFieldKeyMap(tt2.fields)
|
|
49659
|
+
])
|
|
49660
|
+
]);
|
|
49661
|
+
registerPublicContentRoutes(app, registry2, publicRepos, fieldKeyMaps, publicPaths, listRateLimit, programmaticResolver);
|
|
49662
|
+
registerPublicMediaRoutes(app, mediaRepo, publicPaths, listRateLimit);
|
|
49616
49663
|
if (options.graphql?.enabled) {
|
|
49617
49664
|
const gqlOptions = options.graphql;
|
|
49618
49665
|
let gqlHandler = null;
|
|
49619
49666
|
let initError = null;
|
|
49620
|
-
const ready = import("./handler-
|
|
49621
|
-
gqlHandler = createGraphQLHandler(registry2, graphqlRepos, programmaticResolver, db, gqlOptions);
|
|
49667
|
+
const ready = import("./handler-TPEULQ5W-5VGZ4FJU.js").then(({ createGraphQLHandler }) => {
|
|
49668
|
+
gqlHandler = createGraphQLHandler(registry2, graphqlRepos, fieldKeyMaps, programmaticResolver, db, gqlOptions);
|
|
49622
49669
|
}).catch((err) => {
|
|
49623
49670
|
initError = err;
|
|
49624
49671
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -49658,7 +49705,7 @@ function createCmsApp(options) {
|
|
|
49658
49705
|
);
|
|
49659
49706
|
registerSchemaRoute(app, registry2, db);
|
|
49660
49707
|
registerUserRoutes(app, db, requirePermission, requireHierarchy);
|
|
49661
|
-
registerAdminContentRoutes(app, registry2, repos, mediaRepo, requirePermission, db);
|
|
49708
|
+
registerAdminContentRoutes(app, registry2, repos, fieldKeyMaps, mediaRepo, requirePermission, db);
|
|
49662
49709
|
registerAdminMediaRoutes(app, mediaRepo, storage, requirePermission, maxFileSize);
|
|
49663
49710
|
return { prefix, app };
|
|
49664
49711
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bobbykim/manguito-cms-cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.4",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Bobby Kim <bobby.sihun.kim@gmail.com>",
|
|
6
6
|
"homepage": "https://github.com/bobbykim89/manguito-cms",
|
|
@@ -29,10 +29,10 @@
|
|
|
29
29
|
"tsup": "^8.5.1",
|
|
30
30
|
"typescript": "^6.0.3",
|
|
31
31
|
"vite": "^8.1.3",
|
|
32
|
-
"@bobbykim/manguito-cms-
|
|
32
|
+
"@bobbykim/manguito-cms-api": "0.4.0",
|
|
33
33
|
"@bobbykim/manguito-cms-core": "0.3.0",
|
|
34
|
-
"@bobbykim/manguito-cms-
|
|
35
|
-
"@bobbykim/manguito-cms-
|
|
34
|
+
"@bobbykim/manguito-cms-admin": "0.4.0",
|
|
35
|
+
"@bobbykim/manguito-cms-db": "0.1.3"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@types/node": "^25.9.5",
|