@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.js
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
canViewWorkflowDraft,
|
|
8
8
|
collectConfigDiagnostics,
|
|
9
9
|
createWorkflowDocument,
|
|
10
|
+
evaluateDetailComputed,
|
|
10
11
|
executeFieldAfterRead,
|
|
11
12
|
executeFieldBeforeChange,
|
|
12
13
|
generateOpenApi,
|
|
@@ -28,7 +29,7 @@ import {
|
|
|
28
29
|
touchAuthSession,
|
|
29
30
|
transitionWorkflow,
|
|
30
31
|
verifyCollectionToken
|
|
31
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-XX7N64NL.js";
|
|
32
33
|
import {
|
|
33
34
|
attachRequestMetrics,
|
|
34
35
|
bindObservabilityRuntime,
|
|
@@ -865,6 +866,22 @@ var CollectionController = class {
|
|
|
865
866
|
toHookRequestContext(c) {
|
|
866
867
|
return toHookRequestContext(c.req);
|
|
867
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
|
+
}
|
|
868
885
|
async evaluateAccess(c, action, options = {}) {
|
|
869
886
|
const config = c.get("config");
|
|
870
887
|
return resolveCollectionAccess(
|
|
@@ -1097,6 +1114,9 @@ var CollectionController = class {
|
|
|
1097
1114
|
depth
|
|
1098
1115
|
);
|
|
1099
1116
|
}
|
|
1117
|
+
if (this.collection.auth && result.docs && Array.isArray(result.docs)) {
|
|
1118
|
+
result.docs = result.docs.map((d) => this.sanitizeDoc(d));
|
|
1119
|
+
}
|
|
1100
1120
|
return c.json(result);
|
|
1101
1121
|
}
|
|
1102
1122
|
async findOne(c) {
|
|
@@ -1113,32 +1133,51 @@ var CollectionController = class {
|
|
|
1113
1133
|
id: id2
|
|
1114
1134
|
});
|
|
1115
1135
|
const externalSubject = localDoc?.externalSubject || id2;
|
|
1116
|
-
let member = null;
|
|
1117
1136
|
if (provider.members.get) {
|
|
1118
|
-
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
|
+
}
|
|
1119
1148
|
} else if (provider.members.list) {
|
|
1120
1149
|
const listResult = await provider.members.list({ req: hookReq });
|
|
1121
|
-
|
|
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
|
+
}
|
|
1122
1158
|
}
|
|
1123
|
-
if (!member) return c.json({ message: "Not Found" }, 404);
|
|
1124
|
-
return c.json({
|
|
1125
|
-
...member,
|
|
1126
|
-
id: localDoc ? localDoc.id : member.id,
|
|
1127
|
-
externalSubject: member.id
|
|
1128
|
-
});
|
|
1129
1159
|
}
|
|
1130
|
-
const readonlyDb = createReadonlyDb(db);
|
|
1131
1160
|
const id = c.req.param("id");
|
|
1132
|
-
const depth = c.req.query("depth") !== void 0 ? Number(c.req.query("depth")) : 1;
|
|
1133
|
-
const user = c.get("user");
|
|
1134
1161
|
if (!id) return c.json({ message: "Missing ID" }, 400);
|
|
1135
|
-
|
|
1136
|
-
|
|
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;
|
|
1137
1172
|
if (this.collection.workflow) {
|
|
1138
|
-
|
|
1139
|
-
|
|
1173
|
+
effectiveDoc = materializeWorkflowDocument(
|
|
1174
|
+
doc,
|
|
1175
|
+
this.collection.workflow,
|
|
1176
|
+
user
|
|
1177
|
+
);
|
|
1178
|
+
if (!effectiveDoc) return c.json({ message: "Document not found" }, 404);
|
|
1140
1179
|
}
|
|
1141
|
-
const access = await this.evaluateAccess(c, "read", { id, doc });
|
|
1180
|
+
const access = await this.evaluateAccess(c, "read", { id, doc: effectiveDoc });
|
|
1142
1181
|
if (!access.allowed) {
|
|
1143
1182
|
return c.json(
|
|
1144
1183
|
{
|
|
@@ -1148,7 +1187,11 @@ var CollectionController = class {
|
|
|
1148
1187
|
403
|
|
1149
1188
|
);
|
|
1150
1189
|
}
|
|
1151
|
-
const docWithDefaults = DefaultsService.apply(
|
|
1190
|
+
const docWithDefaults = DefaultsService.apply(
|
|
1191
|
+
this.collection.fields,
|
|
1192
|
+
effectiveDoc
|
|
1193
|
+
);
|
|
1194
|
+
const readonlyDb = createReadonlyDb(db);
|
|
1152
1195
|
const docWithCollectionHooks = await runCollectionHooks(
|
|
1153
1196
|
this.collection.hooks?.afterRead,
|
|
1154
1197
|
{
|
|
@@ -1174,17 +1217,20 @@ var CollectionController = class {
|
|
|
1174
1217
|
},
|
|
1175
1218
|
docWithFieldHooks
|
|
1176
1219
|
);
|
|
1220
|
+
let finalDoc = docWithFieldAccess;
|
|
1177
1221
|
if (depth > 0 && docWithFieldAccess) {
|
|
1178
1222
|
const populationService = new PopulationService(db, config.collections);
|
|
1179
|
-
|
|
1223
|
+
finalDoc = await populationService.populate({
|
|
1180
1224
|
data: docWithFieldAccess,
|
|
1181
1225
|
fields: this.collection.fields,
|
|
1182
1226
|
currentDepth: 0,
|
|
1183
1227
|
maxDepth: depth
|
|
1184
1228
|
});
|
|
1185
|
-
return c.json(populatedDoc);
|
|
1186
1229
|
}
|
|
1187
|
-
|
|
1230
|
+
if (finalDoc && this.collection.detail) {
|
|
1231
|
+
finalDoc = await evaluateDetailComputed(this.collection.detail, finalDoc, user, readonlyDb);
|
|
1232
|
+
}
|
|
1233
|
+
return c.json(this.sanitizeDoc(finalDoc));
|
|
1188
1234
|
}
|
|
1189
1235
|
async create(c) {
|
|
1190
1236
|
const config = c.get("config");
|
|
@@ -1956,6 +2002,63 @@ var CollectionController = class {
|
|
|
1956
2002
|
201
|
|
1957
2003
|
);
|
|
1958
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
|
+
}
|
|
1959
2062
|
};
|
|
1960
2063
|
|
|
1961
2064
|
// src/controllers/global.controller.ts
|
|
@@ -2038,17 +2141,25 @@ var GlobalController = class {
|
|
|
2038
2141
|
},
|
|
2039
2142
|
docWithFieldHooks
|
|
2040
2143
|
);
|
|
2144
|
+
let finalDoc = docWithFieldAccess;
|
|
2041
2145
|
if (depth > 0 && docWithFieldAccess) {
|
|
2042
2146
|
const populationService = new PopulationService(db, config.collections);
|
|
2043
|
-
|
|
2147
|
+
finalDoc = await populationService.populate({
|
|
2044
2148
|
data: docWithFieldAccess,
|
|
2045
2149
|
fields: this.global.fields,
|
|
2046
2150
|
currentDepth: 0,
|
|
2047
2151
|
maxDepth: depth
|
|
2048
2152
|
});
|
|
2049
|
-
return c.json(populatedData);
|
|
2050
2153
|
}
|
|
2051
|
-
|
|
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);
|
|
2052
2163
|
}
|
|
2053
2164
|
async update(c) {
|
|
2054
2165
|
const config = c.get("config");
|
|
@@ -3391,6 +3502,7 @@ var AdminAuthController = class {
|
|
|
3391
3502
|
});
|
|
3392
3503
|
}
|
|
3393
3504
|
getProvider(config, id) {
|
|
3505
|
+
if (!id) return void 0;
|
|
3394
3506
|
if (config.adminAuth?.mode !== "external") return void 0;
|
|
3395
3507
|
return config.adminAuth.providers.find((provider) => provider.id === id);
|
|
3396
3508
|
}
|
|
@@ -3589,17 +3701,16 @@ var AdminAuthController = class {
|
|
|
3589
3701
|
throw new Error("OIDC nonce verification failed.");
|
|
3590
3702
|
}
|
|
3591
3703
|
claims = payload;
|
|
3592
|
-
} else
|
|
3593
|
-
const
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
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
|
+
});
|
|
3599
3712
|
if (!userInfo.ok) throw new Error("OIDC userinfo request failed.");
|
|
3600
3713
|
claims = await userInfo.json();
|
|
3601
|
-
} else {
|
|
3602
|
-
throw new Error("OIDC provider did not return usable identity claims.");
|
|
3603
3714
|
}
|
|
3604
3715
|
return {
|
|
3605
3716
|
identity: this.mapClaims(claims, provider.claimMapping),
|
|
@@ -4144,10 +4255,7 @@ function accessGate(config, target, action) {
|
|
|
4144
4255
|
req: toHookRequestContext(c.req)
|
|
4145
4256
|
});
|
|
4146
4257
|
if (!allowed) {
|
|
4147
|
-
return c.json(
|
|
4148
|
-
{ error: true, message: `Access denied: ${action} on ${target.slug}` },
|
|
4149
|
-
403
|
|
4150
|
-
);
|
|
4258
|
+
return c.json({ error: true, message: `Access denied: ${action} on ${target.slug}` }, 403);
|
|
4151
4259
|
}
|
|
4152
4260
|
await next();
|
|
4153
4261
|
};
|
|
@@ -4187,6 +4295,80 @@ function serializeBlockForApi(block) {
|
|
|
4187
4295
|
fields: block.fields?.map(serializeFieldForApi)
|
|
4188
4296
|
};
|
|
4189
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
|
+
}
|
|
4190
4372
|
function registerRoutes(app, config) {
|
|
4191
4373
|
const optionsCache = /* @__PURE__ */ new Map();
|
|
4192
4374
|
app.get("/api/schemas", optionalAuth(config), async (c) => {
|
|
@@ -4205,15 +4387,12 @@ function registerRoutes(app, config) {
|
|
|
4205
4387
|
if (typeof access === "boolean") return access;
|
|
4206
4388
|
if (access && typeof access === "object" && typeof access.policy === "string") {
|
|
4207
4389
|
const policy = requestConfig.accessPolicies?.[access.policy];
|
|
4208
|
-
if (typeof policy === "string" || typeof policy === "boolean")
|
|
4209
|
-
return policy;
|
|
4390
|
+
if (typeof policy === "string" || typeof policy === "boolean") return policy;
|
|
4210
4391
|
}
|
|
4211
4392
|
return resolveBooleanAccess(requestConfig, access, accessArgs);
|
|
4212
4393
|
};
|
|
4213
4394
|
const filteredCollections = await Promise.all(
|
|
4214
|
-
collections.filter(
|
|
4215
|
-
(col) => !siteId || col.shared || !col.siteId || col.siteId === siteId
|
|
4216
|
-
).map(async (col) => ({
|
|
4395
|
+
collections.filter((col) => !siteId || col.shared || !col.siteId || col.siteId === siteId).map(async (col) => ({
|
|
4217
4396
|
slug: col.slug,
|
|
4218
4397
|
labels: col.labels,
|
|
4219
4398
|
access: {
|
|
@@ -4251,6 +4430,7 @@ function registerRoutes(app, config) {
|
|
|
4251
4430
|
audit: !!col.audit,
|
|
4252
4431
|
drafts: !!col.drafts,
|
|
4253
4432
|
admin: col.admin,
|
|
4433
|
+
detail: serializeDetailForApi(col.detail),
|
|
4254
4434
|
workflow: col.workflow ? {
|
|
4255
4435
|
initialState: col.workflow.initialState,
|
|
4256
4436
|
draftState: col.workflow.draftState,
|
|
@@ -4269,9 +4449,7 @@ function registerRoutes(app, config) {
|
|
|
4269
4449
|
}))
|
|
4270
4450
|
);
|
|
4271
4451
|
const filteredGlobals = await Promise.all(
|
|
4272
|
-
globals.filter(
|
|
4273
|
-
(glb) => !siteId || glb.shared || !glb.siteId || glb.siteId === siteId
|
|
4274
|
-
).map(async (glb) => ({
|
|
4452
|
+
globals.filter((glb) => !siteId || glb.shared || !glb.siteId || glb.siteId === siteId).map(async (glb) => ({
|
|
4275
4453
|
slug: glb.slug,
|
|
4276
4454
|
label: glb.label,
|
|
4277
4455
|
access: {
|
|
@@ -4301,7 +4479,8 @@ function registerRoutes(app, config) {
|
|
|
4301
4479
|
}
|
|
4302
4480
|
}))
|
|
4303
4481
|
),
|
|
4304
|
-
admin: glb.admin
|
|
4482
|
+
admin: glb.admin,
|
|
4483
|
+
detail: serializeDetailForApi(glb.detail)
|
|
4305
4484
|
}))
|
|
4306
4485
|
);
|
|
4307
4486
|
return c.json({
|
|
@@ -4315,148 +4494,137 @@ function registerRoutes(app, config) {
|
|
|
4315
4494
|
adminHealth: {
|
|
4316
4495
|
emailConfigured: !!requestConfig.email,
|
|
4317
4496
|
secureAuthSecretConfigured: !!process.env.DYRECTED_JWT_SECRET,
|
|
4318
|
-
authCollectionConfigured: requestConfig.collections.some(
|
|
4319
|
-
|
|
4320
|
-
),
|
|
4321
|
-
uploadCollectionConfigured: requestConfig.collections.some(
|
|
4322
|
-
(collection) => !!collection.upload
|
|
4323
|
-
)
|
|
4497
|
+
authCollectionConfigured: requestConfig.collections.some((collection) => !!collection.auth),
|
|
4498
|
+
uploadCollectionConfigured: requestConfig.collections.some((collection) => !!collection.upload)
|
|
4324
4499
|
}
|
|
4325
4500
|
});
|
|
4326
4501
|
});
|
|
4327
|
-
app.get(
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
});
|
|
4345
|
-
if (!allowed) {
|
|
4346
|
-
return c.json(
|
|
4347
|
-
{ error: true, message: `Access denied: read on ${colSlug}` },
|
|
4348
|
-
403
|
|
4349
|
-
);
|
|
4350
|
-
}
|
|
4351
|
-
}
|
|
4352
|
-
field = collection.fields.find((f) => f.name === fieldName);
|
|
4353
|
-
} else {
|
|
4354
|
-
const globals = [...requestConfig.globals];
|
|
4355
|
-
const glb = globals.find((g) => g.slug === colSlug);
|
|
4356
|
-
if (!glb) {
|
|
4357
|
-
return c.json(
|
|
4358
|
-
{
|
|
4359
|
-
error: true,
|
|
4360
|
-
message: `${colSlug} not found as collection or global`
|
|
4361
|
-
},
|
|
4362
|
-
404
|
|
4363
|
-
);
|
|
4364
|
-
}
|
|
4365
|
-
const accessExpr = glb.access?.read;
|
|
4366
|
-
if (accessExpr !== void 0 && accessExpr !== null) {
|
|
4367
|
-
const allowed = await resolveBooleanAccess(config, accessExpr, {
|
|
4368
|
-
user,
|
|
4369
|
-
req: toHookRequestContext(c.req)
|
|
4370
|
-
});
|
|
4371
|
-
if (!allowed) {
|
|
4372
|
-
return c.json(
|
|
4373
|
-
{
|
|
4374
|
-
error: true,
|
|
4375
|
-
message: `Access denied: read on global ${colSlug}`
|
|
4376
|
-
},
|
|
4377
|
-
403
|
|
4378
|
-
);
|
|
4379
|
-
}
|
|
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);
|
|
4380
4519
|
}
|
|
4381
|
-
field = glb.fields.find((f) => f.name === fieldName);
|
|
4382
4520
|
}
|
|
4383
|
-
|
|
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) {
|
|
4384
4526
|
return c.json(
|
|
4385
4527
|
{
|
|
4386
4528
|
error: true,
|
|
4387
|
-
message:
|
|
4529
|
+
message: `${colSlug} not found as collection or global`
|
|
4388
4530
|
},
|
|
4389
4531
|
404
|
|
4390
4532
|
);
|
|
4391
4533
|
}
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
resolver = field.options;
|
|
4396
|
-
} else if (field.options && typeof field.options === "object" && "resolve" in field.options) {
|
|
4397
|
-
resolver = field.options.resolve;
|
|
4398
|
-
cacheTTL = field.options.cacheTTL;
|
|
4399
|
-
}
|
|
4400
|
-
if (!resolver) {
|
|
4401
|
-
return c.json(
|
|
4402
|
-
{
|
|
4403
|
-
error: true,
|
|
4404
|
-
message: `Field ${fieldName} in ${colSlug} is not dynamic`
|
|
4405
|
-
},
|
|
4406
|
-
400
|
|
4407
|
-
);
|
|
4408
|
-
}
|
|
4409
|
-
try {
|
|
4410
|
-
const db = c.get("db") || config.db;
|
|
4411
|
-
const queryParams = c.req.query();
|
|
4412
|
-
const reqContext = {
|
|
4413
|
-
query: queryParams,
|
|
4414
|
-
headers: c.req.header(),
|
|
4415
|
-
raw: c.req.raw
|
|
4416
|
-
};
|
|
4417
|
-
const shouldCache = typeof cacheTTL === "number" && cacheTTL > 0;
|
|
4418
|
-
let cacheKey = "";
|
|
4419
|
-
if (shouldCache) {
|
|
4420
|
-
cacheKey = JSON.stringify([
|
|
4421
|
-
siteId ?? "",
|
|
4422
|
-
colSlug,
|
|
4423
|
-
fieldName,
|
|
4424
|
-
user?.id ?? null,
|
|
4425
|
-
queryParams
|
|
4426
|
-
]);
|
|
4427
|
-
const hit = optionsCache.get(cacheKey);
|
|
4428
|
-
if (hit && hit.expires > Date.now()) {
|
|
4429
|
-
return c.json(hit.value);
|
|
4430
|
-
}
|
|
4431
|
-
}
|
|
4432
|
-
const result = await resolver({
|
|
4433
|
-
db,
|
|
4534
|
+
const accessExpr = glb.access?.read;
|
|
4535
|
+
if (accessExpr !== void 0 && accessExpr !== null) {
|
|
4536
|
+
const allowed = await resolveBooleanAccess(config, accessExpr, {
|
|
4434
4537
|
user,
|
|
4435
|
-
req:
|
|
4538
|
+
req: toHookRequestContext(c.req)
|
|
4436
4539
|
});
|
|
4437
|
-
if (
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
|
|
4540
|
+
if (!allowed) {
|
|
4541
|
+
return c.json(
|
|
4542
|
+
{
|
|
4543
|
+
error: true,
|
|
4544
|
+
message: `Access denied: read on global ${colSlug}`
|
|
4545
|
+
},
|
|
4546
|
+
403
|
|
4547
|
+
);
|
|
4442
4548
|
}
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
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
|
|
4449
4610
|
});
|
|
4450
|
-
return c.json(
|
|
4451
|
-
{
|
|
4452
|
-
error: true,
|
|
4453
|
-
message: err.message || "Failed to resolve dynamic options"
|
|
4454
|
-
},
|
|
4455
|
-
500
|
|
4456
|
-
);
|
|
4457
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
|
+
);
|
|
4458
4626
|
}
|
|
4459
|
-
);
|
|
4627
|
+
});
|
|
4460
4628
|
app.get("/api/openapi.json", (c) => {
|
|
4461
4629
|
return c.json(generateOpenApi(config));
|
|
4462
4630
|
});
|
|
@@ -4469,13 +4637,8 @@ function registerRoutes(app, config) {
|
|
|
4469
4637
|
const key = c.req.param("key");
|
|
4470
4638
|
const scope = c.req.query("scope");
|
|
4471
4639
|
if (!db) return c.json({ message: "Database not configured" }, 500);
|
|
4472
|
-
if (!user?.collection || !user.sub)
|
|
4473
|
-
|
|
4474
|
-
if (!key)
|
|
4475
|
-
return c.json(
|
|
4476
|
-
{ error: true, message: "Preference key is required." },
|
|
4477
|
-
400
|
|
4478
|
-
);
|
|
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);
|
|
4479
4642
|
const getGlobalPreference = async () => {
|
|
4480
4643
|
const globalDoc = await db.findOne({
|
|
4481
4644
|
collection: "__global_preferences",
|
|
@@ -4502,13 +4665,8 @@ function registerRoutes(app, config) {
|
|
|
4502
4665
|
const key = c.req.param("key");
|
|
4503
4666
|
const scope = c.req.query("scope");
|
|
4504
4667
|
if (!db) return c.json({ message: "Database not configured" }, 500);
|
|
4505
|
-
if (!user?.collection || !user.sub)
|
|
4506
|
-
|
|
4507
|
-
if (!key)
|
|
4508
|
-
return c.json(
|
|
4509
|
-
{ error: true, message: "Preference key is required." },
|
|
4510
|
-
400
|
|
4511
|
-
);
|
|
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);
|
|
4512
4670
|
const body = await c.req.json().catch(() => ({}));
|
|
4513
4671
|
if (scope === "global") {
|
|
4514
4672
|
const isAdminUser = Array.isArray(user?.roles) && user.roles.includes("admin");
|
|
@@ -4556,13 +4714,8 @@ function registerRoutes(app, config) {
|
|
|
4556
4714
|
const key = c.req.param("key");
|
|
4557
4715
|
const scope = c.req.query("scope");
|
|
4558
4716
|
if (!db) return c.json({ message: "Database not configured" }, 500);
|
|
4559
|
-
if (!user?.collection || !user.sub)
|
|
4560
|
-
|
|
4561
|
-
if (!key)
|
|
4562
|
-
return c.json(
|
|
4563
|
-
{ error: true, message: "Preference key is required." },
|
|
4564
|
-
400
|
|
4565
|
-
);
|
|
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);
|
|
4566
4719
|
if (scope === "global") {
|
|
4567
4720
|
const isAdminUser = Array.isArray(user?.roles) && user.roles.includes("admin");
|
|
4568
4721
|
if (!isAdminUser) {
|
|
@@ -4601,41 +4754,17 @@ function registerRoutes(app, config) {
|
|
|
4601
4754
|
for (const col of uploadCollections) {
|
|
4602
4755
|
const mediaController = new MediaController(col.slug);
|
|
4603
4756
|
const prefix = `/api/collections/${col.slug}`;
|
|
4604
|
-
app.get(
|
|
4605
|
-
|
|
4606
|
-
|
|
4607
|
-
|
|
4608
|
-
);
|
|
4609
|
-
app.get(
|
|
4610
|
-
`${prefix}/media/:filename{.+$}`,
|
|
4611
|
-
(c) => mediaController.serve(c)
|
|
4612
|
-
);
|
|
4613
|
-
app.post(
|
|
4614
|
-
`${prefix}/media`,
|
|
4615
|
-
accessGate(config, col, "create"),
|
|
4616
|
-
(c) => mediaController.upload(c)
|
|
4617
|
-
);
|
|
4618
|
-
app.delete(
|
|
4619
|
-
`${prefix}/media/:id`,
|
|
4620
|
-
accessGate(config, col, "delete"),
|
|
4621
|
-
(c) => mediaController.delete(c)
|
|
4622
|
-
);
|
|
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));
|
|
4623
4761
|
}
|
|
4624
4762
|
}
|
|
4625
4763
|
const adminAuthController = new AdminAuthController(config);
|
|
4626
4764
|
app.get("/api/admin/auth/providers", (c) => adminAuthController.providers(c));
|
|
4627
|
-
app.get(
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
);
|
|
4631
|
-
app.get(
|
|
4632
|
-
"/api/admin/auth/:provider/callback",
|
|
4633
|
-
(c) => adminAuthController.callback(c)
|
|
4634
|
-
);
|
|
4635
|
-
app.post(
|
|
4636
|
-
"/api/admin/auth/:provider/exchange",
|
|
4637
|
-
(c) => adminAuthController.exchange(c)
|
|
4638
|
-
);
|
|
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));
|
|
4639
4768
|
app.post("/api/admin/logout", (c) => adminAuthController.logout(c));
|
|
4640
4769
|
for (const collection of config.collections) {
|
|
4641
4770
|
if (!collection.auth) continue;
|
|
@@ -4646,21 +4775,10 @@ function registerRoutes(app, config) {
|
|
|
4646
4775
|
app.get(`${path}/init`, (c) => authController.init(c));
|
|
4647
4776
|
app.post(`${path}/first-user`, (c) => authController.registerFirstUser(c));
|
|
4648
4777
|
app.get(`${path}/me`, requireAuth(config), (c) => authController.me(c));
|
|
4649
|
-
app.post(
|
|
4650
|
-
|
|
4651
|
-
requireAuth(config),
|
|
4652
|
-
(c) => authController.refreshToken(c)
|
|
4653
|
-
);
|
|
4654
|
-
app.post(
|
|
4655
|
-
`${path}/forgot-password`,
|
|
4656
|
-
(c) => authController.forgotPassword(c)
|
|
4657
|
-
);
|
|
4778
|
+
app.post(`${path}/refresh-token`, requireAuth(config), (c) => authController.refreshToken(c));
|
|
4779
|
+
app.post(`${path}/forgot-password`, (c) => authController.forgotPassword(c));
|
|
4658
4780
|
app.post(`${path}/reset-password`, (c) => authController.resetPassword(c));
|
|
4659
|
-
app.post(
|
|
4660
|
-
`${path}/invite`,
|
|
4661
|
-
requireAuth(config),
|
|
4662
|
-
(c) => authController.invite(c)
|
|
4663
|
-
);
|
|
4781
|
+
app.post(`${path}/invite`, requireAuth(config), (c) => authController.invite(c));
|
|
4664
4782
|
app.post(`${path}/accept-invite`, (c) => authController.acceptInvite(c));
|
|
4665
4783
|
}
|
|
4666
4784
|
const auditController = new AuditController();
|
|
@@ -4671,33 +4789,19 @@ function registerRoutes(app, config) {
|
|
|
4671
4789
|
app.post(path, (c) => controller.create(c));
|
|
4672
4790
|
app.post(`${path}/media`, (c) => controller.create(c));
|
|
4673
4791
|
app.delete(`${path}/delete-many`, (c) => controller.deleteMany(c));
|
|
4792
|
+
app.post(`${path}/aggregate`, (c) => controller.aggregate(c));
|
|
4674
4793
|
if (collection.audit) {
|
|
4675
|
-
app.get(
|
|
4676
|
-
`${path}/__audit`,
|
|
4677
|
-
(c) => auditController.findForCollection(c, collection)
|
|
4678
|
-
);
|
|
4794
|
+
app.get(`${path}/__audit`, (c) => auditController.findForCollection(c, collection));
|
|
4679
4795
|
}
|
|
4680
4796
|
app.get(`${path}/:id`, (c) => controller.findOne(c));
|
|
4681
4797
|
app.patch(`${path}/:id`, (c) => controller.update(c));
|
|
4682
4798
|
app.delete(`${path}/:id`, (c) => controller.delete(c));
|
|
4683
4799
|
if (collection.auth) {
|
|
4684
|
-
app.post(
|
|
4685
|
-
`${path}/:id/change-password`,
|
|
4686
|
-
requireAuth(config),
|
|
4687
|
-
(c) => controller.changePassword(c)
|
|
4688
|
-
);
|
|
4800
|
+
app.post(`${path}/:id/change-password`, requireAuth(config), (c) => controller.changePassword(c));
|
|
4689
4801
|
}
|
|
4690
4802
|
if (collection.workflow) {
|
|
4691
|
-
app.post(
|
|
4692
|
-
|
|
4693
|
-
requireAuth(config),
|
|
4694
|
-
(c) => controller.transition(c)
|
|
4695
|
-
);
|
|
4696
|
-
app.get(
|
|
4697
|
-
`${path}/:id/workflow-history`,
|
|
4698
|
-
requireAuth(config),
|
|
4699
|
-
(c) => controller.workflowHistory(c)
|
|
4700
|
-
);
|
|
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));
|
|
4701
4805
|
}
|
|
4702
4806
|
}
|
|
4703
4807
|
for (const global of config.globals) {
|
|
@@ -4712,11 +4816,7 @@ function registerRoutes(app, config) {
|
|
|
4712
4816
|
});
|
|
4713
4817
|
}
|
|
4714
4818
|
const previewController = new PreviewController();
|
|
4715
|
-
app.post(
|
|
4716
|
-
"/api/preview-token",
|
|
4717
|
-
requireAuth(config),
|
|
4718
|
-
(c) => previewController.createToken(c)
|
|
4719
|
-
);
|
|
4819
|
+
app.post("/api/preview-token", requireAuth(config), (c) => previewController.createToken(c));
|
|
4720
4820
|
app.get("/api/preview-data", (c) => previewController.getData(c));
|
|
4721
4821
|
app.get("/api/audit", (c) => auditController.findAll(c));
|
|
4722
4822
|
app.get("/api/collections/:slug/__audit", async (c) => {
|
|
@@ -4729,81 +4829,49 @@ function registerRoutes(app, config) {
|
|
|
4729
4829
|
if (!config2.onSchemaFetch || !siteId) {
|
|
4730
4830
|
return c.json({ message: `Collection "${slug}" not found` }, 404);
|
|
4731
4831
|
}
|
|
4732
|
-
const requestConfig = mergeDynamicConfig(
|
|
4733
|
-
|
|
4734
|
-
await config2.onSchemaFetch(siteId)
|
|
4735
|
-
);
|
|
4736
|
-
const collection = requestConfig.collections.find(
|
|
4737
|
-
(col) => col.slug === slug
|
|
4738
|
-
);
|
|
4832
|
+
const requestConfig = mergeDynamicConfig(config2, await config2.onSchemaFetch(siteId));
|
|
4833
|
+
const collection = requestConfig.collections.find((col) => col.slug === slug);
|
|
4739
4834
|
if (!collection?.audit) {
|
|
4740
|
-
return c.json(
|
|
4741
|
-
{ message: `Collection "${slug}" not found or has no audit log` },
|
|
4742
|
-
404
|
|
4743
|
-
);
|
|
4835
|
+
return c.json({ message: `Collection "${slug}" not found or has no audit log` }, 404);
|
|
4744
4836
|
}
|
|
4745
4837
|
return auditController.findForCollection(c, collection);
|
|
4746
4838
|
});
|
|
4747
|
-
app.post(
|
|
4748
|
-
"
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
const config2 = c.get("config");
|
|
4754
|
-
if (config2.collections.some((col) => col.slug === slug)) {
|
|
4755
|
-
return c.json({ message: "Not Found" }, 404);
|
|
4756
|
-
}
|
|
4757
|
-
if (!config2.onSchemaFetch || !siteId) {
|
|
4758
|
-
return c.json({ message: `Collection "${slug}" not found` }, 404);
|
|
4759
|
-
}
|
|
4760
|
-
const requestConfig = mergeDynamicConfig(
|
|
4761
|
-
config2,
|
|
4762
|
-
await config2.onSchemaFetch(siteId)
|
|
4763
|
-
);
|
|
4764
|
-
const collection = requestConfig.collections.find(
|
|
4765
|
-
(col) => col.slug === slug
|
|
4766
|
-
);
|
|
4767
|
-
if (!collection?.workflow) {
|
|
4768
|
-
return c.json(
|
|
4769
|
-
{ message: `Collection "${slug}" not found or has no workflow` },
|
|
4770
|
-
404
|
|
4771
|
-
);
|
|
4772
|
-
}
|
|
4773
|
-
const controller = new CollectionController(collection);
|
|
4774
|
-
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);
|
|
4775
4845
|
}
|
|
4776
|
-
|
|
4777
|
-
|
|
4778
|
-
"/api/collections/:slug/:id/workflow-history",
|
|
4779
|
-
requireAuth(config),
|
|
4780
|
-
async (c) => {
|
|
4781
|
-
const slug = c.req.param("slug");
|
|
4782
|
-
const siteId = c.req.header("X-Site-Id") || c.get("siteId");
|
|
4783
|
-
const config2 = c.get("config");
|
|
4784
|
-
if (config2.collections.some((col) => col.slug === slug)) {
|
|
4785
|
-
return c.json({ message: "Not Found" }, 404);
|
|
4786
|
-
}
|
|
4787
|
-
if (!config2.onSchemaFetch || !siteId) {
|
|
4788
|
-
return c.json({ message: `Collection "${slug}" not found` }, 404);
|
|
4789
|
-
}
|
|
4790
|
-
const requestConfig = mergeDynamicConfig(
|
|
4791
|
-
config2,
|
|
4792
|
-
await config2.onSchemaFetch(siteId)
|
|
4793
|
-
);
|
|
4794
|
-
const collection = requestConfig.collections.find(
|
|
4795
|
-
(col) => col.slug === slug
|
|
4796
|
-
);
|
|
4797
|
-
if (!collection?.workflow) {
|
|
4798
|
-
return c.json(
|
|
4799
|
-
{ message: `Collection "${slug}" not found or has no workflow` },
|
|
4800
|
-
404
|
|
4801
|
-
);
|
|
4802
|
-
}
|
|
4803
|
-
const controller = new CollectionController(collection);
|
|
4804
|
-
return controller.workflowHistory(c);
|
|
4846
|
+
if (!config2.onSchemaFetch || !siteId) {
|
|
4847
|
+
return c.json({ message: `Collection "${slug}" not found` }, 404);
|
|
4805
4848
|
}
|
|
4806
|
-
|
|
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
|
+
});
|
|
4807
4875
|
app.all("/api/collections/:slug/:id?", async (c) => {
|
|
4808
4876
|
const slug = c.req.param("slug");
|
|
4809
4877
|
const id = c.req.param("id");
|
|
@@ -4813,13 +4881,8 @@ function registerRoutes(app, config) {
|
|
|
4813
4881
|
return c.json({ message: "Method Not Allowed" }, 405);
|
|
4814
4882
|
}
|
|
4815
4883
|
if (config2.onSchemaFetch && siteId) {
|
|
4816
|
-
const requestConfig = mergeDynamicConfig(
|
|
4817
|
-
|
|
4818
|
-
await config2.onSchemaFetch(siteId)
|
|
4819
|
-
);
|
|
4820
|
-
let collection = requestConfig.collections.find(
|
|
4821
|
-
(col) => col.slug === slug
|
|
4822
|
-
);
|
|
4884
|
+
const requestConfig = mergeDynamicConfig(config2, await config2.onSchemaFetch(siteId));
|
|
4885
|
+
let collection = requestConfig.collections.find((col) => col.slug === slug);
|
|
4823
4886
|
if (!collection && slug === "media") {
|
|
4824
4887
|
collection = {
|
|
4825
4888
|
slug: "media",
|
|
@@ -4832,25 +4895,19 @@ function registerRoutes(app, config) {
|
|
|
4832
4895
|
if (collection.auth && id) {
|
|
4833
4896
|
const authController = new AuthController(collection);
|
|
4834
4897
|
const method2 = c.req.method;
|
|
4835
|
-
if (method2 === "POST" && id === "login")
|
|
4836
|
-
|
|
4837
|
-
if (method2 === "POST" && id === "logout")
|
|
4838
|
-
return authController.logout(c);
|
|
4898
|
+
if (method2 === "POST" && id === "login") return authController.login(c);
|
|
4899
|
+
if (method2 === "POST" && id === "logout") return authController.logout(c);
|
|
4839
4900
|
if (method2 === "GET" && id === "me") return authController.me(c);
|
|
4840
|
-
if (method2 === "POST" && id === "refresh-token")
|
|
4841
|
-
|
|
4842
|
-
if (method2 === "POST" && id === "
|
|
4843
|
-
return authController.forgotPassword(c);
|
|
4844
|
-
if (method2 === "POST" && id === "reset-password")
|
|
4845
|
-
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);
|
|
4846
4904
|
}
|
|
4847
4905
|
const controller = new CollectionController(collection);
|
|
4848
4906
|
const method = c.req.method;
|
|
4849
4907
|
if (id) {
|
|
4850
4908
|
if (method === "GET") return controller.findOne(c);
|
|
4851
4909
|
if (method === "PATCH") return controller.update(c);
|
|
4852
|
-
if (method === "DELETE" && id === "delete-many")
|
|
4853
|
-
return controller.deleteMany(c);
|
|
4910
|
+
if (method === "DELETE" && id === "delete-many") return controller.deleteMany(c);
|
|
4854
4911
|
if (method === "DELETE") return controller.delete(c);
|
|
4855
4912
|
if (method === "POST" && id === "media") return controller.create(c);
|
|
4856
4913
|
} else {
|
|
@@ -4870,10 +4927,7 @@ function registerRoutes(app, config) {
|
|
|
4870
4927
|
return c.json({ message: "Method Not Allowed" }, 405);
|
|
4871
4928
|
}
|
|
4872
4929
|
if (config2.onSchemaFetch && siteId) {
|
|
4873
|
-
const requestConfig = mergeDynamicConfig(
|
|
4874
|
-
config2,
|
|
4875
|
-
await config2.onSchemaFetch(siteId)
|
|
4876
|
-
);
|
|
4930
|
+
const requestConfig = mergeDynamicConfig(config2, await config2.onSchemaFetch(siteId));
|
|
4877
4931
|
const global = requestConfig.globals.find((glb) => glb.slug === slug);
|
|
4878
4932
|
if (global) {
|
|
4879
4933
|
const controller = new GlobalController(global);
|
|
@@ -4912,7 +4966,7 @@ function resolveClientIp(req, trustProxy = false) {
|
|
|
4912
4966
|
const value = normalizeIp(req.headers[header]);
|
|
4913
4967
|
if (value) return value;
|
|
4914
4968
|
}
|
|
4915
|
-
if (trustProxy) {
|
|
4969
|
+
if (trustProxy === true || typeof trustProxy === "number") {
|
|
4916
4970
|
const forwardedFor = pickForwardedFor(req.headers, trustProxy);
|
|
4917
4971
|
if (forwardedFor) return forwardedFor;
|
|
4918
4972
|
}
|
|
@@ -4924,6 +4978,7 @@ function pickForwardedFor(headers, trustProxy) {
|
|
|
4924
4978
|
const chain2 = forwardedHeader.split(",").map((entry) => normalizeIp(entry)).filter((entry) => !!entry);
|
|
4925
4979
|
if (chain2.length > 0) {
|
|
4926
4980
|
if (trustProxy === true) return chain2[0];
|
|
4981
|
+
if (trustProxy <= 0) return null;
|
|
4927
4982
|
const trustedHops2 = Math.max(1, Math.floor(trustProxy));
|
|
4928
4983
|
const candidateIndex2 = Math.max(0, chain2.length - trustedHops2);
|
|
4929
4984
|
return chain2[candidateIndex2] ?? chain2[0];
|
|
@@ -4936,6 +4991,7 @@ function pickForwardedFor(headers, trustProxy) {
|
|
|
4936
4991
|
const chain = matches.map((match) => normalizeIp(match[1])).filter((entry) => !!entry);
|
|
4937
4992
|
if (chain.length === 0) return null;
|
|
4938
4993
|
if (trustProxy === true) return chain[0];
|
|
4994
|
+
if (trustProxy <= 0) return null;
|
|
4939
4995
|
const trustedHops = Math.max(1, Math.floor(trustProxy));
|
|
4940
4996
|
const candidateIndex = Math.max(0, chain.length - trustedHops);
|
|
4941
4997
|
return chain[candidateIndex] ?? chain[0];
|
|
@@ -5139,7 +5195,7 @@ async function createDyrectedApp(rawConfig) {
|
|
|
5139
5195
|
app.use("*", cors(config.cors ? { origin: config.cors.origins } : {}));
|
|
5140
5196
|
app.get("/health", (c) => c.json({ status: "ok", version: "0.0.1" }));
|
|
5141
5197
|
if (observability.config.metrics.enabled && observability.config.metrics.exporter === "prometheus" && observability.prometheusExporter) {
|
|
5142
|
-
app.get(observability.config.metrics.path, async (
|
|
5198
|
+
app.get(observability.config.metrics.path, async (_c) => {
|
|
5143
5199
|
const metrics = await renderPrometheusMetrics(
|
|
5144
5200
|
observability.prometheusExporter
|
|
5145
5201
|
);
|
|
@@ -5195,5 +5251,6 @@ export {
|
|
|
5195
5251
|
registerRoutes,
|
|
5196
5252
|
renderPrometheusMetrics,
|
|
5197
5253
|
resolveObservabilityConfig,
|
|
5254
|
+
serializeDetailForApi,
|
|
5198
5255
|
shouldSampleRequest
|
|
5199
5256
|
};
|