@dyrected/core 2.7.1 → 2.8.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/app-config-7G-3tF_I.d.cts +2259 -0
- package/dist/app-config-7G-3tF_I.d.ts +2259 -0
- package/dist/app-config-B3ByBjCN.d.cts +2222 -0
- package/dist/app-config-B3ByBjCN.d.ts +2222 -0
- package/dist/app-config-B5tYsZaH.d.cts +2404 -0
- package/dist/app-config-B5tYsZaH.d.ts +2404 -0
- package/dist/app-config-BEuyPktb.d.cts +2556 -0
- package/dist/app-config-BEuyPktb.d.ts +2556 -0
- package/dist/app-config-BIkjQWzU.d.cts +2218 -0
- package/dist/app-config-BIkjQWzU.d.ts +2218 -0
- package/dist/app-config-BfpeE3bF.d.cts +2100 -0
- package/dist/app-config-BfpeE3bF.d.ts +2100 -0
- package/dist/app-config-CCe-yq9q.d.cts +2222 -0
- package/dist/app-config-CCe-yq9q.d.ts +2222 -0
- package/dist/app-config-CPOs7THF.d.cts +2217 -0
- package/dist/app-config-CPOs7THF.d.ts +2217 -0
- package/dist/app-config-Ctp454zB.d.cts +2214 -0
- package/dist/app-config-Ctp454zB.d.ts +2214 -0
- package/dist/app-config-DJmNYgYh.d.cts +2305 -0
- package/dist/app-config-DJmNYgYh.d.ts +2305 -0
- package/dist/app-config-DO3laV58.d.cts +2397 -0
- package/dist/app-config-DO3laV58.d.ts +2397 -0
- package/dist/app-config-DZf_mmi9.d.cts +2211 -0
- package/dist/app-config-DZf_mmi9.d.ts +2211 -0
- package/dist/app-config-SkUoN5JU.d.cts +2192 -0
- package/dist/app-config-SkUoN5JU.d.ts +2192 -0
- package/dist/app-config-WzqpuZMr.d.cts +2566 -0
- package/dist/app-config-WzqpuZMr.d.ts +2566 -0
- package/dist/app-config-fEjRm1o9.d.cts +2402 -0
- package/dist/app-config-fEjRm1o9.d.ts +2402 -0
- package/dist/app-config-oz8H_MOX.d.cts +2224 -0
- package/dist/app-config-oz8H_MOX.d.ts +2224 -0
- package/dist/chunk-3TRRTN6U.js +3441 -0
- package/dist/chunk-5ZIA5COH.js +3414 -0
- package/dist/chunk-6TWQ6GTD.js +3480 -0
- package/dist/chunk-AQO4HI2Y.js +3204 -0
- package/dist/chunk-DFBLNYUH.js +3402 -0
- package/dist/chunk-GFFGDHST.js +3543 -0
- package/dist/chunk-KEKOEAX6.js +3139 -0
- package/dist/chunk-PRQ2RF5C.js +3485 -0
- package/dist/chunk-PZMDK3MH.js +3203 -0
- package/dist/chunk-SM32ND5M.js +3472 -0
- package/dist/chunk-XX7N64NL.js +3550 -0
- package/dist/chunk-ZISFMAEL.js +3550 -0
- package/dist/index.cjs +439 -9
- package/dist/index.d.cts +166 -3
- package/dist/index.d.ts +166 -3
- package/dist/index.js +53 -1
- package/dist/server.cjs +623 -367
- package/dist/server.d.cts +26 -7
- package/dist/server.d.ts +26 -7
- package/dist/server.js +420 -359
- package/package.json +1 -1
package/dist/server.cjs
CHANGED
|
@@ -627,6 +627,7 @@ __export(server_exports, {
|
|
|
627
627
|
registerRoutes: () => registerRoutes,
|
|
628
628
|
renderPrometheusMetrics: () => renderPrometheusMetrics,
|
|
629
629
|
resolveObservabilityConfig: () => resolveObservabilityConfig,
|
|
630
|
+
serializeDetailForApi: () => serializeDetailForApi,
|
|
630
631
|
shouldSampleRequest: () => shouldSampleRequest
|
|
631
632
|
});
|
|
632
633
|
module.exports = __toCommonJS(server_exports);
|
|
@@ -746,9 +747,13 @@ var PopulationService = class {
|
|
|
746
747
|
}
|
|
747
748
|
if (!field.name) continue;
|
|
748
749
|
const value = populatedDoc[field.name];
|
|
749
|
-
|
|
750
|
+
const fieldType = field.type;
|
|
751
|
+
const isRelationship = fieldType === "relationship";
|
|
752
|
+
const isUpload = fieldType === "upload" || fieldType === "image" || fieldType === "media";
|
|
753
|
+
const targetRelationTo = field.relationTo || (isUpload ? this.collections.find((c) => c.upload)?.slug || "media" : void 0);
|
|
754
|
+
if ((isRelationship || isUpload) && targetRelationTo && value) {
|
|
750
755
|
const relatedCollection = this.collections.find(
|
|
751
|
-
(c) => c.slug ===
|
|
756
|
+
(c) => c.slug === targetRelationTo
|
|
752
757
|
);
|
|
753
758
|
if (!relatedCollection) continue;
|
|
754
759
|
if (Array.isArray(value)) {
|
|
@@ -758,7 +763,7 @@ var PopulationService = class {
|
|
|
758
763
|
let doc = id;
|
|
759
764
|
if (typeof id === "string") {
|
|
760
765
|
doc = await this.db.findOne({
|
|
761
|
-
collection:
|
|
766
|
+
collection: targetRelationTo,
|
|
762
767
|
id
|
|
763
768
|
});
|
|
764
769
|
}
|
|
@@ -779,7 +784,7 @@ var PopulationService = class {
|
|
|
779
784
|
let doc = value;
|
|
780
785
|
if (typeof value === "string") {
|
|
781
786
|
doc = await this.db.findOne({
|
|
782
|
-
collection:
|
|
787
|
+
collection: targetRelationTo,
|
|
783
788
|
id: value
|
|
784
789
|
});
|
|
785
790
|
}
|
|
@@ -1259,9 +1264,42 @@ function round(val, decimals = 0) {
|
|
|
1259
1264
|
}
|
|
1260
1265
|
function clamp(val, minVal, maxVal) {
|
|
1261
1266
|
const num = Number(val) || 0;
|
|
1262
|
-
const
|
|
1263
|
-
const
|
|
1264
|
-
return Math.min(Math.max(num,
|
|
1267
|
+
const min2 = Number(minVal) || 0;
|
|
1268
|
+
const max2 = Number(maxVal) || 0;
|
|
1269
|
+
return Math.min(Math.max(num, min2), max2);
|
|
1270
|
+
}
|
|
1271
|
+
function ceil(val) {
|
|
1272
|
+
return Math.ceil(Number(val) || 0);
|
|
1273
|
+
}
|
|
1274
|
+
function floor(val) {
|
|
1275
|
+
return Math.floor(Number(val) || 0);
|
|
1276
|
+
}
|
|
1277
|
+
function abs(val) {
|
|
1278
|
+
return Math.abs(Number(val) || 0);
|
|
1279
|
+
}
|
|
1280
|
+
function min(...args) {
|
|
1281
|
+
const nums = args.flat().map((n) => Number(n) || 0);
|
|
1282
|
+
return nums.length ? Math.min(...nums) : 0;
|
|
1283
|
+
}
|
|
1284
|
+
function max(...args) {
|
|
1285
|
+
const nums = args.flat().map((n) => Number(n) || 0);
|
|
1286
|
+
return nums.length ? Math.max(...nums) : 0;
|
|
1287
|
+
}
|
|
1288
|
+
function pow(base, exp) {
|
|
1289
|
+
return Math.pow(Number(base) || 0, Number(exp) || 0);
|
|
1290
|
+
}
|
|
1291
|
+
function sqrt(val) {
|
|
1292
|
+
return Math.sqrt(Number(val) || 0);
|
|
1293
|
+
}
|
|
1294
|
+
function sum(arr) {
|
|
1295
|
+
if (!Array.isArray(arr)) return Number(arr) || 0;
|
|
1296
|
+
return arr.reduce((acc, val) => acc + (Number(val) || 0), 0);
|
|
1297
|
+
}
|
|
1298
|
+
function count(val) {
|
|
1299
|
+
if (Array.isArray(val)) return val.length;
|
|
1300
|
+
if (typeof val === "string") return val.length;
|
|
1301
|
+
if (val !== void 0 && val !== null && val !== false) return 1;
|
|
1302
|
+
return 0;
|
|
1265
1303
|
}
|
|
1266
1304
|
function defaultValue(val, fallback) {
|
|
1267
1305
|
if (val === void 0 || val === null || val === "") return fallback;
|
|
@@ -1280,13 +1318,12 @@ function isEmpty(val) {
|
|
|
1280
1318
|
return false;
|
|
1281
1319
|
}
|
|
1282
1320
|
function get(obj, path, fallback = null) {
|
|
1283
|
-
if (
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
const parts = pathStr.split(".");
|
|
1321
|
+
if (obj === void 0 || obj === null || typeof obj !== "object") return fallback;
|
|
1322
|
+
if (typeof path !== "string") return fallback;
|
|
1323
|
+
const parts = path.split(".").filter(Boolean);
|
|
1287
1324
|
let curr = obj;
|
|
1288
1325
|
for (const part of parts) {
|
|
1289
|
-
if (curr === void 0 || curr === null) return fallback;
|
|
1326
|
+
if (curr === void 0 || curr === null || typeof curr !== "object") return fallback;
|
|
1290
1327
|
curr = curr[part];
|
|
1291
1328
|
}
|
|
1292
1329
|
return curr === void 0 || curr === null ? fallback : curr;
|
|
@@ -1324,6 +1361,15 @@ function registerJexlHelpers(jexlInstance = import_jexl2.default) {
|
|
|
1324
1361
|
// Math
|
|
1325
1362
|
round,
|
|
1326
1363
|
clamp,
|
|
1364
|
+
ceil,
|
|
1365
|
+
floor,
|
|
1366
|
+
abs,
|
|
1367
|
+
min,
|
|
1368
|
+
max,
|
|
1369
|
+
pow,
|
|
1370
|
+
sqrt,
|
|
1371
|
+
sum,
|
|
1372
|
+
count,
|
|
1327
1373
|
// Objects & Logical
|
|
1328
1374
|
default: defaultValue,
|
|
1329
1375
|
coalesce,
|
|
@@ -1335,9 +1381,33 @@ function registerJexlHelpers(jexlInstance = import_jexl2.default) {
|
|
|
1335
1381
|
jexlInstance.addFunction(name, fn);
|
|
1336
1382
|
} catch {
|
|
1337
1383
|
}
|
|
1384
|
+
try {
|
|
1385
|
+
jexlInstance.addTransform(name, fn);
|
|
1386
|
+
} catch {
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
try {
|
|
1390
|
+
jexlInstance.addBinaryOp("and", 20, (left, right) => Boolean(left && right));
|
|
1391
|
+
jexlInstance.addBinaryOp("or", 10, (left, right) => Boolean(left || right));
|
|
1392
|
+
} catch {
|
|
1338
1393
|
}
|
|
1339
1394
|
}
|
|
1340
1395
|
registerJexlHelpers(import_jexl2.default);
|
|
1396
|
+
async function evaluateJexl(expression, context = {}) {
|
|
1397
|
+
if (!expression || typeof expression !== "string") return void 0;
|
|
1398
|
+
try {
|
|
1399
|
+
const evalContext = {
|
|
1400
|
+
null: null,
|
|
1401
|
+
undefined: void 0,
|
|
1402
|
+
...context,
|
|
1403
|
+
math: Math,
|
|
1404
|
+
Math
|
|
1405
|
+
};
|
|
1406
|
+
return await import_jexl2.default.eval(expression, evalContext);
|
|
1407
|
+
} catch (_err) {
|
|
1408
|
+
return void 0;
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1341
1411
|
var BUILTIN_JEXL_HELPERS = [
|
|
1342
1412
|
// Strings
|
|
1343
1413
|
"slugify",
|
|
@@ -1919,7 +1989,7 @@ function collectConfigDiagnostics(config) {
|
|
|
1919
1989
|
function formatConfigDiagnostics(source, diagnostics, options = {}) {
|
|
1920
1990
|
const color = options.color ?? false;
|
|
1921
1991
|
const header = `${colorize("Config validation failed", 31, color)} in ${colorize(source, 36, color)}`;
|
|
1922
|
-
const docLink = `${colorize(" Learn how to write declarative rules:", 90, color)} ${colorize("https://docs.dyrected.com/docs/
|
|
1992
|
+
const docLink = `${colorize(" Learn how to write declarative rules:", 90, color)} ${colorize("https://docs.dyrected.com/docs/editor-experience/form-and-field-hooks", 34, color)}`;
|
|
1923
1993
|
const lines = diagnostics.flatMap((issue, index) => [
|
|
1924
1994
|
`${colorize(`${index + 1}.`, 31, color)} ${colorize(issue.path, 1, color)}`,
|
|
1925
1995
|
` ${issue.message}`,
|
|
@@ -3015,6 +3085,71 @@ async function transitionWorkflow(args) {
|
|
|
3015
3085
|
|
|
3016
3086
|
// src/controllers/collection.controller.ts
|
|
3017
3087
|
init_observability();
|
|
3088
|
+
|
|
3089
|
+
// src/detail.ts
|
|
3090
|
+
function normalizeDetailItem(item) {
|
|
3091
|
+
if (typeof item === "string") {
|
|
3092
|
+
return {
|
|
3093
|
+
type: "field",
|
|
3094
|
+
field: item
|
|
3095
|
+
};
|
|
3096
|
+
}
|
|
3097
|
+
return item;
|
|
3098
|
+
}
|
|
3099
|
+
async function evaluateDetailComputed(detail, doc, user, db) {
|
|
3100
|
+
if (!doc || !detail || !Array.isArray(detail)) {
|
|
3101
|
+
return doc;
|
|
3102
|
+
}
|
|
3103
|
+
const computedItems = [];
|
|
3104
|
+
const extractComputed = (items) => {
|
|
3105
|
+
for (const rawItem of items) {
|
|
3106
|
+
if (!rawItem) continue;
|
|
3107
|
+
const item = normalizeDetailItem(rawItem);
|
|
3108
|
+
if (item.type === "computed") {
|
|
3109
|
+
const id = item.id || item.label?.toLowerCase().replace(/[^a-z0-9]+/g, "_");
|
|
3110
|
+
if (id) {
|
|
3111
|
+
computedItems.push({
|
|
3112
|
+
id,
|
|
3113
|
+
label: item.label,
|
|
3114
|
+
expression: item.expression,
|
|
3115
|
+
handler: item.handler
|
|
3116
|
+
});
|
|
3117
|
+
}
|
|
3118
|
+
} else if (item.type === "section" || item.type === "grid" || item.type === "repeat") {
|
|
3119
|
+
if (item.items && Array.isArray(item.items)) {
|
|
3120
|
+
extractComputed(item.items);
|
|
3121
|
+
}
|
|
3122
|
+
} else if (item.type === "tabs" && item.tabs && Array.isArray(item.tabs)) {
|
|
3123
|
+
for (const tab of item.tabs) {
|
|
3124
|
+
if (tab.items) extractComputed(tab.items);
|
|
3125
|
+
}
|
|
3126
|
+
}
|
|
3127
|
+
}
|
|
3128
|
+
};
|
|
3129
|
+
extractComputed(detail);
|
|
3130
|
+
if (computedItems.length === 0) return doc;
|
|
3131
|
+
const evaluated = {};
|
|
3132
|
+
for (const item of computedItems) {
|
|
3133
|
+
try {
|
|
3134
|
+
if (typeof item.handler === "function") {
|
|
3135
|
+
evaluated[item.id] = await item.handler({ doc, user, db });
|
|
3136
|
+
} else if (item.expression) {
|
|
3137
|
+
evaluated[item.id] = await evaluateJexl(item.expression, { doc, user });
|
|
3138
|
+
}
|
|
3139
|
+
} catch (_err) {
|
|
3140
|
+
evaluated[item.id] = null;
|
|
3141
|
+
}
|
|
3142
|
+
}
|
|
3143
|
+
return {
|
|
3144
|
+
...doc,
|
|
3145
|
+
_meta: {
|
|
3146
|
+
...doc._meta || {},
|
|
3147
|
+
computed: evaluated
|
|
3148
|
+
}
|
|
3149
|
+
};
|
|
3150
|
+
}
|
|
3151
|
+
|
|
3152
|
+
// src/controllers/collection.controller.ts
|
|
3018
3153
|
var CollectionController = class {
|
|
3019
3154
|
collection;
|
|
3020
3155
|
constructor(collection) {
|
|
@@ -3031,6 +3166,22 @@ var CollectionController = class {
|
|
|
3031
3166
|
toHookRequestContext(c) {
|
|
3032
3167
|
return toHookRequestContext(c.req);
|
|
3033
3168
|
}
|
|
3169
|
+
sanitizeDoc(doc) {
|
|
3170
|
+
if (!doc || typeof doc !== "object") return doc;
|
|
3171
|
+
if (this.collection.auth) {
|
|
3172
|
+
const {
|
|
3173
|
+
password: _p,
|
|
3174
|
+
salt: _s,
|
|
3175
|
+
resetPasswordToken: _rpt,
|
|
3176
|
+
resetPasswordExpires: _rpe,
|
|
3177
|
+
loginAttempts: _la,
|
|
3178
|
+
lockUntil: _lu,
|
|
3179
|
+
...safeDoc
|
|
3180
|
+
} = doc;
|
|
3181
|
+
return safeDoc;
|
|
3182
|
+
}
|
|
3183
|
+
return doc;
|
|
3184
|
+
}
|
|
3034
3185
|
async evaluateAccess(c, action, options = {}) {
|
|
3035
3186
|
const config = c.get("config");
|
|
3036
3187
|
return resolveCollectionAccess(
|
|
@@ -3263,6 +3414,9 @@ var CollectionController = class {
|
|
|
3263
3414
|
depth
|
|
3264
3415
|
);
|
|
3265
3416
|
}
|
|
3417
|
+
if (this.collection.auth && result.docs && Array.isArray(result.docs)) {
|
|
3418
|
+
result.docs = result.docs.map((d) => this.sanitizeDoc(d));
|
|
3419
|
+
}
|
|
3266
3420
|
return c.json(result);
|
|
3267
3421
|
}
|
|
3268
3422
|
async findOne(c) {
|
|
@@ -3279,32 +3433,51 @@ var CollectionController = class {
|
|
|
3279
3433
|
id: id2
|
|
3280
3434
|
});
|
|
3281
3435
|
const externalSubject = localDoc?.externalSubject || id2;
|
|
3282
|
-
let member = null;
|
|
3283
3436
|
if (provider.members.get) {
|
|
3284
|
-
member = await provider.members.get({
|
|
3437
|
+
const member = await provider.members.get({
|
|
3438
|
+
externalSubject,
|
|
3439
|
+
req: hookReq
|
|
3440
|
+
});
|
|
3441
|
+
if (member) {
|
|
3442
|
+
return c.json({
|
|
3443
|
+
...member,
|
|
3444
|
+
id: localDoc ? localDoc.id : member.id,
|
|
3445
|
+
externalSubject: member.id
|
|
3446
|
+
});
|
|
3447
|
+
}
|
|
3285
3448
|
} else if (provider.members.list) {
|
|
3286
3449
|
const listResult = await provider.members.list({ req: hookReq });
|
|
3287
|
-
|
|
3450
|
+
const matched = listResult.docs.find((m) => m.id === externalSubject || m.id === id2);
|
|
3451
|
+
if (matched) {
|
|
3452
|
+
return c.json({
|
|
3453
|
+
...matched,
|
|
3454
|
+
id: localDoc ? localDoc.id : matched.id,
|
|
3455
|
+
externalSubject: matched.id
|
|
3456
|
+
});
|
|
3457
|
+
}
|
|
3288
3458
|
}
|
|
3289
|
-
if (!member) return c.json({ message: "Not Found" }, 404);
|
|
3290
|
-
return c.json({
|
|
3291
|
-
...member,
|
|
3292
|
-
id: localDoc ? localDoc.id : member.id,
|
|
3293
|
-
externalSubject: member.id
|
|
3294
|
-
});
|
|
3295
3459
|
}
|
|
3296
|
-
const readonlyDb = createReadonlyDb(db);
|
|
3297
3460
|
const id = c.req.param("id");
|
|
3298
|
-
const depth = c.req.query("depth") !== void 0 ? Number(c.req.query("depth")) : 1;
|
|
3299
|
-
const user = c.get("user");
|
|
3300
3461
|
if (!id) return c.json({ message: "Missing ID" }, 400);
|
|
3301
|
-
|
|
3302
|
-
|
|
3462
|
+
const depth = Number.parseInt(c.req.query("depth") || "0", 10);
|
|
3463
|
+
const user = c.get("user");
|
|
3464
|
+
const doc = await db.findOne({
|
|
3465
|
+
collection: this.collection.slug,
|
|
3466
|
+
id
|
|
3467
|
+
});
|
|
3468
|
+
if (!doc) {
|
|
3469
|
+
return c.json({ message: "Document not found" }, 404);
|
|
3470
|
+
}
|
|
3471
|
+
let effectiveDoc = doc;
|
|
3303
3472
|
if (this.collection.workflow) {
|
|
3304
|
-
|
|
3305
|
-
|
|
3473
|
+
effectiveDoc = materializeWorkflowDocument(
|
|
3474
|
+
doc,
|
|
3475
|
+
this.collection.workflow,
|
|
3476
|
+
user
|
|
3477
|
+
);
|
|
3478
|
+
if (!effectiveDoc) return c.json({ message: "Document not found" }, 404);
|
|
3306
3479
|
}
|
|
3307
|
-
const access = await this.evaluateAccess(c, "read", { id, doc });
|
|
3480
|
+
const access = await this.evaluateAccess(c, "read", { id, doc: effectiveDoc });
|
|
3308
3481
|
if (!access.allowed) {
|
|
3309
3482
|
return c.json(
|
|
3310
3483
|
{
|
|
@@ -3314,7 +3487,11 @@ var CollectionController = class {
|
|
|
3314
3487
|
403
|
|
3315
3488
|
);
|
|
3316
3489
|
}
|
|
3317
|
-
const docWithDefaults = DefaultsService.apply(
|
|
3490
|
+
const docWithDefaults = DefaultsService.apply(
|
|
3491
|
+
this.collection.fields,
|
|
3492
|
+
effectiveDoc
|
|
3493
|
+
);
|
|
3494
|
+
const readonlyDb = createReadonlyDb(db);
|
|
3318
3495
|
const docWithCollectionHooks = await runCollectionHooks(
|
|
3319
3496
|
this.collection.hooks?.afterRead,
|
|
3320
3497
|
{
|
|
@@ -3340,17 +3517,20 @@ var CollectionController = class {
|
|
|
3340
3517
|
},
|
|
3341
3518
|
docWithFieldHooks
|
|
3342
3519
|
);
|
|
3520
|
+
let finalDoc = docWithFieldAccess;
|
|
3343
3521
|
if (depth > 0 && docWithFieldAccess) {
|
|
3344
3522
|
const populationService = new PopulationService(db, config.collections);
|
|
3345
|
-
|
|
3523
|
+
finalDoc = await populationService.populate({
|
|
3346
3524
|
data: docWithFieldAccess,
|
|
3347
3525
|
fields: this.collection.fields,
|
|
3348
3526
|
currentDepth: 0,
|
|
3349
3527
|
maxDepth: depth
|
|
3350
3528
|
});
|
|
3351
|
-
return c.json(populatedDoc);
|
|
3352
3529
|
}
|
|
3353
|
-
|
|
3530
|
+
if (finalDoc && this.collection.detail) {
|
|
3531
|
+
finalDoc = await evaluateDetailComputed(this.collection.detail, finalDoc, user, readonlyDb);
|
|
3532
|
+
}
|
|
3533
|
+
return c.json(this.sanitizeDoc(finalDoc));
|
|
3354
3534
|
}
|
|
3355
3535
|
async create(c) {
|
|
3356
3536
|
const config = c.get("config");
|
|
@@ -4122,6 +4302,63 @@ var CollectionController = class {
|
|
|
4122
4302
|
201
|
|
4123
4303
|
);
|
|
4124
4304
|
}
|
|
4305
|
+
async aggregate(c) {
|
|
4306
|
+
const config = c.get("config");
|
|
4307
|
+
const db = config.db;
|
|
4308
|
+
if (!db) return c.json({ message: "Database not configured" }, 500);
|
|
4309
|
+
const access = await this.evaluateAccess(c, "read");
|
|
4310
|
+
if (!access.allowed) {
|
|
4311
|
+
return c.json(
|
|
4312
|
+
{
|
|
4313
|
+
error: true,
|
|
4314
|
+
message: `Access denied: read on ${this.collection.slug}`
|
|
4315
|
+
},
|
|
4316
|
+
403
|
|
4317
|
+
);
|
|
4318
|
+
}
|
|
4319
|
+
let body;
|
|
4320
|
+
try {
|
|
4321
|
+
body = await c.req.json();
|
|
4322
|
+
} catch {
|
|
4323
|
+
return c.json({ message: "Invalid JSON body" }, 400);
|
|
4324
|
+
}
|
|
4325
|
+
if (!body || typeof body !== "object" || Array.isArray(body)) {
|
|
4326
|
+
return c.json(
|
|
4327
|
+
{ message: "Aggregate request body must be a JSON object" },
|
|
4328
|
+
400
|
|
4329
|
+
);
|
|
4330
|
+
}
|
|
4331
|
+
const { sanitizeWhereClause: sanitizeWhereClause2 } = await Promise.resolve().then(() => (init_where_sanitizer(), where_sanitizer_exports));
|
|
4332
|
+
const sanitizedAggregates = {};
|
|
4333
|
+
for (const [key, op] of Object.entries(body)) {
|
|
4334
|
+
if (!op || typeof op !== "object" || Array.isArray(op)) {
|
|
4335
|
+
return c.json(
|
|
4336
|
+
{
|
|
4337
|
+
message: `Aggregate operation "${key}" must be an object`
|
|
4338
|
+
},
|
|
4339
|
+
400
|
|
4340
|
+
);
|
|
4341
|
+
}
|
|
4342
|
+
const operation = op;
|
|
4343
|
+
let opWhere = operation.where && typeof operation.where === "object" ? operation.where : void 0;
|
|
4344
|
+
if (opWhere) {
|
|
4345
|
+
opWhere = sanitizeWhereClause2(opWhere, this.collection.fields);
|
|
4346
|
+
if (Object.keys(opWhere).length === 0) opWhere = void 0;
|
|
4347
|
+
}
|
|
4348
|
+
if (access.constraint) {
|
|
4349
|
+
opWhere = opWhere ? mergeWhereConstraint(opWhere, access.constraint) : access.constraint;
|
|
4350
|
+
}
|
|
4351
|
+
sanitizedAggregates[key] = {
|
|
4352
|
+
...operation,
|
|
4353
|
+
...opWhere !== void 0 ? { where: opWhere } : { where: void 0 }
|
|
4354
|
+
};
|
|
4355
|
+
}
|
|
4356
|
+
const result = await db.aggregate({
|
|
4357
|
+
collection: this.collection.slug,
|
|
4358
|
+
aggregates: sanitizedAggregates
|
|
4359
|
+
});
|
|
4360
|
+
return c.json(result);
|
|
4361
|
+
}
|
|
4125
4362
|
};
|
|
4126
4363
|
|
|
4127
4364
|
// src/controllers/global.controller.ts
|
|
@@ -4205,17 +4442,25 @@ var GlobalController = class {
|
|
|
4205
4442
|
},
|
|
4206
4443
|
docWithFieldHooks
|
|
4207
4444
|
);
|
|
4445
|
+
let finalDoc = docWithFieldAccess;
|
|
4208
4446
|
if (depth > 0 && docWithFieldAccess) {
|
|
4209
4447
|
const populationService = new PopulationService(db, config.collections);
|
|
4210
|
-
|
|
4448
|
+
finalDoc = await populationService.populate({
|
|
4211
4449
|
data: docWithFieldAccess,
|
|
4212
4450
|
fields: this.global.fields,
|
|
4213
4451
|
currentDepth: 0,
|
|
4214
4452
|
maxDepth: depth
|
|
4215
4453
|
});
|
|
4216
|
-
return c.json(populatedData);
|
|
4217
4454
|
}
|
|
4218
|
-
|
|
4455
|
+
if (finalDoc && this.global.detail) {
|
|
4456
|
+
finalDoc = await evaluateDetailComputed(
|
|
4457
|
+
this.global.detail,
|
|
4458
|
+
finalDoc,
|
|
4459
|
+
user,
|
|
4460
|
+
readonlyDb
|
|
4461
|
+
);
|
|
4462
|
+
}
|
|
4463
|
+
return c.json(finalDoc);
|
|
4219
4464
|
}
|
|
4220
4465
|
async update(c) {
|
|
4221
4466
|
const config = c.get("config");
|
|
@@ -5668,6 +5913,7 @@ var AdminAuthController = class {
|
|
|
5668
5913
|
});
|
|
5669
5914
|
}
|
|
5670
5915
|
getProvider(config, id) {
|
|
5916
|
+
if (!id) return void 0;
|
|
5671
5917
|
if (config.adminAuth?.mode !== "external") return void 0;
|
|
5672
5918
|
return config.adminAuth.providers.find((provider) => provider.id === id);
|
|
5673
5919
|
}
|
|
@@ -5866,17 +6112,16 @@ var AdminAuthController = class {
|
|
|
5866
6112
|
throw new Error("OIDC nonce verification failed.");
|
|
5867
6113
|
}
|
|
5868
6114
|
claims = payload;
|
|
5869
|
-
} else
|
|
5870
|
-
const
|
|
5871
|
-
|
|
5872
|
-
|
|
5873
|
-
|
|
5874
|
-
|
|
5875
|
-
|
|
6115
|
+
} else {
|
|
6116
|
+
const userInfoEndpoint = provider.userInfoEndpoint ?? discovery.userinfo_endpoint;
|
|
6117
|
+
if (!tokenBody.access_token || !userInfoEndpoint) {
|
|
6118
|
+
throw new Error("OIDC provider did not return usable identity claims.");
|
|
6119
|
+
}
|
|
6120
|
+
const userInfo = await fetch(userInfoEndpoint, {
|
|
6121
|
+
headers: { Authorization: `Bearer ${tokenBody.access_token}` }
|
|
6122
|
+
});
|
|
5876
6123
|
if (!userInfo.ok) throw new Error("OIDC userinfo request failed.");
|
|
5877
6124
|
claims = await userInfo.json();
|
|
5878
|
-
} else {
|
|
5879
|
-
throw new Error("OIDC provider did not return usable identity claims.");
|
|
5880
6125
|
}
|
|
5881
6126
|
return {
|
|
5882
6127
|
identity: this.mapClaims(claims, provider.claimMapping),
|
|
@@ -6791,6 +7036,71 @@ function generateOpenApi(config) {
|
|
|
6791
7036
|
responses: { 200: { description: "Deleted and failed document IDs" } }
|
|
6792
7037
|
}
|
|
6793
7038
|
};
|
|
7039
|
+
spec.paths[`${path}/aggregate`] = {
|
|
7040
|
+
post: {
|
|
7041
|
+
tags: [collectionTag],
|
|
7042
|
+
summary: `Aggregate ${labels.plural}`,
|
|
7043
|
+
description: `Compute statistical aggregations (count, sum, avg, min, max) across ${labels.plural}.`,
|
|
7044
|
+
requestBody: {
|
|
7045
|
+
required: true,
|
|
7046
|
+
content: {
|
|
7047
|
+
"application/json": {
|
|
7048
|
+
schema: {
|
|
7049
|
+
type: "object",
|
|
7050
|
+
additionalProperties: {
|
|
7051
|
+
type: "object",
|
|
7052
|
+
properties: {
|
|
7053
|
+
count: { type: "string", example: "*" },
|
|
7054
|
+
sum: { type: "string", description: "Field to sum" },
|
|
7055
|
+
avg: { type: "string", description: "Field to average" },
|
|
7056
|
+
min: { type: "string", description: "Field for minimum" },
|
|
7057
|
+
max: { type: "string", description: "Field for maximum" },
|
|
7058
|
+
cast: {
|
|
7059
|
+
type: "string",
|
|
7060
|
+
enum: [
|
|
7061
|
+
"number",
|
|
7062
|
+
"integer",
|
|
7063
|
+
"float",
|
|
7064
|
+
"boolean",
|
|
7065
|
+
"date",
|
|
7066
|
+
"string"
|
|
7067
|
+
]
|
|
7068
|
+
},
|
|
7069
|
+
where: {
|
|
7070
|
+
type: "object",
|
|
7071
|
+
description: "Per-aggregate filter condition",
|
|
7072
|
+
additionalProperties: true
|
|
7073
|
+
}
|
|
7074
|
+
}
|
|
7075
|
+
}
|
|
7076
|
+
}
|
|
7077
|
+
}
|
|
7078
|
+
}
|
|
7079
|
+
},
|
|
7080
|
+
responses: {
|
|
7081
|
+
200: {
|
|
7082
|
+
description: "Success",
|
|
7083
|
+
content: {
|
|
7084
|
+
"application/json": {
|
|
7085
|
+
schema: {
|
|
7086
|
+
type: "object",
|
|
7087
|
+
additionalProperties: {
|
|
7088
|
+
type: "number",
|
|
7089
|
+
nullable: true
|
|
7090
|
+
}
|
|
7091
|
+
}
|
|
7092
|
+
}
|
|
7093
|
+
}
|
|
7094
|
+
},
|
|
7095
|
+
400: {
|
|
7096
|
+
description: "Bad Request - Invalid aggregate input"
|
|
7097
|
+
},
|
|
7098
|
+
403: {
|
|
7099
|
+
description: "Forbidden - Access denied by read rule"
|
|
7100
|
+
}
|
|
7101
|
+
}
|
|
7102
|
+
}
|
|
7103
|
+
};
|
|
6794
7104
|
if (collection.audit) {
|
|
6795
7105
|
spec.paths[`${path}/__audit`] = {
|
|
6796
7106
|
get: {
|
|
@@ -7387,10 +7697,7 @@ function accessGate(config, target, action) {
|
|
|
7387
7697
|
req: toHookRequestContext(c.req)
|
|
7388
7698
|
});
|
|
7389
7699
|
if (!allowed) {
|
|
7390
|
-
return c.json(
|
|
7391
|
-
{ error: true, message: `Access denied: ${action} on ${target.slug}` },
|
|
7392
|
-
403
|
|
7393
|
-
);
|
|
7700
|
+
return c.json({ error: true, message: `Access denied: ${action} on ${target.slug}` }, 403);
|
|
7394
7701
|
}
|
|
7395
7702
|
await next();
|
|
7396
7703
|
};
|
|
@@ -7430,6 +7737,80 @@ function serializeBlockForApi(block) {
|
|
|
7430
7737
|
fields: block.fields?.map(serializeFieldForApi)
|
|
7431
7738
|
};
|
|
7432
7739
|
}
|
|
7740
|
+
function serializeDetailItemForApi(item) {
|
|
7741
|
+
if (!item) return item;
|
|
7742
|
+
if (typeof item === "string") {
|
|
7743
|
+
return { type: "field", field: item };
|
|
7744
|
+
}
|
|
7745
|
+
if (item.type === "section") {
|
|
7746
|
+
return {
|
|
7747
|
+
type: "section",
|
|
7748
|
+
title: item.title,
|
|
7749
|
+
items: (item.items || []).map(serializeDetailItemForApi),
|
|
7750
|
+
options: item.options
|
|
7751
|
+
};
|
|
7752
|
+
}
|
|
7753
|
+
if (item.type === "tab") {
|
|
7754
|
+
return {
|
|
7755
|
+
type: "tab",
|
|
7756
|
+
label: item.label,
|
|
7757
|
+
items: (item.items || []).map(serializeDetailItemForApi),
|
|
7758
|
+
options: item.options
|
|
7759
|
+
};
|
|
7760
|
+
}
|
|
7761
|
+
if (item.type === "tabs") {
|
|
7762
|
+
return {
|
|
7763
|
+
type: "tabs",
|
|
7764
|
+
tabs: (item.tabs || []).map(serializeDetailItemForApi),
|
|
7765
|
+
options: item.options
|
|
7766
|
+
};
|
|
7767
|
+
}
|
|
7768
|
+
if (item.type === "grid") {
|
|
7769
|
+
return {
|
|
7770
|
+
type: "grid",
|
|
7771
|
+
columns: item.columns,
|
|
7772
|
+
items: (item.items || []).map(serializeDetailItemForApi),
|
|
7773
|
+
options: item.options
|
|
7774
|
+
};
|
|
7775
|
+
}
|
|
7776
|
+
if (item.type === "repeat") {
|
|
7777
|
+
return {
|
|
7778
|
+
type: "repeat",
|
|
7779
|
+
field: item.field,
|
|
7780
|
+
items: (item.items || []).map(serializeDetailItemForApi),
|
|
7781
|
+
options: item.options
|
|
7782
|
+
};
|
|
7783
|
+
}
|
|
7784
|
+
if (item.type === "computed") {
|
|
7785
|
+
return {
|
|
7786
|
+
type: "computed",
|
|
7787
|
+
id: item.id || item.label?.toLowerCase().replace(/[^a-z0-9]+/g, "_"),
|
|
7788
|
+
label: item.label,
|
|
7789
|
+
expression: item.expression,
|
|
7790
|
+
options: {
|
|
7791
|
+
id: item.id,
|
|
7792
|
+
span: item.options?.span,
|
|
7793
|
+
format: item.options?.format,
|
|
7794
|
+
currency: item.options?.currency,
|
|
7795
|
+
visible: item.options?.visible
|
|
7796
|
+
}
|
|
7797
|
+
};
|
|
7798
|
+
}
|
|
7799
|
+
if (item.type === "field") {
|
|
7800
|
+
return {
|
|
7801
|
+
type: "field",
|
|
7802
|
+
field: item.field,
|
|
7803
|
+
options: item.options
|
|
7804
|
+
};
|
|
7805
|
+
}
|
|
7806
|
+
return item;
|
|
7807
|
+
}
|
|
7808
|
+
function serializeDetailForApi(detail) {
|
|
7809
|
+
if (detail === false) return false;
|
|
7810
|
+
if (detail === true) return true;
|
|
7811
|
+
if (!detail || !Array.isArray(detail)) return void 0;
|
|
7812
|
+
return detail.map(serializeDetailItemForApi);
|
|
7813
|
+
}
|
|
7433
7814
|
function registerRoutes(app, config) {
|
|
7434
7815
|
const optionsCache = /* @__PURE__ */ new Map();
|
|
7435
7816
|
app.get("/api/schemas", optionalAuth(config), async (c) => {
|
|
@@ -7448,15 +7829,12 @@ function registerRoutes(app, config) {
|
|
|
7448
7829
|
if (typeof access === "boolean") return access;
|
|
7449
7830
|
if (access && typeof access === "object" && typeof access.policy === "string") {
|
|
7450
7831
|
const policy = requestConfig.accessPolicies?.[access.policy];
|
|
7451
|
-
if (typeof policy === "string" || typeof policy === "boolean")
|
|
7452
|
-
return policy;
|
|
7832
|
+
if (typeof policy === "string" || typeof policy === "boolean") return policy;
|
|
7453
7833
|
}
|
|
7454
7834
|
return resolveBooleanAccess(requestConfig, access, accessArgs);
|
|
7455
7835
|
};
|
|
7456
7836
|
const filteredCollections = await Promise.all(
|
|
7457
|
-
collections.filter(
|
|
7458
|
-
(col) => !siteId || col.shared || !col.siteId || col.siteId === siteId
|
|
7459
|
-
).map(async (col) => ({
|
|
7837
|
+
collections.filter((col) => !siteId || col.shared || !col.siteId || col.siteId === siteId).map(async (col) => ({
|
|
7460
7838
|
slug: col.slug,
|
|
7461
7839
|
labels: col.labels,
|
|
7462
7840
|
access: {
|
|
@@ -7494,6 +7872,7 @@ function registerRoutes(app, config) {
|
|
|
7494
7872
|
audit: !!col.audit,
|
|
7495
7873
|
drafts: !!col.drafts,
|
|
7496
7874
|
admin: col.admin,
|
|
7875
|
+
detail: serializeDetailForApi(col.detail),
|
|
7497
7876
|
workflow: col.workflow ? {
|
|
7498
7877
|
initialState: col.workflow.initialState,
|
|
7499
7878
|
draftState: col.workflow.draftState,
|
|
@@ -7512,9 +7891,7 @@ function registerRoutes(app, config) {
|
|
|
7512
7891
|
}))
|
|
7513
7892
|
);
|
|
7514
7893
|
const filteredGlobals = await Promise.all(
|
|
7515
|
-
globals.filter(
|
|
7516
|
-
(glb) => !siteId || glb.shared || !glb.siteId || glb.siteId === siteId
|
|
7517
|
-
).map(async (glb) => ({
|
|
7894
|
+
globals.filter((glb) => !siteId || glb.shared || !glb.siteId || glb.siteId === siteId).map(async (glb) => ({
|
|
7518
7895
|
slug: glb.slug,
|
|
7519
7896
|
label: glb.label,
|
|
7520
7897
|
access: {
|
|
@@ -7544,7 +7921,8 @@ function registerRoutes(app, config) {
|
|
|
7544
7921
|
}
|
|
7545
7922
|
}))
|
|
7546
7923
|
),
|
|
7547
|
-
admin: glb.admin
|
|
7924
|
+
admin: glb.admin,
|
|
7925
|
+
detail: serializeDetailForApi(glb.detail)
|
|
7548
7926
|
}))
|
|
7549
7927
|
);
|
|
7550
7928
|
return c.json({
|
|
@@ -7558,148 +7936,137 @@ function registerRoutes(app, config) {
|
|
|
7558
7936
|
adminHealth: {
|
|
7559
7937
|
emailConfigured: !!requestConfig.email,
|
|
7560
7938
|
secureAuthSecretConfigured: !!process.env.DYRECTED_JWT_SECRET,
|
|
7561
|
-
authCollectionConfigured: requestConfig.collections.some(
|
|
7562
|
-
|
|
7563
|
-
),
|
|
7564
|
-
uploadCollectionConfigured: requestConfig.collections.some(
|
|
7565
|
-
(collection) => !!collection.upload
|
|
7566
|
-
)
|
|
7939
|
+
authCollectionConfigured: requestConfig.collections.some((collection) => !!collection.auth),
|
|
7940
|
+
uploadCollectionConfigured: requestConfig.collections.some((collection) => !!collection.upload)
|
|
7567
7941
|
}
|
|
7568
7942
|
});
|
|
7569
7943
|
});
|
|
7570
|
-
app.get(
|
|
7571
|
-
|
|
7572
|
-
|
|
7573
|
-
|
|
7574
|
-
|
|
7575
|
-
|
|
7576
|
-
|
|
7577
|
-
|
|
7578
|
-
|
|
7579
|
-
|
|
7580
|
-
|
|
7581
|
-
|
|
7582
|
-
|
|
7583
|
-
|
|
7584
|
-
|
|
7585
|
-
|
|
7586
|
-
|
|
7587
|
-
});
|
|
7588
|
-
if (!allowed) {
|
|
7589
|
-
return c.json(
|
|
7590
|
-
{ error: true, message: `Access denied: read on ${colSlug}` },
|
|
7591
|
-
403
|
|
7592
|
-
);
|
|
7593
|
-
}
|
|
7594
|
-
}
|
|
7595
|
-
field = collection.fields.find((f) => f.name === fieldName);
|
|
7596
|
-
} else {
|
|
7597
|
-
const globals = [...requestConfig.globals];
|
|
7598
|
-
const glb = globals.find((g) => g.slug === colSlug);
|
|
7599
|
-
if (!glb) {
|
|
7600
|
-
return c.json(
|
|
7601
|
-
{
|
|
7602
|
-
error: true,
|
|
7603
|
-
message: `${colSlug} not found as collection or global`
|
|
7604
|
-
},
|
|
7605
|
-
404
|
|
7606
|
-
);
|
|
7607
|
-
}
|
|
7608
|
-
const accessExpr = glb.access?.read;
|
|
7609
|
-
if (accessExpr !== void 0 && accessExpr !== null) {
|
|
7610
|
-
const allowed = await resolveBooleanAccess(config, accessExpr, {
|
|
7611
|
-
user,
|
|
7612
|
-
req: toHookRequestContext(c.req)
|
|
7613
|
-
});
|
|
7614
|
-
if (!allowed) {
|
|
7615
|
-
return c.json(
|
|
7616
|
-
{
|
|
7617
|
-
error: true,
|
|
7618
|
-
message: `Access denied: read on global ${colSlug}`
|
|
7619
|
-
},
|
|
7620
|
-
403
|
|
7621
|
-
);
|
|
7622
|
-
}
|
|
7944
|
+
app.get("/api/dyrected/options/:collection/:field", optionalAuth(config), async (c) => {
|
|
7945
|
+
const { collection: colSlug, field: fieldName } = c.req.param();
|
|
7946
|
+
const siteId = c.req.header("X-Site-Id");
|
|
7947
|
+
const requestConfig = siteId && config.onSchemaFetch ? mergeDynamicConfig(config, await config.onSchemaFetch(siteId)) : config;
|
|
7948
|
+
let collections = [...requestConfig.collections];
|
|
7949
|
+
const user = c.get("user");
|
|
7950
|
+
let collection = collections.find((col) => col.slug === colSlug);
|
|
7951
|
+
let field;
|
|
7952
|
+
if (collection) {
|
|
7953
|
+
const accessExpr = collection.access?.read;
|
|
7954
|
+
if (accessExpr !== void 0 && accessExpr !== null) {
|
|
7955
|
+
const allowed = await resolveBooleanAccess(config, accessExpr, {
|
|
7956
|
+
user,
|
|
7957
|
+
req: toHookRequestContext(c.req)
|
|
7958
|
+
});
|
|
7959
|
+
if (!allowed) {
|
|
7960
|
+
return c.json({ error: true, message: `Access denied: read on ${colSlug}` }, 403);
|
|
7623
7961
|
}
|
|
7624
|
-
field = glb.fields.find((f) => f.name === fieldName);
|
|
7625
7962
|
}
|
|
7626
|
-
|
|
7963
|
+
field = collection.fields.find((f) => f.name === fieldName);
|
|
7964
|
+
} else {
|
|
7965
|
+
const globals = [...requestConfig.globals];
|
|
7966
|
+
const glb = globals.find((g) => g.slug === colSlug);
|
|
7967
|
+
if (!glb) {
|
|
7627
7968
|
return c.json(
|
|
7628
7969
|
{
|
|
7629
7970
|
error: true,
|
|
7630
|
-
message:
|
|
7971
|
+
message: `${colSlug} not found as collection or global`
|
|
7631
7972
|
},
|
|
7632
7973
|
404
|
|
7633
7974
|
);
|
|
7634
7975
|
}
|
|
7635
|
-
|
|
7636
|
-
|
|
7637
|
-
|
|
7638
|
-
resolver = field.options;
|
|
7639
|
-
} else if (field.options && typeof field.options === "object" && "resolve" in field.options) {
|
|
7640
|
-
resolver = field.options.resolve;
|
|
7641
|
-
cacheTTL = field.options.cacheTTL;
|
|
7642
|
-
}
|
|
7643
|
-
if (!resolver) {
|
|
7644
|
-
return c.json(
|
|
7645
|
-
{
|
|
7646
|
-
error: true,
|
|
7647
|
-
message: `Field ${fieldName} in ${colSlug} is not dynamic`
|
|
7648
|
-
},
|
|
7649
|
-
400
|
|
7650
|
-
);
|
|
7651
|
-
}
|
|
7652
|
-
try {
|
|
7653
|
-
const db = c.get("db") || config.db;
|
|
7654
|
-
const queryParams = c.req.query();
|
|
7655
|
-
const reqContext = {
|
|
7656
|
-
query: queryParams,
|
|
7657
|
-
headers: c.req.header(),
|
|
7658
|
-
raw: c.req.raw
|
|
7659
|
-
};
|
|
7660
|
-
const shouldCache = typeof cacheTTL === "number" && cacheTTL > 0;
|
|
7661
|
-
let cacheKey = "";
|
|
7662
|
-
if (shouldCache) {
|
|
7663
|
-
cacheKey = JSON.stringify([
|
|
7664
|
-
siteId ?? "",
|
|
7665
|
-
colSlug,
|
|
7666
|
-
fieldName,
|
|
7667
|
-
user?.id ?? null,
|
|
7668
|
-
queryParams
|
|
7669
|
-
]);
|
|
7670
|
-
const hit = optionsCache.get(cacheKey);
|
|
7671
|
-
if (hit && hit.expires > Date.now()) {
|
|
7672
|
-
return c.json(hit.value);
|
|
7673
|
-
}
|
|
7674
|
-
}
|
|
7675
|
-
const result = await resolver({
|
|
7676
|
-
db,
|
|
7976
|
+
const accessExpr = glb.access?.read;
|
|
7977
|
+
if (accessExpr !== void 0 && accessExpr !== null) {
|
|
7978
|
+
const allowed = await resolveBooleanAccess(config, accessExpr, {
|
|
7677
7979
|
user,
|
|
7678
|
-
req:
|
|
7980
|
+
req: toHookRequestContext(c.req)
|
|
7679
7981
|
});
|
|
7680
|
-
if (
|
|
7681
|
-
|
|
7682
|
-
|
|
7683
|
-
|
|
7684
|
-
|
|
7982
|
+
if (!allowed) {
|
|
7983
|
+
return c.json(
|
|
7984
|
+
{
|
|
7985
|
+
error: true,
|
|
7986
|
+
message: `Access denied: read on global ${colSlug}`
|
|
7987
|
+
},
|
|
7988
|
+
403
|
|
7989
|
+
);
|
|
7685
7990
|
}
|
|
7686
|
-
|
|
7687
|
-
|
|
7688
|
-
|
|
7689
|
-
|
|
7690
|
-
|
|
7691
|
-
|
|
7991
|
+
}
|
|
7992
|
+
field = glb.fields.find((f) => f.name === fieldName);
|
|
7993
|
+
}
|
|
7994
|
+
if (!field) {
|
|
7995
|
+
return c.json(
|
|
7996
|
+
{
|
|
7997
|
+
error: true,
|
|
7998
|
+
message: `Field ${fieldName} not found in ${colSlug}`
|
|
7999
|
+
},
|
|
8000
|
+
404
|
|
8001
|
+
);
|
|
8002
|
+
}
|
|
8003
|
+
let resolver;
|
|
8004
|
+
let cacheTTL;
|
|
8005
|
+
if (typeof field.options === "function") {
|
|
8006
|
+
resolver = field.options;
|
|
8007
|
+
} else if (field.options && typeof field.options === "object" && "resolve" in field.options) {
|
|
8008
|
+
resolver = field.options.resolve;
|
|
8009
|
+
cacheTTL = field.options.cacheTTL;
|
|
8010
|
+
}
|
|
8011
|
+
if (!resolver) {
|
|
8012
|
+
return c.json(
|
|
8013
|
+
{
|
|
8014
|
+
error: true,
|
|
8015
|
+
message: `Field ${fieldName} in ${colSlug} is not dynamic`
|
|
8016
|
+
},
|
|
8017
|
+
400
|
|
8018
|
+
);
|
|
8019
|
+
}
|
|
8020
|
+
try {
|
|
8021
|
+
const db = c.get("db") || config.db;
|
|
8022
|
+
const queryParams = c.req.query();
|
|
8023
|
+
const reqContext = {
|
|
8024
|
+
query: queryParams,
|
|
8025
|
+
headers: c.req.header(),
|
|
8026
|
+
raw: c.req.raw
|
|
8027
|
+
};
|
|
8028
|
+
const shouldCache = typeof cacheTTL === "number" && cacheTTL > 0;
|
|
8029
|
+
let cacheKey = "";
|
|
8030
|
+
if (shouldCache) {
|
|
8031
|
+
cacheKey = JSON.stringify([
|
|
8032
|
+
siteId ?? "",
|
|
8033
|
+
colSlug,
|
|
8034
|
+
fieldName,
|
|
8035
|
+
user?.id ?? null,
|
|
8036
|
+
queryParams
|
|
8037
|
+
]);
|
|
8038
|
+
const hit = optionsCache.get(cacheKey);
|
|
8039
|
+
if (hit && hit.expires > Date.now()) {
|
|
8040
|
+
return c.json(hit.value);
|
|
8041
|
+
}
|
|
8042
|
+
}
|
|
8043
|
+
const result = await resolver({
|
|
8044
|
+
db,
|
|
8045
|
+
user,
|
|
8046
|
+
req: reqContext
|
|
8047
|
+
});
|
|
8048
|
+
if (shouldCache) {
|
|
8049
|
+
optionsCache.set(cacheKey, {
|
|
8050
|
+
expires: Date.now() + cacheTTL * 1e3,
|
|
8051
|
+
value: result
|
|
7692
8052
|
});
|
|
7693
|
-
return c.json(
|
|
7694
|
-
{
|
|
7695
|
-
error: true,
|
|
7696
|
-
message: err.message || "Failed to resolve dynamic options"
|
|
7697
|
-
},
|
|
7698
|
-
500
|
|
7699
|
-
);
|
|
7700
8053
|
}
|
|
8054
|
+
return c.json(result);
|
|
8055
|
+
} catch (err) {
|
|
8056
|
+
getRequestLogger(c, "router").error({
|
|
8057
|
+
err,
|
|
8058
|
+
msg: "Failed to resolve dynamic field options",
|
|
8059
|
+
fieldName
|
|
8060
|
+
});
|
|
8061
|
+
return c.json(
|
|
8062
|
+
{
|
|
8063
|
+
error: true,
|
|
8064
|
+
message: err.message || "Failed to resolve dynamic options"
|
|
8065
|
+
},
|
|
8066
|
+
500
|
|
8067
|
+
);
|
|
7701
8068
|
}
|
|
7702
|
-
);
|
|
8069
|
+
});
|
|
7703
8070
|
app.get("/api/openapi.json", (c) => {
|
|
7704
8071
|
return c.json(generateOpenApi(config));
|
|
7705
8072
|
});
|
|
@@ -7712,13 +8079,8 @@ function registerRoutes(app, config) {
|
|
|
7712
8079
|
const key = c.req.param("key");
|
|
7713
8080
|
const scope = c.req.query("scope");
|
|
7714
8081
|
if (!db) return c.json({ message: "Database not configured" }, 500);
|
|
7715
|
-
if (!user?.collection || !user.sub)
|
|
7716
|
-
|
|
7717
|
-
if (!key)
|
|
7718
|
-
return c.json(
|
|
7719
|
-
{ error: true, message: "Preference key is required." },
|
|
7720
|
-
400
|
|
7721
|
-
);
|
|
8082
|
+
if (!user?.collection || !user.sub) return c.json({ error: true, message: "Authentication required." }, 401);
|
|
8083
|
+
if (!key) return c.json({ error: true, message: "Preference key is required." }, 400);
|
|
7722
8084
|
const getGlobalPreference = async () => {
|
|
7723
8085
|
const globalDoc = await db.findOne({
|
|
7724
8086
|
collection: "__global_preferences",
|
|
@@ -7745,13 +8107,8 @@ function registerRoutes(app, config) {
|
|
|
7745
8107
|
const key = c.req.param("key");
|
|
7746
8108
|
const scope = c.req.query("scope");
|
|
7747
8109
|
if (!db) return c.json({ message: "Database not configured" }, 500);
|
|
7748
|
-
if (!user?.collection || !user.sub)
|
|
7749
|
-
|
|
7750
|
-
if (!key)
|
|
7751
|
-
return c.json(
|
|
7752
|
-
{ error: true, message: "Preference key is required." },
|
|
7753
|
-
400
|
|
7754
|
-
);
|
|
8110
|
+
if (!user?.collection || !user.sub) return c.json({ error: true, message: "Authentication required." }, 401);
|
|
8111
|
+
if (!key) return c.json({ error: true, message: "Preference key is required." }, 400);
|
|
7755
8112
|
const body = await c.req.json().catch(() => ({}));
|
|
7756
8113
|
if (scope === "global") {
|
|
7757
8114
|
const isAdminUser = Array.isArray(user?.roles) && user.roles.includes("admin");
|
|
@@ -7799,13 +8156,8 @@ function registerRoutes(app, config) {
|
|
|
7799
8156
|
const key = c.req.param("key");
|
|
7800
8157
|
const scope = c.req.query("scope");
|
|
7801
8158
|
if (!db) return c.json({ message: "Database not configured" }, 500);
|
|
7802
|
-
if (!user?.collection || !user.sub)
|
|
7803
|
-
|
|
7804
|
-
if (!key)
|
|
7805
|
-
return c.json(
|
|
7806
|
-
{ error: true, message: "Preference key is required." },
|
|
7807
|
-
400
|
|
7808
|
-
);
|
|
8159
|
+
if (!user?.collection || !user.sub) return c.json({ error: true, message: "Authentication required." }, 401);
|
|
8160
|
+
if (!key) return c.json({ error: true, message: "Preference key is required." }, 400);
|
|
7809
8161
|
if (scope === "global") {
|
|
7810
8162
|
const isAdminUser = Array.isArray(user?.roles) && user.roles.includes("admin");
|
|
7811
8163
|
if (!isAdminUser) {
|
|
@@ -7844,41 +8196,17 @@ function registerRoutes(app, config) {
|
|
|
7844
8196
|
for (const col of uploadCollections) {
|
|
7845
8197
|
const mediaController = new MediaController(col.slug);
|
|
7846
8198
|
const prefix = `/api/collections/${col.slug}`;
|
|
7847
|
-
app.get(
|
|
7848
|
-
|
|
7849
|
-
|
|
7850
|
-
|
|
7851
|
-
);
|
|
7852
|
-
app.get(
|
|
7853
|
-
`${prefix}/media/:filename{.+$}`,
|
|
7854
|
-
(c) => mediaController.serve(c)
|
|
7855
|
-
);
|
|
7856
|
-
app.post(
|
|
7857
|
-
`${prefix}/media`,
|
|
7858
|
-
accessGate(config, col, "create"),
|
|
7859
|
-
(c) => mediaController.upload(c)
|
|
7860
|
-
);
|
|
7861
|
-
app.delete(
|
|
7862
|
-
`${prefix}/media/:id`,
|
|
7863
|
-
accessGate(config, col, "delete"),
|
|
7864
|
-
(c) => mediaController.delete(c)
|
|
7865
|
-
);
|
|
8199
|
+
app.get(`${prefix}/media`, accessGate(config, col, "read"), (c) => mediaController.find(c));
|
|
8200
|
+
app.get(`${prefix}/media/:filename{.+$}`, (c) => mediaController.serve(c));
|
|
8201
|
+
app.post(`${prefix}/media`, accessGate(config, col, "create"), (c) => mediaController.upload(c));
|
|
8202
|
+
app.delete(`${prefix}/media/:id`, accessGate(config, col, "delete"), (c) => mediaController.delete(c));
|
|
7866
8203
|
}
|
|
7867
8204
|
}
|
|
7868
8205
|
const adminAuthController = new AdminAuthController(config);
|
|
7869
8206
|
app.get("/api/admin/auth/providers", (c) => adminAuthController.providers(c));
|
|
7870
|
-
app.get(
|
|
7871
|
-
|
|
7872
|
-
|
|
7873
|
-
);
|
|
7874
|
-
app.get(
|
|
7875
|
-
"/api/admin/auth/:provider/callback",
|
|
7876
|
-
(c) => adminAuthController.callback(c)
|
|
7877
|
-
);
|
|
7878
|
-
app.post(
|
|
7879
|
-
"/api/admin/auth/:provider/exchange",
|
|
7880
|
-
(c) => adminAuthController.exchange(c)
|
|
7881
|
-
);
|
|
8207
|
+
app.get("/api/admin/auth/:provider/start", (c) => adminAuthController.start(c));
|
|
8208
|
+
app.get("/api/admin/auth/:provider/callback", (c) => adminAuthController.callback(c));
|
|
8209
|
+
app.post("/api/admin/auth/:provider/exchange", (c) => adminAuthController.exchange(c));
|
|
7882
8210
|
app.post("/api/admin/logout", (c) => adminAuthController.logout(c));
|
|
7883
8211
|
for (const collection of config.collections) {
|
|
7884
8212
|
if (!collection.auth) continue;
|
|
@@ -7889,21 +8217,10 @@ function registerRoutes(app, config) {
|
|
|
7889
8217
|
app.get(`${path}/init`, (c) => authController.init(c));
|
|
7890
8218
|
app.post(`${path}/first-user`, (c) => authController.registerFirstUser(c));
|
|
7891
8219
|
app.get(`${path}/me`, requireAuth(config), (c) => authController.me(c));
|
|
7892
|
-
app.post(
|
|
7893
|
-
|
|
7894
|
-
requireAuth(config),
|
|
7895
|
-
(c) => authController.refreshToken(c)
|
|
7896
|
-
);
|
|
7897
|
-
app.post(
|
|
7898
|
-
`${path}/forgot-password`,
|
|
7899
|
-
(c) => authController.forgotPassword(c)
|
|
7900
|
-
);
|
|
8220
|
+
app.post(`${path}/refresh-token`, requireAuth(config), (c) => authController.refreshToken(c));
|
|
8221
|
+
app.post(`${path}/forgot-password`, (c) => authController.forgotPassword(c));
|
|
7901
8222
|
app.post(`${path}/reset-password`, (c) => authController.resetPassword(c));
|
|
7902
|
-
app.post(
|
|
7903
|
-
`${path}/invite`,
|
|
7904
|
-
requireAuth(config),
|
|
7905
|
-
(c) => authController.invite(c)
|
|
7906
|
-
);
|
|
8223
|
+
app.post(`${path}/invite`, requireAuth(config), (c) => authController.invite(c));
|
|
7907
8224
|
app.post(`${path}/accept-invite`, (c) => authController.acceptInvite(c));
|
|
7908
8225
|
}
|
|
7909
8226
|
const auditController = new AuditController();
|
|
@@ -7914,33 +8231,19 @@ function registerRoutes(app, config) {
|
|
|
7914
8231
|
app.post(path, (c) => controller.create(c));
|
|
7915
8232
|
app.post(`${path}/media`, (c) => controller.create(c));
|
|
7916
8233
|
app.delete(`${path}/delete-many`, (c) => controller.deleteMany(c));
|
|
8234
|
+
app.post(`${path}/aggregate`, (c) => controller.aggregate(c));
|
|
7917
8235
|
if (collection.audit) {
|
|
7918
|
-
app.get(
|
|
7919
|
-
`${path}/__audit`,
|
|
7920
|
-
(c) => auditController.findForCollection(c, collection)
|
|
7921
|
-
);
|
|
8236
|
+
app.get(`${path}/__audit`, (c) => auditController.findForCollection(c, collection));
|
|
7922
8237
|
}
|
|
7923
8238
|
app.get(`${path}/:id`, (c) => controller.findOne(c));
|
|
7924
8239
|
app.patch(`${path}/:id`, (c) => controller.update(c));
|
|
7925
8240
|
app.delete(`${path}/:id`, (c) => controller.delete(c));
|
|
7926
8241
|
if (collection.auth) {
|
|
7927
|
-
app.post(
|
|
7928
|
-
`${path}/:id/change-password`,
|
|
7929
|
-
requireAuth(config),
|
|
7930
|
-
(c) => controller.changePassword(c)
|
|
7931
|
-
);
|
|
8242
|
+
app.post(`${path}/:id/change-password`, requireAuth(config), (c) => controller.changePassword(c));
|
|
7932
8243
|
}
|
|
7933
8244
|
if (collection.workflow) {
|
|
7934
|
-
app.post(
|
|
7935
|
-
|
|
7936
|
-
requireAuth(config),
|
|
7937
|
-
(c) => controller.transition(c)
|
|
7938
|
-
);
|
|
7939
|
-
app.get(
|
|
7940
|
-
`${path}/:id/workflow-history`,
|
|
7941
|
-
requireAuth(config),
|
|
7942
|
-
(c) => controller.workflowHistory(c)
|
|
7943
|
-
);
|
|
8245
|
+
app.post(`${path}/:id/transitions/:transition`, requireAuth(config), (c) => controller.transition(c));
|
|
8246
|
+
app.get(`${path}/:id/workflow-history`, requireAuth(config), (c) => controller.workflowHistory(c));
|
|
7944
8247
|
}
|
|
7945
8248
|
}
|
|
7946
8249
|
for (const global of config.globals) {
|
|
@@ -7955,11 +8258,7 @@ function registerRoutes(app, config) {
|
|
|
7955
8258
|
});
|
|
7956
8259
|
}
|
|
7957
8260
|
const previewController = new PreviewController();
|
|
7958
|
-
app.post(
|
|
7959
|
-
"/api/preview-token",
|
|
7960
|
-
requireAuth(config),
|
|
7961
|
-
(c) => previewController.createToken(c)
|
|
7962
|
-
);
|
|
8261
|
+
app.post("/api/preview-token", requireAuth(config), (c) => previewController.createToken(c));
|
|
7963
8262
|
app.get("/api/preview-data", (c) => previewController.getData(c));
|
|
7964
8263
|
app.get("/api/audit", (c) => auditController.findAll(c));
|
|
7965
8264
|
app.get("/api/collections/:slug/__audit", async (c) => {
|
|
@@ -7972,81 +8271,49 @@ function registerRoutes(app, config) {
|
|
|
7972
8271
|
if (!config2.onSchemaFetch || !siteId) {
|
|
7973
8272
|
return c.json({ message: `Collection "${slug}" not found` }, 404);
|
|
7974
8273
|
}
|
|
7975
|
-
const requestConfig = mergeDynamicConfig(
|
|
7976
|
-
|
|
7977
|
-
await config2.onSchemaFetch(siteId)
|
|
7978
|
-
);
|
|
7979
|
-
const collection = requestConfig.collections.find(
|
|
7980
|
-
(col) => col.slug === slug
|
|
7981
|
-
);
|
|
8274
|
+
const requestConfig = mergeDynamicConfig(config2, await config2.onSchemaFetch(siteId));
|
|
8275
|
+
const collection = requestConfig.collections.find((col) => col.slug === slug);
|
|
7982
8276
|
if (!collection?.audit) {
|
|
7983
|
-
return c.json(
|
|
7984
|
-
{ message: `Collection "${slug}" not found or has no audit log` },
|
|
7985
|
-
404
|
|
7986
|
-
);
|
|
8277
|
+
return c.json({ message: `Collection "${slug}" not found or has no audit log` }, 404);
|
|
7987
8278
|
}
|
|
7988
8279
|
return auditController.findForCollection(c, collection);
|
|
7989
8280
|
});
|
|
7990
|
-
app.post(
|
|
7991
|
-
"
|
|
7992
|
-
|
|
7993
|
-
|
|
7994
|
-
|
|
7995
|
-
|
|
7996
|
-
const config2 = c.get("config");
|
|
7997
|
-
if (config2.collections.some((col) => col.slug === slug)) {
|
|
7998
|
-
return c.json({ message: "Not Found" }, 404);
|
|
7999
|
-
}
|
|
8000
|
-
if (!config2.onSchemaFetch || !siteId) {
|
|
8001
|
-
return c.json({ message: `Collection "${slug}" not found` }, 404);
|
|
8002
|
-
}
|
|
8003
|
-
const requestConfig = mergeDynamicConfig(
|
|
8004
|
-
config2,
|
|
8005
|
-
await config2.onSchemaFetch(siteId)
|
|
8006
|
-
);
|
|
8007
|
-
const collection = requestConfig.collections.find(
|
|
8008
|
-
(col) => col.slug === slug
|
|
8009
|
-
);
|
|
8010
|
-
if (!collection?.workflow) {
|
|
8011
|
-
return c.json(
|
|
8012
|
-
{ message: `Collection "${slug}" not found or has no workflow` },
|
|
8013
|
-
404
|
|
8014
|
-
);
|
|
8015
|
-
}
|
|
8016
|
-
const controller = new CollectionController(collection);
|
|
8017
|
-
return controller.transition(c);
|
|
8281
|
+
app.post("/api/collections/:slug/:id/transitions/:transition", requireAuth(config), async (c) => {
|
|
8282
|
+
const slug = c.req.param("slug");
|
|
8283
|
+
const siteId = c.req.header("X-Site-Id") || c.get("siteId");
|
|
8284
|
+
const config2 = c.get("config");
|
|
8285
|
+
if (config2.collections.some((col) => col.slug === slug)) {
|
|
8286
|
+
return c.json({ message: "Not Found" }, 404);
|
|
8018
8287
|
}
|
|
8019
|
-
|
|
8020
|
-
|
|
8021
|
-
"/api/collections/:slug/:id/workflow-history",
|
|
8022
|
-
requireAuth(config),
|
|
8023
|
-
async (c) => {
|
|
8024
|
-
const slug = c.req.param("slug");
|
|
8025
|
-
const siteId = c.req.header("X-Site-Id") || c.get("siteId");
|
|
8026
|
-
const config2 = c.get("config");
|
|
8027
|
-
if (config2.collections.some((col) => col.slug === slug)) {
|
|
8028
|
-
return c.json({ message: "Not Found" }, 404);
|
|
8029
|
-
}
|
|
8030
|
-
if (!config2.onSchemaFetch || !siteId) {
|
|
8031
|
-
return c.json({ message: `Collection "${slug}" not found` }, 404);
|
|
8032
|
-
}
|
|
8033
|
-
const requestConfig = mergeDynamicConfig(
|
|
8034
|
-
config2,
|
|
8035
|
-
await config2.onSchemaFetch(siteId)
|
|
8036
|
-
);
|
|
8037
|
-
const collection = requestConfig.collections.find(
|
|
8038
|
-
(col) => col.slug === slug
|
|
8039
|
-
);
|
|
8040
|
-
if (!collection?.workflow) {
|
|
8041
|
-
return c.json(
|
|
8042
|
-
{ message: `Collection "${slug}" not found or has no workflow` },
|
|
8043
|
-
404
|
|
8044
|
-
);
|
|
8045
|
-
}
|
|
8046
|
-
const controller = new CollectionController(collection);
|
|
8047
|
-
return controller.workflowHistory(c);
|
|
8288
|
+
if (!config2.onSchemaFetch || !siteId) {
|
|
8289
|
+
return c.json({ message: `Collection "${slug}" not found` }, 404);
|
|
8048
8290
|
}
|
|
8049
|
-
|
|
8291
|
+
const requestConfig = mergeDynamicConfig(config2, await config2.onSchemaFetch(siteId));
|
|
8292
|
+
const collection = requestConfig.collections.find((col) => col.slug === slug);
|
|
8293
|
+
if (!collection?.workflow) {
|
|
8294
|
+
return c.json({ message: `Collection "${slug}" not found or has no workflow` }, 404);
|
|
8295
|
+
}
|
|
8296
|
+
const controller = new CollectionController(collection);
|
|
8297
|
+
return controller.transition(c);
|
|
8298
|
+
});
|
|
8299
|
+
app.get("/api/collections/:slug/:id/workflow-history", requireAuth(config), async (c) => {
|
|
8300
|
+
const slug = c.req.param("slug");
|
|
8301
|
+
const siteId = c.req.header("X-Site-Id") || c.get("siteId");
|
|
8302
|
+
const config2 = c.get("config");
|
|
8303
|
+
if (config2.collections.some((col) => col.slug === slug)) {
|
|
8304
|
+
return c.json({ message: "Not Found" }, 404);
|
|
8305
|
+
}
|
|
8306
|
+
if (!config2.onSchemaFetch || !siteId) {
|
|
8307
|
+
return c.json({ message: `Collection "${slug}" not found` }, 404);
|
|
8308
|
+
}
|
|
8309
|
+
const requestConfig = mergeDynamicConfig(config2, await config2.onSchemaFetch(siteId));
|
|
8310
|
+
const collection = requestConfig.collections.find((col) => col.slug === slug);
|
|
8311
|
+
if (!collection?.workflow) {
|
|
8312
|
+
return c.json({ message: `Collection "${slug}" not found or has no workflow` }, 404);
|
|
8313
|
+
}
|
|
8314
|
+
const controller = new CollectionController(collection);
|
|
8315
|
+
return controller.workflowHistory(c);
|
|
8316
|
+
});
|
|
8050
8317
|
app.all("/api/collections/:slug/:id?", async (c) => {
|
|
8051
8318
|
const slug = c.req.param("slug");
|
|
8052
8319
|
const id = c.req.param("id");
|
|
@@ -8056,13 +8323,8 @@ function registerRoutes(app, config) {
|
|
|
8056
8323
|
return c.json({ message: "Method Not Allowed" }, 405);
|
|
8057
8324
|
}
|
|
8058
8325
|
if (config2.onSchemaFetch && siteId) {
|
|
8059
|
-
const requestConfig = mergeDynamicConfig(
|
|
8060
|
-
|
|
8061
|
-
await config2.onSchemaFetch(siteId)
|
|
8062
|
-
);
|
|
8063
|
-
let collection = requestConfig.collections.find(
|
|
8064
|
-
(col) => col.slug === slug
|
|
8065
|
-
);
|
|
8326
|
+
const requestConfig = mergeDynamicConfig(config2, await config2.onSchemaFetch(siteId));
|
|
8327
|
+
let collection = requestConfig.collections.find((col) => col.slug === slug);
|
|
8066
8328
|
if (!collection && slug === "media") {
|
|
8067
8329
|
collection = {
|
|
8068
8330
|
slug: "media",
|
|
@@ -8075,25 +8337,19 @@ function registerRoutes(app, config) {
|
|
|
8075
8337
|
if (collection.auth && id) {
|
|
8076
8338
|
const authController = new AuthController(collection);
|
|
8077
8339
|
const method2 = c.req.method;
|
|
8078
|
-
if (method2 === "POST" && id === "login")
|
|
8079
|
-
|
|
8080
|
-
if (method2 === "POST" && id === "logout")
|
|
8081
|
-
return authController.logout(c);
|
|
8340
|
+
if (method2 === "POST" && id === "login") return authController.login(c);
|
|
8341
|
+
if (method2 === "POST" && id === "logout") return authController.logout(c);
|
|
8082
8342
|
if (method2 === "GET" && id === "me") return authController.me(c);
|
|
8083
|
-
if (method2 === "POST" && id === "refresh-token")
|
|
8084
|
-
|
|
8085
|
-
if (method2 === "POST" && id === "
|
|
8086
|
-
return authController.forgotPassword(c);
|
|
8087
|
-
if (method2 === "POST" && id === "reset-password")
|
|
8088
|
-
return authController.resetPassword(c);
|
|
8343
|
+
if (method2 === "POST" && id === "refresh-token") return authController.refreshToken(c);
|
|
8344
|
+
if (method2 === "POST" && id === "forgot-password") return authController.forgotPassword(c);
|
|
8345
|
+
if (method2 === "POST" && id === "reset-password") return authController.resetPassword(c);
|
|
8089
8346
|
}
|
|
8090
8347
|
const controller = new CollectionController(collection);
|
|
8091
8348
|
const method = c.req.method;
|
|
8092
8349
|
if (id) {
|
|
8093
8350
|
if (method === "GET") return controller.findOne(c);
|
|
8094
8351
|
if (method === "PATCH") return controller.update(c);
|
|
8095
|
-
if (method === "DELETE" && id === "delete-many")
|
|
8096
|
-
return controller.deleteMany(c);
|
|
8352
|
+
if (method === "DELETE" && id === "delete-many") return controller.deleteMany(c);
|
|
8097
8353
|
if (method === "DELETE") return controller.delete(c);
|
|
8098
8354
|
if (method === "POST" && id === "media") return controller.create(c);
|
|
8099
8355
|
} else {
|
|
@@ -8113,10 +8369,7 @@ function registerRoutes(app, config) {
|
|
|
8113
8369
|
return c.json({ message: "Method Not Allowed" }, 405);
|
|
8114
8370
|
}
|
|
8115
8371
|
if (config2.onSchemaFetch && siteId) {
|
|
8116
|
-
const requestConfig = mergeDynamicConfig(
|
|
8117
|
-
config2,
|
|
8118
|
-
await config2.onSchemaFetch(siteId)
|
|
8119
|
-
);
|
|
8372
|
+
const requestConfig = mergeDynamicConfig(config2, await config2.onSchemaFetch(siteId));
|
|
8120
8373
|
const global = requestConfig.globals.find((glb) => glb.slug === slug);
|
|
8121
8374
|
if (global) {
|
|
8122
8375
|
const controller = new GlobalController(global);
|
|
@@ -8482,7 +8735,7 @@ function resolveClientIp(req, trustProxy = false) {
|
|
|
8482
8735
|
const value = normalizeIp(req.headers[header]);
|
|
8483
8736
|
if (value) return value;
|
|
8484
8737
|
}
|
|
8485
|
-
if (trustProxy) {
|
|
8738
|
+
if (trustProxy === true || typeof trustProxy === "number") {
|
|
8486
8739
|
const forwardedFor = pickForwardedFor(req.headers, trustProxy);
|
|
8487
8740
|
if (forwardedFor) return forwardedFor;
|
|
8488
8741
|
}
|
|
@@ -8494,6 +8747,7 @@ function pickForwardedFor(headers, trustProxy) {
|
|
|
8494
8747
|
const chain2 = forwardedHeader.split(",").map((entry) => normalizeIp(entry)).filter((entry) => !!entry);
|
|
8495
8748
|
if (chain2.length > 0) {
|
|
8496
8749
|
if (trustProxy === true) return chain2[0];
|
|
8750
|
+
if (trustProxy <= 0) return null;
|
|
8497
8751
|
const trustedHops2 = Math.max(1, Math.floor(trustProxy));
|
|
8498
8752
|
const candidateIndex2 = Math.max(0, chain2.length - trustedHops2);
|
|
8499
8753
|
return chain2[candidateIndex2] ?? chain2[0];
|
|
@@ -8506,6 +8760,7 @@ function pickForwardedFor(headers, trustProxy) {
|
|
|
8506
8760
|
const chain = matches.map((match) => normalizeIp(match[1])).filter((entry) => !!entry);
|
|
8507
8761
|
if (chain.length === 0) return null;
|
|
8508
8762
|
if (trustProxy === true) return chain[0];
|
|
8763
|
+
if (trustProxy <= 0) return null;
|
|
8509
8764
|
const trustedHops = Math.max(1, Math.floor(trustProxy));
|
|
8510
8765
|
const candidateIndex = Math.max(0, chain.length - trustedHops);
|
|
8511
8766
|
return chain[candidateIndex] ?? chain[0];
|
|
@@ -8710,7 +8965,7 @@ async function createDyrectedApp(rawConfig) {
|
|
|
8710
8965
|
app.use("*", (0, import_cors.cors)(config.cors ? { origin: config.cors.origins } : {}));
|
|
8711
8966
|
app.get("/health", (c) => c.json({ status: "ok", version: "0.0.1" }));
|
|
8712
8967
|
if (observability.config.metrics.enabled && observability.config.metrics.exporter === "prometheus" && observability.prometheusExporter) {
|
|
8713
|
-
app.get(observability.config.metrics.path, async (
|
|
8968
|
+
app.get(observability.config.metrics.path, async (_c) => {
|
|
8714
8969
|
const metrics = await renderPrometheusMetrics(
|
|
8715
8970
|
observability.prometheusExporter
|
|
8716
8971
|
);
|
|
@@ -8770,5 +9025,6 @@ init_observability();
|
|
|
8770
9025
|
registerRoutes,
|
|
8771
9026
|
renderPrometheusMetrics,
|
|
8772
9027
|
resolveObservabilityConfig,
|
|
9028
|
+
serializeDetailForApi,
|
|
8773
9029
|
shouldSampleRequest
|
|
8774
9030
|
});
|