@bobbykim/manguito-cms-api 0.8.4 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-IHEIX6P4.mjs → chunk-GUZZQZSN.mjs} +25 -7
- package/dist/chunk-GUZZQZSN.mjs.map +1 -0
- package/dist/{chunk-Y5SWT7PO.mjs → chunk-ITOSMGDT.mjs} +2 -2
- package/dist/graphql/index.js +5 -2
- package/dist/graphql/index.js.map +1 -1
- package/dist/graphql/index.mjs +2 -2
- package/dist/handler-MFF32JB4.mjs +8 -0
- package/dist/index.js +208 -62
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +145 -13
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/dist/chunk-IHEIX6P4.mjs.map +0 -1
- package/dist/handler-VEOWJRAL.mjs +0 -8
- /package/dist/{chunk-Y5SWT7PO.mjs.map → chunk-ITOSMGDT.mjs.map} +0 -0
- /package/dist/{handler-VEOWJRAL.mjs.map → handler-MFF32JB4.mjs.map} +0 -0
package/dist/index.mjs
CHANGED
|
@@ -4,13 +4,14 @@ import {
|
|
|
4
4
|
createFieldKeyMapFromProjection,
|
|
5
5
|
deleteParagraphField,
|
|
6
6
|
isColumnBacked,
|
|
7
|
+
junctionOrderBy,
|
|
7
8
|
oneOrMany,
|
|
8
9
|
paragraphItems,
|
|
9
10
|
persistJunctionField,
|
|
10
11
|
persistParagraphField,
|
|
11
12
|
resolveRelationBareIds,
|
|
12
13
|
resolveRelationField
|
|
13
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-GUZZQZSN.mjs";
|
|
14
15
|
|
|
15
16
|
// src/paths.ts
|
|
16
17
|
function normalizePrefix(prefix) {
|
|
@@ -1169,8 +1170,7 @@ function checkRelationInput(fields, body, registry, path = "") {
|
|
|
1169
1170
|
});
|
|
1170
1171
|
continue;
|
|
1171
1172
|
}
|
|
1172
|
-
const
|
|
1173
|
-
const pType = comp.component === "paragraph-embed" ? registry.paragraph_types[comp.ref] : void 0;
|
|
1173
|
+
const pType = paragraphTypeOf(field, registry);
|
|
1174
1174
|
if (!pType) continue;
|
|
1175
1175
|
items.forEach((item, i) => {
|
|
1176
1176
|
const prefix = cardinality === "one" ? `${name}.` : `${name}[${i}].`;
|
|
@@ -1179,12 +1179,15 @@ function checkRelationInput(fields, body, registry, path = "") {
|
|
|
1179
1179
|
}
|
|
1180
1180
|
return errors;
|
|
1181
1181
|
}
|
|
1182
|
+
function paragraphTypeOf(field, registry) {
|
|
1183
|
+
const comp = field.ui_component;
|
|
1184
|
+
return comp.component === "paragraph-embed" ? registry.paragraph_types[comp.ref] : void 0;
|
|
1185
|
+
}
|
|
1182
1186
|
function paragraphItemsIn(fields, body, registry, path) {
|
|
1183
1187
|
const out = [];
|
|
1184
1188
|
for (const field of fields) {
|
|
1185
1189
|
if (field.field_type !== "paragraph" || !(field.name in body)) continue;
|
|
1186
|
-
const
|
|
1187
|
-
const pType = comp.component === "paragraph-embed" ? registry.paragraph_types[comp.ref] : void 0;
|
|
1190
|
+
const pType = paragraphTypeOf(field, registry);
|
|
1188
1191
|
if (!pType) continue;
|
|
1189
1192
|
const value = body[field.name];
|
|
1190
1193
|
const name = `${path}${field.name}`;
|
|
@@ -1256,6 +1259,133 @@ async function findMissingReferences(db, fields, body, registry) {
|
|
|
1256
1259
|
return uses.filter((u) => !found.get(u.table).has(u.id.toLowerCase())).map((u) => ({ field: u.field, message: `${u.field} refers to an item that does not exist.` }));
|
|
1257
1260
|
}
|
|
1258
1261
|
|
|
1262
|
+
// src/field-values.ts
|
|
1263
|
+
var VARCHAR_DEFAULT_LIMIT = 255;
|
|
1264
|
+
var INTEGER_MIN = -2147483648;
|
|
1265
|
+
var INTEGER_MAX = 2147483647;
|
|
1266
|
+
var DECIMAL_LIMIT = 1e6;
|
|
1267
|
+
var ISO_TIMESTAMP = /^(\d{4})-(\d{2})-(\d{2})(?:[T ](\d{2}):(\d{2})(?::(\d{2})(?:\.\d+)?)?(?:Z|[+-]\d{2}:?\d{2})?)?$/;
|
|
1268
|
+
var DECIMAL_STRING = /^-?\d+(?:\.\d+)?$/;
|
|
1269
|
+
var EMPTY_MEANS_NULL = /* @__PURE__ */ new Set(["date", "integer", "float", "enum"]);
|
|
1270
|
+
function emptyValuesToNull(fields, body, registry) {
|
|
1271
|
+
const out = { ...body };
|
|
1272
|
+
for (const f of fields) {
|
|
1273
|
+
if (!(f.name in out)) continue;
|
|
1274
|
+
const value = out[f.name];
|
|
1275
|
+
if (value === "" && EMPTY_MEANS_NULL.has(f.field_type)) {
|
|
1276
|
+
out[f.name] = null;
|
|
1277
|
+
continue;
|
|
1278
|
+
}
|
|
1279
|
+
if (f.field_type !== "paragraph") continue;
|
|
1280
|
+
const pType = paragraphTypeOf(f, registry);
|
|
1281
|
+
if (!pType) continue;
|
|
1282
|
+
if (isPlainObject(value)) out[f.name] = emptyValuesToNull(pType.fields, value, registry);
|
|
1283
|
+
else if (Array.isArray(value)) {
|
|
1284
|
+
out[f.name] = value.map((item) => isPlainObject(item) ? emptyValuesToNull(pType.fields, item, registry) : item);
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
return out;
|
|
1288
|
+
}
|
|
1289
|
+
var compiled = /* @__PURE__ */ new Map();
|
|
1290
|
+
function patternOf(source) {
|
|
1291
|
+
if (!compiled.has(source)) {
|
|
1292
|
+
let re;
|
|
1293
|
+
try {
|
|
1294
|
+
re = new RegExp(source);
|
|
1295
|
+
} catch {
|
|
1296
|
+
re = null;
|
|
1297
|
+
}
|
|
1298
|
+
compiled.set(source, re);
|
|
1299
|
+
}
|
|
1300
|
+
return compiled.get(source) ?? null;
|
|
1301
|
+
}
|
|
1302
|
+
function isRealDate(value) {
|
|
1303
|
+
const m = ISO_TIMESTAMP.exec(value);
|
|
1304
|
+
if (!m) return false;
|
|
1305
|
+
const [y, mo, d, h = 0, mi = 0, sec = 0] = m.slice(1).map((part) => part === void 0 ? void 0 : Number(part));
|
|
1306
|
+
if (y === void 0 || mo === void 0 || d === void 0 || y < 1) return false;
|
|
1307
|
+
const date = new Date(Date.UTC(y, mo - 1, d));
|
|
1308
|
+
date.setUTCFullYear(y);
|
|
1309
|
+
return date.getUTCMonth() === mo - 1 && date.getUTCDate() === d && h <= 23 && mi <= 59 && sec <= 59;
|
|
1310
|
+
}
|
|
1311
|
+
function valueError(f, value, name) {
|
|
1312
|
+
const v = f.validation;
|
|
1313
|
+
if (Array.isArray(value) && (f.field_type === "paragraph" || f.field_type === "reference")) {
|
|
1314
|
+
if (v.max_items !== void 0 && value.length > v.max_items) {
|
|
1315
|
+
return `${name} can hold at most ${v.max_items} items.`;
|
|
1316
|
+
}
|
|
1317
|
+
return null;
|
|
1318
|
+
}
|
|
1319
|
+
const col = f.db_column;
|
|
1320
|
+
if (!col || col.junction || col.column_type === "uuid") return null;
|
|
1321
|
+
if (value === null || value === void 0) {
|
|
1322
|
+
if (col.nullable) return null;
|
|
1323
|
+
return f.required ? `${name} is required.` : `${name} cannot be empty.`;
|
|
1324
|
+
}
|
|
1325
|
+
switch (f.field_type) {
|
|
1326
|
+
case "text/plain": {
|
|
1327
|
+
if (typeof value !== "string") return `${name} must be text.`;
|
|
1328
|
+
if (value.includes("\0")) return `${name} contains a character that cannot be stored (U+0000).`;
|
|
1329
|
+
const limit = v.limit ?? VARCHAR_DEFAULT_LIMIT;
|
|
1330
|
+
if ([...value].length > limit) return `${name} can be at most ${limit} characters.`;
|
|
1331
|
+
const pattern = v.pattern === void 0 ? null : patternOf(v.pattern);
|
|
1332
|
+
if (value !== "" && pattern && !pattern.test(value)) return `${name} is not in the correct format.`;
|
|
1333
|
+
return null;
|
|
1334
|
+
}
|
|
1335
|
+
case "text/rich":
|
|
1336
|
+
if (typeof value !== "string") return `${name} must be text.`;
|
|
1337
|
+
return value.includes("\0") ? `${name} contains a character that cannot be stored (U+0000).` : null;
|
|
1338
|
+
case "integer":
|
|
1339
|
+
if (typeof value !== "number" || !Number.isInteger(value)) return `${name} must be a whole number.`;
|
|
1340
|
+
if (value < INTEGER_MIN || value > INTEGER_MAX) {
|
|
1341
|
+
return `${name} must be between ${INTEGER_MIN} and ${INTEGER_MAX}.`;
|
|
1342
|
+
}
|
|
1343
|
+
return rangeError(name, value, v.min, v.max);
|
|
1344
|
+
case "float": {
|
|
1345
|
+
const n = typeof value === "number" ? value : typeof value === "string" && DECIMAL_STRING.test(value) ? Number(value) : NaN;
|
|
1346
|
+
if (!Number.isFinite(n)) return `${name} must be a number.`;
|
|
1347
|
+
if (roundedSize(typeof value === "string" ? value : String(n)) >= DECIMAL_LIMIT) {
|
|
1348
|
+
return `${name} must be less than ${DECIMAL_LIMIT} in size.`;
|
|
1349
|
+
}
|
|
1350
|
+
return rangeError(name, n, v.min, v.max);
|
|
1351
|
+
}
|
|
1352
|
+
case "boolean":
|
|
1353
|
+
return typeof value === "boolean" ? null : `${name} must be true or false.`;
|
|
1354
|
+
case "date":
|
|
1355
|
+
return typeof value === "string" && isRealDate(value) ? null : `${name} must be a date, such as 2026-10-11 or 2026-10-11T09:30:00Z.`;
|
|
1356
|
+
case "enum": {
|
|
1357
|
+
const allowed = v.allowed_values ?? [];
|
|
1358
|
+
return typeof value === "string" && allowed.includes(value) ? null : `${name} must be one of: ${allowed.join(", ")}.`;
|
|
1359
|
+
}
|
|
1360
|
+
default:
|
|
1361
|
+
return null;
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
function roundedSize(text) {
|
|
1365
|
+
if (text.includes("e")) return Math.abs(Number(text));
|
|
1366
|
+
const [int = "0", frac = ""] = text.replace("-", "").split(".");
|
|
1367
|
+
const kept = Number(`${int}.${frac.slice(0, 4) || "0"}`);
|
|
1368
|
+
return frac.length > 4 && frac.charAt(4) >= "5" ? kept + 1e-4 : kept;
|
|
1369
|
+
}
|
|
1370
|
+
function rangeError(name, value, min, max) {
|
|
1371
|
+
if (min !== void 0 && value < min) return `${name} must be at least ${min}.`;
|
|
1372
|
+
if (max !== void 0 && value > max) return `${name} must be at most ${max}.`;
|
|
1373
|
+
return null;
|
|
1374
|
+
}
|
|
1375
|
+
function checkFieldValues(fields, body, registry, path = "") {
|
|
1376
|
+
const errors = [];
|
|
1377
|
+
for (const f of fields) {
|
|
1378
|
+
if (!(f.name in body) || f.removed === true) continue;
|
|
1379
|
+
const name = `${path}${f.name}`;
|
|
1380
|
+
const message = valueError(f, body[f.name], name);
|
|
1381
|
+
if (message) errors.push({ field: name, message });
|
|
1382
|
+
}
|
|
1383
|
+
for (const { item, fields: itemFields, prefix } of paragraphItemsIn(fields, body, registry, path)) {
|
|
1384
|
+
errors.push(...checkFieldValues(itemFields, item, registry, prefix));
|
|
1385
|
+
}
|
|
1386
|
+
return errors;
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1259
1389
|
// src/routes/admin/content.ts
|
|
1260
1390
|
function quoteIdent3(name) {
|
|
1261
1391
|
if (!/^[a-z][a-z0-9_-]*$/.test(name)) throw new Error(`Unsafe identifier: ${name}`);
|
|
@@ -1292,7 +1422,7 @@ async function loadRelationsForEdit(db, registry, fields, id, row) {
|
|
|
1292
1422
|
} else if (f.db_column.junction) {
|
|
1293
1423
|
const j = f.db_column.junction;
|
|
1294
1424
|
const r = await db.execute(
|
|
1295
|
-
sql4`SELECT ${sql4.raw(quoteIdent3(j.right_column))} FROM ${sql4.raw(quoteIdent3(j.table_name))} WHERE ${sql4.raw(quoteIdent3(j.left_column))} = ${id}`
|
|
1425
|
+
sql4`SELECT ${sql4.raw(quoteIdent3(j.right_column))} FROM ${sql4.raw(quoteIdent3(j.table_name))} WHERE ${sql4.raw(quoteIdent3(j.left_column))} = ${id}${junctionOrderBy(j)}`
|
|
1296
1426
|
);
|
|
1297
1427
|
row[f.name] = r.rows.map((linked) => linked[j.right_column]);
|
|
1298
1428
|
}
|
|
@@ -1335,6 +1465,8 @@ async function relationInputError(c, fields, body, registry, db) {
|
|
|
1335
1465
|
const refuse = (message, details) => c.json({ ok: false, error: { code: "VALIDATION_ERROR", message, details } }, 422);
|
|
1336
1466
|
const shape = checkRelationInput(fields, body, registry);
|
|
1337
1467
|
if (shape.length > 0) return refuse("Relation fields have the wrong shape", shape);
|
|
1468
|
+
const values = checkFieldValues(fields, body, registry);
|
|
1469
|
+
if (values.length > 0) return refuse("Some values are not valid", values);
|
|
1338
1470
|
const required = checkRequiredInParagraphItems(fields, body, registry);
|
|
1339
1471
|
if (required.length > 0) return refuse("Required fields are missing", required);
|
|
1340
1472
|
if (db) {
|
|
@@ -1485,7 +1617,7 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
|
|
|
1485
1617
|
`/admin/api/${basePath}`,
|
|
1486
1618
|
requirePermission("content:create"),
|
|
1487
1619
|
async (c) => {
|
|
1488
|
-
const body = await c.req.json();
|
|
1620
|
+
const body = emptyValuesToNull(contentType.fields, await c.req.json(), registry);
|
|
1489
1621
|
if (contentType.only_one) {
|
|
1490
1622
|
const existing = await repo.findMany({ page: 1, per_page: 1 });
|
|
1491
1623
|
if (existing.meta.total > 0) {
|
|
@@ -1612,7 +1744,7 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
|
|
|
1612
1744
|
requirePermission("content:edit"),
|
|
1613
1745
|
async (c) => {
|
|
1614
1746
|
const id = c.req.param("id");
|
|
1615
|
-
const body = await c.req.json();
|
|
1747
|
+
const body = emptyValuesToNull(contentType.fields, await c.req.json(), registry);
|
|
1616
1748
|
const existing = await repo.findOne(id);
|
|
1617
1749
|
if (!existing) {
|
|
1618
1750
|
return c.json(
|
|
@@ -1719,7 +1851,7 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
|
|
|
1719
1851
|
`/admin/api/${basePath}`,
|
|
1720
1852
|
requirePermission("content:edit"),
|
|
1721
1853
|
async (c) => {
|
|
1722
|
-
const body = await c.req.json();
|
|
1854
|
+
const body = emptyValuesToNull(contentType.fields, await c.req.json(), registry);
|
|
1723
1855
|
const existingResult = await repo.findMany({ page: 1, per_page: 1 });
|
|
1724
1856
|
const existing = existingResult.data[0];
|
|
1725
1857
|
if (!existing) {
|
|
@@ -1846,7 +1978,7 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
|
|
|
1846
1978
|
`/admin/api/taxonomy/${typeName}`,
|
|
1847
1979
|
requirePermission("content:create"),
|
|
1848
1980
|
async (c) => {
|
|
1849
|
-
const body = await c.req.json();
|
|
1981
|
+
const body = emptyValuesToNull(taxonomyType.fields, await c.req.json(), registry);
|
|
1850
1982
|
const inputError = await relationInputError(c, taxonomyType.fields, body, registry, db);
|
|
1851
1983
|
if (inputError) return inputError;
|
|
1852
1984
|
const storageBody = fieldKeys.toStorage(body);
|
|
@@ -1894,7 +2026,7 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
|
|
|
1894
2026
|
requirePermission("content:edit"),
|
|
1895
2027
|
async (c) => {
|
|
1896
2028
|
const id = c.req.param("id");
|
|
1897
|
-
const body = await c.req.json();
|
|
2029
|
+
const body = emptyValuesToNull(taxonomyType.fields, await c.req.json(), registry);
|
|
1898
2030
|
const storageBody = fieldKeys.toStorage(body);
|
|
1899
2031
|
const existing = await repo.findOne(id);
|
|
1900
2032
|
if (!existing) {
|
|
@@ -2951,7 +3083,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
2951
3083
|
import { sql as sql7 } from "drizzle-orm";
|
|
2952
3084
|
var API_VERSION = (() => {
|
|
2953
3085
|
try {
|
|
2954
|
-
return "0.
|
|
3086
|
+
return "0.9.1";
|
|
2955
3087
|
} catch {
|
|
2956
3088
|
return "0.0.0";
|
|
2957
3089
|
}
|
|
@@ -3649,7 +3781,7 @@ function createCmsApp(options) {
|
|
|
3649
3781
|
handler: null,
|
|
3650
3782
|
error: null
|
|
3651
3783
|
}));
|
|
3652
|
-
const ready = import("./handler-
|
|
3784
|
+
const ready = import("./handler-MFF32JB4.mjs").then(({ createGraphQLHandler }) => {
|
|
3653
3785
|
gqlPasses.forEach((pass, i) => {
|
|
3654
3786
|
const mount = mounts[i];
|
|
3655
3787
|
try {
|