@bobbykim/manguito-cms-cli 0.4.0 → 0.4.3

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-FBS2RQI3.mjs
52
+ // ../api/dist/chunk-TK4DNRFF.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-FBS2RQI3.mjs
1215
+ // ../api/dist/chunk-TK4DNRFF.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}"`;
@@ -1239,16 +1239,46 @@ async function fetchParagraphMediaIds(db, pType, itemId, fieldName) {
1239
1239
  }
1240
1240
  return ids;
1241
1241
  }
1242
- async function persistParagraphField(db, itemId, parentType, fieldName, pType, items) {
1242
+ function nestedParagraphFields(pType, registry) {
1243
+ if (!registry) return [];
1244
+ const out = [];
1245
+ for (const f of pType.fields) {
1246
+ if (f.db_column !== null) continue;
1247
+ const comp = f.ui_component;
1248
+ if (comp.component !== "paragraph-embed" || !comp.ref) continue;
1249
+ const nType = registry.paragraph_types[comp.ref];
1250
+ if (nType) out.push({ fieldName: f.name, nType });
1251
+ }
1252
+ return out;
1253
+ }
1254
+ async function fetchParagraphRowIds(db, pType, itemId, fieldName) {
1255
+ const tbl = sql.raw(quoteIdent(pType.db.table_name));
1256
+ const result = await db.execute(
1257
+ sql`SELECT id FROM ${tbl} WHERE parent_id = ${itemId} AND parent_field = ${fieldName}`
1258
+ );
1259
+ return result.rows.map((r) => r["id"]).filter((id) => typeof id === "string");
1260
+ }
1261
+ async function persistParagraphField(db, itemId, parentType, fieldName, pType, items, registry) {
1243
1262
  const removed = await fetchParagraphMediaIds(db, pType, itemId, fieldName);
1263
+ const nested = nestedParagraphFields(pType, registry);
1264
+ if (nested.length > 0) {
1265
+ const staleIds = await fetchParagraphRowIds(db, pType, itemId, fieldName);
1266
+ for (const rowId of staleIds) {
1267
+ for (const n of nested) {
1268
+ const d = await deleteParagraphField(db, rowId, n.fieldName, n.nType, registry);
1269
+ removed.push(...d.removed);
1270
+ }
1271
+ }
1272
+ }
1244
1273
  const tbl = sql.raw(quoteIdent(pType.db.table_name));
1245
1274
  await db.execute(sql`DELETE FROM ${tbl} WHERE parent_id = ${itemId} AND parent_field = ${fieldName}`);
1246
1275
  const mediaFieldNames = paragraphMediaFieldNames(pType);
1247
1276
  const added = [];
1248
1277
  for (let i = 0; i < items.length; i++) {
1249
1278
  const pItem = items[i];
1279
+ const rowId = crypto.randomUUID();
1250
1280
  const data = {
1251
- id: crypto.randomUUID(),
1281
+ id: rowId,
1252
1282
  parent_id: itemId,
1253
1283
  parent_type: parentType,
1254
1284
  parent_field: fieldName,
@@ -1267,11 +1297,35 @@ async function persistParagraphField(db, itemId, parentType, fieldName, pType, i
1267
1297
  const val = pItem[name];
1268
1298
  if (typeof val === "string" && val !== "") added.push(val);
1269
1299
  }
1300
+ for (const n of nested) {
1301
+ const nestedItems = Array.isArray(pItem[n.fieldName]) ? pItem[n.fieldName] : [];
1302
+ const d = await persistParagraphField(
1303
+ db,
1304
+ rowId,
1305
+ pType.db.table_name,
1306
+ n.fieldName,
1307
+ n.nType,
1308
+ nestedItems,
1309
+ registry
1310
+ );
1311
+ added.push(...d.added);
1312
+ removed.push(...d.removed);
1313
+ }
1270
1314
  }
1271
1315
  return { added, removed };
1272
1316
  }
1273
- async function deleteParagraphField(db, itemId, fieldName, pType) {
1317
+ async function deleteParagraphField(db, itemId, fieldName, pType, registry) {
1274
1318
  const removed = await fetchParagraphMediaIds(db, pType, itemId, fieldName);
1319
+ const nested = nestedParagraphFields(pType, registry);
1320
+ if (nested.length > 0) {
1321
+ const rowIds = await fetchParagraphRowIds(db, pType, itemId, fieldName);
1322
+ for (const rowId of rowIds) {
1323
+ for (const n of nested) {
1324
+ const d = await deleteParagraphField(db, rowId, n.fieldName, n.nType, registry);
1325
+ removed.push(...d.removed);
1326
+ }
1327
+ }
1328
+ }
1275
1329
  const tbl = sql.raw(quoteIdent(pType.db.table_name));
1276
1330
  await db.execute(sql`DELETE FROM ${tbl} WHERE parent_id = ${itemId} AND parent_field = ${fieldName}`);
1277
1331
  return { added: [], removed };
@@ -1334,7 +1388,7 @@ async function resolveRelationField(db, rows, fieldName, rel, cache, publishedOn
1334
1388
  const parentIds = rows.map((r) => r["id"]);
1335
1389
  const inList = sql.join(parentIds.map((id) => sql`${id}`), sql`, `);
1336
1390
  const result = await db.execute(
1337
- sql`SELECT * FROM ${sql.raw(quoteIdent(rel.table))} WHERE parent_id IN (${inList}) ORDER BY "order" ASC`
1391
+ sql`SELECT * FROM ${sql.raw(quoteIdent(rel.table))} WHERE parent_id IN (${inList}) AND parent_field = ${fieldName} ORDER BY "order" ASC`
1338
1392
  );
1339
1393
  const byParent = groupBy(result.rows, "parent_id");
1340
1394
  for (const row of rows) {
@@ -1414,7 +1468,7 @@ async function resolveRelationBareIds(db, rows, fieldName, rel) {
1414
1468
  const parentIds = rows.map((r) => r["id"]);
1415
1469
  const inList = sql.join(parentIds.map((id) => sql`${id}`), sql`, `);
1416
1470
  const result = await db.execute(
1417
- sql`SELECT id, parent_id FROM ${sql.raw(quoteIdent(rel.table))} WHERE parent_id IN (${inList}) ORDER BY "order" ASC`
1471
+ sql`SELECT id, parent_id FROM ${sql.raw(quoteIdent(rel.table))} WHERE parent_id IN (${inList}) AND parent_field = ${fieldName} ORDER BY "order" ASC`
1418
1472
  );
1419
1473
  const byParent = groupBy(result.rows, "parent_id");
1420
1474
  for (const row of rows) {
@@ -10,7 +10,7 @@ import {
10
10
  buildRelationsMap,
11
11
  init_esm_shims,
12
12
  resolveRelationField
13
- } from "./chunk-UVGV7NVV.js";
13
+ } from "./chunk-OAUBUE7N.js";
14
14
 
15
15
  // ../../node_modules/.pnpm/@whatwg-node+fetch@0.10.13/node_modules/@whatwg-node/fetch/dist/shouldSkipPonyfill.js
16
16
  var require_shouldSkipPonyfill = __commonJS({
@@ -10785,10 +10785,13 @@ var require_parser = __commonJS({
10785
10785
  }
10786
10786
  });
10787
10787
 
10788
- // ../api/dist/handler-GMCFYZY5.mjs
10788
+ // ../api/dist/handler-CGHZG3J5.mjs
10789
10789
  init_esm_shims();
10790
10790
 
10791
- // ../api/dist/chunk-QXMW74MR.mjs
10791
+ // ../api/dist/chunk-GM55HGWA.mjs
10792
+ init_esm_shims();
10793
+
10794
+ // ../../node_modules/.pnpm/graphql-yoga@5.21.2_graphql@16.14.2/node_modules/graphql-yoga/esm/index.js
10792
10795
  init_esm_shims();
10793
10796
 
10794
10797
  // ../../node_modules/.pnpm/@graphql-yoga+logger@2.0.1/node_modules/@graphql-yoga/logger/esm/index.js
@@ -30118,7 +30121,7 @@ function createYoga(options) {
30118
30121
  });
30119
30122
  }
30120
30123
 
30121
- // ../api/dist/chunk-QXMW74MR.mjs
30124
+ // ../api/dist/chunk-GM55HGWA.mjs
30122
30125
  var import_dataloader = __toESM(require_dataloader(), 1);
30123
30126
 
30124
30127
  // ../../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
@@ -30876,7 +30879,7 @@ var EnvelopArmor = class {
30876
30879
  }
30877
30880
  };
30878
30881
 
30879
- // ../api/dist/chunk-QXMW74MR.mjs
30882
+ // ../api/dist/chunk-GM55HGWA.mjs
30880
30883
  var DateTimeScalar = new GraphQLScalarType({
30881
30884
  name: "DateTime",
30882
30885
  description: "ISO-8601 date-time string",
@@ -31071,16 +31074,22 @@ function scalarFieldResolver(schemaName) {
31071
31074
  function relationFieldResolver(typeName, schemaFieldName) {
31072
31075
  return (parent, _args, ctx) => ctx.loaders.load(typeName, schemaFieldName, parent);
31073
31076
  }
31074
- function programmaticFieldResolver(typeName, schemaFieldName) {
31077
+ function programmaticFieldResolver(typeName, schemaFieldName, mediaFieldNames = []) {
31075
31078
  return async (parent, _args, ctx) => {
31076
31079
  let p = ctx.programmaticMemo.get(parent);
31077
31080
  if (!p) {
31078
- p = ctx.resolver.resolveItem(typeName, parent);
31081
+ p = resolveProgrammaticRow(typeName, parent, ctx, mediaFieldNames);
31079
31082
  ctx.programmaticMemo.set(parent, p);
31080
31083
  }
31081
31084
  return (await p)[schemaFieldName];
31082
31085
  };
31083
31086
  }
31087
+ async function resolveProgrammaticRow(typeName, parent, ctx, mediaFieldNames) {
31088
+ if (mediaFieldNames.length === 0) return ctx.resolver.resolveItem(typeName, parent);
31089
+ const enriched = { ...parent };
31090
+ await Promise.all(mediaFieldNames.map((name) => ctx.loaders.load(typeName, name, enriched)));
31091
+ return ctx.resolver.resolveItem(typeName, enriched);
31092
+ }
31084
31093
  function collectionResolver(typeName, nameMap) {
31085
31094
  return async (_root, args, ctx) => {
31086
31095
  const page = args.page ?? 1;
@@ -31206,6 +31215,7 @@ function buildGraphQLSchema(registry) {
31206
31215
  return GraphQLString;
31207
31216
  }
31208
31217
  function buildObjectType(machineName, type) {
31218
+ const mediaFieldNames = type.fields.filter((f) => f.field_type === "image" || f.field_type === "video" || f.field_type === "file").map((f) => f.name);
31209
31219
  return new GraphQLObjectType({
31210
31220
  name: graphqlTypeName(machineName),
31211
31221
  fields: () => {
@@ -31224,7 +31234,7 @@ function buildGraphQLSchema(registry) {
31224
31234
  const outType = outputTypeForField(field);
31225
31235
  let resolve;
31226
31236
  if (field.field_type === "programmatic") {
31227
- resolve = programmaticFieldResolver(machineName, field.name);
31237
+ resolve = programmaticFieldResolver(machineName, field.name, mediaFieldNames);
31228
31238
  } else if (field.field_type === "reference" || field.field_type === "paragraph" || field.field_type === "image" || field.field_type === "video" || field.field_type === "file") {
31229
31239
  resolve = relationFieldResolver(machineName, field.name);
31230
31240
  } else {
@@ -31375,6 +31385,14 @@ function introspectionPlugin(enabled) {
31375
31385
  }
31376
31386
  };
31377
31387
  }
31388
+ function isClientSafeGraphQLError(error) {
31389
+ let current = error;
31390
+ while (current != null && typeof current === "object" && current.name === "GraphQLError") {
31391
+ if (current.originalError == null) return true;
31392
+ current = current.originalError;
31393
+ }
31394
+ return false;
31395
+ }
31378
31396
  function createGraphQLHandler(registry, repos, resolver, db, options) {
31379
31397
  const schema = buildGraphQLSchema(registry);
31380
31398
  const { plugins } = buildArmorPlugin({
@@ -31386,6 +31404,9 @@ function createGraphQLHandler(registry, repos, resolver, db, options) {
31386
31404
  graphqlEndpoint: "/graphql",
31387
31405
  graphiql: options.graphiql,
31388
31406
  landingPage: false,
31407
+ maskedErrors: {
31408
+ maskError: (error, message, isDev2) => isClientSafeGraphQLError(error) ? error : maskError(error, message, isDev2)
31409
+ },
31389
31410
  // buildArmorPlugin's return type is `unknown[]` (see security.ts) since Armor's
31390
31411
  // plugin shapes aren't part of its public typings; Yoga's `plugins` option only
31391
31412
  // accepts its own `Plugin` type, so the cast bridges the two.
package/dist/index.js CHANGED
@@ -36,7 +36,7 @@ import {
36
36
  resolveRelationField,
37
37
  sql,
38
38
  tracer
39
- } from "./chunk-UVGV7NVV.js";
39
+ } from "./chunk-OAUBUE7N.js";
40
40
 
41
41
  // ../../node_modules/.pnpm/yaml@2.9.0/node_modules/yaml/dist/nodes/identity.js
42
42
  var require_identity = __commonJS({
@@ -47458,6 +47458,23 @@ function quoteIdent(name) {
47458
47458
  if (!/^[a-z][a-z0-9_-]*$/.test(name)) throw new Error(`Unsafe identifier: ${name}`);
47459
47459
  return `"${name}"`;
47460
47460
  }
47461
+ async function loadParagraphRows(db, registry2, pType, parentId, fieldName) {
47462
+ const result = await db.execute(
47463
+ sql`SELECT * FROM ${sql.raw(quoteIdent(pType.db.table_name))} WHERE parent_id = ${parentId} AND parent_field = ${fieldName} ORDER BY "order" ASC`
47464
+ );
47465
+ const rows = result.rows;
47466
+ for (const nf of pType.fields) {
47467
+ if (nf.db_column !== null) continue;
47468
+ const comp = nf.ui_component;
47469
+ if (comp.component !== "paragraph-embed" || !comp.ref) continue;
47470
+ const nType = registry2.paragraph_types[comp.ref];
47471
+ if (!nType) continue;
47472
+ for (const row of rows) {
47473
+ row[nf.name] = await loadParagraphRows(db, registry2, nType, row["id"], nf.name);
47474
+ }
47475
+ }
47476
+ return rows;
47477
+ }
47461
47478
  async function lookupBasePathId(db, pathOrName) {
47462
47479
  const r = await db.execute(
47463
47480
  sql`SELECT id FROM base_paths WHERE path = ${pathOrName} OR name = ${pathOrName} LIMIT 1`
@@ -47609,10 +47626,7 @@ function registerAdminContentRoutes(app, registry2, repos, mediaRepo, requirePer
47609
47626
  if (comp.component !== "paragraph-embed" || !comp.ref) continue;
47610
47627
  const pType = registry2.paragraph_types[comp.ref];
47611
47628
  if (!pType) continue;
47612
- const r = await db.execute(
47613
- sql`SELECT * FROM ${sql.raw(quoteIdent(pType.db.table_name))} WHERE parent_id = ${id} AND parent_field = ${f.name} ORDER BY "order" ASC`
47614
- );
47615
- row[f.name] = r.rows;
47629
+ row[f.name] = await loadParagraphRows(db, registry2, pType, id, f.name);
47616
47630
  } else if (f.db_column.junction) {
47617
47631
  const j = f.db_column.junction;
47618
47632
  const r = await db.execute(
@@ -47687,82 +47701,85 @@ function registerAdminContentRoutes(app, registry2, repos, mediaRepo, requirePer
47687
47701
  );
47688
47702
  }
47689
47703
  }
47690
- const fieldErrors = checkRequiredFields(requiredFields, body);
47691
- if (fieldErrors.length > 0) {
47704
+ return writeNewItem(c, body);
47705
+ }
47706
+ );
47707
+ const writeNewItem = async (c, body) => {
47708
+ const fieldErrors = checkRequiredFields(requiredFields, body);
47709
+ if (fieldErrors.length > 0) {
47710
+ return c.json(
47711
+ {
47712
+ ok: false,
47713
+ error: {
47714
+ code: "VALIDATION_ERROR",
47715
+ message: "Required fields are missing",
47716
+ details: fieldErrors
47717
+ }
47718
+ },
47719
+ 422
47720
+ );
47721
+ }
47722
+ if (body["published"] === true) {
47723
+ const publishDeny = await requirePermission("content:edit")(c, async () => {
47724
+ });
47725
+ if (publishDeny) return publishDeny;
47726
+ }
47727
+ const paragraphFields = contentType.fields.filter((f) => f.db_column === null);
47728
+ const junctionFields = contentType.fields.filter(
47729
+ (f) => f.db_column !== null && f.db_column.junction !== void 0
47730
+ );
47731
+ const columnFields = contentType.fields.filter(
47732
+ (f) => f.db_column !== null && f.db_column.junction === void 0
47733
+ );
47734
+ const columnData = {
47735
+ published: body["published"] ?? false
47736
+ };
47737
+ if (contentType.only_one) {
47738
+ columnData["slug"] = typeName;
47739
+ } else {
47740
+ columnData["slug"] = body["slug"];
47741
+ }
47742
+ for (const f of columnFields) {
47743
+ const val = body[f.name];
47744
+ columnData[f.db_column.column_name] = val !== void 0 ? val : null;
47745
+ }
47746
+ if (db) {
47747
+ const basePathId = await lookupBasePathId(db, contentType.default_base_path);
47748
+ if (!basePathId) {
47692
47749
  return c.json(
47693
47750
  {
47694
47751
  ok: false,
47695
47752
  error: {
47696
- code: "VALIDATION_ERROR",
47697
- message: "Required fields are missing",
47698
- details: fieldErrors
47753
+ code: "BASE_PATH_NOT_FOUND",
47754
+ message: `Base path '${contentType.default_base_path}' not found \u2014 run manguito migrate`
47699
47755
  }
47700
47756
  },
47701
- 422
47757
+ 500
47702
47758
  );
47703
47759
  }
47704
- if (body["published"] === true) {
47705
- const publishDeny = await requirePermission("content:edit")(c, async () => {
47706
- });
47707
- if (publishDeny) return publishDeny;
47708
- }
47709
- const paragraphFields = contentType.fields.filter((f) => f.db_column === null);
47710
- const junctionFields = contentType.fields.filter(
47711
- (f) => f.db_column !== null && f.db_column.junction !== void 0
47712
- );
47713
- const columnFields = contentType.fields.filter(
47714
- (f) => f.db_column !== null && f.db_column.junction === void 0
47715
- );
47716
- const columnData = {
47717
- published: body["published"] ?? false
47718
- };
47719
- if (contentType.only_one) {
47720
- columnData["slug"] = typeName;
47721
- } else {
47722
- columnData["slug"] = body["slug"];
47760
+ columnData["base_path_id"] = basePathId;
47761
+ }
47762
+ const item = await repo.create(columnData);
47763
+ const itemId = item["id"];
47764
+ const mediaDeltas = [topLevelMediaDelta(mediaFields, null, body)];
47765
+ if (db) {
47766
+ for (const f of paragraphFields) {
47767
+ const comp = f.ui_component;
47768
+ if (comp.component !== "paragraph-embed" || !comp.ref) continue;
47769
+ const pType = registry2.paragraph_types[comp.ref];
47770
+ if (!pType) continue;
47771
+ const items = Array.isArray(body[f.name]) ? body[f.name] : [];
47772
+ mediaDeltas.push(await persistParagraphField(db, itemId, contentType.db.table_name, f.name, pType, items, registry2));
47723
47773
  }
47724
- for (const f of columnFields) {
47725
- const val = body[f.name];
47726
- columnData[f.db_column.column_name] = val !== void 0 ? val : null;
47774
+ for (const f of junctionFields) {
47775
+ const junction = f.db_column.junction;
47776
+ const relatedIds = Array.isArray(body[f.name]) ? body[f.name].filter((v2) => typeof v2 === "string") : [];
47777
+ await persistJunctionField(db, itemId, junction, relatedIds);
47727
47778
  }
47728
- if (db) {
47729
- const basePathId = await lookupBasePathId(db, contentType.default_base_path);
47730
- if (!basePathId) {
47731
- return c.json(
47732
- {
47733
- ok: false,
47734
- error: {
47735
- code: "BASE_PATH_NOT_FOUND",
47736
- message: `Base path '${contentType.default_base_path}' not found \u2014 run manguito migrate`
47737
- }
47738
- },
47739
- 500
47740
- );
47741
- }
47742
- columnData["base_path_id"] = basePathId;
47743
- }
47744
- const item = await repo.create(columnData);
47745
- const itemId = item["id"];
47746
- const mediaDeltas = [topLevelMediaDelta(mediaFields, null, body)];
47747
- if (db) {
47748
- for (const f of paragraphFields) {
47749
- const comp = f.ui_component;
47750
- if (comp.component !== "paragraph-embed" || !comp.ref) continue;
47751
- const pType = registry2.paragraph_types[comp.ref];
47752
- if (!pType) continue;
47753
- const items = Array.isArray(body[f.name]) ? body[f.name] : [];
47754
- mediaDeltas.push(await persistParagraphField(db, itemId, contentType.db.table_name, f.name, pType, items));
47755
- }
47756
- for (const f of junctionFields) {
47757
- const junction = f.db_column.junction;
47758
- const relatedIds = Array.isArray(body[f.name]) ? body[f.name].filter((v2) => typeof v2 === "string") : [];
47759
- await persistJunctionField(db, itemId, junction, relatedIds);
47760
- }
47761
- }
47762
- await applyMediaReferenceDelta(mergeMediaDeltas(...mediaDeltas), mediaRepo);
47763
- return c.json({ ok: true, data: item }, 201);
47764
47779
  }
47765
- );
47780
+ await applyMediaReferenceDelta(mergeMediaDeltas(...mediaDeltas), mediaRepo);
47781
+ return c.json({ ok: true, data: item }, 201);
47782
+ };
47766
47783
  app.patch(
47767
47784
  `/admin/api/${basePath}/:id`,
47768
47785
  requirePermission("content:edit"),
@@ -47817,71 +47834,92 @@ function registerAdminContentRoutes(app, registry2, repos, mediaRepo, requirePer
47817
47834
  );
47818
47835
  }
47819
47836
  }
47820
- if (body["published"] === true) {
47821
- const publishDeny = await requirePermission("content:edit")(c, async () => {
47822
- });
47823
- if (publishDeny) return publishDeny;
47824
- const merged = { ...existing, ...body };
47825
- const fieldErrors = checkRequiredFields(requiredFields, merged);
47826
- if (fieldErrors.length > 0) {
47827
- return c.json(
47828
- {
47829
- ok: false,
47830
- error: {
47831
- code: "PUBLISH_VALIDATION_ERROR",
47832
- message: "Cannot publish \u2014 required fields are missing",
47833
- details: fieldErrors
47834
- }
47835
- },
47836
- 422
47837
- );
47838
- }
47839
- }
47840
- const patchParagraphFields = contentType.fields.filter((f) => f.db_column === null);
47841
- const patchJunctionFields = contentType.fields.filter(
47842
- (f) => f.db_column !== null && f.db_column.junction !== void 0
47843
- );
47844
- const patchColumnFields = contentType.fields.filter(
47845
- (f) => f.db_column !== null && f.db_column.junction === void 0
47846
- );
47847
- const patchData = {};
47848
- if (!contentType.only_one && "slug" in body) patchData["slug"] = body["slug"];
47849
- if ("published" in body) patchData["published"] = body["published"];
47850
- for (const f of patchColumnFields) {
47851
- if (f.name in body) {
47852
- const val = body[f.name];
47853
- patchData[f.db_column.column_name] = val !== void 0 ? val : null;
47854
- }
47855
- }
47856
- const updated = await repo.update(id, patchData);
47857
- if (!updated) {
47837
+ return writeExistingItem(c, id, existing, body);
47838
+ }
47839
+ );
47840
+ const writeExistingItem = async (c, id, existing, body) => {
47841
+ if (body["published"] === true) {
47842
+ const publishDeny = await requirePermission("content:edit")(c, async () => {
47843
+ });
47844
+ if (publishDeny) return publishDeny;
47845
+ const merged = { ...existing, ...body };
47846
+ const fieldErrors = checkRequiredFields(requiredFields, merged);
47847
+ if (fieldErrors.length > 0) {
47858
47848
  return c.json(
47859
- { ok: false, error: { code: "NOT_FOUND", message: "Not found" } },
47860
- 404
47849
+ {
47850
+ ok: false,
47851
+ error: {
47852
+ code: "PUBLISH_VALIDATION_ERROR",
47853
+ message: "Cannot publish \u2014 required fields are missing",
47854
+ details: fieldErrors
47855
+ }
47856
+ },
47857
+ 422
47861
47858
  );
47862
47859
  }
47863
- const mediaDeltas = [
47864
- topLevelMediaDelta(mediaFields, existing, body)
47865
- ];
47866
- if (db) {
47867
- for (const f of patchParagraphFields) {
47868
- const comp = f.ui_component;
47869
- if (comp.component !== "paragraph-embed" || !comp.ref) continue;
47870
- const pType = registry2.paragraph_types[comp.ref];
47871
- if (!pType) continue;
47872
- const items = Array.isArray(body[f.name]) ? body[f.name] : [];
47873
- mediaDeltas.push(await persistParagraphField(db, id, contentType.db.table_name, f.name, pType, items));
47874
- }
47875
- for (const f of patchJunctionFields) {
47876
- const junction = f.db_column.junction;
47877
- const relatedIds = Array.isArray(body[f.name]) ? body[f.name].filter((v2) => typeof v2 === "string") : [];
47878
- await persistJunctionField(db, id, junction, relatedIds);
47879
- }
47860
+ }
47861
+ const patchParagraphFields = contentType.fields.filter((f) => f.db_column === null);
47862
+ const patchJunctionFields = contentType.fields.filter(
47863
+ (f) => f.db_column !== null && f.db_column.junction !== void 0
47864
+ );
47865
+ const patchColumnFields = contentType.fields.filter(
47866
+ (f) => f.db_column !== null && f.db_column.junction === void 0
47867
+ );
47868
+ const patchData = {};
47869
+ if (!contentType.only_one && "slug" in body) patchData["slug"] = body["slug"];
47870
+ if ("published" in body) patchData["published"] = body["published"];
47871
+ for (const f of patchColumnFields) {
47872
+ if (f.name in body) {
47873
+ const val = body[f.name];
47874
+ patchData[f.db_column.column_name] = val !== void 0 ? val : null;
47880
47875
  }
47881
- await applyMediaReferenceDelta(mergeMediaDeltas(...mediaDeltas), mediaRepo);
47882
- return c.json({ ok: true, data: updated });
47883
47876
  }
47884
- );
47877
+ const updated = await repo.update(id, patchData);
47878
+ if (!updated) {
47879
+ return c.json(
47880
+ { ok: false, error: { code: "NOT_FOUND", message: "Not found" } },
47881
+ 404
47882
+ );
47883
+ }
47884
+ const mediaDeltas = [
47885
+ topLevelMediaDelta(mediaFields, existing, body)
47886
+ ];
47887
+ if (db) {
47888
+ for (const f of patchParagraphFields) {
47889
+ const comp = f.ui_component;
47890
+ if (comp.component !== "paragraph-embed" || !comp.ref) continue;
47891
+ const pType = registry2.paragraph_types[comp.ref];
47892
+ if (!pType) continue;
47893
+ const items = Array.isArray(body[f.name]) ? body[f.name] : [];
47894
+ mediaDeltas.push(await persistParagraphField(db, id, contentType.db.table_name, f.name, pType, items, registry2));
47895
+ }
47896
+ for (const f of patchJunctionFields) {
47897
+ const junction = f.db_column.junction;
47898
+ const relatedIds = Array.isArray(body[f.name]) ? body[f.name].filter((v2) => typeof v2 === "string") : [];
47899
+ await persistJunctionField(db, id, junction, relatedIds);
47900
+ }
47901
+ }
47902
+ await applyMediaReferenceDelta(mergeMediaDeltas(...mediaDeltas), mediaRepo);
47903
+ return c.json({ ok: true, data: updated });
47904
+ };
47905
+ if (contentType.only_one) {
47906
+ app.put(
47907
+ `/admin/api/${basePath}`,
47908
+ requirePermission("content:edit"),
47909
+ async (c) => {
47910
+ const body = await c.req.json();
47911
+ const existingResult = await repo.findMany({ page: 1, per_page: 1 });
47912
+ const existing = existingResult.data[0];
47913
+ if (!existing) {
47914
+ const createDeny = await requirePermission("content:create")(c, async () => {
47915
+ });
47916
+ if (createDeny) return createDeny;
47917
+ return writeNewItem(c, body);
47918
+ }
47919
+ return writeExistingItem(c, existing["id"], existing, body);
47920
+ }
47921
+ );
47922
+ }
47885
47923
  app.delete(
47886
47924
  `/admin/api/${basePath}/:id`,
47887
47925
  requirePermission("content:delete"),
@@ -47903,7 +47941,7 @@ function registerAdminContentRoutes(app, registry2, repos, mediaRepo, requirePer
47903
47941
  if (comp.component !== "paragraph-embed" || !comp.ref) continue;
47904
47942
  const pType = registry2.paragraph_types[comp.ref];
47905
47943
  if (!pType) continue;
47906
- mediaDeltas.push(await deleteParagraphField(db, id, f.name, pType));
47944
+ mediaDeltas.push(await deleteParagraphField(db, id, f.name, pType, registry2));
47907
47945
  }
47908
47946
  }
47909
47947
  await applyMediaReferenceDelta(mergeMediaDeltas(...mediaDeltas), mediaRepo);
@@ -48018,7 +48056,7 @@ function registerAdminContentRoutes(app, registry2, repos, mediaRepo, requirePer
48018
48056
  const pType = registry2.paragraph_types[comp.ref];
48019
48057
  if (!pType) continue;
48020
48058
  const items = Array.isArray(body[f.name]) ? body[f.name] : [];
48021
- mediaDeltas.push(await persistParagraphField(db, taxItemId, taxonomyType.db.table_name, f.name, pType, items));
48059
+ mediaDeltas.push(await persistParagraphField(db, taxItemId, taxonomyType.db.table_name, f.name, pType, items, registry2));
48022
48060
  }
48023
48061
  }
48024
48062
  await applyMediaReferenceDelta(mergeMediaDeltas(...mediaDeltas), mediaRepo);
@@ -48087,7 +48125,7 @@ function registerAdminContentRoutes(app, registry2, repos, mediaRepo, requirePer
48087
48125
  const pType = registry2.paragraph_types[comp.ref];
48088
48126
  if (!pType) continue;
48089
48127
  const items = Array.isArray(body[f.name]) ? body[f.name] : [];
48090
- mediaDeltas.push(await persistParagraphField(db, id, taxonomyType.db.table_name, f.name, pType, items));
48128
+ mediaDeltas.push(await persistParagraphField(db, id, taxonomyType.db.table_name, f.name, pType, items, registry2));
48091
48129
  }
48092
48130
  }
48093
48131
  await applyMediaReferenceDelta(mergeMediaDeltas(...mediaDeltas), mediaRepo);
@@ -48115,7 +48153,7 @@ function registerAdminContentRoutes(app, registry2, repos, mediaRepo, requirePer
48115
48153
  if (comp.component !== "paragraph-embed" || !comp.ref) continue;
48116
48154
  const pType = registry2.paragraph_types[comp.ref];
48117
48155
  if (!pType) continue;
48118
- mediaDeltas.push(await deleteParagraphField(db, id, f.name, pType));
48156
+ mediaDeltas.push(await deleteParagraphField(db, id, f.name, pType, registry2));
48119
48157
  }
48120
48158
  }
48121
48159
  await applyMediaReferenceDelta(mergeMediaDeltas(...mediaDeltas), mediaRepo);
@@ -49064,7 +49102,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
49064
49102
  }
49065
49103
  var API_VERSION = (() => {
49066
49104
  try {
49067
- return "0.3.0";
49105
+ return "0.3.3";
49068
49106
  } catch {
49069
49107
  return "0.0.0";
49070
49108
  }
@@ -49529,6 +49567,16 @@ function createCmsApp(options) {
49529
49567
  ])
49530
49568
  );
49531
49569
  const publicRepos = { ...publicContentRepos, ...publicTaxonomyRepos };
49570
+ const graphqlRepos = Object.fromEntries([
49571
+ ...Object.entries(registry2.content_types).map(([typeName, ct2]) => [
49572
+ typeName,
49573
+ createDrizzleContentRepository(db, ct2.db.table_name)
49574
+ ]),
49575
+ ...Object.entries(registry2.taxonomy_types).map(([typeName, tt2]) => [
49576
+ typeName,
49577
+ createDrizzleContentRepository(db, tt2.db.table_name)
49578
+ ])
49579
+ ]);
49532
49580
  registerAuthRoutes(app, db);
49533
49581
  app.use("/admin/api/*", authMiddleware);
49534
49582
  app.use("/admin/api/*", mustChangePasswordCheck);
@@ -49569,8 +49617,8 @@ function createCmsApp(options) {
49569
49617
  const gqlOptions = options.graphql;
49570
49618
  let gqlHandler = null;
49571
49619
  let initError = null;
49572
- const ready = import("./handler-GMCFYZY5-F3656HCZ.js").then(({ createGraphQLHandler }) => {
49573
- gqlHandler = createGraphQLHandler(registry2, publicRepos, programmaticResolver, db, gqlOptions);
49620
+ const ready = import("./handler-CGHZG3J5-TCG6K3FE.js").then(({ createGraphQLHandler }) => {
49621
+ gqlHandler = createGraphQLHandler(registry2, graphqlRepos, programmaticResolver, db, gqlOptions);
49574
49622
  }).catch((err) => {
49575
49623
  initError = err;
49576
49624
  const message = err instanceof Error ? err.message : String(err);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobbykim/manguito-cms-cli",
3
- "version": "0.4.0",
3
+ "version": "0.4.3",
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-admin": "0.3.1",
32
+ "@bobbykim/manguito-cms-admin": "0.4.0",
33
33
  "@bobbykim/manguito-cms-core": "0.3.0",
34
34
  "@bobbykim/manguito-cms-db": "0.1.3",
35
- "@bobbykim/manguito-cms-api": "0.3.0"
35
+ "@bobbykim/manguito-cms-api": "0.3.3"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@types/node": "^25.9.5",