@dyrected/core 2.7.1 → 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/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-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-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-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/index.cjs +446 -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 +615 -363
- package/dist/server.d.cts +26 -7
- package/dist/server.d.ts +26 -7
- package/dist/server.js +412 -355
- 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);
|
|
@@ -1259,9 +1260,42 @@ function round(val, decimals = 0) {
|
|
|
1259
1260
|
}
|
|
1260
1261
|
function clamp(val, minVal, maxVal) {
|
|
1261
1262
|
const num = Number(val) || 0;
|
|
1262
|
-
const
|
|
1263
|
-
const
|
|
1264
|
-
return Math.min(Math.max(num,
|
|
1263
|
+
const min2 = Number(minVal) || 0;
|
|
1264
|
+
const max2 = Number(maxVal) || 0;
|
|
1265
|
+
return Math.min(Math.max(num, min2), max2);
|
|
1266
|
+
}
|
|
1267
|
+
function ceil(val) {
|
|
1268
|
+
return Math.ceil(Number(val) || 0);
|
|
1269
|
+
}
|
|
1270
|
+
function floor(val) {
|
|
1271
|
+
return Math.floor(Number(val) || 0);
|
|
1272
|
+
}
|
|
1273
|
+
function abs(val) {
|
|
1274
|
+
return Math.abs(Number(val) || 0);
|
|
1275
|
+
}
|
|
1276
|
+
function min(...args) {
|
|
1277
|
+
const nums = args.flat().map((n) => Number(n) || 0);
|
|
1278
|
+
return nums.length ? Math.min(...nums) : 0;
|
|
1279
|
+
}
|
|
1280
|
+
function max(...args) {
|
|
1281
|
+
const nums = args.flat().map((n) => Number(n) || 0);
|
|
1282
|
+
return nums.length ? Math.max(...nums) : 0;
|
|
1283
|
+
}
|
|
1284
|
+
function pow(base, exp) {
|
|
1285
|
+
return Math.pow(Number(base) || 0, Number(exp) || 0);
|
|
1286
|
+
}
|
|
1287
|
+
function sqrt(val) {
|
|
1288
|
+
return Math.sqrt(Number(val) || 0);
|
|
1289
|
+
}
|
|
1290
|
+
function sum(arr) {
|
|
1291
|
+
if (!Array.isArray(arr)) return Number(arr) || 0;
|
|
1292
|
+
return arr.reduce((acc, val) => acc + (Number(val) || 0), 0);
|
|
1293
|
+
}
|
|
1294
|
+
function count(val) {
|
|
1295
|
+
if (Array.isArray(val)) return val.length;
|
|
1296
|
+
if (typeof val === "string") return val.length;
|
|
1297
|
+
if (val !== void 0 && val !== null && val !== false) return 1;
|
|
1298
|
+
return 0;
|
|
1265
1299
|
}
|
|
1266
1300
|
function defaultValue(val, fallback) {
|
|
1267
1301
|
if (val === void 0 || val === null || val === "") return fallback;
|
|
@@ -1280,13 +1314,12 @@ function isEmpty(val) {
|
|
|
1280
1314
|
return false;
|
|
1281
1315
|
}
|
|
1282
1316
|
function get(obj, path, fallback = null) {
|
|
1283
|
-
if (
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
const parts = pathStr.split(".");
|
|
1317
|
+
if (obj === void 0 || obj === null || typeof obj !== "object") return fallback;
|
|
1318
|
+
if (typeof path !== "string") return fallback;
|
|
1319
|
+
const parts = path.split(".").filter(Boolean);
|
|
1287
1320
|
let curr = obj;
|
|
1288
1321
|
for (const part of parts) {
|
|
1289
|
-
if (curr === void 0 || curr === null) return fallback;
|
|
1322
|
+
if (curr === void 0 || curr === null || typeof curr !== "object") return fallback;
|
|
1290
1323
|
curr = curr[part];
|
|
1291
1324
|
}
|
|
1292
1325
|
return curr === void 0 || curr === null ? fallback : curr;
|
|
@@ -1324,6 +1357,15 @@ function registerJexlHelpers(jexlInstance = import_jexl2.default) {
|
|
|
1324
1357
|
// Math
|
|
1325
1358
|
round,
|
|
1326
1359
|
clamp,
|
|
1360
|
+
ceil,
|
|
1361
|
+
floor,
|
|
1362
|
+
abs,
|
|
1363
|
+
min,
|
|
1364
|
+
max,
|
|
1365
|
+
pow,
|
|
1366
|
+
sqrt,
|
|
1367
|
+
sum,
|
|
1368
|
+
count,
|
|
1327
1369
|
// Objects & Logical
|
|
1328
1370
|
default: defaultValue,
|
|
1329
1371
|
coalesce,
|
|
@@ -1335,9 +1377,33 @@ function registerJexlHelpers(jexlInstance = import_jexl2.default) {
|
|
|
1335
1377
|
jexlInstance.addFunction(name, fn);
|
|
1336
1378
|
} catch {
|
|
1337
1379
|
}
|
|
1380
|
+
try {
|
|
1381
|
+
jexlInstance.addTransform(name, fn);
|
|
1382
|
+
} catch {
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
try {
|
|
1386
|
+
jexlInstance.addBinaryOp("and", 20, (left, right) => Boolean(left && right));
|
|
1387
|
+
jexlInstance.addBinaryOp("or", 10, (left, right) => Boolean(left || right));
|
|
1388
|
+
} catch {
|
|
1338
1389
|
}
|
|
1339
1390
|
}
|
|
1340
1391
|
registerJexlHelpers(import_jexl2.default);
|
|
1392
|
+
async function evaluateJexl(expression, context = {}) {
|
|
1393
|
+
if (!expression || typeof expression !== "string") return void 0;
|
|
1394
|
+
try {
|
|
1395
|
+
const evalContext = {
|
|
1396
|
+
null: null,
|
|
1397
|
+
undefined: void 0,
|
|
1398
|
+
...context,
|
|
1399
|
+
math: Math,
|
|
1400
|
+
Math
|
|
1401
|
+
};
|
|
1402
|
+
return await import_jexl2.default.eval(expression, evalContext);
|
|
1403
|
+
} catch (_err) {
|
|
1404
|
+
return void 0;
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1341
1407
|
var BUILTIN_JEXL_HELPERS = [
|
|
1342
1408
|
// Strings
|
|
1343
1409
|
"slugify",
|
|
@@ -1919,7 +1985,7 @@ function collectConfigDiagnostics(config) {
|
|
|
1919
1985
|
function formatConfigDiagnostics(source, diagnostics, options = {}) {
|
|
1920
1986
|
const color = options.color ?? false;
|
|
1921
1987
|
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/
|
|
1988
|
+
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
1989
|
const lines = diagnostics.flatMap((issue, index) => [
|
|
1924
1990
|
`${colorize(`${index + 1}.`, 31, color)} ${colorize(issue.path, 1, color)}`,
|
|
1925
1991
|
` ${issue.message}`,
|
|
@@ -3015,6 +3081,71 @@ async function transitionWorkflow(args) {
|
|
|
3015
3081
|
|
|
3016
3082
|
// src/controllers/collection.controller.ts
|
|
3017
3083
|
init_observability();
|
|
3084
|
+
|
|
3085
|
+
// src/detail.ts
|
|
3086
|
+
function normalizeDetailItem(item) {
|
|
3087
|
+
if (typeof item === "string") {
|
|
3088
|
+
return {
|
|
3089
|
+
type: "field",
|
|
3090
|
+
field: item
|
|
3091
|
+
};
|
|
3092
|
+
}
|
|
3093
|
+
return item;
|
|
3094
|
+
}
|
|
3095
|
+
async function evaluateDetailComputed(detail, doc, user, db) {
|
|
3096
|
+
if (!doc || !detail || !Array.isArray(detail)) {
|
|
3097
|
+
return doc;
|
|
3098
|
+
}
|
|
3099
|
+
const computedItems = [];
|
|
3100
|
+
const extractComputed = (items) => {
|
|
3101
|
+
for (const rawItem of items) {
|
|
3102
|
+
if (!rawItem) continue;
|
|
3103
|
+
const item = normalizeDetailItem(rawItem);
|
|
3104
|
+
if (item.type === "computed") {
|
|
3105
|
+
const id = item.id || item.label?.toLowerCase().replace(/[^a-z0-9]+/g, "_");
|
|
3106
|
+
if (id) {
|
|
3107
|
+
computedItems.push({
|
|
3108
|
+
id,
|
|
3109
|
+
label: item.label,
|
|
3110
|
+
expression: item.expression,
|
|
3111
|
+
handler: item.handler
|
|
3112
|
+
});
|
|
3113
|
+
}
|
|
3114
|
+
} else if (item.type === "section" || item.type === "grid" || item.type === "repeat") {
|
|
3115
|
+
if (item.items && Array.isArray(item.items)) {
|
|
3116
|
+
extractComputed(item.items);
|
|
3117
|
+
}
|
|
3118
|
+
} else if (item.type === "tabs" && item.tabs && Array.isArray(item.tabs)) {
|
|
3119
|
+
for (const tab of item.tabs) {
|
|
3120
|
+
if (tab.items) extractComputed(tab.items);
|
|
3121
|
+
}
|
|
3122
|
+
}
|
|
3123
|
+
}
|
|
3124
|
+
};
|
|
3125
|
+
extractComputed(detail);
|
|
3126
|
+
if (computedItems.length === 0) return doc;
|
|
3127
|
+
const evaluated = {};
|
|
3128
|
+
for (const item of computedItems) {
|
|
3129
|
+
try {
|
|
3130
|
+
if (typeof item.handler === "function") {
|
|
3131
|
+
evaluated[item.id] = await item.handler({ doc, user, db });
|
|
3132
|
+
} else if (item.expression) {
|
|
3133
|
+
evaluated[item.id] = await evaluateJexl(item.expression, { doc, user });
|
|
3134
|
+
}
|
|
3135
|
+
} catch (_err) {
|
|
3136
|
+
evaluated[item.id] = null;
|
|
3137
|
+
}
|
|
3138
|
+
}
|
|
3139
|
+
return {
|
|
3140
|
+
...doc,
|
|
3141
|
+
_meta: {
|
|
3142
|
+
...doc._meta || {},
|
|
3143
|
+
computed: evaluated
|
|
3144
|
+
}
|
|
3145
|
+
};
|
|
3146
|
+
}
|
|
3147
|
+
|
|
3148
|
+
// src/controllers/collection.controller.ts
|
|
3018
3149
|
var CollectionController = class {
|
|
3019
3150
|
collection;
|
|
3020
3151
|
constructor(collection) {
|
|
@@ -3031,6 +3162,22 @@ var CollectionController = class {
|
|
|
3031
3162
|
toHookRequestContext(c) {
|
|
3032
3163
|
return toHookRequestContext(c.req);
|
|
3033
3164
|
}
|
|
3165
|
+
sanitizeDoc(doc) {
|
|
3166
|
+
if (!doc || typeof doc !== "object") return doc;
|
|
3167
|
+
if (this.collection.auth) {
|
|
3168
|
+
const {
|
|
3169
|
+
password: _p,
|
|
3170
|
+
salt: _s,
|
|
3171
|
+
resetPasswordToken: _rpt,
|
|
3172
|
+
resetPasswordExpires: _rpe,
|
|
3173
|
+
loginAttempts: _la,
|
|
3174
|
+
lockUntil: _lu,
|
|
3175
|
+
...safeDoc
|
|
3176
|
+
} = doc;
|
|
3177
|
+
return safeDoc;
|
|
3178
|
+
}
|
|
3179
|
+
return doc;
|
|
3180
|
+
}
|
|
3034
3181
|
async evaluateAccess(c, action, options = {}) {
|
|
3035
3182
|
const config = c.get("config");
|
|
3036
3183
|
return resolveCollectionAccess(
|
|
@@ -3263,6 +3410,9 @@ var CollectionController = class {
|
|
|
3263
3410
|
depth
|
|
3264
3411
|
);
|
|
3265
3412
|
}
|
|
3413
|
+
if (this.collection.auth && result.docs && Array.isArray(result.docs)) {
|
|
3414
|
+
result.docs = result.docs.map((d) => this.sanitizeDoc(d));
|
|
3415
|
+
}
|
|
3266
3416
|
return c.json(result);
|
|
3267
3417
|
}
|
|
3268
3418
|
async findOne(c) {
|
|
@@ -3279,32 +3429,51 @@ var CollectionController = class {
|
|
|
3279
3429
|
id: id2
|
|
3280
3430
|
});
|
|
3281
3431
|
const externalSubject = localDoc?.externalSubject || id2;
|
|
3282
|
-
let member = null;
|
|
3283
3432
|
if (provider.members.get) {
|
|
3284
|
-
member = await provider.members.get({
|
|
3433
|
+
const member = await provider.members.get({
|
|
3434
|
+
externalSubject,
|
|
3435
|
+
req: hookReq
|
|
3436
|
+
});
|
|
3437
|
+
if (member) {
|
|
3438
|
+
return c.json({
|
|
3439
|
+
...member,
|
|
3440
|
+
id: localDoc ? localDoc.id : member.id,
|
|
3441
|
+
externalSubject: member.id
|
|
3442
|
+
});
|
|
3443
|
+
}
|
|
3285
3444
|
} else if (provider.members.list) {
|
|
3286
3445
|
const listResult = await provider.members.list({ req: hookReq });
|
|
3287
|
-
|
|
3446
|
+
const matched = listResult.docs.find((m) => m.id === externalSubject || m.id === id2);
|
|
3447
|
+
if (matched) {
|
|
3448
|
+
return c.json({
|
|
3449
|
+
...matched,
|
|
3450
|
+
id: localDoc ? localDoc.id : matched.id,
|
|
3451
|
+
externalSubject: matched.id
|
|
3452
|
+
});
|
|
3453
|
+
}
|
|
3288
3454
|
}
|
|
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
3455
|
}
|
|
3296
|
-
const readonlyDb = createReadonlyDb(db);
|
|
3297
3456
|
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
3457
|
if (!id) return c.json({ message: "Missing ID" }, 400);
|
|
3301
|
-
|
|
3302
|
-
|
|
3458
|
+
const depth = Number.parseInt(c.req.query("depth") || "0", 10);
|
|
3459
|
+
const user = c.get("user");
|
|
3460
|
+
const doc = await db.findOne({
|
|
3461
|
+
collection: this.collection.slug,
|
|
3462
|
+
id
|
|
3463
|
+
});
|
|
3464
|
+
if (!doc) {
|
|
3465
|
+
return c.json({ message: "Document not found" }, 404);
|
|
3466
|
+
}
|
|
3467
|
+
let effectiveDoc = doc;
|
|
3303
3468
|
if (this.collection.workflow) {
|
|
3304
|
-
|
|
3305
|
-
|
|
3469
|
+
effectiveDoc = materializeWorkflowDocument(
|
|
3470
|
+
doc,
|
|
3471
|
+
this.collection.workflow,
|
|
3472
|
+
user
|
|
3473
|
+
);
|
|
3474
|
+
if (!effectiveDoc) return c.json({ message: "Document not found" }, 404);
|
|
3306
3475
|
}
|
|
3307
|
-
const access = await this.evaluateAccess(c, "read", { id, doc });
|
|
3476
|
+
const access = await this.evaluateAccess(c, "read", { id, doc: effectiveDoc });
|
|
3308
3477
|
if (!access.allowed) {
|
|
3309
3478
|
return c.json(
|
|
3310
3479
|
{
|
|
@@ -3314,7 +3483,11 @@ var CollectionController = class {
|
|
|
3314
3483
|
403
|
|
3315
3484
|
);
|
|
3316
3485
|
}
|
|
3317
|
-
const docWithDefaults = DefaultsService.apply(
|
|
3486
|
+
const docWithDefaults = DefaultsService.apply(
|
|
3487
|
+
this.collection.fields,
|
|
3488
|
+
effectiveDoc
|
|
3489
|
+
);
|
|
3490
|
+
const readonlyDb = createReadonlyDb(db);
|
|
3318
3491
|
const docWithCollectionHooks = await runCollectionHooks(
|
|
3319
3492
|
this.collection.hooks?.afterRead,
|
|
3320
3493
|
{
|
|
@@ -3340,17 +3513,20 @@ var CollectionController = class {
|
|
|
3340
3513
|
},
|
|
3341
3514
|
docWithFieldHooks
|
|
3342
3515
|
);
|
|
3516
|
+
let finalDoc = docWithFieldAccess;
|
|
3343
3517
|
if (depth > 0 && docWithFieldAccess) {
|
|
3344
3518
|
const populationService = new PopulationService(db, config.collections);
|
|
3345
|
-
|
|
3519
|
+
finalDoc = await populationService.populate({
|
|
3346
3520
|
data: docWithFieldAccess,
|
|
3347
3521
|
fields: this.collection.fields,
|
|
3348
3522
|
currentDepth: 0,
|
|
3349
3523
|
maxDepth: depth
|
|
3350
3524
|
});
|
|
3351
|
-
return c.json(populatedDoc);
|
|
3352
3525
|
}
|
|
3353
|
-
|
|
3526
|
+
if (finalDoc && this.collection.detail) {
|
|
3527
|
+
finalDoc = await evaluateDetailComputed(this.collection.detail, finalDoc, user, readonlyDb);
|
|
3528
|
+
}
|
|
3529
|
+
return c.json(this.sanitizeDoc(finalDoc));
|
|
3354
3530
|
}
|
|
3355
3531
|
async create(c) {
|
|
3356
3532
|
const config = c.get("config");
|
|
@@ -4122,6 +4298,63 @@ var CollectionController = class {
|
|
|
4122
4298
|
201
|
|
4123
4299
|
);
|
|
4124
4300
|
}
|
|
4301
|
+
async aggregate(c) {
|
|
4302
|
+
const config = c.get("config");
|
|
4303
|
+
const db = config.db;
|
|
4304
|
+
if (!db) return c.json({ message: "Database not configured" }, 500);
|
|
4305
|
+
const access = await this.evaluateAccess(c, "read");
|
|
4306
|
+
if (!access.allowed) {
|
|
4307
|
+
return c.json(
|
|
4308
|
+
{
|
|
4309
|
+
error: true,
|
|
4310
|
+
message: `Access denied: read on ${this.collection.slug}`
|
|
4311
|
+
},
|
|
4312
|
+
403
|
|
4313
|
+
);
|
|
4314
|
+
}
|
|
4315
|
+
let body;
|
|
4316
|
+
try {
|
|
4317
|
+
body = await c.req.json();
|
|
4318
|
+
} catch {
|
|
4319
|
+
return c.json({ message: "Invalid JSON body" }, 400);
|
|
4320
|
+
}
|
|
4321
|
+
if (!body || typeof body !== "object" || Array.isArray(body)) {
|
|
4322
|
+
return c.json(
|
|
4323
|
+
{ message: "Aggregate request body must be a JSON object" },
|
|
4324
|
+
400
|
|
4325
|
+
);
|
|
4326
|
+
}
|
|
4327
|
+
const { sanitizeWhereClause: sanitizeWhereClause2 } = await Promise.resolve().then(() => (init_where_sanitizer(), where_sanitizer_exports));
|
|
4328
|
+
const sanitizedAggregates = {};
|
|
4329
|
+
for (const [key, op] of Object.entries(body)) {
|
|
4330
|
+
if (!op || typeof op !== "object" || Array.isArray(op)) {
|
|
4331
|
+
return c.json(
|
|
4332
|
+
{
|
|
4333
|
+
message: `Aggregate operation "${key}" must be an object`
|
|
4334
|
+
},
|
|
4335
|
+
400
|
|
4336
|
+
);
|
|
4337
|
+
}
|
|
4338
|
+
const operation = op;
|
|
4339
|
+
let opWhere = operation.where && typeof operation.where === "object" ? operation.where : void 0;
|
|
4340
|
+
if (opWhere) {
|
|
4341
|
+
opWhere = sanitizeWhereClause2(opWhere, this.collection.fields);
|
|
4342
|
+
if (Object.keys(opWhere).length === 0) opWhere = void 0;
|
|
4343
|
+
}
|
|
4344
|
+
if (access.constraint) {
|
|
4345
|
+
opWhere = opWhere ? mergeWhereConstraint(opWhere, access.constraint) : access.constraint;
|
|
4346
|
+
}
|
|
4347
|
+
sanitizedAggregates[key] = {
|
|
4348
|
+
...operation,
|
|
4349
|
+
...opWhere !== void 0 ? { where: opWhere } : { where: void 0 }
|
|
4350
|
+
};
|
|
4351
|
+
}
|
|
4352
|
+
const result = await db.aggregate({
|
|
4353
|
+
collection: this.collection.slug,
|
|
4354
|
+
aggregates: sanitizedAggregates
|
|
4355
|
+
});
|
|
4356
|
+
return c.json(result);
|
|
4357
|
+
}
|
|
4125
4358
|
};
|
|
4126
4359
|
|
|
4127
4360
|
// src/controllers/global.controller.ts
|
|
@@ -4205,17 +4438,25 @@ var GlobalController = class {
|
|
|
4205
4438
|
},
|
|
4206
4439
|
docWithFieldHooks
|
|
4207
4440
|
);
|
|
4441
|
+
let finalDoc = docWithFieldAccess;
|
|
4208
4442
|
if (depth > 0 && docWithFieldAccess) {
|
|
4209
4443
|
const populationService = new PopulationService(db, config.collections);
|
|
4210
|
-
|
|
4444
|
+
finalDoc = await populationService.populate({
|
|
4211
4445
|
data: docWithFieldAccess,
|
|
4212
4446
|
fields: this.global.fields,
|
|
4213
4447
|
currentDepth: 0,
|
|
4214
4448
|
maxDepth: depth
|
|
4215
4449
|
});
|
|
4216
|
-
return c.json(populatedData);
|
|
4217
4450
|
}
|
|
4218
|
-
|
|
4451
|
+
if (finalDoc && this.global.detail) {
|
|
4452
|
+
finalDoc = await evaluateDetailComputed(
|
|
4453
|
+
this.global.detail,
|
|
4454
|
+
finalDoc,
|
|
4455
|
+
user,
|
|
4456
|
+
readonlyDb
|
|
4457
|
+
);
|
|
4458
|
+
}
|
|
4459
|
+
return c.json(finalDoc);
|
|
4219
4460
|
}
|
|
4220
4461
|
async update(c) {
|
|
4221
4462
|
const config = c.get("config");
|
|
@@ -5668,6 +5909,7 @@ var AdminAuthController = class {
|
|
|
5668
5909
|
});
|
|
5669
5910
|
}
|
|
5670
5911
|
getProvider(config, id) {
|
|
5912
|
+
if (!id) return void 0;
|
|
5671
5913
|
if (config.adminAuth?.mode !== "external") return void 0;
|
|
5672
5914
|
return config.adminAuth.providers.find((provider) => provider.id === id);
|
|
5673
5915
|
}
|
|
@@ -5866,17 +6108,16 @@ var AdminAuthController = class {
|
|
|
5866
6108
|
throw new Error("OIDC nonce verification failed.");
|
|
5867
6109
|
}
|
|
5868
6110
|
claims = payload;
|
|
5869
|
-
} else
|
|
5870
|
-
const
|
|
5871
|
-
|
|
5872
|
-
|
|
5873
|
-
|
|
5874
|
-
|
|
5875
|
-
|
|
6111
|
+
} else {
|
|
6112
|
+
const userInfoEndpoint = provider.userInfoEndpoint ?? discovery.userinfo_endpoint;
|
|
6113
|
+
if (!tokenBody.access_token || !userInfoEndpoint) {
|
|
6114
|
+
throw new Error("OIDC provider did not return usable identity claims.");
|
|
6115
|
+
}
|
|
6116
|
+
const userInfo = await fetch(userInfoEndpoint, {
|
|
6117
|
+
headers: { Authorization: `Bearer ${tokenBody.access_token}` }
|
|
6118
|
+
});
|
|
5876
6119
|
if (!userInfo.ok) throw new Error("OIDC userinfo request failed.");
|
|
5877
6120
|
claims = await userInfo.json();
|
|
5878
|
-
} else {
|
|
5879
|
-
throw new Error("OIDC provider did not return usable identity claims.");
|
|
5880
6121
|
}
|
|
5881
6122
|
return {
|
|
5882
6123
|
identity: this.mapClaims(claims, provider.claimMapping),
|
|
@@ -6791,6 +7032,71 @@ function generateOpenApi(config) {
|
|
|
6791
7032
|
responses: { 200: { description: "Deleted and failed document IDs" } }
|
|
6792
7033
|
}
|
|
6793
7034
|
};
|
|
7035
|
+
spec.paths[`${path}/aggregate`] = {
|
|
7036
|
+
post: {
|
|
7037
|
+
tags: [collectionTag],
|
|
7038
|
+
summary: `Aggregate ${labels.plural}`,
|
|
7039
|
+
description: `Compute statistical aggregations (count, sum, avg, min, max) across ${labels.plural}.`,
|
|
7040
|
+
requestBody: {
|
|
7041
|
+
required: true,
|
|
7042
|
+
content: {
|
|
7043
|
+
"application/json": {
|
|
7044
|
+
schema: {
|
|
7045
|
+
type: "object",
|
|
7046
|
+
additionalProperties: {
|
|
7047
|
+
type: "object",
|
|
7048
|
+
properties: {
|
|
7049
|
+
count: { type: "string", example: "*" },
|
|
7050
|
+
sum: { type: "string", description: "Field to sum" },
|
|
7051
|
+
avg: { type: "string", description: "Field to average" },
|
|
7052
|
+
min: { type: "string", description: "Field for minimum" },
|
|
7053
|
+
max: { type: "string", description: "Field for maximum" },
|
|
7054
|
+
cast: {
|
|
7055
|
+
type: "string",
|
|
7056
|
+
enum: [
|
|
7057
|
+
"number",
|
|
7058
|
+
"integer",
|
|
7059
|
+
"float",
|
|
7060
|
+
"boolean",
|
|
7061
|
+
"date",
|
|
7062
|
+
"string"
|
|
7063
|
+
]
|
|
7064
|
+
},
|
|
7065
|
+
where: {
|
|
7066
|
+
type: "object",
|
|
7067
|
+
description: "Per-aggregate filter condition",
|
|
7068
|
+
additionalProperties: true
|
|
7069
|
+
}
|
|
7070
|
+
}
|
|
7071
|
+
}
|
|
7072
|
+
}
|
|
7073
|
+
}
|
|
7074
|
+
}
|
|
7075
|
+
},
|
|
7076
|
+
responses: {
|
|
7077
|
+
200: {
|
|
7078
|
+
description: "Success",
|
|
7079
|
+
content: {
|
|
7080
|
+
"application/json": {
|
|
7081
|
+
schema: {
|
|
7082
|
+
type: "object",
|
|
7083
|
+
additionalProperties: {
|
|
7084
|
+
type: "number",
|
|
7085
|
+
nullable: true
|
|
7086
|
+
}
|
|
7087
|
+
}
|
|
7088
|
+
}
|
|
7089
|
+
}
|
|
7090
|
+
},
|
|
7091
|
+
400: {
|
|
7092
|
+
description: "Bad Request - Invalid aggregate input"
|
|
7093
|
+
},
|
|
7094
|
+
403: {
|
|
7095
|
+
description: "Forbidden - Access denied by read rule"
|
|
7096
|
+
}
|
|
7097
|
+
}
|
|
7098
|
+
}
|
|
7099
|
+
};
|
|
6794
7100
|
if (collection.audit) {
|
|
6795
7101
|
spec.paths[`${path}/__audit`] = {
|
|
6796
7102
|
get: {
|
|
@@ -7387,10 +7693,7 @@ function accessGate(config, target, action) {
|
|
|
7387
7693
|
req: toHookRequestContext(c.req)
|
|
7388
7694
|
});
|
|
7389
7695
|
if (!allowed) {
|
|
7390
|
-
return c.json(
|
|
7391
|
-
{ error: true, message: `Access denied: ${action} on ${target.slug}` },
|
|
7392
|
-
403
|
|
7393
|
-
);
|
|
7696
|
+
return c.json({ error: true, message: `Access denied: ${action} on ${target.slug}` }, 403);
|
|
7394
7697
|
}
|
|
7395
7698
|
await next();
|
|
7396
7699
|
};
|
|
@@ -7430,6 +7733,80 @@ function serializeBlockForApi(block) {
|
|
|
7430
7733
|
fields: block.fields?.map(serializeFieldForApi)
|
|
7431
7734
|
};
|
|
7432
7735
|
}
|
|
7736
|
+
function serializeDetailItemForApi(item) {
|
|
7737
|
+
if (!item) return item;
|
|
7738
|
+
if (typeof item === "string") {
|
|
7739
|
+
return { type: "field", field: item };
|
|
7740
|
+
}
|
|
7741
|
+
if (item.type === "section") {
|
|
7742
|
+
return {
|
|
7743
|
+
type: "section",
|
|
7744
|
+
title: item.title,
|
|
7745
|
+
items: (item.items || []).map(serializeDetailItemForApi),
|
|
7746
|
+
options: item.options
|
|
7747
|
+
};
|
|
7748
|
+
}
|
|
7749
|
+
if (item.type === "tab") {
|
|
7750
|
+
return {
|
|
7751
|
+
type: "tab",
|
|
7752
|
+
label: item.label,
|
|
7753
|
+
items: (item.items || []).map(serializeDetailItemForApi),
|
|
7754
|
+
options: item.options
|
|
7755
|
+
};
|
|
7756
|
+
}
|
|
7757
|
+
if (item.type === "tabs") {
|
|
7758
|
+
return {
|
|
7759
|
+
type: "tabs",
|
|
7760
|
+
tabs: (item.tabs || []).map(serializeDetailItemForApi),
|
|
7761
|
+
options: item.options
|
|
7762
|
+
};
|
|
7763
|
+
}
|
|
7764
|
+
if (item.type === "grid") {
|
|
7765
|
+
return {
|
|
7766
|
+
type: "grid",
|
|
7767
|
+
columns: item.columns,
|
|
7768
|
+
items: (item.items || []).map(serializeDetailItemForApi),
|
|
7769
|
+
options: item.options
|
|
7770
|
+
};
|
|
7771
|
+
}
|
|
7772
|
+
if (item.type === "repeat") {
|
|
7773
|
+
return {
|
|
7774
|
+
type: "repeat",
|
|
7775
|
+
field: item.field,
|
|
7776
|
+
items: (item.items || []).map(serializeDetailItemForApi),
|
|
7777
|
+
options: item.options
|
|
7778
|
+
};
|
|
7779
|
+
}
|
|
7780
|
+
if (item.type === "computed") {
|
|
7781
|
+
return {
|
|
7782
|
+
type: "computed",
|
|
7783
|
+
id: item.id || item.label?.toLowerCase().replace(/[^a-z0-9]+/g, "_"),
|
|
7784
|
+
label: item.label,
|
|
7785
|
+
expression: item.expression,
|
|
7786
|
+
options: {
|
|
7787
|
+
id: item.id,
|
|
7788
|
+
span: item.options?.span,
|
|
7789
|
+
format: item.options?.format,
|
|
7790
|
+
currency: item.options?.currency,
|
|
7791
|
+
visible: item.options?.visible
|
|
7792
|
+
}
|
|
7793
|
+
};
|
|
7794
|
+
}
|
|
7795
|
+
if (item.type === "field") {
|
|
7796
|
+
return {
|
|
7797
|
+
type: "field",
|
|
7798
|
+
field: item.field,
|
|
7799
|
+
options: item.options
|
|
7800
|
+
};
|
|
7801
|
+
}
|
|
7802
|
+
return item;
|
|
7803
|
+
}
|
|
7804
|
+
function serializeDetailForApi(detail) {
|
|
7805
|
+
if (detail === false) return false;
|
|
7806
|
+
if (detail === true) return true;
|
|
7807
|
+
if (!detail || !Array.isArray(detail)) return void 0;
|
|
7808
|
+
return detail.map(serializeDetailItemForApi);
|
|
7809
|
+
}
|
|
7433
7810
|
function registerRoutes(app, config) {
|
|
7434
7811
|
const optionsCache = /* @__PURE__ */ new Map();
|
|
7435
7812
|
app.get("/api/schemas", optionalAuth(config), async (c) => {
|
|
@@ -7448,15 +7825,12 @@ function registerRoutes(app, config) {
|
|
|
7448
7825
|
if (typeof access === "boolean") return access;
|
|
7449
7826
|
if (access && typeof access === "object" && typeof access.policy === "string") {
|
|
7450
7827
|
const policy = requestConfig.accessPolicies?.[access.policy];
|
|
7451
|
-
if (typeof policy === "string" || typeof policy === "boolean")
|
|
7452
|
-
return policy;
|
|
7828
|
+
if (typeof policy === "string" || typeof policy === "boolean") return policy;
|
|
7453
7829
|
}
|
|
7454
7830
|
return resolveBooleanAccess(requestConfig, access, accessArgs);
|
|
7455
7831
|
};
|
|
7456
7832
|
const filteredCollections = await Promise.all(
|
|
7457
|
-
collections.filter(
|
|
7458
|
-
(col) => !siteId || col.shared || !col.siteId || col.siteId === siteId
|
|
7459
|
-
).map(async (col) => ({
|
|
7833
|
+
collections.filter((col) => !siteId || col.shared || !col.siteId || col.siteId === siteId).map(async (col) => ({
|
|
7460
7834
|
slug: col.slug,
|
|
7461
7835
|
labels: col.labels,
|
|
7462
7836
|
access: {
|
|
@@ -7494,6 +7868,7 @@ function registerRoutes(app, config) {
|
|
|
7494
7868
|
audit: !!col.audit,
|
|
7495
7869
|
drafts: !!col.drafts,
|
|
7496
7870
|
admin: col.admin,
|
|
7871
|
+
detail: serializeDetailForApi(col.detail),
|
|
7497
7872
|
workflow: col.workflow ? {
|
|
7498
7873
|
initialState: col.workflow.initialState,
|
|
7499
7874
|
draftState: col.workflow.draftState,
|
|
@@ -7512,9 +7887,7 @@ function registerRoutes(app, config) {
|
|
|
7512
7887
|
}))
|
|
7513
7888
|
);
|
|
7514
7889
|
const filteredGlobals = await Promise.all(
|
|
7515
|
-
globals.filter(
|
|
7516
|
-
(glb) => !siteId || glb.shared || !glb.siteId || glb.siteId === siteId
|
|
7517
|
-
).map(async (glb) => ({
|
|
7890
|
+
globals.filter((glb) => !siteId || glb.shared || !glb.siteId || glb.siteId === siteId).map(async (glb) => ({
|
|
7518
7891
|
slug: glb.slug,
|
|
7519
7892
|
label: glb.label,
|
|
7520
7893
|
access: {
|
|
@@ -7544,7 +7917,8 @@ function registerRoutes(app, config) {
|
|
|
7544
7917
|
}
|
|
7545
7918
|
}))
|
|
7546
7919
|
),
|
|
7547
|
-
admin: glb.admin
|
|
7920
|
+
admin: glb.admin,
|
|
7921
|
+
detail: serializeDetailForApi(glb.detail)
|
|
7548
7922
|
}))
|
|
7549
7923
|
);
|
|
7550
7924
|
return c.json({
|
|
@@ -7558,148 +7932,137 @@ function registerRoutes(app, config) {
|
|
|
7558
7932
|
adminHealth: {
|
|
7559
7933
|
emailConfigured: !!requestConfig.email,
|
|
7560
7934
|
secureAuthSecretConfigured: !!process.env.DYRECTED_JWT_SECRET,
|
|
7561
|
-
authCollectionConfigured: requestConfig.collections.some(
|
|
7562
|
-
|
|
7563
|
-
),
|
|
7564
|
-
uploadCollectionConfigured: requestConfig.collections.some(
|
|
7565
|
-
(collection) => !!collection.upload
|
|
7566
|
-
)
|
|
7935
|
+
authCollectionConfigured: requestConfig.collections.some((collection) => !!collection.auth),
|
|
7936
|
+
uploadCollectionConfigured: requestConfig.collections.some((collection) => !!collection.upload)
|
|
7567
7937
|
}
|
|
7568
7938
|
});
|
|
7569
7939
|
});
|
|
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
|
-
}
|
|
7940
|
+
app.get("/api/dyrected/options/:collection/:field", optionalAuth(config), async (c) => {
|
|
7941
|
+
const { collection: colSlug, field: fieldName } = c.req.param();
|
|
7942
|
+
const siteId = c.req.header("X-Site-Id");
|
|
7943
|
+
const requestConfig = siteId && config.onSchemaFetch ? mergeDynamicConfig(config, await config.onSchemaFetch(siteId)) : config;
|
|
7944
|
+
let collections = [...requestConfig.collections];
|
|
7945
|
+
const user = c.get("user");
|
|
7946
|
+
let collection = collections.find((col) => col.slug === colSlug);
|
|
7947
|
+
let field;
|
|
7948
|
+
if (collection) {
|
|
7949
|
+
const accessExpr = collection.access?.read;
|
|
7950
|
+
if (accessExpr !== void 0 && accessExpr !== null) {
|
|
7951
|
+
const allowed = await resolveBooleanAccess(config, accessExpr, {
|
|
7952
|
+
user,
|
|
7953
|
+
req: toHookRequestContext(c.req)
|
|
7954
|
+
});
|
|
7955
|
+
if (!allowed) {
|
|
7956
|
+
return c.json({ error: true, message: `Access denied: read on ${colSlug}` }, 403);
|
|
7623
7957
|
}
|
|
7624
|
-
field = glb.fields.find((f) => f.name === fieldName);
|
|
7625
7958
|
}
|
|
7626
|
-
|
|
7959
|
+
field = collection.fields.find((f) => f.name === fieldName);
|
|
7960
|
+
} else {
|
|
7961
|
+
const globals = [...requestConfig.globals];
|
|
7962
|
+
const glb = globals.find((g) => g.slug === colSlug);
|
|
7963
|
+
if (!glb) {
|
|
7627
7964
|
return c.json(
|
|
7628
7965
|
{
|
|
7629
7966
|
error: true,
|
|
7630
|
-
message:
|
|
7967
|
+
message: `${colSlug} not found as collection or global`
|
|
7631
7968
|
},
|
|
7632
7969
|
404
|
|
7633
7970
|
);
|
|
7634
7971
|
}
|
|
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,
|
|
7972
|
+
const accessExpr = glb.access?.read;
|
|
7973
|
+
if (accessExpr !== void 0 && accessExpr !== null) {
|
|
7974
|
+
const allowed = await resolveBooleanAccess(config, accessExpr, {
|
|
7677
7975
|
user,
|
|
7678
|
-
req:
|
|
7976
|
+
req: toHookRequestContext(c.req)
|
|
7679
7977
|
});
|
|
7680
|
-
if (
|
|
7681
|
-
|
|
7682
|
-
|
|
7683
|
-
|
|
7684
|
-
|
|
7978
|
+
if (!allowed) {
|
|
7979
|
+
return c.json(
|
|
7980
|
+
{
|
|
7981
|
+
error: true,
|
|
7982
|
+
message: `Access denied: read on global ${colSlug}`
|
|
7983
|
+
},
|
|
7984
|
+
403
|
|
7985
|
+
);
|
|
7685
7986
|
}
|
|
7686
|
-
|
|
7687
|
-
|
|
7688
|
-
|
|
7689
|
-
|
|
7690
|
-
|
|
7691
|
-
|
|
7987
|
+
}
|
|
7988
|
+
field = glb.fields.find((f) => f.name === fieldName);
|
|
7989
|
+
}
|
|
7990
|
+
if (!field) {
|
|
7991
|
+
return c.json(
|
|
7992
|
+
{
|
|
7993
|
+
error: true,
|
|
7994
|
+
message: `Field ${fieldName} not found in ${colSlug}`
|
|
7995
|
+
},
|
|
7996
|
+
404
|
|
7997
|
+
);
|
|
7998
|
+
}
|
|
7999
|
+
let resolver;
|
|
8000
|
+
let cacheTTL;
|
|
8001
|
+
if (typeof field.options === "function") {
|
|
8002
|
+
resolver = field.options;
|
|
8003
|
+
} else if (field.options && typeof field.options === "object" && "resolve" in field.options) {
|
|
8004
|
+
resolver = field.options.resolve;
|
|
8005
|
+
cacheTTL = field.options.cacheTTL;
|
|
8006
|
+
}
|
|
8007
|
+
if (!resolver) {
|
|
8008
|
+
return c.json(
|
|
8009
|
+
{
|
|
8010
|
+
error: true,
|
|
8011
|
+
message: `Field ${fieldName} in ${colSlug} is not dynamic`
|
|
8012
|
+
},
|
|
8013
|
+
400
|
|
8014
|
+
);
|
|
8015
|
+
}
|
|
8016
|
+
try {
|
|
8017
|
+
const db = c.get("db") || config.db;
|
|
8018
|
+
const queryParams = c.req.query();
|
|
8019
|
+
const reqContext = {
|
|
8020
|
+
query: queryParams,
|
|
8021
|
+
headers: c.req.header(),
|
|
8022
|
+
raw: c.req.raw
|
|
8023
|
+
};
|
|
8024
|
+
const shouldCache = typeof cacheTTL === "number" && cacheTTL > 0;
|
|
8025
|
+
let cacheKey = "";
|
|
8026
|
+
if (shouldCache) {
|
|
8027
|
+
cacheKey = JSON.stringify([
|
|
8028
|
+
siteId ?? "",
|
|
8029
|
+
colSlug,
|
|
8030
|
+
fieldName,
|
|
8031
|
+
user?.id ?? null,
|
|
8032
|
+
queryParams
|
|
8033
|
+
]);
|
|
8034
|
+
const hit = optionsCache.get(cacheKey);
|
|
8035
|
+
if (hit && hit.expires > Date.now()) {
|
|
8036
|
+
return c.json(hit.value);
|
|
8037
|
+
}
|
|
8038
|
+
}
|
|
8039
|
+
const result = await resolver({
|
|
8040
|
+
db,
|
|
8041
|
+
user,
|
|
8042
|
+
req: reqContext
|
|
8043
|
+
});
|
|
8044
|
+
if (shouldCache) {
|
|
8045
|
+
optionsCache.set(cacheKey, {
|
|
8046
|
+
expires: Date.now() + cacheTTL * 1e3,
|
|
8047
|
+
value: result
|
|
7692
8048
|
});
|
|
7693
|
-
return c.json(
|
|
7694
|
-
{
|
|
7695
|
-
error: true,
|
|
7696
|
-
message: err.message || "Failed to resolve dynamic options"
|
|
7697
|
-
},
|
|
7698
|
-
500
|
|
7699
|
-
);
|
|
7700
8049
|
}
|
|
8050
|
+
return c.json(result);
|
|
8051
|
+
} catch (err) {
|
|
8052
|
+
getRequestLogger(c, "router").error({
|
|
8053
|
+
err,
|
|
8054
|
+
msg: "Failed to resolve dynamic field options",
|
|
8055
|
+
fieldName
|
|
8056
|
+
});
|
|
8057
|
+
return c.json(
|
|
8058
|
+
{
|
|
8059
|
+
error: true,
|
|
8060
|
+
message: err.message || "Failed to resolve dynamic options"
|
|
8061
|
+
},
|
|
8062
|
+
500
|
|
8063
|
+
);
|
|
7701
8064
|
}
|
|
7702
|
-
);
|
|
8065
|
+
});
|
|
7703
8066
|
app.get("/api/openapi.json", (c) => {
|
|
7704
8067
|
return c.json(generateOpenApi(config));
|
|
7705
8068
|
});
|
|
@@ -7712,13 +8075,8 @@ function registerRoutes(app, config) {
|
|
|
7712
8075
|
const key = c.req.param("key");
|
|
7713
8076
|
const scope = c.req.query("scope");
|
|
7714
8077
|
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
|
-
);
|
|
8078
|
+
if (!user?.collection || !user.sub) return c.json({ error: true, message: "Authentication required." }, 401);
|
|
8079
|
+
if (!key) return c.json({ error: true, message: "Preference key is required." }, 400);
|
|
7722
8080
|
const getGlobalPreference = async () => {
|
|
7723
8081
|
const globalDoc = await db.findOne({
|
|
7724
8082
|
collection: "__global_preferences",
|
|
@@ -7745,13 +8103,8 @@ function registerRoutes(app, config) {
|
|
|
7745
8103
|
const key = c.req.param("key");
|
|
7746
8104
|
const scope = c.req.query("scope");
|
|
7747
8105
|
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
|
-
);
|
|
8106
|
+
if (!user?.collection || !user.sub) return c.json({ error: true, message: "Authentication required." }, 401);
|
|
8107
|
+
if (!key) return c.json({ error: true, message: "Preference key is required." }, 400);
|
|
7755
8108
|
const body = await c.req.json().catch(() => ({}));
|
|
7756
8109
|
if (scope === "global") {
|
|
7757
8110
|
const isAdminUser = Array.isArray(user?.roles) && user.roles.includes("admin");
|
|
@@ -7799,13 +8152,8 @@ function registerRoutes(app, config) {
|
|
|
7799
8152
|
const key = c.req.param("key");
|
|
7800
8153
|
const scope = c.req.query("scope");
|
|
7801
8154
|
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
|
-
);
|
|
8155
|
+
if (!user?.collection || !user.sub) return c.json({ error: true, message: "Authentication required." }, 401);
|
|
8156
|
+
if (!key) return c.json({ error: true, message: "Preference key is required." }, 400);
|
|
7809
8157
|
if (scope === "global") {
|
|
7810
8158
|
const isAdminUser = Array.isArray(user?.roles) && user.roles.includes("admin");
|
|
7811
8159
|
if (!isAdminUser) {
|
|
@@ -7844,41 +8192,17 @@ function registerRoutes(app, config) {
|
|
|
7844
8192
|
for (const col of uploadCollections) {
|
|
7845
8193
|
const mediaController = new MediaController(col.slug);
|
|
7846
8194
|
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
|
-
);
|
|
8195
|
+
app.get(`${prefix}/media`, accessGate(config, col, "read"), (c) => mediaController.find(c));
|
|
8196
|
+
app.get(`${prefix}/media/:filename{.+$}`, (c) => mediaController.serve(c));
|
|
8197
|
+
app.post(`${prefix}/media`, accessGate(config, col, "create"), (c) => mediaController.upload(c));
|
|
8198
|
+
app.delete(`${prefix}/media/:id`, accessGate(config, col, "delete"), (c) => mediaController.delete(c));
|
|
7866
8199
|
}
|
|
7867
8200
|
}
|
|
7868
8201
|
const adminAuthController = new AdminAuthController(config);
|
|
7869
8202
|
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
|
-
);
|
|
8203
|
+
app.get("/api/admin/auth/:provider/start", (c) => adminAuthController.start(c));
|
|
8204
|
+
app.get("/api/admin/auth/:provider/callback", (c) => adminAuthController.callback(c));
|
|
8205
|
+
app.post("/api/admin/auth/:provider/exchange", (c) => adminAuthController.exchange(c));
|
|
7882
8206
|
app.post("/api/admin/logout", (c) => adminAuthController.logout(c));
|
|
7883
8207
|
for (const collection of config.collections) {
|
|
7884
8208
|
if (!collection.auth) continue;
|
|
@@ -7889,21 +8213,10 @@ function registerRoutes(app, config) {
|
|
|
7889
8213
|
app.get(`${path}/init`, (c) => authController.init(c));
|
|
7890
8214
|
app.post(`${path}/first-user`, (c) => authController.registerFirstUser(c));
|
|
7891
8215
|
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
|
-
);
|
|
8216
|
+
app.post(`${path}/refresh-token`, requireAuth(config), (c) => authController.refreshToken(c));
|
|
8217
|
+
app.post(`${path}/forgot-password`, (c) => authController.forgotPassword(c));
|
|
7901
8218
|
app.post(`${path}/reset-password`, (c) => authController.resetPassword(c));
|
|
7902
|
-
app.post(
|
|
7903
|
-
`${path}/invite`,
|
|
7904
|
-
requireAuth(config),
|
|
7905
|
-
(c) => authController.invite(c)
|
|
7906
|
-
);
|
|
8219
|
+
app.post(`${path}/invite`, requireAuth(config), (c) => authController.invite(c));
|
|
7907
8220
|
app.post(`${path}/accept-invite`, (c) => authController.acceptInvite(c));
|
|
7908
8221
|
}
|
|
7909
8222
|
const auditController = new AuditController();
|
|
@@ -7914,33 +8227,19 @@ function registerRoutes(app, config) {
|
|
|
7914
8227
|
app.post(path, (c) => controller.create(c));
|
|
7915
8228
|
app.post(`${path}/media`, (c) => controller.create(c));
|
|
7916
8229
|
app.delete(`${path}/delete-many`, (c) => controller.deleteMany(c));
|
|
8230
|
+
app.post(`${path}/aggregate`, (c) => controller.aggregate(c));
|
|
7917
8231
|
if (collection.audit) {
|
|
7918
|
-
app.get(
|
|
7919
|
-
`${path}/__audit`,
|
|
7920
|
-
(c) => auditController.findForCollection(c, collection)
|
|
7921
|
-
);
|
|
8232
|
+
app.get(`${path}/__audit`, (c) => auditController.findForCollection(c, collection));
|
|
7922
8233
|
}
|
|
7923
8234
|
app.get(`${path}/:id`, (c) => controller.findOne(c));
|
|
7924
8235
|
app.patch(`${path}/:id`, (c) => controller.update(c));
|
|
7925
8236
|
app.delete(`${path}/:id`, (c) => controller.delete(c));
|
|
7926
8237
|
if (collection.auth) {
|
|
7927
|
-
app.post(
|
|
7928
|
-
`${path}/:id/change-password`,
|
|
7929
|
-
requireAuth(config),
|
|
7930
|
-
(c) => controller.changePassword(c)
|
|
7931
|
-
);
|
|
8238
|
+
app.post(`${path}/:id/change-password`, requireAuth(config), (c) => controller.changePassword(c));
|
|
7932
8239
|
}
|
|
7933
8240
|
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
|
-
);
|
|
8241
|
+
app.post(`${path}/:id/transitions/:transition`, requireAuth(config), (c) => controller.transition(c));
|
|
8242
|
+
app.get(`${path}/:id/workflow-history`, requireAuth(config), (c) => controller.workflowHistory(c));
|
|
7944
8243
|
}
|
|
7945
8244
|
}
|
|
7946
8245
|
for (const global of config.globals) {
|
|
@@ -7955,11 +8254,7 @@ function registerRoutes(app, config) {
|
|
|
7955
8254
|
});
|
|
7956
8255
|
}
|
|
7957
8256
|
const previewController = new PreviewController();
|
|
7958
|
-
app.post(
|
|
7959
|
-
"/api/preview-token",
|
|
7960
|
-
requireAuth(config),
|
|
7961
|
-
(c) => previewController.createToken(c)
|
|
7962
|
-
);
|
|
8257
|
+
app.post("/api/preview-token", requireAuth(config), (c) => previewController.createToken(c));
|
|
7963
8258
|
app.get("/api/preview-data", (c) => previewController.getData(c));
|
|
7964
8259
|
app.get("/api/audit", (c) => auditController.findAll(c));
|
|
7965
8260
|
app.get("/api/collections/:slug/__audit", async (c) => {
|
|
@@ -7972,81 +8267,49 @@ function registerRoutes(app, config) {
|
|
|
7972
8267
|
if (!config2.onSchemaFetch || !siteId) {
|
|
7973
8268
|
return c.json({ message: `Collection "${slug}" not found` }, 404);
|
|
7974
8269
|
}
|
|
7975
|
-
const requestConfig = mergeDynamicConfig(
|
|
7976
|
-
|
|
7977
|
-
await config2.onSchemaFetch(siteId)
|
|
7978
|
-
);
|
|
7979
|
-
const collection = requestConfig.collections.find(
|
|
7980
|
-
(col) => col.slug === slug
|
|
7981
|
-
);
|
|
8270
|
+
const requestConfig = mergeDynamicConfig(config2, await config2.onSchemaFetch(siteId));
|
|
8271
|
+
const collection = requestConfig.collections.find((col) => col.slug === slug);
|
|
7982
8272
|
if (!collection?.audit) {
|
|
7983
|
-
return c.json(
|
|
7984
|
-
{ message: `Collection "${slug}" not found or has no audit log` },
|
|
7985
|
-
404
|
|
7986
|
-
);
|
|
8273
|
+
return c.json({ message: `Collection "${slug}" not found or has no audit log` }, 404);
|
|
7987
8274
|
}
|
|
7988
8275
|
return auditController.findForCollection(c, collection);
|
|
7989
8276
|
});
|
|
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);
|
|
8277
|
+
app.post("/api/collections/:slug/:id/transitions/:transition", requireAuth(config), async (c) => {
|
|
8278
|
+
const slug = c.req.param("slug");
|
|
8279
|
+
const siteId = c.req.header("X-Site-Id") || c.get("siteId");
|
|
8280
|
+
const config2 = c.get("config");
|
|
8281
|
+
if (config2.collections.some((col) => col.slug === slug)) {
|
|
8282
|
+
return c.json({ message: "Not Found" }, 404);
|
|
8018
8283
|
}
|
|
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);
|
|
8284
|
+
if (!config2.onSchemaFetch || !siteId) {
|
|
8285
|
+
return c.json({ message: `Collection "${slug}" not found` }, 404);
|
|
8048
8286
|
}
|
|
8049
|
-
|
|
8287
|
+
const requestConfig = mergeDynamicConfig(config2, await config2.onSchemaFetch(siteId));
|
|
8288
|
+
const collection = requestConfig.collections.find((col) => col.slug === slug);
|
|
8289
|
+
if (!collection?.workflow) {
|
|
8290
|
+
return c.json({ message: `Collection "${slug}" not found or has no workflow` }, 404);
|
|
8291
|
+
}
|
|
8292
|
+
const controller = new CollectionController(collection);
|
|
8293
|
+
return controller.transition(c);
|
|
8294
|
+
});
|
|
8295
|
+
app.get("/api/collections/:slug/:id/workflow-history", requireAuth(config), async (c) => {
|
|
8296
|
+
const slug = c.req.param("slug");
|
|
8297
|
+
const siteId = c.req.header("X-Site-Id") || c.get("siteId");
|
|
8298
|
+
const config2 = c.get("config");
|
|
8299
|
+
if (config2.collections.some((col) => col.slug === slug)) {
|
|
8300
|
+
return c.json({ message: "Not Found" }, 404);
|
|
8301
|
+
}
|
|
8302
|
+
if (!config2.onSchemaFetch || !siteId) {
|
|
8303
|
+
return c.json({ message: `Collection "${slug}" not found` }, 404);
|
|
8304
|
+
}
|
|
8305
|
+
const requestConfig = mergeDynamicConfig(config2, await config2.onSchemaFetch(siteId));
|
|
8306
|
+
const collection = requestConfig.collections.find((col) => col.slug === slug);
|
|
8307
|
+
if (!collection?.workflow) {
|
|
8308
|
+
return c.json({ message: `Collection "${slug}" not found or has no workflow` }, 404);
|
|
8309
|
+
}
|
|
8310
|
+
const controller = new CollectionController(collection);
|
|
8311
|
+
return controller.workflowHistory(c);
|
|
8312
|
+
});
|
|
8050
8313
|
app.all("/api/collections/:slug/:id?", async (c) => {
|
|
8051
8314
|
const slug = c.req.param("slug");
|
|
8052
8315
|
const id = c.req.param("id");
|
|
@@ -8056,13 +8319,8 @@ function registerRoutes(app, config) {
|
|
|
8056
8319
|
return c.json({ message: "Method Not Allowed" }, 405);
|
|
8057
8320
|
}
|
|
8058
8321
|
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
|
-
);
|
|
8322
|
+
const requestConfig = mergeDynamicConfig(config2, await config2.onSchemaFetch(siteId));
|
|
8323
|
+
let collection = requestConfig.collections.find((col) => col.slug === slug);
|
|
8066
8324
|
if (!collection && slug === "media") {
|
|
8067
8325
|
collection = {
|
|
8068
8326
|
slug: "media",
|
|
@@ -8075,25 +8333,19 @@ function registerRoutes(app, config) {
|
|
|
8075
8333
|
if (collection.auth && id) {
|
|
8076
8334
|
const authController = new AuthController(collection);
|
|
8077
8335
|
const method2 = c.req.method;
|
|
8078
|
-
if (method2 === "POST" && id === "login")
|
|
8079
|
-
|
|
8080
|
-
if (method2 === "POST" && id === "logout")
|
|
8081
|
-
return authController.logout(c);
|
|
8336
|
+
if (method2 === "POST" && id === "login") return authController.login(c);
|
|
8337
|
+
if (method2 === "POST" && id === "logout") return authController.logout(c);
|
|
8082
8338
|
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);
|
|
8339
|
+
if (method2 === "POST" && id === "refresh-token") return authController.refreshToken(c);
|
|
8340
|
+
if (method2 === "POST" && id === "forgot-password") return authController.forgotPassword(c);
|
|
8341
|
+
if (method2 === "POST" && id === "reset-password") return authController.resetPassword(c);
|
|
8089
8342
|
}
|
|
8090
8343
|
const controller = new CollectionController(collection);
|
|
8091
8344
|
const method = c.req.method;
|
|
8092
8345
|
if (id) {
|
|
8093
8346
|
if (method === "GET") return controller.findOne(c);
|
|
8094
8347
|
if (method === "PATCH") return controller.update(c);
|
|
8095
|
-
if (method === "DELETE" && id === "delete-many")
|
|
8096
|
-
return controller.deleteMany(c);
|
|
8348
|
+
if (method === "DELETE" && id === "delete-many") return controller.deleteMany(c);
|
|
8097
8349
|
if (method === "DELETE") return controller.delete(c);
|
|
8098
8350
|
if (method === "POST" && id === "media") return controller.create(c);
|
|
8099
8351
|
} else {
|
|
@@ -8113,10 +8365,7 @@ function registerRoutes(app, config) {
|
|
|
8113
8365
|
return c.json({ message: "Method Not Allowed" }, 405);
|
|
8114
8366
|
}
|
|
8115
8367
|
if (config2.onSchemaFetch && siteId) {
|
|
8116
|
-
const requestConfig = mergeDynamicConfig(
|
|
8117
|
-
config2,
|
|
8118
|
-
await config2.onSchemaFetch(siteId)
|
|
8119
|
-
);
|
|
8368
|
+
const requestConfig = mergeDynamicConfig(config2, await config2.onSchemaFetch(siteId));
|
|
8120
8369
|
const global = requestConfig.globals.find((glb) => glb.slug === slug);
|
|
8121
8370
|
if (global) {
|
|
8122
8371
|
const controller = new GlobalController(global);
|
|
@@ -8482,7 +8731,7 @@ function resolveClientIp(req, trustProxy = false) {
|
|
|
8482
8731
|
const value = normalizeIp(req.headers[header]);
|
|
8483
8732
|
if (value) return value;
|
|
8484
8733
|
}
|
|
8485
|
-
if (trustProxy) {
|
|
8734
|
+
if (trustProxy === true || typeof trustProxy === "number") {
|
|
8486
8735
|
const forwardedFor = pickForwardedFor(req.headers, trustProxy);
|
|
8487
8736
|
if (forwardedFor) return forwardedFor;
|
|
8488
8737
|
}
|
|
@@ -8494,6 +8743,7 @@ function pickForwardedFor(headers, trustProxy) {
|
|
|
8494
8743
|
const chain2 = forwardedHeader.split(",").map((entry) => normalizeIp(entry)).filter((entry) => !!entry);
|
|
8495
8744
|
if (chain2.length > 0) {
|
|
8496
8745
|
if (trustProxy === true) return chain2[0];
|
|
8746
|
+
if (trustProxy <= 0) return null;
|
|
8497
8747
|
const trustedHops2 = Math.max(1, Math.floor(trustProxy));
|
|
8498
8748
|
const candidateIndex2 = Math.max(0, chain2.length - trustedHops2);
|
|
8499
8749
|
return chain2[candidateIndex2] ?? chain2[0];
|
|
@@ -8506,6 +8756,7 @@ function pickForwardedFor(headers, trustProxy) {
|
|
|
8506
8756
|
const chain = matches.map((match) => normalizeIp(match[1])).filter((entry) => !!entry);
|
|
8507
8757
|
if (chain.length === 0) return null;
|
|
8508
8758
|
if (trustProxy === true) return chain[0];
|
|
8759
|
+
if (trustProxy <= 0) return null;
|
|
8509
8760
|
const trustedHops = Math.max(1, Math.floor(trustProxy));
|
|
8510
8761
|
const candidateIndex = Math.max(0, chain.length - trustedHops);
|
|
8511
8762
|
return chain[candidateIndex] ?? chain[0];
|
|
@@ -8710,7 +8961,7 @@ async function createDyrectedApp(rawConfig) {
|
|
|
8710
8961
|
app.use("*", (0, import_cors.cors)(config.cors ? { origin: config.cors.origins } : {}));
|
|
8711
8962
|
app.get("/health", (c) => c.json({ status: "ok", version: "0.0.1" }));
|
|
8712
8963
|
if (observability.config.metrics.enabled && observability.config.metrics.exporter === "prometheus" && observability.prometheusExporter) {
|
|
8713
|
-
app.get(observability.config.metrics.path, async (
|
|
8964
|
+
app.get(observability.config.metrics.path, async (_c) => {
|
|
8714
8965
|
const metrics = await renderPrometheusMetrics(
|
|
8715
8966
|
observability.prometheusExporter
|
|
8716
8967
|
);
|
|
@@ -8770,5 +9021,6 @@ init_observability();
|
|
|
8770
9021
|
registerRoutes,
|
|
8771
9022
|
renderPrometheusMetrics,
|
|
8772
9023
|
resolveObservabilityConfig,
|
|
9024
|
+
serializeDetailForApi,
|
|
8773
9025
|
shouldSampleRequest
|
|
8774
9026
|
});
|