@dyrected/core 2.7.0 → 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-2UXLA4YN.js +3139 -0
- package/dist/chunk-3TRRTN6U.js +3441 -0
- package/dist/chunk-5U2WRCNH.js +3140 -0
- package/dist/chunk-5ZIA5COH.js +3414 -0
- package/dist/chunk-6TWQ6GTD.js +3480 -0
- package/dist/chunk-AKYYGMFS.js +3365 -0
- package/dist/chunk-AQO4HI2Y.js +3204 -0
- package/dist/chunk-DFBLNYUH.js +3402 -0
- package/dist/chunk-EJG6AMYB.js +3537 -0
- package/dist/chunk-FRSJJO27.js +519 -0
- package/dist/chunk-G43REXPX.js +3147 -0
- package/dist/chunk-GFKBPBJJ.js +3637 -0
- package/dist/chunk-KEKOEAX6.js +3139 -0
- package/dist/chunk-LFGSRQLX.js +3568 -0
- package/dist/chunk-NNZRYF3U.js +3537 -0
- package/dist/chunk-PRQ2RF5C.js +3485 -0
- package/dist/chunk-PYE2JSY4.js +3568 -0
- package/dist/chunk-PZMDK3MH.js +3203 -0
- package/dist/chunk-QCXLDKZO.js +3272 -0
- package/dist/chunk-SM32ND5M.js +3472 -0
- package/dist/chunk-UKUEKTAO.js +513 -0
- package/dist/chunk-VM43G3IG.js +506 -0
- package/dist/chunk-VWJKFZQU.js +3139 -0
- package/dist/chunk-XX7N64NL.js +3550 -0
- package/dist/chunk-YSSHLGQ6.js +3139 -0
- package/dist/chunk-Z7CFUYVP.js +3653 -0
- package/dist/index.cjs +1421 -82
- package/dist/index.d.cts +362 -3
- package/dist/index.d.ts +362 -3
- package/dist/index.js +119 -1
- package/dist/observability-CNM4QFE4.js +34 -0
- package/dist/observability-KCMTV7GO.js +34 -0
- package/dist/observability-L5BORBB2.js +34 -0
- package/dist/server.cjs +1879 -1247
- package/dist/server.d.cts +52 -8
- package/dist/server.d.ts +52 -8
- package/dist/server.js +449 -387
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -4,25 +4,16 @@ import {
|
|
|
4
4
|
assertValidDeclarativeAccessInConfig,
|
|
5
5
|
assertValidDeclarativeHooksInConfig,
|
|
6
6
|
assertValidPreviewUrlsInConfig,
|
|
7
|
-
attachRequestMetrics,
|
|
8
|
-
bindObservabilityRuntime,
|
|
9
|
-
buildRequestLogger,
|
|
10
7
|
canViewWorkflowDraft,
|
|
11
|
-
captureRequestBody,
|
|
12
8
|
collectConfigDiagnostics,
|
|
13
|
-
createObservabilityRuntime,
|
|
14
|
-
createRequestSpan,
|
|
15
9
|
createWorkflowDocument,
|
|
16
|
-
|
|
10
|
+
evaluateDetailComputed,
|
|
17
11
|
executeFieldAfterRead,
|
|
18
12
|
executeFieldBeforeChange,
|
|
19
13
|
generateOpenApi,
|
|
20
14
|
getAdminAuthCollection,
|
|
21
15
|
getAuthSession,
|
|
22
|
-
getConfigLogger,
|
|
23
|
-
getObservabilityRuntime,
|
|
24
16
|
getPublicAdminAuthConfig,
|
|
25
|
-
getRequestLogger,
|
|
26
17
|
initializeWorkflowDocument,
|
|
27
18
|
isAuthSessionActive,
|
|
28
19
|
issueAuthSessionToken,
|
|
@@ -30,18 +21,32 @@ import {
|
|
|
30
21
|
mergeDynamicConfig,
|
|
31
22
|
normalizeConfig,
|
|
32
23
|
publishedStateName,
|
|
33
|
-
redactHeaders,
|
|
34
|
-
renderPrometheusMetrics,
|
|
35
24
|
revokeAllAuthSessions,
|
|
36
25
|
revokeAuthSession,
|
|
37
26
|
runCollectionHooks,
|
|
38
27
|
saveWorkflowDraft,
|
|
39
|
-
shouldSampleRequest,
|
|
40
28
|
signCollectionToken,
|
|
41
29
|
touchAuthSession,
|
|
42
30
|
transitionWorkflow,
|
|
43
31
|
verifyCollectionToken
|
|
44
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-XX7N64NL.js";
|
|
33
|
+
import {
|
|
34
|
+
attachRequestMetrics,
|
|
35
|
+
bindObservabilityRuntime,
|
|
36
|
+
buildRequestLogger,
|
|
37
|
+
captureRequestBody,
|
|
38
|
+
createObservabilityRuntime,
|
|
39
|
+
createRequestSpan,
|
|
40
|
+
endRequestSpan,
|
|
41
|
+
getConfigLogger,
|
|
42
|
+
getObservabilityRuntime,
|
|
43
|
+
getRequestLogger,
|
|
44
|
+
redactBody,
|
|
45
|
+
redactHeaders,
|
|
46
|
+
renderPrometheusMetrics,
|
|
47
|
+
resolveObservabilityConfig,
|
|
48
|
+
shouldSampleRequest
|
|
49
|
+
} from "./chunk-FRSJJO27.js";
|
|
45
50
|
|
|
46
51
|
// src/app.ts
|
|
47
52
|
import { Hono } from "hono";
|
|
@@ -436,11 +441,7 @@ async function evaluateAccess(expression, context) {
|
|
|
436
441
|
}
|
|
437
442
|
return !!result;
|
|
438
443
|
} catch (err) {
|
|
439
|
-
|
|
440
|
-
err,
|
|
441
|
-
msg: "Jexl evaluation failed",
|
|
442
|
-
expression
|
|
443
|
-
});
|
|
444
|
+
console.error("[dyrected:access] Failed to evaluate access expression:", err);
|
|
444
445
|
return false;
|
|
445
446
|
}
|
|
446
447
|
}
|
|
@@ -458,20 +459,14 @@ async function resolveAccess(config, access, args) {
|
|
|
458
459
|
try {
|
|
459
460
|
return await access(args);
|
|
460
461
|
} catch (err) {
|
|
461
|
-
|
|
462
|
-
err,
|
|
463
|
-
msg: "Functional access check failed"
|
|
464
|
-
});
|
|
462
|
+
console.error("[dyrected:access] Functional access check failed:", err);
|
|
465
463
|
return false;
|
|
466
464
|
}
|
|
467
465
|
}
|
|
468
466
|
if (isNamedAccessPolicy(access)) {
|
|
469
467
|
const policy = config.accessPolicies?.[access.policy];
|
|
470
468
|
if (policy === void 0) {
|
|
471
|
-
|
|
472
|
-
msg: "Unknown access policy",
|
|
473
|
-
policy: access.policy
|
|
474
|
-
});
|
|
469
|
+
console.error(`[dyrected:access] Unknown access policy "${access.policy}"`);
|
|
475
470
|
return false;
|
|
476
471
|
}
|
|
477
472
|
if (typeof policy === "string" || typeof policy === "boolean") {
|
|
@@ -481,11 +476,7 @@ async function resolveAccess(config, access, args) {
|
|
|
481
476
|
const resolver = policy;
|
|
482
477
|
return await resolver({ ...args, params: access.params });
|
|
483
478
|
} catch (err) {
|
|
484
|
-
|
|
485
|
-
err,
|
|
486
|
-
msg: "Access policy failed",
|
|
487
|
-
policy: access.policy
|
|
488
|
-
});
|
|
479
|
+
console.error(`[dyrected:access] Access policy "${access.policy}" failed:`, err);
|
|
489
480
|
return false;
|
|
490
481
|
}
|
|
491
482
|
}
|
|
@@ -875,6 +866,22 @@ var CollectionController = class {
|
|
|
875
866
|
toHookRequestContext(c) {
|
|
876
867
|
return toHookRequestContext(c.req);
|
|
877
868
|
}
|
|
869
|
+
sanitizeDoc(doc) {
|
|
870
|
+
if (!doc || typeof doc !== "object") return doc;
|
|
871
|
+
if (this.collection.auth) {
|
|
872
|
+
const {
|
|
873
|
+
password: _p,
|
|
874
|
+
salt: _s,
|
|
875
|
+
resetPasswordToken: _rpt,
|
|
876
|
+
resetPasswordExpires: _rpe,
|
|
877
|
+
loginAttempts: _la,
|
|
878
|
+
lockUntil: _lu,
|
|
879
|
+
...safeDoc
|
|
880
|
+
} = doc;
|
|
881
|
+
return safeDoc;
|
|
882
|
+
}
|
|
883
|
+
return doc;
|
|
884
|
+
}
|
|
878
885
|
async evaluateAccess(c, action, options = {}) {
|
|
879
886
|
const config = c.get("config");
|
|
880
887
|
return resolveCollectionAccess(
|
|
@@ -1107,6 +1114,9 @@ var CollectionController = class {
|
|
|
1107
1114
|
depth
|
|
1108
1115
|
);
|
|
1109
1116
|
}
|
|
1117
|
+
if (this.collection.auth && result.docs && Array.isArray(result.docs)) {
|
|
1118
|
+
result.docs = result.docs.map((d) => this.sanitizeDoc(d));
|
|
1119
|
+
}
|
|
1110
1120
|
return c.json(result);
|
|
1111
1121
|
}
|
|
1112
1122
|
async findOne(c) {
|
|
@@ -1123,32 +1133,51 @@ var CollectionController = class {
|
|
|
1123
1133
|
id: id2
|
|
1124
1134
|
});
|
|
1125
1135
|
const externalSubject = localDoc?.externalSubject || id2;
|
|
1126
|
-
let member = null;
|
|
1127
1136
|
if (provider.members.get) {
|
|
1128
|
-
member = await provider.members.get({
|
|
1137
|
+
const member = await provider.members.get({
|
|
1138
|
+
externalSubject,
|
|
1139
|
+
req: hookReq
|
|
1140
|
+
});
|
|
1141
|
+
if (member) {
|
|
1142
|
+
return c.json({
|
|
1143
|
+
...member,
|
|
1144
|
+
id: localDoc ? localDoc.id : member.id,
|
|
1145
|
+
externalSubject: member.id
|
|
1146
|
+
});
|
|
1147
|
+
}
|
|
1129
1148
|
} else if (provider.members.list) {
|
|
1130
1149
|
const listResult = await provider.members.list({ req: hookReq });
|
|
1131
|
-
|
|
1150
|
+
const matched = listResult.docs.find((m) => m.id === externalSubject || m.id === id2);
|
|
1151
|
+
if (matched) {
|
|
1152
|
+
return c.json({
|
|
1153
|
+
...matched,
|
|
1154
|
+
id: localDoc ? localDoc.id : matched.id,
|
|
1155
|
+
externalSubject: matched.id
|
|
1156
|
+
});
|
|
1157
|
+
}
|
|
1132
1158
|
}
|
|
1133
|
-
if (!member) return c.json({ message: "Not Found" }, 404);
|
|
1134
|
-
return c.json({
|
|
1135
|
-
...member,
|
|
1136
|
-
id: localDoc ? localDoc.id : member.id,
|
|
1137
|
-
externalSubject: member.id
|
|
1138
|
-
});
|
|
1139
1159
|
}
|
|
1140
|
-
const readonlyDb = createReadonlyDb(db);
|
|
1141
1160
|
const id = c.req.param("id");
|
|
1142
|
-
const depth = c.req.query("depth") !== void 0 ? Number(c.req.query("depth")) : 1;
|
|
1143
|
-
const user = c.get("user");
|
|
1144
1161
|
if (!id) return c.json({ message: "Missing ID" }, 400);
|
|
1145
|
-
|
|
1146
|
-
|
|
1162
|
+
const depth = Number.parseInt(c.req.query("depth") || "0", 10);
|
|
1163
|
+
const user = c.get("user");
|
|
1164
|
+
const doc = await db.findOne({
|
|
1165
|
+
collection: this.collection.slug,
|
|
1166
|
+
id
|
|
1167
|
+
});
|
|
1168
|
+
if (!doc) {
|
|
1169
|
+
return c.json({ message: "Document not found" }, 404);
|
|
1170
|
+
}
|
|
1171
|
+
let effectiveDoc = doc;
|
|
1147
1172
|
if (this.collection.workflow) {
|
|
1148
|
-
|
|
1149
|
-
|
|
1173
|
+
effectiveDoc = materializeWorkflowDocument(
|
|
1174
|
+
doc,
|
|
1175
|
+
this.collection.workflow,
|
|
1176
|
+
user
|
|
1177
|
+
);
|
|
1178
|
+
if (!effectiveDoc) return c.json({ message: "Document not found" }, 404);
|
|
1150
1179
|
}
|
|
1151
|
-
const access = await this.evaluateAccess(c, "read", { id, doc });
|
|
1180
|
+
const access = await this.evaluateAccess(c, "read", { id, doc: effectiveDoc });
|
|
1152
1181
|
if (!access.allowed) {
|
|
1153
1182
|
return c.json(
|
|
1154
1183
|
{
|
|
@@ -1158,7 +1187,11 @@ var CollectionController = class {
|
|
|
1158
1187
|
403
|
|
1159
1188
|
);
|
|
1160
1189
|
}
|
|
1161
|
-
const docWithDefaults = DefaultsService.apply(
|
|
1190
|
+
const docWithDefaults = DefaultsService.apply(
|
|
1191
|
+
this.collection.fields,
|
|
1192
|
+
effectiveDoc
|
|
1193
|
+
);
|
|
1194
|
+
const readonlyDb = createReadonlyDb(db);
|
|
1162
1195
|
const docWithCollectionHooks = await runCollectionHooks(
|
|
1163
1196
|
this.collection.hooks?.afterRead,
|
|
1164
1197
|
{
|
|
@@ -1184,17 +1217,20 @@ var CollectionController = class {
|
|
|
1184
1217
|
},
|
|
1185
1218
|
docWithFieldHooks
|
|
1186
1219
|
);
|
|
1220
|
+
let finalDoc = docWithFieldAccess;
|
|
1187
1221
|
if (depth > 0 && docWithFieldAccess) {
|
|
1188
1222
|
const populationService = new PopulationService(db, config.collections);
|
|
1189
|
-
|
|
1223
|
+
finalDoc = await populationService.populate({
|
|
1190
1224
|
data: docWithFieldAccess,
|
|
1191
1225
|
fields: this.collection.fields,
|
|
1192
1226
|
currentDepth: 0,
|
|
1193
1227
|
maxDepth: depth
|
|
1194
1228
|
});
|
|
1195
|
-
return c.json(populatedDoc);
|
|
1196
1229
|
}
|
|
1197
|
-
|
|
1230
|
+
if (finalDoc && this.collection.detail) {
|
|
1231
|
+
finalDoc = await evaluateDetailComputed(this.collection.detail, finalDoc, user, readonlyDb);
|
|
1232
|
+
}
|
|
1233
|
+
return c.json(this.sanitizeDoc(finalDoc));
|
|
1198
1234
|
}
|
|
1199
1235
|
async create(c) {
|
|
1200
1236
|
const config = c.get("config");
|
|
@@ -1966,6 +2002,63 @@ var CollectionController = class {
|
|
|
1966
2002
|
201
|
|
1967
2003
|
);
|
|
1968
2004
|
}
|
|
2005
|
+
async aggregate(c) {
|
|
2006
|
+
const config = c.get("config");
|
|
2007
|
+
const db = config.db;
|
|
2008
|
+
if (!db) return c.json({ message: "Database not configured" }, 500);
|
|
2009
|
+
const access = await this.evaluateAccess(c, "read");
|
|
2010
|
+
if (!access.allowed) {
|
|
2011
|
+
return c.json(
|
|
2012
|
+
{
|
|
2013
|
+
error: true,
|
|
2014
|
+
message: `Access denied: read on ${this.collection.slug}`
|
|
2015
|
+
},
|
|
2016
|
+
403
|
|
2017
|
+
);
|
|
2018
|
+
}
|
|
2019
|
+
let body;
|
|
2020
|
+
try {
|
|
2021
|
+
body = await c.req.json();
|
|
2022
|
+
} catch {
|
|
2023
|
+
return c.json({ message: "Invalid JSON body" }, 400);
|
|
2024
|
+
}
|
|
2025
|
+
if (!body || typeof body !== "object" || Array.isArray(body)) {
|
|
2026
|
+
return c.json(
|
|
2027
|
+
{ message: "Aggregate request body must be a JSON object" },
|
|
2028
|
+
400
|
|
2029
|
+
);
|
|
2030
|
+
}
|
|
2031
|
+
const { sanitizeWhereClause } = await import("./where-sanitizer-WJ2W2QCE.js");
|
|
2032
|
+
const sanitizedAggregates = {};
|
|
2033
|
+
for (const [key, op] of Object.entries(body)) {
|
|
2034
|
+
if (!op || typeof op !== "object" || Array.isArray(op)) {
|
|
2035
|
+
return c.json(
|
|
2036
|
+
{
|
|
2037
|
+
message: `Aggregate operation "${key}" must be an object`
|
|
2038
|
+
},
|
|
2039
|
+
400
|
|
2040
|
+
);
|
|
2041
|
+
}
|
|
2042
|
+
const operation = op;
|
|
2043
|
+
let opWhere = operation.where && typeof operation.where === "object" ? operation.where : void 0;
|
|
2044
|
+
if (opWhere) {
|
|
2045
|
+
opWhere = sanitizeWhereClause(opWhere, this.collection.fields);
|
|
2046
|
+
if (Object.keys(opWhere).length === 0) opWhere = void 0;
|
|
2047
|
+
}
|
|
2048
|
+
if (access.constraint) {
|
|
2049
|
+
opWhere = opWhere ? mergeWhereConstraint(opWhere, access.constraint) : access.constraint;
|
|
2050
|
+
}
|
|
2051
|
+
sanitizedAggregates[key] = {
|
|
2052
|
+
...operation,
|
|
2053
|
+
...opWhere !== void 0 ? { where: opWhere } : { where: void 0 }
|
|
2054
|
+
};
|
|
2055
|
+
}
|
|
2056
|
+
const result = await db.aggregate({
|
|
2057
|
+
collection: this.collection.slug,
|
|
2058
|
+
aggregates: sanitizedAggregates
|
|
2059
|
+
});
|
|
2060
|
+
return c.json(result);
|
|
2061
|
+
}
|
|
1969
2062
|
};
|
|
1970
2063
|
|
|
1971
2064
|
// src/controllers/global.controller.ts
|
|
@@ -2048,17 +2141,25 @@ var GlobalController = class {
|
|
|
2048
2141
|
},
|
|
2049
2142
|
docWithFieldHooks
|
|
2050
2143
|
);
|
|
2144
|
+
let finalDoc = docWithFieldAccess;
|
|
2051
2145
|
if (depth > 0 && docWithFieldAccess) {
|
|
2052
2146
|
const populationService = new PopulationService(db, config.collections);
|
|
2053
|
-
|
|
2147
|
+
finalDoc = await populationService.populate({
|
|
2054
2148
|
data: docWithFieldAccess,
|
|
2055
2149
|
fields: this.global.fields,
|
|
2056
2150
|
currentDepth: 0,
|
|
2057
2151
|
maxDepth: depth
|
|
2058
2152
|
});
|
|
2059
|
-
return c.json(populatedData);
|
|
2060
2153
|
}
|
|
2061
|
-
|
|
2154
|
+
if (finalDoc && this.global.detail) {
|
|
2155
|
+
finalDoc = await evaluateDetailComputed(
|
|
2156
|
+
this.global.detail,
|
|
2157
|
+
finalDoc,
|
|
2158
|
+
user,
|
|
2159
|
+
readonlyDb
|
|
2160
|
+
);
|
|
2161
|
+
}
|
|
2162
|
+
return c.json(finalDoc);
|
|
2062
2163
|
}
|
|
2063
2164
|
async update(c) {
|
|
2064
2165
|
const config = c.get("config");
|
|
@@ -3401,6 +3502,7 @@ var AdminAuthController = class {
|
|
|
3401
3502
|
});
|
|
3402
3503
|
}
|
|
3403
3504
|
getProvider(config, id) {
|
|
3505
|
+
if (!id) return void 0;
|
|
3404
3506
|
if (config.adminAuth?.mode !== "external") return void 0;
|
|
3405
3507
|
return config.adminAuth.providers.find((provider) => provider.id === id);
|
|
3406
3508
|
}
|
|
@@ -3599,17 +3701,16 @@ var AdminAuthController = class {
|
|
|
3599
3701
|
throw new Error("OIDC nonce verification failed.");
|
|
3600
3702
|
}
|
|
3601
3703
|
claims = payload;
|
|
3602
|
-
} else
|
|
3603
|
-
const
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3704
|
+
} else {
|
|
3705
|
+
const userInfoEndpoint = provider.userInfoEndpoint ?? discovery.userinfo_endpoint;
|
|
3706
|
+
if (!tokenBody.access_token || !userInfoEndpoint) {
|
|
3707
|
+
throw new Error("OIDC provider did not return usable identity claims.");
|
|
3708
|
+
}
|
|
3709
|
+
const userInfo = await fetch(userInfoEndpoint, {
|
|
3710
|
+
headers: { Authorization: `Bearer ${tokenBody.access_token}` }
|
|
3711
|
+
});
|
|
3609
3712
|
if (!userInfo.ok) throw new Error("OIDC userinfo request failed.");
|
|
3610
3713
|
claims = await userInfo.json();
|
|
3611
|
-
} else {
|
|
3612
|
-
throw new Error("OIDC provider did not return usable identity claims.");
|
|
3613
3714
|
}
|
|
3614
3715
|
return {
|
|
3615
3716
|
identity: this.mapClaims(claims, provider.claimMapping),
|
|
@@ -4154,10 +4255,7 @@ function accessGate(config, target, action) {
|
|
|
4154
4255
|
req: toHookRequestContext(c.req)
|
|
4155
4256
|
});
|
|
4156
4257
|
if (!allowed) {
|
|
4157
|
-
return c.json(
|
|
4158
|
-
{ error: true, message: `Access denied: ${action} on ${target.slug}` },
|
|
4159
|
-
403
|
|
4160
|
-
);
|
|
4258
|
+
return c.json({ error: true, message: `Access denied: ${action} on ${target.slug}` }, 403);
|
|
4161
4259
|
}
|
|
4162
4260
|
await next();
|
|
4163
4261
|
};
|
|
@@ -4197,6 +4295,80 @@ function serializeBlockForApi(block) {
|
|
|
4197
4295
|
fields: block.fields?.map(serializeFieldForApi)
|
|
4198
4296
|
};
|
|
4199
4297
|
}
|
|
4298
|
+
function serializeDetailItemForApi(item) {
|
|
4299
|
+
if (!item) return item;
|
|
4300
|
+
if (typeof item === "string") {
|
|
4301
|
+
return { type: "field", field: item };
|
|
4302
|
+
}
|
|
4303
|
+
if (item.type === "section") {
|
|
4304
|
+
return {
|
|
4305
|
+
type: "section",
|
|
4306
|
+
title: item.title,
|
|
4307
|
+
items: (item.items || []).map(serializeDetailItemForApi),
|
|
4308
|
+
options: item.options
|
|
4309
|
+
};
|
|
4310
|
+
}
|
|
4311
|
+
if (item.type === "tab") {
|
|
4312
|
+
return {
|
|
4313
|
+
type: "tab",
|
|
4314
|
+
label: item.label,
|
|
4315
|
+
items: (item.items || []).map(serializeDetailItemForApi),
|
|
4316
|
+
options: item.options
|
|
4317
|
+
};
|
|
4318
|
+
}
|
|
4319
|
+
if (item.type === "tabs") {
|
|
4320
|
+
return {
|
|
4321
|
+
type: "tabs",
|
|
4322
|
+
tabs: (item.tabs || []).map(serializeDetailItemForApi),
|
|
4323
|
+
options: item.options
|
|
4324
|
+
};
|
|
4325
|
+
}
|
|
4326
|
+
if (item.type === "grid") {
|
|
4327
|
+
return {
|
|
4328
|
+
type: "grid",
|
|
4329
|
+
columns: item.columns,
|
|
4330
|
+
items: (item.items || []).map(serializeDetailItemForApi),
|
|
4331
|
+
options: item.options
|
|
4332
|
+
};
|
|
4333
|
+
}
|
|
4334
|
+
if (item.type === "repeat") {
|
|
4335
|
+
return {
|
|
4336
|
+
type: "repeat",
|
|
4337
|
+
field: item.field,
|
|
4338
|
+
items: (item.items || []).map(serializeDetailItemForApi),
|
|
4339
|
+
options: item.options
|
|
4340
|
+
};
|
|
4341
|
+
}
|
|
4342
|
+
if (item.type === "computed") {
|
|
4343
|
+
return {
|
|
4344
|
+
type: "computed",
|
|
4345
|
+
id: item.id || item.label?.toLowerCase().replace(/[^a-z0-9]+/g, "_"),
|
|
4346
|
+
label: item.label,
|
|
4347
|
+
expression: item.expression,
|
|
4348
|
+
options: {
|
|
4349
|
+
id: item.id,
|
|
4350
|
+
span: item.options?.span,
|
|
4351
|
+
format: item.options?.format,
|
|
4352
|
+
currency: item.options?.currency,
|
|
4353
|
+
visible: item.options?.visible
|
|
4354
|
+
}
|
|
4355
|
+
};
|
|
4356
|
+
}
|
|
4357
|
+
if (item.type === "field") {
|
|
4358
|
+
return {
|
|
4359
|
+
type: "field",
|
|
4360
|
+
field: item.field,
|
|
4361
|
+
options: item.options
|
|
4362
|
+
};
|
|
4363
|
+
}
|
|
4364
|
+
return item;
|
|
4365
|
+
}
|
|
4366
|
+
function serializeDetailForApi(detail) {
|
|
4367
|
+
if (detail === false) return false;
|
|
4368
|
+
if (detail === true) return true;
|
|
4369
|
+
if (!detail || !Array.isArray(detail)) return void 0;
|
|
4370
|
+
return detail.map(serializeDetailItemForApi);
|
|
4371
|
+
}
|
|
4200
4372
|
function registerRoutes(app, config) {
|
|
4201
4373
|
const optionsCache = /* @__PURE__ */ new Map();
|
|
4202
4374
|
app.get("/api/schemas", optionalAuth(config), async (c) => {
|
|
@@ -4215,15 +4387,12 @@ function registerRoutes(app, config) {
|
|
|
4215
4387
|
if (typeof access === "boolean") return access;
|
|
4216
4388
|
if (access && typeof access === "object" && typeof access.policy === "string") {
|
|
4217
4389
|
const policy = requestConfig.accessPolicies?.[access.policy];
|
|
4218
|
-
if (typeof policy === "string" || typeof policy === "boolean")
|
|
4219
|
-
return policy;
|
|
4390
|
+
if (typeof policy === "string" || typeof policy === "boolean") return policy;
|
|
4220
4391
|
}
|
|
4221
4392
|
return resolveBooleanAccess(requestConfig, access, accessArgs);
|
|
4222
4393
|
};
|
|
4223
4394
|
const filteredCollections = await Promise.all(
|
|
4224
|
-
collections.filter(
|
|
4225
|
-
(col) => !siteId || col.shared || !col.siteId || col.siteId === siteId
|
|
4226
|
-
).map(async (col) => ({
|
|
4395
|
+
collections.filter((col) => !siteId || col.shared || !col.siteId || col.siteId === siteId).map(async (col) => ({
|
|
4227
4396
|
slug: col.slug,
|
|
4228
4397
|
labels: col.labels,
|
|
4229
4398
|
access: {
|
|
@@ -4261,6 +4430,7 @@ function registerRoutes(app, config) {
|
|
|
4261
4430
|
audit: !!col.audit,
|
|
4262
4431
|
drafts: !!col.drafts,
|
|
4263
4432
|
admin: col.admin,
|
|
4433
|
+
detail: serializeDetailForApi(col.detail),
|
|
4264
4434
|
workflow: col.workflow ? {
|
|
4265
4435
|
initialState: col.workflow.initialState,
|
|
4266
4436
|
draftState: col.workflow.draftState,
|
|
@@ -4279,9 +4449,7 @@ function registerRoutes(app, config) {
|
|
|
4279
4449
|
}))
|
|
4280
4450
|
);
|
|
4281
4451
|
const filteredGlobals = await Promise.all(
|
|
4282
|
-
globals.filter(
|
|
4283
|
-
(glb) => !siteId || glb.shared || !glb.siteId || glb.siteId === siteId
|
|
4284
|
-
).map(async (glb) => ({
|
|
4452
|
+
globals.filter((glb) => !siteId || glb.shared || !glb.siteId || glb.siteId === siteId).map(async (glb) => ({
|
|
4285
4453
|
slug: glb.slug,
|
|
4286
4454
|
label: glb.label,
|
|
4287
4455
|
access: {
|
|
@@ -4311,7 +4479,8 @@ function registerRoutes(app, config) {
|
|
|
4311
4479
|
}
|
|
4312
4480
|
}))
|
|
4313
4481
|
),
|
|
4314
|
-
admin: glb.admin
|
|
4482
|
+
admin: glb.admin,
|
|
4483
|
+
detail: serializeDetailForApi(glb.detail)
|
|
4315
4484
|
}))
|
|
4316
4485
|
);
|
|
4317
4486
|
return c.json({
|
|
@@ -4325,148 +4494,137 @@ function registerRoutes(app, config) {
|
|
|
4325
4494
|
adminHealth: {
|
|
4326
4495
|
emailConfigured: !!requestConfig.email,
|
|
4327
4496
|
secureAuthSecretConfigured: !!process.env.DYRECTED_JWT_SECRET,
|
|
4328
|
-
authCollectionConfigured: requestConfig.collections.some(
|
|
4329
|
-
|
|
4330
|
-
),
|
|
4331
|
-
uploadCollectionConfigured: requestConfig.collections.some(
|
|
4332
|
-
(collection) => !!collection.upload
|
|
4333
|
-
)
|
|
4497
|
+
authCollectionConfigured: requestConfig.collections.some((collection) => !!collection.auth),
|
|
4498
|
+
uploadCollectionConfigured: requestConfig.collections.some((collection) => !!collection.upload)
|
|
4334
4499
|
}
|
|
4335
4500
|
});
|
|
4336
4501
|
});
|
|
4337
|
-
app.get(
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
});
|
|
4355
|
-
if (!allowed) {
|
|
4356
|
-
return c.json(
|
|
4357
|
-
{ error: true, message: `Access denied: read on ${colSlug}` },
|
|
4358
|
-
403
|
|
4359
|
-
);
|
|
4360
|
-
}
|
|
4361
|
-
}
|
|
4362
|
-
field = collection.fields.find((f) => f.name === fieldName);
|
|
4363
|
-
} else {
|
|
4364
|
-
const globals = [...requestConfig.globals];
|
|
4365
|
-
const glb = globals.find((g) => g.slug === colSlug);
|
|
4366
|
-
if (!glb) {
|
|
4367
|
-
return c.json(
|
|
4368
|
-
{
|
|
4369
|
-
error: true,
|
|
4370
|
-
message: `${colSlug} not found as collection or global`
|
|
4371
|
-
},
|
|
4372
|
-
404
|
|
4373
|
-
);
|
|
4374
|
-
}
|
|
4375
|
-
const accessExpr = glb.access?.read;
|
|
4376
|
-
if (accessExpr !== void 0 && accessExpr !== null) {
|
|
4377
|
-
const allowed = await resolveBooleanAccess(config, accessExpr, {
|
|
4378
|
-
user,
|
|
4379
|
-
req: toHookRequestContext(c.req)
|
|
4380
|
-
});
|
|
4381
|
-
if (!allowed) {
|
|
4382
|
-
return c.json(
|
|
4383
|
-
{
|
|
4384
|
-
error: true,
|
|
4385
|
-
message: `Access denied: read on global ${colSlug}`
|
|
4386
|
-
},
|
|
4387
|
-
403
|
|
4388
|
-
);
|
|
4389
|
-
}
|
|
4502
|
+
app.get("/api/dyrected/options/:collection/:field", optionalAuth(config), async (c) => {
|
|
4503
|
+
const { collection: colSlug, field: fieldName } = c.req.param();
|
|
4504
|
+
const siteId = c.req.header("X-Site-Id");
|
|
4505
|
+
const requestConfig = siteId && config.onSchemaFetch ? mergeDynamicConfig(config, await config.onSchemaFetch(siteId)) : config;
|
|
4506
|
+
let collections = [...requestConfig.collections];
|
|
4507
|
+
const user = c.get("user");
|
|
4508
|
+
let collection = collections.find((col) => col.slug === colSlug);
|
|
4509
|
+
let field;
|
|
4510
|
+
if (collection) {
|
|
4511
|
+
const accessExpr = collection.access?.read;
|
|
4512
|
+
if (accessExpr !== void 0 && accessExpr !== null) {
|
|
4513
|
+
const allowed = await resolveBooleanAccess(config, accessExpr, {
|
|
4514
|
+
user,
|
|
4515
|
+
req: toHookRequestContext(c.req)
|
|
4516
|
+
});
|
|
4517
|
+
if (!allowed) {
|
|
4518
|
+
return c.json({ error: true, message: `Access denied: read on ${colSlug}` }, 403);
|
|
4390
4519
|
}
|
|
4391
|
-
field = glb.fields.find((f) => f.name === fieldName);
|
|
4392
4520
|
}
|
|
4393
|
-
|
|
4521
|
+
field = collection.fields.find((f) => f.name === fieldName);
|
|
4522
|
+
} else {
|
|
4523
|
+
const globals = [...requestConfig.globals];
|
|
4524
|
+
const glb = globals.find((g) => g.slug === colSlug);
|
|
4525
|
+
if (!glb) {
|
|
4394
4526
|
return c.json(
|
|
4395
4527
|
{
|
|
4396
4528
|
error: true,
|
|
4397
|
-
message:
|
|
4529
|
+
message: `${colSlug} not found as collection or global`
|
|
4398
4530
|
},
|
|
4399
4531
|
404
|
|
4400
4532
|
);
|
|
4401
4533
|
}
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
resolver = field.options;
|
|
4406
|
-
} else if (field.options && typeof field.options === "object" && "resolve" in field.options) {
|
|
4407
|
-
resolver = field.options.resolve;
|
|
4408
|
-
cacheTTL = field.options.cacheTTL;
|
|
4409
|
-
}
|
|
4410
|
-
if (!resolver) {
|
|
4411
|
-
return c.json(
|
|
4412
|
-
{
|
|
4413
|
-
error: true,
|
|
4414
|
-
message: `Field ${fieldName} in ${colSlug} is not dynamic`
|
|
4415
|
-
},
|
|
4416
|
-
400
|
|
4417
|
-
);
|
|
4418
|
-
}
|
|
4419
|
-
try {
|
|
4420
|
-
const db = c.get("db") || config.db;
|
|
4421
|
-
const queryParams = c.req.query();
|
|
4422
|
-
const reqContext = {
|
|
4423
|
-
query: queryParams,
|
|
4424
|
-
headers: c.req.header(),
|
|
4425
|
-
raw: c.req.raw
|
|
4426
|
-
};
|
|
4427
|
-
const shouldCache = typeof cacheTTL === "number" && cacheTTL > 0;
|
|
4428
|
-
let cacheKey = "";
|
|
4429
|
-
if (shouldCache) {
|
|
4430
|
-
cacheKey = JSON.stringify([
|
|
4431
|
-
siteId ?? "",
|
|
4432
|
-
colSlug,
|
|
4433
|
-
fieldName,
|
|
4434
|
-
user?.id ?? null,
|
|
4435
|
-
queryParams
|
|
4436
|
-
]);
|
|
4437
|
-
const hit = optionsCache.get(cacheKey);
|
|
4438
|
-
if (hit && hit.expires > Date.now()) {
|
|
4439
|
-
return c.json(hit.value);
|
|
4440
|
-
}
|
|
4441
|
-
}
|
|
4442
|
-
const result = await resolver({
|
|
4443
|
-
db,
|
|
4534
|
+
const accessExpr = glb.access?.read;
|
|
4535
|
+
if (accessExpr !== void 0 && accessExpr !== null) {
|
|
4536
|
+
const allowed = await resolveBooleanAccess(config, accessExpr, {
|
|
4444
4537
|
user,
|
|
4445
|
-
req:
|
|
4538
|
+
req: toHookRequestContext(c.req)
|
|
4446
4539
|
});
|
|
4447
|
-
if (
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4540
|
+
if (!allowed) {
|
|
4541
|
+
return c.json(
|
|
4542
|
+
{
|
|
4543
|
+
error: true,
|
|
4544
|
+
message: `Access denied: read on global ${colSlug}`
|
|
4545
|
+
},
|
|
4546
|
+
403
|
|
4547
|
+
);
|
|
4452
4548
|
}
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
|
|
4549
|
+
}
|
|
4550
|
+
field = glb.fields.find((f) => f.name === fieldName);
|
|
4551
|
+
}
|
|
4552
|
+
if (!field) {
|
|
4553
|
+
return c.json(
|
|
4554
|
+
{
|
|
4555
|
+
error: true,
|
|
4556
|
+
message: `Field ${fieldName} not found in ${colSlug}`
|
|
4557
|
+
},
|
|
4558
|
+
404
|
|
4559
|
+
);
|
|
4560
|
+
}
|
|
4561
|
+
let resolver;
|
|
4562
|
+
let cacheTTL;
|
|
4563
|
+
if (typeof field.options === "function") {
|
|
4564
|
+
resolver = field.options;
|
|
4565
|
+
} else if (field.options && typeof field.options === "object" && "resolve" in field.options) {
|
|
4566
|
+
resolver = field.options.resolve;
|
|
4567
|
+
cacheTTL = field.options.cacheTTL;
|
|
4568
|
+
}
|
|
4569
|
+
if (!resolver) {
|
|
4570
|
+
return c.json(
|
|
4571
|
+
{
|
|
4572
|
+
error: true,
|
|
4573
|
+
message: `Field ${fieldName} in ${colSlug} is not dynamic`
|
|
4574
|
+
},
|
|
4575
|
+
400
|
|
4576
|
+
);
|
|
4577
|
+
}
|
|
4578
|
+
try {
|
|
4579
|
+
const db = c.get("db") || config.db;
|
|
4580
|
+
const queryParams = c.req.query();
|
|
4581
|
+
const reqContext = {
|
|
4582
|
+
query: queryParams,
|
|
4583
|
+
headers: c.req.header(),
|
|
4584
|
+
raw: c.req.raw
|
|
4585
|
+
};
|
|
4586
|
+
const shouldCache = typeof cacheTTL === "number" && cacheTTL > 0;
|
|
4587
|
+
let cacheKey = "";
|
|
4588
|
+
if (shouldCache) {
|
|
4589
|
+
cacheKey = JSON.stringify([
|
|
4590
|
+
siteId ?? "",
|
|
4591
|
+
colSlug,
|
|
4592
|
+
fieldName,
|
|
4593
|
+
user?.id ?? null,
|
|
4594
|
+
queryParams
|
|
4595
|
+
]);
|
|
4596
|
+
const hit = optionsCache.get(cacheKey);
|
|
4597
|
+
if (hit && hit.expires > Date.now()) {
|
|
4598
|
+
return c.json(hit.value);
|
|
4599
|
+
}
|
|
4600
|
+
}
|
|
4601
|
+
const result = await resolver({
|
|
4602
|
+
db,
|
|
4603
|
+
user,
|
|
4604
|
+
req: reqContext
|
|
4605
|
+
});
|
|
4606
|
+
if (shouldCache) {
|
|
4607
|
+
optionsCache.set(cacheKey, {
|
|
4608
|
+
expires: Date.now() + cacheTTL * 1e3,
|
|
4609
|
+
value: result
|
|
4459
4610
|
});
|
|
4460
|
-
return c.json(
|
|
4461
|
-
{
|
|
4462
|
-
error: true,
|
|
4463
|
-
message: err.message || "Failed to resolve dynamic options"
|
|
4464
|
-
},
|
|
4465
|
-
500
|
|
4466
|
-
);
|
|
4467
4611
|
}
|
|
4612
|
+
return c.json(result);
|
|
4613
|
+
} catch (err) {
|
|
4614
|
+
getRequestLogger(c, "router").error({
|
|
4615
|
+
err,
|
|
4616
|
+
msg: "Failed to resolve dynamic field options",
|
|
4617
|
+
fieldName
|
|
4618
|
+
});
|
|
4619
|
+
return c.json(
|
|
4620
|
+
{
|
|
4621
|
+
error: true,
|
|
4622
|
+
message: err.message || "Failed to resolve dynamic options"
|
|
4623
|
+
},
|
|
4624
|
+
500
|
|
4625
|
+
);
|
|
4468
4626
|
}
|
|
4469
|
-
);
|
|
4627
|
+
});
|
|
4470
4628
|
app.get("/api/openapi.json", (c) => {
|
|
4471
4629
|
return c.json(generateOpenApi(config));
|
|
4472
4630
|
});
|
|
@@ -4479,13 +4637,8 @@ function registerRoutes(app, config) {
|
|
|
4479
4637
|
const key = c.req.param("key");
|
|
4480
4638
|
const scope = c.req.query("scope");
|
|
4481
4639
|
if (!db) return c.json({ message: "Database not configured" }, 500);
|
|
4482
|
-
if (!user?.collection || !user.sub)
|
|
4483
|
-
|
|
4484
|
-
if (!key)
|
|
4485
|
-
return c.json(
|
|
4486
|
-
{ error: true, message: "Preference key is required." },
|
|
4487
|
-
400
|
|
4488
|
-
);
|
|
4640
|
+
if (!user?.collection || !user.sub) return c.json({ error: true, message: "Authentication required." }, 401);
|
|
4641
|
+
if (!key) return c.json({ error: true, message: "Preference key is required." }, 400);
|
|
4489
4642
|
const getGlobalPreference = async () => {
|
|
4490
4643
|
const globalDoc = await db.findOne({
|
|
4491
4644
|
collection: "__global_preferences",
|
|
@@ -4512,13 +4665,8 @@ function registerRoutes(app, config) {
|
|
|
4512
4665
|
const key = c.req.param("key");
|
|
4513
4666
|
const scope = c.req.query("scope");
|
|
4514
4667
|
if (!db) return c.json({ message: "Database not configured" }, 500);
|
|
4515
|
-
if (!user?.collection || !user.sub)
|
|
4516
|
-
|
|
4517
|
-
if (!key)
|
|
4518
|
-
return c.json(
|
|
4519
|
-
{ error: true, message: "Preference key is required." },
|
|
4520
|
-
400
|
|
4521
|
-
);
|
|
4668
|
+
if (!user?.collection || !user.sub) return c.json({ error: true, message: "Authentication required." }, 401);
|
|
4669
|
+
if (!key) return c.json({ error: true, message: "Preference key is required." }, 400);
|
|
4522
4670
|
const body = await c.req.json().catch(() => ({}));
|
|
4523
4671
|
if (scope === "global") {
|
|
4524
4672
|
const isAdminUser = Array.isArray(user?.roles) && user.roles.includes("admin");
|
|
@@ -4566,13 +4714,8 @@ function registerRoutes(app, config) {
|
|
|
4566
4714
|
const key = c.req.param("key");
|
|
4567
4715
|
const scope = c.req.query("scope");
|
|
4568
4716
|
if (!db) return c.json({ message: "Database not configured" }, 500);
|
|
4569
|
-
if (!user?.collection || !user.sub)
|
|
4570
|
-
|
|
4571
|
-
if (!key)
|
|
4572
|
-
return c.json(
|
|
4573
|
-
{ error: true, message: "Preference key is required." },
|
|
4574
|
-
400
|
|
4575
|
-
);
|
|
4717
|
+
if (!user?.collection || !user.sub) return c.json({ error: true, message: "Authentication required." }, 401);
|
|
4718
|
+
if (!key) return c.json({ error: true, message: "Preference key is required." }, 400);
|
|
4576
4719
|
if (scope === "global") {
|
|
4577
4720
|
const isAdminUser = Array.isArray(user?.roles) && user.roles.includes("admin");
|
|
4578
4721
|
if (!isAdminUser) {
|
|
@@ -4611,41 +4754,17 @@ function registerRoutes(app, config) {
|
|
|
4611
4754
|
for (const col of uploadCollections) {
|
|
4612
4755
|
const mediaController = new MediaController(col.slug);
|
|
4613
4756
|
const prefix = `/api/collections/${col.slug}`;
|
|
4614
|
-
app.get(
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
);
|
|
4619
|
-
app.get(
|
|
4620
|
-
`${prefix}/media/:filename{.+$}`,
|
|
4621
|
-
(c) => mediaController.serve(c)
|
|
4622
|
-
);
|
|
4623
|
-
app.post(
|
|
4624
|
-
`${prefix}/media`,
|
|
4625
|
-
accessGate(config, col, "create"),
|
|
4626
|
-
(c) => mediaController.upload(c)
|
|
4627
|
-
);
|
|
4628
|
-
app.delete(
|
|
4629
|
-
`${prefix}/media/:id`,
|
|
4630
|
-
accessGate(config, col, "delete"),
|
|
4631
|
-
(c) => mediaController.delete(c)
|
|
4632
|
-
);
|
|
4757
|
+
app.get(`${prefix}/media`, accessGate(config, col, "read"), (c) => mediaController.find(c));
|
|
4758
|
+
app.get(`${prefix}/media/:filename{.+$}`, (c) => mediaController.serve(c));
|
|
4759
|
+
app.post(`${prefix}/media`, accessGate(config, col, "create"), (c) => mediaController.upload(c));
|
|
4760
|
+
app.delete(`${prefix}/media/:id`, accessGate(config, col, "delete"), (c) => mediaController.delete(c));
|
|
4633
4761
|
}
|
|
4634
4762
|
}
|
|
4635
4763
|
const adminAuthController = new AdminAuthController(config);
|
|
4636
4764
|
app.get("/api/admin/auth/providers", (c) => adminAuthController.providers(c));
|
|
4637
|
-
app.get(
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
);
|
|
4641
|
-
app.get(
|
|
4642
|
-
"/api/admin/auth/:provider/callback",
|
|
4643
|
-
(c) => adminAuthController.callback(c)
|
|
4644
|
-
);
|
|
4645
|
-
app.post(
|
|
4646
|
-
"/api/admin/auth/:provider/exchange",
|
|
4647
|
-
(c) => adminAuthController.exchange(c)
|
|
4648
|
-
);
|
|
4765
|
+
app.get("/api/admin/auth/:provider/start", (c) => adminAuthController.start(c));
|
|
4766
|
+
app.get("/api/admin/auth/:provider/callback", (c) => adminAuthController.callback(c));
|
|
4767
|
+
app.post("/api/admin/auth/:provider/exchange", (c) => adminAuthController.exchange(c));
|
|
4649
4768
|
app.post("/api/admin/logout", (c) => adminAuthController.logout(c));
|
|
4650
4769
|
for (const collection of config.collections) {
|
|
4651
4770
|
if (!collection.auth) continue;
|
|
@@ -4656,21 +4775,10 @@ function registerRoutes(app, config) {
|
|
|
4656
4775
|
app.get(`${path}/init`, (c) => authController.init(c));
|
|
4657
4776
|
app.post(`${path}/first-user`, (c) => authController.registerFirstUser(c));
|
|
4658
4777
|
app.get(`${path}/me`, requireAuth(config), (c) => authController.me(c));
|
|
4659
|
-
app.post(
|
|
4660
|
-
|
|
4661
|
-
requireAuth(config),
|
|
4662
|
-
(c) => authController.refreshToken(c)
|
|
4663
|
-
);
|
|
4664
|
-
app.post(
|
|
4665
|
-
`${path}/forgot-password`,
|
|
4666
|
-
(c) => authController.forgotPassword(c)
|
|
4667
|
-
);
|
|
4778
|
+
app.post(`${path}/refresh-token`, requireAuth(config), (c) => authController.refreshToken(c));
|
|
4779
|
+
app.post(`${path}/forgot-password`, (c) => authController.forgotPassword(c));
|
|
4668
4780
|
app.post(`${path}/reset-password`, (c) => authController.resetPassword(c));
|
|
4669
|
-
app.post(
|
|
4670
|
-
`${path}/invite`,
|
|
4671
|
-
requireAuth(config),
|
|
4672
|
-
(c) => authController.invite(c)
|
|
4673
|
-
);
|
|
4781
|
+
app.post(`${path}/invite`, requireAuth(config), (c) => authController.invite(c));
|
|
4674
4782
|
app.post(`${path}/accept-invite`, (c) => authController.acceptInvite(c));
|
|
4675
4783
|
}
|
|
4676
4784
|
const auditController = new AuditController();
|
|
@@ -4681,33 +4789,19 @@ function registerRoutes(app, config) {
|
|
|
4681
4789
|
app.post(path, (c) => controller.create(c));
|
|
4682
4790
|
app.post(`${path}/media`, (c) => controller.create(c));
|
|
4683
4791
|
app.delete(`${path}/delete-many`, (c) => controller.deleteMany(c));
|
|
4792
|
+
app.post(`${path}/aggregate`, (c) => controller.aggregate(c));
|
|
4684
4793
|
if (collection.audit) {
|
|
4685
|
-
app.get(
|
|
4686
|
-
`${path}/__audit`,
|
|
4687
|
-
(c) => auditController.findForCollection(c, collection)
|
|
4688
|
-
);
|
|
4794
|
+
app.get(`${path}/__audit`, (c) => auditController.findForCollection(c, collection));
|
|
4689
4795
|
}
|
|
4690
4796
|
app.get(`${path}/:id`, (c) => controller.findOne(c));
|
|
4691
4797
|
app.patch(`${path}/:id`, (c) => controller.update(c));
|
|
4692
4798
|
app.delete(`${path}/:id`, (c) => controller.delete(c));
|
|
4693
4799
|
if (collection.auth) {
|
|
4694
|
-
app.post(
|
|
4695
|
-
`${path}/:id/change-password`,
|
|
4696
|
-
requireAuth(config),
|
|
4697
|
-
(c) => controller.changePassword(c)
|
|
4698
|
-
);
|
|
4800
|
+
app.post(`${path}/:id/change-password`, requireAuth(config), (c) => controller.changePassword(c));
|
|
4699
4801
|
}
|
|
4700
4802
|
if (collection.workflow) {
|
|
4701
|
-
app.post(
|
|
4702
|
-
|
|
4703
|
-
requireAuth(config),
|
|
4704
|
-
(c) => controller.transition(c)
|
|
4705
|
-
);
|
|
4706
|
-
app.get(
|
|
4707
|
-
`${path}/:id/workflow-history`,
|
|
4708
|
-
requireAuth(config),
|
|
4709
|
-
(c) => controller.workflowHistory(c)
|
|
4710
|
-
);
|
|
4803
|
+
app.post(`${path}/:id/transitions/:transition`, requireAuth(config), (c) => controller.transition(c));
|
|
4804
|
+
app.get(`${path}/:id/workflow-history`, requireAuth(config), (c) => controller.workflowHistory(c));
|
|
4711
4805
|
}
|
|
4712
4806
|
}
|
|
4713
4807
|
for (const global of config.globals) {
|
|
@@ -4722,11 +4816,7 @@ function registerRoutes(app, config) {
|
|
|
4722
4816
|
});
|
|
4723
4817
|
}
|
|
4724
4818
|
const previewController = new PreviewController();
|
|
4725
|
-
app.post(
|
|
4726
|
-
"/api/preview-token",
|
|
4727
|
-
requireAuth(config),
|
|
4728
|
-
(c) => previewController.createToken(c)
|
|
4729
|
-
);
|
|
4819
|
+
app.post("/api/preview-token", requireAuth(config), (c) => previewController.createToken(c));
|
|
4730
4820
|
app.get("/api/preview-data", (c) => previewController.getData(c));
|
|
4731
4821
|
app.get("/api/audit", (c) => auditController.findAll(c));
|
|
4732
4822
|
app.get("/api/collections/:slug/__audit", async (c) => {
|
|
@@ -4739,81 +4829,49 @@ function registerRoutes(app, config) {
|
|
|
4739
4829
|
if (!config2.onSchemaFetch || !siteId) {
|
|
4740
4830
|
return c.json({ message: `Collection "${slug}" not found` }, 404);
|
|
4741
4831
|
}
|
|
4742
|
-
const requestConfig = mergeDynamicConfig(
|
|
4743
|
-
|
|
4744
|
-
await config2.onSchemaFetch(siteId)
|
|
4745
|
-
);
|
|
4746
|
-
const collection = requestConfig.collections.find(
|
|
4747
|
-
(col) => col.slug === slug
|
|
4748
|
-
);
|
|
4832
|
+
const requestConfig = mergeDynamicConfig(config2, await config2.onSchemaFetch(siteId));
|
|
4833
|
+
const collection = requestConfig.collections.find((col) => col.slug === slug);
|
|
4749
4834
|
if (!collection?.audit) {
|
|
4750
|
-
return c.json(
|
|
4751
|
-
{ message: `Collection "${slug}" not found or has no audit log` },
|
|
4752
|
-
404
|
|
4753
|
-
);
|
|
4835
|
+
return c.json({ message: `Collection "${slug}" not found or has no audit log` }, 404);
|
|
4754
4836
|
}
|
|
4755
4837
|
return auditController.findForCollection(c, collection);
|
|
4756
4838
|
});
|
|
4757
|
-
app.post(
|
|
4758
|
-
"
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
const config2 = c.get("config");
|
|
4764
|
-
if (config2.collections.some((col) => col.slug === slug)) {
|
|
4765
|
-
return c.json({ message: "Not Found" }, 404);
|
|
4766
|
-
}
|
|
4767
|
-
if (!config2.onSchemaFetch || !siteId) {
|
|
4768
|
-
return c.json({ message: `Collection "${slug}" not found` }, 404);
|
|
4769
|
-
}
|
|
4770
|
-
const requestConfig = mergeDynamicConfig(
|
|
4771
|
-
config2,
|
|
4772
|
-
await config2.onSchemaFetch(siteId)
|
|
4773
|
-
);
|
|
4774
|
-
const collection = requestConfig.collections.find(
|
|
4775
|
-
(col) => col.slug === slug
|
|
4776
|
-
);
|
|
4777
|
-
if (!collection?.workflow) {
|
|
4778
|
-
return c.json(
|
|
4779
|
-
{ message: `Collection "${slug}" not found or has no workflow` },
|
|
4780
|
-
404
|
|
4781
|
-
);
|
|
4782
|
-
}
|
|
4783
|
-
const controller = new CollectionController(collection);
|
|
4784
|
-
return controller.transition(c);
|
|
4839
|
+
app.post("/api/collections/:slug/:id/transitions/:transition", requireAuth(config), async (c) => {
|
|
4840
|
+
const slug = c.req.param("slug");
|
|
4841
|
+
const siteId = c.req.header("X-Site-Id") || c.get("siteId");
|
|
4842
|
+
const config2 = c.get("config");
|
|
4843
|
+
if (config2.collections.some((col) => col.slug === slug)) {
|
|
4844
|
+
return c.json({ message: "Not Found" }, 404);
|
|
4785
4845
|
}
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
"/api/collections/:slug/:id/workflow-history",
|
|
4789
|
-
requireAuth(config),
|
|
4790
|
-
async (c) => {
|
|
4791
|
-
const slug = c.req.param("slug");
|
|
4792
|
-
const siteId = c.req.header("X-Site-Id") || c.get("siteId");
|
|
4793
|
-
const config2 = c.get("config");
|
|
4794
|
-
if (config2.collections.some((col) => col.slug === slug)) {
|
|
4795
|
-
return c.json({ message: "Not Found" }, 404);
|
|
4796
|
-
}
|
|
4797
|
-
if (!config2.onSchemaFetch || !siteId) {
|
|
4798
|
-
return c.json({ message: `Collection "${slug}" not found` }, 404);
|
|
4799
|
-
}
|
|
4800
|
-
const requestConfig = mergeDynamicConfig(
|
|
4801
|
-
config2,
|
|
4802
|
-
await config2.onSchemaFetch(siteId)
|
|
4803
|
-
);
|
|
4804
|
-
const collection = requestConfig.collections.find(
|
|
4805
|
-
(col) => col.slug === slug
|
|
4806
|
-
);
|
|
4807
|
-
if (!collection?.workflow) {
|
|
4808
|
-
return c.json(
|
|
4809
|
-
{ message: `Collection "${slug}" not found or has no workflow` },
|
|
4810
|
-
404
|
|
4811
|
-
);
|
|
4812
|
-
}
|
|
4813
|
-
const controller = new CollectionController(collection);
|
|
4814
|
-
return controller.workflowHistory(c);
|
|
4846
|
+
if (!config2.onSchemaFetch || !siteId) {
|
|
4847
|
+
return c.json({ message: `Collection "${slug}" not found` }, 404);
|
|
4815
4848
|
}
|
|
4816
|
-
|
|
4849
|
+
const requestConfig = mergeDynamicConfig(config2, await config2.onSchemaFetch(siteId));
|
|
4850
|
+
const collection = requestConfig.collections.find((col) => col.slug === slug);
|
|
4851
|
+
if (!collection?.workflow) {
|
|
4852
|
+
return c.json({ message: `Collection "${slug}" not found or has no workflow` }, 404);
|
|
4853
|
+
}
|
|
4854
|
+
const controller = new CollectionController(collection);
|
|
4855
|
+
return controller.transition(c);
|
|
4856
|
+
});
|
|
4857
|
+
app.get("/api/collections/:slug/:id/workflow-history", requireAuth(config), async (c) => {
|
|
4858
|
+
const slug = c.req.param("slug");
|
|
4859
|
+
const siteId = c.req.header("X-Site-Id") || c.get("siteId");
|
|
4860
|
+
const config2 = c.get("config");
|
|
4861
|
+
if (config2.collections.some((col) => col.slug === slug)) {
|
|
4862
|
+
return c.json({ message: "Not Found" }, 404);
|
|
4863
|
+
}
|
|
4864
|
+
if (!config2.onSchemaFetch || !siteId) {
|
|
4865
|
+
return c.json({ message: `Collection "${slug}" not found` }, 404);
|
|
4866
|
+
}
|
|
4867
|
+
const requestConfig = mergeDynamicConfig(config2, await config2.onSchemaFetch(siteId));
|
|
4868
|
+
const collection = requestConfig.collections.find((col) => col.slug === slug);
|
|
4869
|
+
if (!collection?.workflow) {
|
|
4870
|
+
return c.json({ message: `Collection "${slug}" not found or has no workflow` }, 404);
|
|
4871
|
+
}
|
|
4872
|
+
const controller = new CollectionController(collection);
|
|
4873
|
+
return controller.workflowHistory(c);
|
|
4874
|
+
});
|
|
4817
4875
|
app.all("/api/collections/:slug/:id?", async (c) => {
|
|
4818
4876
|
const slug = c.req.param("slug");
|
|
4819
4877
|
const id = c.req.param("id");
|
|
@@ -4823,13 +4881,8 @@ function registerRoutes(app, config) {
|
|
|
4823
4881
|
return c.json({ message: "Method Not Allowed" }, 405);
|
|
4824
4882
|
}
|
|
4825
4883
|
if (config2.onSchemaFetch && siteId) {
|
|
4826
|
-
const requestConfig = mergeDynamicConfig(
|
|
4827
|
-
|
|
4828
|
-
await config2.onSchemaFetch(siteId)
|
|
4829
|
-
);
|
|
4830
|
-
let collection = requestConfig.collections.find(
|
|
4831
|
-
(col) => col.slug === slug
|
|
4832
|
-
);
|
|
4884
|
+
const requestConfig = mergeDynamicConfig(config2, await config2.onSchemaFetch(siteId));
|
|
4885
|
+
let collection = requestConfig.collections.find((col) => col.slug === slug);
|
|
4833
4886
|
if (!collection && slug === "media") {
|
|
4834
4887
|
collection = {
|
|
4835
4888
|
slug: "media",
|
|
@@ -4842,25 +4895,19 @@ function registerRoutes(app, config) {
|
|
|
4842
4895
|
if (collection.auth && id) {
|
|
4843
4896
|
const authController = new AuthController(collection);
|
|
4844
4897
|
const method2 = c.req.method;
|
|
4845
|
-
if (method2 === "POST" && id === "login")
|
|
4846
|
-
|
|
4847
|
-
if (method2 === "POST" && id === "logout")
|
|
4848
|
-
return authController.logout(c);
|
|
4898
|
+
if (method2 === "POST" && id === "login") return authController.login(c);
|
|
4899
|
+
if (method2 === "POST" && id === "logout") return authController.logout(c);
|
|
4849
4900
|
if (method2 === "GET" && id === "me") return authController.me(c);
|
|
4850
|
-
if (method2 === "POST" && id === "refresh-token")
|
|
4851
|
-
|
|
4852
|
-
if (method2 === "POST" && id === "
|
|
4853
|
-
return authController.forgotPassword(c);
|
|
4854
|
-
if (method2 === "POST" && id === "reset-password")
|
|
4855
|
-
return authController.resetPassword(c);
|
|
4901
|
+
if (method2 === "POST" && id === "refresh-token") return authController.refreshToken(c);
|
|
4902
|
+
if (method2 === "POST" && id === "forgot-password") return authController.forgotPassword(c);
|
|
4903
|
+
if (method2 === "POST" && id === "reset-password") return authController.resetPassword(c);
|
|
4856
4904
|
}
|
|
4857
4905
|
const controller = new CollectionController(collection);
|
|
4858
4906
|
const method = c.req.method;
|
|
4859
4907
|
if (id) {
|
|
4860
4908
|
if (method === "GET") return controller.findOne(c);
|
|
4861
4909
|
if (method === "PATCH") return controller.update(c);
|
|
4862
|
-
if (method === "DELETE" && id === "delete-many")
|
|
4863
|
-
return controller.deleteMany(c);
|
|
4910
|
+
if (method === "DELETE" && id === "delete-many") return controller.deleteMany(c);
|
|
4864
4911
|
if (method === "DELETE") return controller.delete(c);
|
|
4865
4912
|
if (method === "POST" && id === "media") return controller.create(c);
|
|
4866
4913
|
} else {
|
|
@@ -4880,10 +4927,7 @@ function registerRoutes(app, config) {
|
|
|
4880
4927
|
return c.json({ message: "Method Not Allowed" }, 405);
|
|
4881
4928
|
}
|
|
4882
4929
|
if (config2.onSchemaFetch && siteId) {
|
|
4883
|
-
const requestConfig = mergeDynamicConfig(
|
|
4884
|
-
config2,
|
|
4885
|
-
await config2.onSchemaFetch(siteId)
|
|
4886
|
-
);
|
|
4930
|
+
const requestConfig = mergeDynamicConfig(config2, await config2.onSchemaFetch(siteId));
|
|
4887
4931
|
const global = requestConfig.globals.find((glb) => glb.slug === slug);
|
|
4888
4932
|
if (global) {
|
|
4889
4933
|
const controller = new GlobalController(global);
|
|
@@ -4922,7 +4966,7 @@ function resolveClientIp(req, trustProxy = false) {
|
|
|
4922
4966
|
const value = normalizeIp(req.headers[header]);
|
|
4923
4967
|
if (value) return value;
|
|
4924
4968
|
}
|
|
4925
|
-
if (trustProxy) {
|
|
4969
|
+
if (trustProxy === true || typeof trustProxy === "number") {
|
|
4926
4970
|
const forwardedFor = pickForwardedFor(req.headers, trustProxy);
|
|
4927
4971
|
if (forwardedFor) return forwardedFor;
|
|
4928
4972
|
}
|
|
@@ -4934,6 +4978,7 @@ function pickForwardedFor(headers, trustProxy) {
|
|
|
4934
4978
|
const chain2 = forwardedHeader.split(",").map((entry) => normalizeIp(entry)).filter((entry) => !!entry);
|
|
4935
4979
|
if (chain2.length > 0) {
|
|
4936
4980
|
if (trustProxy === true) return chain2[0];
|
|
4981
|
+
if (trustProxy <= 0) return null;
|
|
4937
4982
|
const trustedHops2 = Math.max(1, Math.floor(trustProxy));
|
|
4938
4983
|
const candidateIndex2 = Math.max(0, chain2.length - trustedHops2);
|
|
4939
4984
|
return chain2[candidateIndex2] ?? chain2[0];
|
|
@@ -4946,6 +4991,7 @@ function pickForwardedFor(headers, trustProxy) {
|
|
|
4946
4991
|
const chain = matches.map((match) => normalizeIp(match[1])).filter((entry) => !!entry);
|
|
4947
4992
|
if (chain.length === 0) return null;
|
|
4948
4993
|
if (trustProxy === true) return chain[0];
|
|
4994
|
+
if (trustProxy <= 0) return null;
|
|
4949
4995
|
const trustedHops = Math.max(1, Math.floor(trustProxy));
|
|
4950
4996
|
const candidateIndex = Math.max(0, chain.length - trustedHops);
|
|
4951
4997
|
return chain[candidateIndex] ?? chain[0];
|
|
@@ -5149,7 +5195,7 @@ async function createDyrectedApp(rawConfig) {
|
|
|
5149
5195
|
app.use("*", cors(config.cors ? { origin: config.cors.origins } : {}));
|
|
5150
5196
|
app.get("/health", (c) => c.json({ status: "ok", version: "0.0.1" }));
|
|
5151
5197
|
if (observability.config.metrics.enabled && observability.config.metrics.exporter === "prometheus" && observability.prometheusExporter) {
|
|
5152
|
-
app.get(observability.config.metrics.path, async (
|
|
5198
|
+
app.get(observability.config.metrics.path, async (_c) => {
|
|
5153
5199
|
const metrics = await renderPrometheusMetrics(
|
|
5154
5200
|
observability.prometheusExporter
|
|
5155
5201
|
);
|
|
@@ -5189,6 +5235,22 @@ export {
|
|
|
5189
5235
|
GlobalController,
|
|
5190
5236
|
MediaController,
|
|
5191
5237
|
PreviewController,
|
|
5238
|
+
attachRequestMetrics,
|
|
5239
|
+
bindObservabilityRuntime,
|
|
5240
|
+
buildRequestLogger,
|
|
5241
|
+
captureRequestBody,
|
|
5192
5242
|
createDyrectedApp,
|
|
5193
|
-
|
|
5243
|
+
createObservabilityRuntime,
|
|
5244
|
+
createRequestSpan,
|
|
5245
|
+
endRequestSpan,
|
|
5246
|
+
getConfigLogger,
|
|
5247
|
+
getObservabilityRuntime,
|
|
5248
|
+
getRequestLogger,
|
|
5249
|
+
redactBody,
|
|
5250
|
+
redactHeaders,
|
|
5251
|
+
registerRoutes,
|
|
5252
|
+
renderPrometheusMetrics,
|
|
5253
|
+
resolveObservabilityConfig,
|
|
5254
|
+
serializeDetailForApi,
|
|
5255
|
+
shouldSampleRequest
|
|
5194
5256
|
};
|