@dyrected/core 2.7.1 → 2.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app-config-7G-3tF_I.d.cts +2259 -0
- package/dist/app-config-7G-3tF_I.d.ts +2259 -0
- package/dist/app-config-B3ByBjCN.d.cts +2222 -0
- package/dist/app-config-B3ByBjCN.d.ts +2222 -0
- package/dist/app-config-B5tYsZaH.d.cts +2404 -0
- package/dist/app-config-B5tYsZaH.d.ts +2404 -0
- package/dist/app-config-BEuyPktb.d.cts +2556 -0
- package/dist/app-config-BEuyPktb.d.ts +2556 -0
- package/dist/app-config-BIkjQWzU.d.cts +2218 -0
- package/dist/app-config-BIkjQWzU.d.ts +2218 -0
- package/dist/app-config-BfpeE3bF.d.cts +2100 -0
- package/dist/app-config-BfpeE3bF.d.ts +2100 -0
- package/dist/app-config-CCe-yq9q.d.cts +2222 -0
- package/dist/app-config-CCe-yq9q.d.ts +2222 -0
- package/dist/app-config-CPOs7THF.d.cts +2217 -0
- package/dist/app-config-CPOs7THF.d.ts +2217 -0
- package/dist/app-config-Ctp454zB.d.cts +2214 -0
- package/dist/app-config-Ctp454zB.d.ts +2214 -0
- package/dist/app-config-DJmNYgYh.d.cts +2305 -0
- package/dist/app-config-DJmNYgYh.d.ts +2305 -0
- package/dist/app-config-DO3laV58.d.cts +2397 -0
- package/dist/app-config-DO3laV58.d.ts +2397 -0
- package/dist/app-config-DZf_mmi9.d.cts +2211 -0
- package/dist/app-config-DZf_mmi9.d.ts +2211 -0
- package/dist/app-config-SkUoN5JU.d.cts +2192 -0
- package/dist/app-config-SkUoN5JU.d.ts +2192 -0
- package/dist/app-config-WzqpuZMr.d.cts +2566 -0
- package/dist/app-config-WzqpuZMr.d.ts +2566 -0
- package/dist/app-config-fEjRm1o9.d.cts +2402 -0
- package/dist/app-config-fEjRm1o9.d.ts +2402 -0
- package/dist/app-config-oz8H_MOX.d.cts +2224 -0
- package/dist/app-config-oz8H_MOX.d.ts +2224 -0
- package/dist/chunk-3TRRTN6U.js +3441 -0
- package/dist/chunk-5ZIA5COH.js +3414 -0
- package/dist/chunk-6TWQ6GTD.js +3480 -0
- package/dist/chunk-AQO4HI2Y.js +3204 -0
- package/dist/chunk-DFBLNYUH.js +3402 -0
- package/dist/chunk-GFFGDHST.js +3543 -0
- package/dist/chunk-KEKOEAX6.js +3139 -0
- package/dist/chunk-PRQ2RF5C.js +3485 -0
- package/dist/chunk-PZMDK3MH.js +3203 -0
- package/dist/chunk-SM32ND5M.js +3472 -0
- package/dist/chunk-XX7N64NL.js +3550 -0
- package/dist/chunk-ZISFMAEL.js +3550 -0
- package/dist/index.cjs +439 -9
- package/dist/index.d.cts +166 -3
- package/dist/index.d.ts +166 -3
- package/dist/index.js +53 -1
- package/dist/server.cjs +623 -367
- package/dist/server.d.cts +26 -7
- package/dist/server.d.ts +26 -7
- package/dist/server.js +420 -359
- package/package.json +1 -1
package/dist/server.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-GFFGDHST.js";
|
|
32
33
|
import {
|
|
33
34
|
attachRequestMetrics,
|
|
34
35
|
bindObservabilityRuntime,
|
|
@@ -162,9 +163,13 @@ var PopulationService = class {
|
|
|
162
163
|
}
|
|
163
164
|
if (!field.name) continue;
|
|
164
165
|
const value = populatedDoc[field.name];
|
|
165
|
-
|
|
166
|
+
const fieldType = field.type;
|
|
167
|
+
const isRelationship = fieldType === "relationship";
|
|
168
|
+
const isUpload = fieldType === "upload" || fieldType === "image" || fieldType === "media";
|
|
169
|
+
const targetRelationTo = field.relationTo || (isUpload ? this.collections.find((c) => c.upload)?.slug || "media" : void 0);
|
|
170
|
+
if ((isRelationship || isUpload) && targetRelationTo && value) {
|
|
166
171
|
const relatedCollection = this.collections.find(
|
|
167
|
-
(c) => c.slug ===
|
|
172
|
+
(c) => c.slug === targetRelationTo
|
|
168
173
|
);
|
|
169
174
|
if (!relatedCollection) continue;
|
|
170
175
|
if (Array.isArray(value)) {
|
|
@@ -174,7 +179,7 @@ var PopulationService = class {
|
|
|
174
179
|
let doc = id;
|
|
175
180
|
if (typeof id === "string") {
|
|
176
181
|
doc = await this.db.findOne({
|
|
177
|
-
collection:
|
|
182
|
+
collection: targetRelationTo,
|
|
178
183
|
id
|
|
179
184
|
});
|
|
180
185
|
}
|
|
@@ -195,7 +200,7 @@ var PopulationService = class {
|
|
|
195
200
|
let doc = value;
|
|
196
201
|
if (typeof value === "string") {
|
|
197
202
|
doc = await this.db.findOne({
|
|
198
|
-
collection:
|
|
203
|
+
collection: targetRelationTo,
|
|
199
204
|
id: value
|
|
200
205
|
});
|
|
201
206
|
}
|
|
@@ -865,6 +870,22 @@ var CollectionController = class {
|
|
|
865
870
|
toHookRequestContext(c) {
|
|
866
871
|
return toHookRequestContext(c.req);
|
|
867
872
|
}
|
|
873
|
+
sanitizeDoc(doc) {
|
|
874
|
+
if (!doc || typeof doc !== "object") return doc;
|
|
875
|
+
if (this.collection.auth) {
|
|
876
|
+
const {
|
|
877
|
+
password: _p,
|
|
878
|
+
salt: _s,
|
|
879
|
+
resetPasswordToken: _rpt,
|
|
880
|
+
resetPasswordExpires: _rpe,
|
|
881
|
+
loginAttempts: _la,
|
|
882
|
+
lockUntil: _lu,
|
|
883
|
+
...safeDoc
|
|
884
|
+
} = doc;
|
|
885
|
+
return safeDoc;
|
|
886
|
+
}
|
|
887
|
+
return doc;
|
|
888
|
+
}
|
|
868
889
|
async evaluateAccess(c, action, options = {}) {
|
|
869
890
|
const config = c.get("config");
|
|
870
891
|
return resolveCollectionAccess(
|
|
@@ -1097,6 +1118,9 @@ var CollectionController = class {
|
|
|
1097
1118
|
depth
|
|
1098
1119
|
);
|
|
1099
1120
|
}
|
|
1121
|
+
if (this.collection.auth && result.docs && Array.isArray(result.docs)) {
|
|
1122
|
+
result.docs = result.docs.map((d) => this.sanitizeDoc(d));
|
|
1123
|
+
}
|
|
1100
1124
|
return c.json(result);
|
|
1101
1125
|
}
|
|
1102
1126
|
async findOne(c) {
|
|
@@ -1113,32 +1137,51 @@ var CollectionController = class {
|
|
|
1113
1137
|
id: id2
|
|
1114
1138
|
});
|
|
1115
1139
|
const externalSubject = localDoc?.externalSubject || id2;
|
|
1116
|
-
let member = null;
|
|
1117
1140
|
if (provider.members.get) {
|
|
1118
|
-
member = await provider.members.get({
|
|
1141
|
+
const member = await provider.members.get({
|
|
1142
|
+
externalSubject,
|
|
1143
|
+
req: hookReq
|
|
1144
|
+
});
|
|
1145
|
+
if (member) {
|
|
1146
|
+
return c.json({
|
|
1147
|
+
...member,
|
|
1148
|
+
id: localDoc ? localDoc.id : member.id,
|
|
1149
|
+
externalSubject: member.id
|
|
1150
|
+
});
|
|
1151
|
+
}
|
|
1119
1152
|
} else if (provider.members.list) {
|
|
1120
1153
|
const listResult = await provider.members.list({ req: hookReq });
|
|
1121
|
-
|
|
1154
|
+
const matched = listResult.docs.find((m) => m.id === externalSubject || m.id === id2);
|
|
1155
|
+
if (matched) {
|
|
1156
|
+
return c.json({
|
|
1157
|
+
...matched,
|
|
1158
|
+
id: localDoc ? localDoc.id : matched.id,
|
|
1159
|
+
externalSubject: matched.id
|
|
1160
|
+
});
|
|
1161
|
+
}
|
|
1122
1162
|
}
|
|
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
1163
|
}
|
|
1130
|
-
const readonlyDb = createReadonlyDb(db);
|
|
1131
1164
|
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
1165
|
if (!id) return c.json({ message: "Missing ID" }, 400);
|
|
1135
|
-
|
|
1136
|
-
|
|
1166
|
+
const depth = Number.parseInt(c.req.query("depth") || "0", 10);
|
|
1167
|
+
const user = c.get("user");
|
|
1168
|
+
const doc = await db.findOne({
|
|
1169
|
+
collection: this.collection.slug,
|
|
1170
|
+
id
|
|
1171
|
+
});
|
|
1172
|
+
if (!doc) {
|
|
1173
|
+
return c.json({ message: "Document not found" }, 404);
|
|
1174
|
+
}
|
|
1175
|
+
let effectiveDoc = doc;
|
|
1137
1176
|
if (this.collection.workflow) {
|
|
1138
|
-
|
|
1139
|
-
|
|
1177
|
+
effectiveDoc = materializeWorkflowDocument(
|
|
1178
|
+
doc,
|
|
1179
|
+
this.collection.workflow,
|
|
1180
|
+
user
|
|
1181
|
+
);
|
|
1182
|
+
if (!effectiveDoc) return c.json({ message: "Document not found" }, 404);
|
|
1140
1183
|
}
|
|
1141
|
-
const access = await this.evaluateAccess(c, "read", { id, doc });
|
|
1184
|
+
const access = await this.evaluateAccess(c, "read", { id, doc: effectiveDoc });
|
|
1142
1185
|
if (!access.allowed) {
|
|
1143
1186
|
return c.json(
|
|
1144
1187
|
{
|
|
@@ -1148,7 +1191,11 @@ var CollectionController = class {
|
|
|
1148
1191
|
403
|
|
1149
1192
|
);
|
|
1150
1193
|
}
|
|
1151
|
-
const docWithDefaults = DefaultsService.apply(
|
|
1194
|
+
const docWithDefaults = DefaultsService.apply(
|
|
1195
|
+
this.collection.fields,
|
|
1196
|
+
effectiveDoc
|
|
1197
|
+
);
|
|
1198
|
+
const readonlyDb = createReadonlyDb(db);
|
|
1152
1199
|
const docWithCollectionHooks = await runCollectionHooks(
|
|
1153
1200
|
this.collection.hooks?.afterRead,
|
|
1154
1201
|
{
|
|
@@ -1174,17 +1221,20 @@ var CollectionController = class {
|
|
|
1174
1221
|
},
|
|
1175
1222
|
docWithFieldHooks
|
|
1176
1223
|
);
|
|
1224
|
+
let finalDoc = docWithFieldAccess;
|
|
1177
1225
|
if (depth > 0 && docWithFieldAccess) {
|
|
1178
1226
|
const populationService = new PopulationService(db, config.collections);
|
|
1179
|
-
|
|
1227
|
+
finalDoc = await populationService.populate({
|
|
1180
1228
|
data: docWithFieldAccess,
|
|
1181
1229
|
fields: this.collection.fields,
|
|
1182
1230
|
currentDepth: 0,
|
|
1183
1231
|
maxDepth: depth
|
|
1184
1232
|
});
|
|
1185
|
-
return c.json(populatedDoc);
|
|
1186
1233
|
}
|
|
1187
|
-
|
|
1234
|
+
if (finalDoc && this.collection.detail) {
|
|
1235
|
+
finalDoc = await evaluateDetailComputed(this.collection.detail, finalDoc, user, readonlyDb);
|
|
1236
|
+
}
|
|
1237
|
+
return c.json(this.sanitizeDoc(finalDoc));
|
|
1188
1238
|
}
|
|
1189
1239
|
async create(c) {
|
|
1190
1240
|
const config = c.get("config");
|
|
@@ -1956,6 +2006,63 @@ var CollectionController = class {
|
|
|
1956
2006
|
201
|
|
1957
2007
|
);
|
|
1958
2008
|
}
|
|
2009
|
+
async aggregate(c) {
|
|
2010
|
+
const config = c.get("config");
|
|
2011
|
+
const db = config.db;
|
|
2012
|
+
if (!db) return c.json({ message: "Database not configured" }, 500);
|
|
2013
|
+
const access = await this.evaluateAccess(c, "read");
|
|
2014
|
+
if (!access.allowed) {
|
|
2015
|
+
return c.json(
|
|
2016
|
+
{
|
|
2017
|
+
error: true,
|
|
2018
|
+
message: `Access denied: read on ${this.collection.slug}`
|
|
2019
|
+
},
|
|
2020
|
+
403
|
|
2021
|
+
);
|
|
2022
|
+
}
|
|
2023
|
+
let body;
|
|
2024
|
+
try {
|
|
2025
|
+
body = await c.req.json();
|
|
2026
|
+
} catch {
|
|
2027
|
+
return c.json({ message: "Invalid JSON body" }, 400);
|
|
2028
|
+
}
|
|
2029
|
+
if (!body || typeof body !== "object" || Array.isArray(body)) {
|
|
2030
|
+
return c.json(
|
|
2031
|
+
{ message: "Aggregate request body must be a JSON object" },
|
|
2032
|
+
400
|
|
2033
|
+
);
|
|
2034
|
+
}
|
|
2035
|
+
const { sanitizeWhereClause } = await import("./where-sanitizer-WJ2W2QCE.js");
|
|
2036
|
+
const sanitizedAggregates = {};
|
|
2037
|
+
for (const [key, op] of Object.entries(body)) {
|
|
2038
|
+
if (!op || typeof op !== "object" || Array.isArray(op)) {
|
|
2039
|
+
return c.json(
|
|
2040
|
+
{
|
|
2041
|
+
message: `Aggregate operation "${key}" must be an object`
|
|
2042
|
+
},
|
|
2043
|
+
400
|
|
2044
|
+
);
|
|
2045
|
+
}
|
|
2046
|
+
const operation = op;
|
|
2047
|
+
let opWhere = operation.where && typeof operation.where === "object" ? operation.where : void 0;
|
|
2048
|
+
if (opWhere) {
|
|
2049
|
+
opWhere = sanitizeWhereClause(opWhere, this.collection.fields);
|
|
2050
|
+
if (Object.keys(opWhere).length === 0) opWhere = void 0;
|
|
2051
|
+
}
|
|
2052
|
+
if (access.constraint) {
|
|
2053
|
+
opWhere = opWhere ? mergeWhereConstraint(opWhere, access.constraint) : access.constraint;
|
|
2054
|
+
}
|
|
2055
|
+
sanitizedAggregates[key] = {
|
|
2056
|
+
...operation,
|
|
2057
|
+
...opWhere !== void 0 ? { where: opWhere } : { where: void 0 }
|
|
2058
|
+
};
|
|
2059
|
+
}
|
|
2060
|
+
const result = await db.aggregate({
|
|
2061
|
+
collection: this.collection.slug,
|
|
2062
|
+
aggregates: sanitizedAggregates
|
|
2063
|
+
});
|
|
2064
|
+
return c.json(result);
|
|
2065
|
+
}
|
|
1959
2066
|
};
|
|
1960
2067
|
|
|
1961
2068
|
// src/controllers/global.controller.ts
|
|
@@ -2038,17 +2145,25 @@ var GlobalController = class {
|
|
|
2038
2145
|
},
|
|
2039
2146
|
docWithFieldHooks
|
|
2040
2147
|
);
|
|
2148
|
+
let finalDoc = docWithFieldAccess;
|
|
2041
2149
|
if (depth > 0 && docWithFieldAccess) {
|
|
2042
2150
|
const populationService = new PopulationService(db, config.collections);
|
|
2043
|
-
|
|
2151
|
+
finalDoc = await populationService.populate({
|
|
2044
2152
|
data: docWithFieldAccess,
|
|
2045
2153
|
fields: this.global.fields,
|
|
2046
2154
|
currentDepth: 0,
|
|
2047
2155
|
maxDepth: depth
|
|
2048
2156
|
});
|
|
2049
|
-
return c.json(populatedData);
|
|
2050
2157
|
}
|
|
2051
|
-
|
|
2158
|
+
if (finalDoc && this.global.detail) {
|
|
2159
|
+
finalDoc = await evaluateDetailComputed(
|
|
2160
|
+
this.global.detail,
|
|
2161
|
+
finalDoc,
|
|
2162
|
+
user,
|
|
2163
|
+
readonlyDb
|
|
2164
|
+
);
|
|
2165
|
+
}
|
|
2166
|
+
return c.json(finalDoc);
|
|
2052
2167
|
}
|
|
2053
2168
|
async update(c) {
|
|
2054
2169
|
const config = c.get("config");
|
|
@@ -3391,6 +3506,7 @@ var AdminAuthController = class {
|
|
|
3391
3506
|
});
|
|
3392
3507
|
}
|
|
3393
3508
|
getProvider(config, id) {
|
|
3509
|
+
if (!id) return void 0;
|
|
3394
3510
|
if (config.adminAuth?.mode !== "external") return void 0;
|
|
3395
3511
|
return config.adminAuth.providers.find((provider) => provider.id === id);
|
|
3396
3512
|
}
|
|
@@ -3589,17 +3705,16 @@ var AdminAuthController = class {
|
|
|
3589
3705
|
throw new Error("OIDC nonce verification failed.");
|
|
3590
3706
|
}
|
|
3591
3707
|
claims = payload;
|
|
3592
|
-
} else
|
|
3593
|
-
const
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3708
|
+
} else {
|
|
3709
|
+
const userInfoEndpoint = provider.userInfoEndpoint ?? discovery.userinfo_endpoint;
|
|
3710
|
+
if (!tokenBody.access_token || !userInfoEndpoint) {
|
|
3711
|
+
throw new Error("OIDC provider did not return usable identity claims.");
|
|
3712
|
+
}
|
|
3713
|
+
const userInfo = await fetch(userInfoEndpoint, {
|
|
3714
|
+
headers: { Authorization: `Bearer ${tokenBody.access_token}` }
|
|
3715
|
+
});
|
|
3599
3716
|
if (!userInfo.ok) throw new Error("OIDC userinfo request failed.");
|
|
3600
3717
|
claims = await userInfo.json();
|
|
3601
|
-
} else {
|
|
3602
|
-
throw new Error("OIDC provider did not return usable identity claims.");
|
|
3603
3718
|
}
|
|
3604
3719
|
return {
|
|
3605
3720
|
identity: this.mapClaims(claims, provider.claimMapping),
|
|
@@ -4144,10 +4259,7 @@ function accessGate(config, target, action) {
|
|
|
4144
4259
|
req: toHookRequestContext(c.req)
|
|
4145
4260
|
});
|
|
4146
4261
|
if (!allowed) {
|
|
4147
|
-
return c.json(
|
|
4148
|
-
{ error: true, message: `Access denied: ${action} on ${target.slug}` },
|
|
4149
|
-
403
|
|
4150
|
-
);
|
|
4262
|
+
return c.json({ error: true, message: `Access denied: ${action} on ${target.slug}` }, 403);
|
|
4151
4263
|
}
|
|
4152
4264
|
await next();
|
|
4153
4265
|
};
|
|
@@ -4187,6 +4299,80 @@ function serializeBlockForApi(block) {
|
|
|
4187
4299
|
fields: block.fields?.map(serializeFieldForApi)
|
|
4188
4300
|
};
|
|
4189
4301
|
}
|
|
4302
|
+
function serializeDetailItemForApi(item) {
|
|
4303
|
+
if (!item) return item;
|
|
4304
|
+
if (typeof item === "string") {
|
|
4305
|
+
return { type: "field", field: item };
|
|
4306
|
+
}
|
|
4307
|
+
if (item.type === "section") {
|
|
4308
|
+
return {
|
|
4309
|
+
type: "section",
|
|
4310
|
+
title: item.title,
|
|
4311
|
+
items: (item.items || []).map(serializeDetailItemForApi),
|
|
4312
|
+
options: item.options
|
|
4313
|
+
};
|
|
4314
|
+
}
|
|
4315
|
+
if (item.type === "tab") {
|
|
4316
|
+
return {
|
|
4317
|
+
type: "tab",
|
|
4318
|
+
label: item.label,
|
|
4319
|
+
items: (item.items || []).map(serializeDetailItemForApi),
|
|
4320
|
+
options: item.options
|
|
4321
|
+
};
|
|
4322
|
+
}
|
|
4323
|
+
if (item.type === "tabs") {
|
|
4324
|
+
return {
|
|
4325
|
+
type: "tabs",
|
|
4326
|
+
tabs: (item.tabs || []).map(serializeDetailItemForApi),
|
|
4327
|
+
options: item.options
|
|
4328
|
+
};
|
|
4329
|
+
}
|
|
4330
|
+
if (item.type === "grid") {
|
|
4331
|
+
return {
|
|
4332
|
+
type: "grid",
|
|
4333
|
+
columns: item.columns,
|
|
4334
|
+
items: (item.items || []).map(serializeDetailItemForApi),
|
|
4335
|
+
options: item.options
|
|
4336
|
+
};
|
|
4337
|
+
}
|
|
4338
|
+
if (item.type === "repeat") {
|
|
4339
|
+
return {
|
|
4340
|
+
type: "repeat",
|
|
4341
|
+
field: item.field,
|
|
4342
|
+
items: (item.items || []).map(serializeDetailItemForApi),
|
|
4343
|
+
options: item.options
|
|
4344
|
+
};
|
|
4345
|
+
}
|
|
4346
|
+
if (item.type === "computed") {
|
|
4347
|
+
return {
|
|
4348
|
+
type: "computed",
|
|
4349
|
+
id: item.id || item.label?.toLowerCase().replace(/[^a-z0-9]+/g, "_"),
|
|
4350
|
+
label: item.label,
|
|
4351
|
+
expression: item.expression,
|
|
4352
|
+
options: {
|
|
4353
|
+
id: item.id,
|
|
4354
|
+
span: item.options?.span,
|
|
4355
|
+
format: item.options?.format,
|
|
4356
|
+
currency: item.options?.currency,
|
|
4357
|
+
visible: item.options?.visible
|
|
4358
|
+
}
|
|
4359
|
+
};
|
|
4360
|
+
}
|
|
4361
|
+
if (item.type === "field") {
|
|
4362
|
+
return {
|
|
4363
|
+
type: "field",
|
|
4364
|
+
field: item.field,
|
|
4365
|
+
options: item.options
|
|
4366
|
+
};
|
|
4367
|
+
}
|
|
4368
|
+
return item;
|
|
4369
|
+
}
|
|
4370
|
+
function serializeDetailForApi(detail) {
|
|
4371
|
+
if (detail === false) return false;
|
|
4372
|
+
if (detail === true) return true;
|
|
4373
|
+
if (!detail || !Array.isArray(detail)) return void 0;
|
|
4374
|
+
return detail.map(serializeDetailItemForApi);
|
|
4375
|
+
}
|
|
4190
4376
|
function registerRoutes(app, config) {
|
|
4191
4377
|
const optionsCache = /* @__PURE__ */ new Map();
|
|
4192
4378
|
app.get("/api/schemas", optionalAuth(config), async (c) => {
|
|
@@ -4205,15 +4391,12 @@ function registerRoutes(app, config) {
|
|
|
4205
4391
|
if (typeof access === "boolean") return access;
|
|
4206
4392
|
if (access && typeof access === "object" && typeof access.policy === "string") {
|
|
4207
4393
|
const policy = requestConfig.accessPolicies?.[access.policy];
|
|
4208
|
-
if (typeof policy === "string" || typeof policy === "boolean")
|
|
4209
|
-
return policy;
|
|
4394
|
+
if (typeof policy === "string" || typeof policy === "boolean") return policy;
|
|
4210
4395
|
}
|
|
4211
4396
|
return resolveBooleanAccess(requestConfig, access, accessArgs);
|
|
4212
4397
|
};
|
|
4213
4398
|
const filteredCollections = await Promise.all(
|
|
4214
|
-
collections.filter(
|
|
4215
|
-
(col) => !siteId || col.shared || !col.siteId || col.siteId === siteId
|
|
4216
|
-
).map(async (col) => ({
|
|
4399
|
+
collections.filter((col) => !siteId || col.shared || !col.siteId || col.siteId === siteId).map(async (col) => ({
|
|
4217
4400
|
slug: col.slug,
|
|
4218
4401
|
labels: col.labels,
|
|
4219
4402
|
access: {
|
|
@@ -4251,6 +4434,7 @@ function registerRoutes(app, config) {
|
|
|
4251
4434
|
audit: !!col.audit,
|
|
4252
4435
|
drafts: !!col.drafts,
|
|
4253
4436
|
admin: col.admin,
|
|
4437
|
+
detail: serializeDetailForApi(col.detail),
|
|
4254
4438
|
workflow: col.workflow ? {
|
|
4255
4439
|
initialState: col.workflow.initialState,
|
|
4256
4440
|
draftState: col.workflow.draftState,
|
|
@@ -4269,9 +4453,7 @@ function registerRoutes(app, config) {
|
|
|
4269
4453
|
}))
|
|
4270
4454
|
);
|
|
4271
4455
|
const filteredGlobals = await Promise.all(
|
|
4272
|
-
globals.filter(
|
|
4273
|
-
(glb) => !siteId || glb.shared || !glb.siteId || glb.siteId === siteId
|
|
4274
|
-
).map(async (glb) => ({
|
|
4456
|
+
globals.filter((glb) => !siteId || glb.shared || !glb.siteId || glb.siteId === siteId).map(async (glb) => ({
|
|
4275
4457
|
slug: glb.slug,
|
|
4276
4458
|
label: glb.label,
|
|
4277
4459
|
access: {
|
|
@@ -4301,7 +4483,8 @@ function registerRoutes(app, config) {
|
|
|
4301
4483
|
}
|
|
4302
4484
|
}))
|
|
4303
4485
|
),
|
|
4304
|
-
admin: glb.admin
|
|
4486
|
+
admin: glb.admin,
|
|
4487
|
+
detail: serializeDetailForApi(glb.detail)
|
|
4305
4488
|
}))
|
|
4306
4489
|
);
|
|
4307
4490
|
return c.json({
|
|
@@ -4315,148 +4498,137 @@ function registerRoutes(app, config) {
|
|
|
4315
4498
|
adminHealth: {
|
|
4316
4499
|
emailConfigured: !!requestConfig.email,
|
|
4317
4500
|
secureAuthSecretConfigured: !!process.env.DYRECTED_JWT_SECRET,
|
|
4318
|
-
authCollectionConfigured: requestConfig.collections.some(
|
|
4319
|
-
|
|
4320
|
-
),
|
|
4321
|
-
uploadCollectionConfigured: requestConfig.collections.some(
|
|
4322
|
-
(collection) => !!collection.upload
|
|
4323
|
-
)
|
|
4501
|
+
authCollectionConfigured: requestConfig.collections.some((collection) => !!collection.auth),
|
|
4502
|
+
uploadCollectionConfigured: requestConfig.collections.some((collection) => !!collection.upload)
|
|
4324
4503
|
}
|
|
4325
4504
|
});
|
|
4326
4505
|
});
|
|
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
|
-
}
|
|
4506
|
+
app.get("/api/dyrected/options/:collection/:field", optionalAuth(config), async (c) => {
|
|
4507
|
+
const { collection: colSlug, field: fieldName } = c.req.param();
|
|
4508
|
+
const siteId = c.req.header("X-Site-Id");
|
|
4509
|
+
const requestConfig = siteId && config.onSchemaFetch ? mergeDynamicConfig(config, await config.onSchemaFetch(siteId)) : config;
|
|
4510
|
+
let collections = [...requestConfig.collections];
|
|
4511
|
+
const user = c.get("user");
|
|
4512
|
+
let collection = collections.find((col) => col.slug === colSlug);
|
|
4513
|
+
let field;
|
|
4514
|
+
if (collection) {
|
|
4515
|
+
const accessExpr = collection.access?.read;
|
|
4516
|
+
if (accessExpr !== void 0 && accessExpr !== null) {
|
|
4517
|
+
const allowed = await resolveBooleanAccess(config, accessExpr, {
|
|
4518
|
+
user,
|
|
4519
|
+
req: toHookRequestContext(c.req)
|
|
4520
|
+
});
|
|
4521
|
+
if (!allowed) {
|
|
4522
|
+
return c.json({ error: true, message: `Access denied: read on ${colSlug}` }, 403);
|
|
4380
4523
|
}
|
|
4381
|
-
field = glb.fields.find((f) => f.name === fieldName);
|
|
4382
4524
|
}
|
|
4383
|
-
|
|
4525
|
+
field = collection.fields.find((f) => f.name === fieldName);
|
|
4526
|
+
} else {
|
|
4527
|
+
const globals = [...requestConfig.globals];
|
|
4528
|
+
const glb = globals.find((g) => g.slug === colSlug);
|
|
4529
|
+
if (!glb) {
|
|
4384
4530
|
return c.json(
|
|
4385
4531
|
{
|
|
4386
4532
|
error: true,
|
|
4387
|
-
message:
|
|
4533
|
+
message: `${colSlug} not found as collection or global`
|
|
4388
4534
|
},
|
|
4389
4535
|
404
|
|
4390
4536
|
);
|
|
4391
4537
|
}
|
|
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,
|
|
4538
|
+
const accessExpr = glb.access?.read;
|
|
4539
|
+
if (accessExpr !== void 0 && accessExpr !== null) {
|
|
4540
|
+
const allowed = await resolveBooleanAccess(config, accessExpr, {
|
|
4434
4541
|
user,
|
|
4435
|
-
req:
|
|
4542
|
+
req: toHookRequestContext(c.req)
|
|
4436
4543
|
});
|
|
4437
|
-
if (
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
|
|
4544
|
+
if (!allowed) {
|
|
4545
|
+
return c.json(
|
|
4546
|
+
{
|
|
4547
|
+
error: true,
|
|
4548
|
+
message: `Access denied: read on global ${colSlug}`
|
|
4549
|
+
},
|
|
4550
|
+
403
|
|
4551
|
+
);
|
|
4442
4552
|
}
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4553
|
+
}
|
|
4554
|
+
field = glb.fields.find((f) => f.name === fieldName);
|
|
4555
|
+
}
|
|
4556
|
+
if (!field) {
|
|
4557
|
+
return c.json(
|
|
4558
|
+
{
|
|
4559
|
+
error: true,
|
|
4560
|
+
message: `Field ${fieldName} not found in ${colSlug}`
|
|
4561
|
+
},
|
|
4562
|
+
404
|
|
4563
|
+
);
|
|
4564
|
+
}
|
|
4565
|
+
let resolver;
|
|
4566
|
+
let cacheTTL;
|
|
4567
|
+
if (typeof field.options === "function") {
|
|
4568
|
+
resolver = field.options;
|
|
4569
|
+
} else if (field.options && typeof field.options === "object" && "resolve" in field.options) {
|
|
4570
|
+
resolver = field.options.resolve;
|
|
4571
|
+
cacheTTL = field.options.cacheTTL;
|
|
4572
|
+
}
|
|
4573
|
+
if (!resolver) {
|
|
4574
|
+
return c.json(
|
|
4575
|
+
{
|
|
4576
|
+
error: true,
|
|
4577
|
+
message: `Field ${fieldName} in ${colSlug} is not dynamic`
|
|
4578
|
+
},
|
|
4579
|
+
400
|
|
4580
|
+
);
|
|
4581
|
+
}
|
|
4582
|
+
try {
|
|
4583
|
+
const db = c.get("db") || config.db;
|
|
4584
|
+
const queryParams = c.req.query();
|
|
4585
|
+
const reqContext = {
|
|
4586
|
+
query: queryParams,
|
|
4587
|
+
headers: c.req.header(),
|
|
4588
|
+
raw: c.req.raw
|
|
4589
|
+
};
|
|
4590
|
+
const shouldCache = typeof cacheTTL === "number" && cacheTTL > 0;
|
|
4591
|
+
let cacheKey = "";
|
|
4592
|
+
if (shouldCache) {
|
|
4593
|
+
cacheKey = JSON.stringify([
|
|
4594
|
+
siteId ?? "",
|
|
4595
|
+
colSlug,
|
|
4596
|
+
fieldName,
|
|
4597
|
+
user?.id ?? null,
|
|
4598
|
+
queryParams
|
|
4599
|
+
]);
|
|
4600
|
+
const hit = optionsCache.get(cacheKey);
|
|
4601
|
+
if (hit && hit.expires > Date.now()) {
|
|
4602
|
+
return c.json(hit.value);
|
|
4603
|
+
}
|
|
4604
|
+
}
|
|
4605
|
+
const result = await resolver({
|
|
4606
|
+
db,
|
|
4607
|
+
user,
|
|
4608
|
+
req: reqContext
|
|
4609
|
+
});
|
|
4610
|
+
if (shouldCache) {
|
|
4611
|
+
optionsCache.set(cacheKey, {
|
|
4612
|
+
expires: Date.now() + cacheTTL * 1e3,
|
|
4613
|
+
value: result
|
|
4449
4614
|
});
|
|
4450
|
-
return c.json(
|
|
4451
|
-
{
|
|
4452
|
-
error: true,
|
|
4453
|
-
message: err.message || "Failed to resolve dynamic options"
|
|
4454
|
-
},
|
|
4455
|
-
500
|
|
4456
|
-
);
|
|
4457
4615
|
}
|
|
4616
|
+
return c.json(result);
|
|
4617
|
+
} catch (err) {
|
|
4618
|
+
getRequestLogger(c, "router").error({
|
|
4619
|
+
err,
|
|
4620
|
+
msg: "Failed to resolve dynamic field options",
|
|
4621
|
+
fieldName
|
|
4622
|
+
});
|
|
4623
|
+
return c.json(
|
|
4624
|
+
{
|
|
4625
|
+
error: true,
|
|
4626
|
+
message: err.message || "Failed to resolve dynamic options"
|
|
4627
|
+
},
|
|
4628
|
+
500
|
|
4629
|
+
);
|
|
4458
4630
|
}
|
|
4459
|
-
);
|
|
4631
|
+
});
|
|
4460
4632
|
app.get("/api/openapi.json", (c) => {
|
|
4461
4633
|
return c.json(generateOpenApi(config));
|
|
4462
4634
|
});
|
|
@@ -4469,13 +4641,8 @@ function registerRoutes(app, config) {
|
|
|
4469
4641
|
const key = c.req.param("key");
|
|
4470
4642
|
const scope = c.req.query("scope");
|
|
4471
4643
|
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
|
-
);
|
|
4644
|
+
if (!user?.collection || !user.sub) return c.json({ error: true, message: "Authentication required." }, 401);
|
|
4645
|
+
if (!key) return c.json({ error: true, message: "Preference key is required." }, 400);
|
|
4479
4646
|
const getGlobalPreference = async () => {
|
|
4480
4647
|
const globalDoc = await db.findOne({
|
|
4481
4648
|
collection: "__global_preferences",
|
|
@@ -4502,13 +4669,8 @@ function registerRoutes(app, config) {
|
|
|
4502
4669
|
const key = c.req.param("key");
|
|
4503
4670
|
const scope = c.req.query("scope");
|
|
4504
4671
|
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
|
-
);
|
|
4672
|
+
if (!user?.collection || !user.sub) return c.json({ error: true, message: "Authentication required." }, 401);
|
|
4673
|
+
if (!key) return c.json({ error: true, message: "Preference key is required." }, 400);
|
|
4512
4674
|
const body = await c.req.json().catch(() => ({}));
|
|
4513
4675
|
if (scope === "global") {
|
|
4514
4676
|
const isAdminUser = Array.isArray(user?.roles) && user.roles.includes("admin");
|
|
@@ -4556,13 +4718,8 @@ function registerRoutes(app, config) {
|
|
|
4556
4718
|
const key = c.req.param("key");
|
|
4557
4719
|
const scope = c.req.query("scope");
|
|
4558
4720
|
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
|
-
);
|
|
4721
|
+
if (!user?.collection || !user.sub) return c.json({ error: true, message: "Authentication required." }, 401);
|
|
4722
|
+
if (!key) return c.json({ error: true, message: "Preference key is required." }, 400);
|
|
4566
4723
|
if (scope === "global") {
|
|
4567
4724
|
const isAdminUser = Array.isArray(user?.roles) && user.roles.includes("admin");
|
|
4568
4725
|
if (!isAdminUser) {
|
|
@@ -4601,41 +4758,17 @@ function registerRoutes(app, config) {
|
|
|
4601
4758
|
for (const col of uploadCollections) {
|
|
4602
4759
|
const mediaController = new MediaController(col.slug);
|
|
4603
4760
|
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
|
-
);
|
|
4761
|
+
app.get(`${prefix}/media`, accessGate(config, col, "read"), (c) => mediaController.find(c));
|
|
4762
|
+
app.get(`${prefix}/media/:filename{.+$}`, (c) => mediaController.serve(c));
|
|
4763
|
+
app.post(`${prefix}/media`, accessGate(config, col, "create"), (c) => mediaController.upload(c));
|
|
4764
|
+
app.delete(`${prefix}/media/:id`, accessGate(config, col, "delete"), (c) => mediaController.delete(c));
|
|
4623
4765
|
}
|
|
4624
4766
|
}
|
|
4625
4767
|
const adminAuthController = new AdminAuthController(config);
|
|
4626
4768
|
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
|
-
);
|
|
4769
|
+
app.get("/api/admin/auth/:provider/start", (c) => adminAuthController.start(c));
|
|
4770
|
+
app.get("/api/admin/auth/:provider/callback", (c) => adminAuthController.callback(c));
|
|
4771
|
+
app.post("/api/admin/auth/:provider/exchange", (c) => adminAuthController.exchange(c));
|
|
4639
4772
|
app.post("/api/admin/logout", (c) => adminAuthController.logout(c));
|
|
4640
4773
|
for (const collection of config.collections) {
|
|
4641
4774
|
if (!collection.auth) continue;
|
|
@@ -4646,21 +4779,10 @@ function registerRoutes(app, config) {
|
|
|
4646
4779
|
app.get(`${path}/init`, (c) => authController.init(c));
|
|
4647
4780
|
app.post(`${path}/first-user`, (c) => authController.registerFirstUser(c));
|
|
4648
4781
|
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
|
-
);
|
|
4782
|
+
app.post(`${path}/refresh-token`, requireAuth(config), (c) => authController.refreshToken(c));
|
|
4783
|
+
app.post(`${path}/forgot-password`, (c) => authController.forgotPassword(c));
|
|
4658
4784
|
app.post(`${path}/reset-password`, (c) => authController.resetPassword(c));
|
|
4659
|
-
app.post(
|
|
4660
|
-
`${path}/invite`,
|
|
4661
|
-
requireAuth(config),
|
|
4662
|
-
(c) => authController.invite(c)
|
|
4663
|
-
);
|
|
4785
|
+
app.post(`${path}/invite`, requireAuth(config), (c) => authController.invite(c));
|
|
4664
4786
|
app.post(`${path}/accept-invite`, (c) => authController.acceptInvite(c));
|
|
4665
4787
|
}
|
|
4666
4788
|
const auditController = new AuditController();
|
|
@@ -4671,33 +4793,19 @@ function registerRoutes(app, config) {
|
|
|
4671
4793
|
app.post(path, (c) => controller.create(c));
|
|
4672
4794
|
app.post(`${path}/media`, (c) => controller.create(c));
|
|
4673
4795
|
app.delete(`${path}/delete-many`, (c) => controller.deleteMany(c));
|
|
4796
|
+
app.post(`${path}/aggregate`, (c) => controller.aggregate(c));
|
|
4674
4797
|
if (collection.audit) {
|
|
4675
|
-
app.get(
|
|
4676
|
-
`${path}/__audit`,
|
|
4677
|
-
(c) => auditController.findForCollection(c, collection)
|
|
4678
|
-
);
|
|
4798
|
+
app.get(`${path}/__audit`, (c) => auditController.findForCollection(c, collection));
|
|
4679
4799
|
}
|
|
4680
4800
|
app.get(`${path}/:id`, (c) => controller.findOne(c));
|
|
4681
4801
|
app.patch(`${path}/:id`, (c) => controller.update(c));
|
|
4682
4802
|
app.delete(`${path}/:id`, (c) => controller.delete(c));
|
|
4683
4803
|
if (collection.auth) {
|
|
4684
|
-
app.post(
|
|
4685
|
-
`${path}/:id/change-password`,
|
|
4686
|
-
requireAuth(config),
|
|
4687
|
-
(c) => controller.changePassword(c)
|
|
4688
|
-
);
|
|
4804
|
+
app.post(`${path}/:id/change-password`, requireAuth(config), (c) => controller.changePassword(c));
|
|
4689
4805
|
}
|
|
4690
4806
|
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
|
-
);
|
|
4807
|
+
app.post(`${path}/:id/transitions/:transition`, requireAuth(config), (c) => controller.transition(c));
|
|
4808
|
+
app.get(`${path}/:id/workflow-history`, requireAuth(config), (c) => controller.workflowHistory(c));
|
|
4701
4809
|
}
|
|
4702
4810
|
}
|
|
4703
4811
|
for (const global of config.globals) {
|
|
@@ -4712,11 +4820,7 @@ function registerRoutes(app, config) {
|
|
|
4712
4820
|
});
|
|
4713
4821
|
}
|
|
4714
4822
|
const previewController = new PreviewController();
|
|
4715
|
-
app.post(
|
|
4716
|
-
"/api/preview-token",
|
|
4717
|
-
requireAuth(config),
|
|
4718
|
-
(c) => previewController.createToken(c)
|
|
4719
|
-
);
|
|
4823
|
+
app.post("/api/preview-token", requireAuth(config), (c) => previewController.createToken(c));
|
|
4720
4824
|
app.get("/api/preview-data", (c) => previewController.getData(c));
|
|
4721
4825
|
app.get("/api/audit", (c) => auditController.findAll(c));
|
|
4722
4826
|
app.get("/api/collections/:slug/__audit", async (c) => {
|
|
@@ -4729,81 +4833,49 @@ function registerRoutes(app, config) {
|
|
|
4729
4833
|
if (!config2.onSchemaFetch || !siteId) {
|
|
4730
4834
|
return c.json({ message: `Collection "${slug}" not found` }, 404);
|
|
4731
4835
|
}
|
|
4732
|
-
const requestConfig = mergeDynamicConfig(
|
|
4733
|
-
|
|
4734
|
-
await config2.onSchemaFetch(siteId)
|
|
4735
|
-
);
|
|
4736
|
-
const collection = requestConfig.collections.find(
|
|
4737
|
-
(col) => col.slug === slug
|
|
4738
|
-
);
|
|
4836
|
+
const requestConfig = mergeDynamicConfig(config2, await config2.onSchemaFetch(siteId));
|
|
4837
|
+
const collection = requestConfig.collections.find((col) => col.slug === slug);
|
|
4739
4838
|
if (!collection?.audit) {
|
|
4740
|
-
return c.json(
|
|
4741
|
-
{ message: `Collection "${slug}" not found or has no audit log` },
|
|
4742
|
-
404
|
|
4743
|
-
);
|
|
4839
|
+
return c.json({ message: `Collection "${slug}" not found or has no audit log` }, 404);
|
|
4744
4840
|
}
|
|
4745
4841
|
return auditController.findForCollection(c, collection);
|
|
4746
4842
|
});
|
|
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);
|
|
4843
|
+
app.post("/api/collections/:slug/:id/transitions/:transition", requireAuth(config), async (c) => {
|
|
4844
|
+
const slug = c.req.param("slug");
|
|
4845
|
+
const siteId = c.req.header("X-Site-Id") || c.get("siteId");
|
|
4846
|
+
const config2 = c.get("config");
|
|
4847
|
+
if (config2.collections.some((col) => col.slug === slug)) {
|
|
4848
|
+
return c.json({ message: "Not Found" }, 404);
|
|
4775
4849
|
}
|
|
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);
|
|
4850
|
+
if (!config2.onSchemaFetch || !siteId) {
|
|
4851
|
+
return c.json({ message: `Collection "${slug}" not found` }, 404);
|
|
4805
4852
|
}
|
|
4806
|
-
|
|
4853
|
+
const requestConfig = mergeDynamicConfig(config2, await config2.onSchemaFetch(siteId));
|
|
4854
|
+
const collection = requestConfig.collections.find((col) => col.slug === slug);
|
|
4855
|
+
if (!collection?.workflow) {
|
|
4856
|
+
return c.json({ message: `Collection "${slug}" not found or has no workflow` }, 404);
|
|
4857
|
+
}
|
|
4858
|
+
const controller = new CollectionController(collection);
|
|
4859
|
+
return controller.transition(c);
|
|
4860
|
+
});
|
|
4861
|
+
app.get("/api/collections/:slug/:id/workflow-history", requireAuth(config), async (c) => {
|
|
4862
|
+
const slug = c.req.param("slug");
|
|
4863
|
+
const siteId = c.req.header("X-Site-Id") || c.get("siteId");
|
|
4864
|
+
const config2 = c.get("config");
|
|
4865
|
+
if (config2.collections.some((col) => col.slug === slug)) {
|
|
4866
|
+
return c.json({ message: "Not Found" }, 404);
|
|
4867
|
+
}
|
|
4868
|
+
if (!config2.onSchemaFetch || !siteId) {
|
|
4869
|
+
return c.json({ message: `Collection "${slug}" not found` }, 404);
|
|
4870
|
+
}
|
|
4871
|
+
const requestConfig = mergeDynamicConfig(config2, await config2.onSchemaFetch(siteId));
|
|
4872
|
+
const collection = requestConfig.collections.find((col) => col.slug === slug);
|
|
4873
|
+
if (!collection?.workflow) {
|
|
4874
|
+
return c.json({ message: `Collection "${slug}" not found or has no workflow` }, 404);
|
|
4875
|
+
}
|
|
4876
|
+
const controller = new CollectionController(collection);
|
|
4877
|
+
return controller.workflowHistory(c);
|
|
4878
|
+
});
|
|
4807
4879
|
app.all("/api/collections/:slug/:id?", async (c) => {
|
|
4808
4880
|
const slug = c.req.param("slug");
|
|
4809
4881
|
const id = c.req.param("id");
|
|
@@ -4813,13 +4885,8 @@ function registerRoutes(app, config) {
|
|
|
4813
4885
|
return c.json({ message: "Method Not Allowed" }, 405);
|
|
4814
4886
|
}
|
|
4815
4887
|
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
|
-
);
|
|
4888
|
+
const requestConfig = mergeDynamicConfig(config2, await config2.onSchemaFetch(siteId));
|
|
4889
|
+
let collection = requestConfig.collections.find((col) => col.slug === slug);
|
|
4823
4890
|
if (!collection && slug === "media") {
|
|
4824
4891
|
collection = {
|
|
4825
4892
|
slug: "media",
|
|
@@ -4832,25 +4899,19 @@ function registerRoutes(app, config) {
|
|
|
4832
4899
|
if (collection.auth && id) {
|
|
4833
4900
|
const authController = new AuthController(collection);
|
|
4834
4901
|
const method2 = c.req.method;
|
|
4835
|
-
if (method2 === "POST" && id === "login")
|
|
4836
|
-
|
|
4837
|
-
if (method2 === "POST" && id === "logout")
|
|
4838
|
-
return authController.logout(c);
|
|
4902
|
+
if (method2 === "POST" && id === "login") return authController.login(c);
|
|
4903
|
+
if (method2 === "POST" && id === "logout") return authController.logout(c);
|
|
4839
4904
|
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);
|
|
4905
|
+
if (method2 === "POST" && id === "refresh-token") return authController.refreshToken(c);
|
|
4906
|
+
if (method2 === "POST" && id === "forgot-password") return authController.forgotPassword(c);
|
|
4907
|
+
if (method2 === "POST" && id === "reset-password") return authController.resetPassword(c);
|
|
4846
4908
|
}
|
|
4847
4909
|
const controller = new CollectionController(collection);
|
|
4848
4910
|
const method = c.req.method;
|
|
4849
4911
|
if (id) {
|
|
4850
4912
|
if (method === "GET") return controller.findOne(c);
|
|
4851
4913
|
if (method === "PATCH") return controller.update(c);
|
|
4852
|
-
if (method === "DELETE" && id === "delete-many")
|
|
4853
|
-
return controller.deleteMany(c);
|
|
4914
|
+
if (method === "DELETE" && id === "delete-many") return controller.deleteMany(c);
|
|
4854
4915
|
if (method === "DELETE") return controller.delete(c);
|
|
4855
4916
|
if (method === "POST" && id === "media") return controller.create(c);
|
|
4856
4917
|
} else {
|
|
@@ -4870,10 +4931,7 @@ function registerRoutes(app, config) {
|
|
|
4870
4931
|
return c.json({ message: "Method Not Allowed" }, 405);
|
|
4871
4932
|
}
|
|
4872
4933
|
if (config2.onSchemaFetch && siteId) {
|
|
4873
|
-
const requestConfig = mergeDynamicConfig(
|
|
4874
|
-
config2,
|
|
4875
|
-
await config2.onSchemaFetch(siteId)
|
|
4876
|
-
);
|
|
4934
|
+
const requestConfig = mergeDynamicConfig(config2, await config2.onSchemaFetch(siteId));
|
|
4877
4935
|
const global = requestConfig.globals.find((glb) => glb.slug === slug);
|
|
4878
4936
|
if (global) {
|
|
4879
4937
|
const controller = new GlobalController(global);
|
|
@@ -4912,7 +4970,7 @@ function resolveClientIp(req, trustProxy = false) {
|
|
|
4912
4970
|
const value = normalizeIp(req.headers[header]);
|
|
4913
4971
|
if (value) return value;
|
|
4914
4972
|
}
|
|
4915
|
-
if (trustProxy) {
|
|
4973
|
+
if (trustProxy === true || typeof trustProxy === "number") {
|
|
4916
4974
|
const forwardedFor = pickForwardedFor(req.headers, trustProxy);
|
|
4917
4975
|
if (forwardedFor) return forwardedFor;
|
|
4918
4976
|
}
|
|
@@ -4924,6 +4982,7 @@ function pickForwardedFor(headers, trustProxy) {
|
|
|
4924
4982
|
const chain2 = forwardedHeader.split(",").map((entry) => normalizeIp(entry)).filter((entry) => !!entry);
|
|
4925
4983
|
if (chain2.length > 0) {
|
|
4926
4984
|
if (trustProxy === true) return chain2[0];
|
|
4985
|
+
if (trustProxy <= 0) return null;
|
|
4927
4986
|
const trustedHops2 = Math.max(1, Math.floor(trustProxy));
|
|
4928
4987
|
const candidateIndex2 = Math.max(0, chain2.length - trustedHops2);
|
|
4929
4988
|
return chain2[candidateIndex2] ?? chain2[0];
|
|
@@ -4936,6 +4995,7 @@ function pickForwardedFor(headers, trustProxy) {
|
|
|
4936
4995
|
const chain = matches.map((match) => normalizeIp(match[1])).filter((entry) => !!entry);
|
|
4937
4996
|
if (chain.length === 0) return null;
|
|
4938
4997
|
if (trustProxy === true) return chain[0];
|
|
4998
|
+
if (trustProxy <= 0) return null;
|
|
4939
4999
|
const trustedHops = Math.max(1, Math.floor(trustProxy));
|
|
4940
5000
|
const candidateIndex = Math.max(0, chain.length - trustedHops);
|
|
4941
5001
|
return chain[candidateIndex] ?? chain[0];
|
|
@@ -5139,7 +5199,7 @@ async function createDyrectedApp(rawConfig) {
|
|
|
5139
5199
|
app.use("*", cors(config.cors ? { origin: config.cors.origins } : {}));
|
|
5140
5200
|
app.get("/health", (c) => c.json({ status: "ok", version: "0.0.1" }));
|
|
5141
5201
|
if (observability.config.metrics.enabled && observability.config.metrics.exporter === "prometheus" && observability.prometheusExporter) {
|
|
5142
|
-
app.get(observability.config.metrics.path, async (
|
|
5202
|
+
app.get(observability.config.metrics.path, async (_c) => {
|
|
5143
5203
|
const metrics = await renderPrometheusMetrics(
|
|
5144
5204
|
observability.prometheusExporter
|
|
5145
5205
|
);
|
|
@@ -5195,5 +5255,6 @@ export {
|
|
|
5195
5255
|
registerRoutes,
|
|
5196
5256
|
renderPrometheusMetrics,
|
|
5197
5257
|
resolveObservabilityConfig,
|
|
5258
|
+
serializeDetailForApi,
|
|
5198
5259
|
shouldSampleRequest
|
|
5199
5260
|
};
|