@web-ts-toolkit/access-router 0.2.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/LICENSE +201 -0
- package/README.md +377 -0
- package/index.d.mts +1589 -0
- package/index.d.ts +1589 -0
- package/index.js +4022 -0
- package/index.mjs +3979 -0
- package/package.json +55 -0
- package/processors.d.mts +11 -0
- package/processors.d.ts +11 -0
- package/processors.js +59 -0
- package/processors.mjs +35 -0
package/index.mjs
ADDED
|
@@ -0,0 +1,3979 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { isPlainObject as isPlainObject10, isString as isString11, isUndefined as isUndefined4 } from "@web-ts-toolkit/utils";
|
|
3
|
+
|
|
4
|
+
// src/middleware.ts
|
|
5
|
+
import JsonRouter2 from "@web-ts-toolkit/express-json-router";
|
|
6
|
+
import { isArray as isArray9, isFunction as isFunction6, isPlainObject as isPlainObject7, isString as isString8 } from "@web-ts-toolkit/utils";
|
|
7
|
+
|
|
8
|
+
// src/core.ts
|
|
9
|
+
import {
|
|
10
|
+
castArray as castArray5,
|
|
11
|
+
compact as compact3,
|
|
12
|
+
difference,
|
|
13
|
+
forEach as forEach6,
|
|
14
|
+
intersection,
|
|
15
|
+
isArray as isArray8,
|
|
16
|
+
isBoolean as isBoolean5,
|
|
17
|
+
isFunction as isFunction5,
|
|
18
|
+
isString as isString7,
|
|
19
|
+
isUndefined,
|
|
20
|
+
reduce as reduce2
|
|
21
|
+
} from "@web-ts-toolkit/utils";
|
|
22
|
+
|
|
23
|
+
// src/options/manager.ts
|
|
24
|
+
import { assign, get, set } from "@web-ts-toolkit/utils";
|
|
25
|
+
var getNestedOption = (manager, key, defaultValue) => {
|
|
26
|
+
const keys2 = String(key).split(".");
|
|
27
|
+
if (keys2.length === 1) {
|
|
28
|
+
return manager.get(key, defaultValue);
|
|
29
|
+
}
|
|
30
|
+
let option = manager.get(key, void 0);
|
|
31
|
+
if (option !== void 0) {
|
|
32
|
+
return option;
|
|
33
|
+
}
|
|
34
|
+
const parentKey = keys2.slice(0, -1).join(".");
|
|
35
|
+
option = manager.get(`${parentKey}.default`, void 0);
|
|
36
|
+
if (option === void 0) {
|
|
37
|
+
option = manager.get(parentKey, defaultValue);
|
|
38
|
+
}
|
|
39
|
+
return option;
|
|
40
|
+
};
|
|
41
|
+
var OptionsManager = class {
|
|
42
|
+
constructor(defaultOptions) {
|
|
43
|
+
this.defaultOptions = defaultOptions;
|
|
44
|
+
this.listeners = {};
|
|
45
|
+
const _this = this;
|
|
46
|
+
this.currentOptions = new Proxy({}, {
|
|
47
|
+
set(target, key, value) {
|
|
48
|
+
const keystr = String(key);
|
|
49
|
+
const oldvalue = target[key];
|
|
50
|
+
target[key] = value;
|
|
51
|
+
_this.listeners[keystr] && _this.listeners[keystr].call(_this, value, keystr, target, oldvalue);
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
build() {
|
|
57
|
+
this.assign(this.defaultOptions);
|
|
58
|
+
return this;
|
|
59
|
+
}
|
|
60
|
+
get(key, defaultValue) {
|
|
61
|
+
return get(this.currentOptions, key, defaultValue);
|
|
62
|
+
}
|
|
63
|
+
set(key, value) {
|
|
64
|
+
set(this.currentOptions, key, value);
|
|
65
|
+
}
|
|
66
|
+
fetch() {
|
|
67
|
+
return { ...this.currentOptions };
|
|
68
|
+
}
|
|
69
|
+
assign(options) {
|
|
70
|
+
assign(this.currentOptions, options);
|
|
71
|
+
}
|
|
72
|
+
onchange(key, func) {
|
|
73
|
+
set(this.listeners, key, func);
|
|
74
|
+
return this;
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// src/logger.ts
|
|
79
|
+
import util from "util";
|
|
80
|
+
import { createLogger, format, transports } from "winston";
|
|
81
|
+
var colorizer = format.colorize();
|
|
82
|
+
var defaultLogLevel = process.env.WTT_LOG_LEVEL || "silent";
|
|
83
|
+
var inspectValue = (value) => {
|
|
84
|
+
return typeof value === "string" ? value : util.inspect(value, { depth: 5, colors: false });
|
|
85
|
+
};
|
|
86
|
+
var defaultLogger = createLogger({
|
|
87
|
+
level: defaultLogLevel,
|
|
88
|
+
format: format.combine(
|
|
89
|
+
format.errors({ stack: true }),
|
|
90
|
+
format.splat(),
|
|
91
|
+
format.label({ label: "[WTT]" }),
|
|
92
|
+
format.timestamp(),
|
|
93
|
+
format.printf(({ level, label, message, timestamp, stack, ...meta }) => {
|
|
94
|
+
const formattedLabel = colorizer.colorize(level, label);
|
|
95
|
+
const formattedLevel = colorizer.colorize(level, `[${level.toUpperCase()}]`);
|
|
96
|
+
const formattedMessage = colorizer.colorize(level, inspectValue(stack ?? message));
|
|
97
|
+
const formattedMeta = Object.keys(meta).length > 0 ? ` ${inspectValue(meta)}` : "";
|
|
98
|
+
return `${formattedLabel} ${timestamp} ${formattedLevel} ${formattedMessage}${formattedMeta}`;
|
|
99
|
+
})
|
|
100
|
+
),
|
|
101
|
+
transports: [new transports.Console()]
|
|
102
|
+
});
|
|
103
|
+
var passthrough = (level) => (...args) => {
|
|
104
|
+
const globalLogger = getGlobalOption("logger");
|
|
105
|
+
const target = globalLogger?.[level] ?? defaultLogger[level];
|
|
106
|
+
return target.apply(globalLogger ?? defaultLogger, args);
|
|
107
|
+
};
|
|
108
|
+
var logger = {
|
|
109
|
+
debug: passthrough("debug"),
|
|
110
|
+
info: passthrough("info"),
|
|
111
|
+
warn: passthrough("warn"),
|
|
112
|
+
error: passthrough("error")
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
// src/options/global-options.ts
|
|
116
|
+
var globalOptions = new OptionsManager({
|
|
117
|
+
requestPermissionField: "_permissions",
|
|
118
|
+
globalPermissions: () => ({}),
|
|
119
|
+
logger: defaultLogger
|
|
120
|
+
}).build();
|
|
121
|
+
var setGlobalOptions = (options) => {
|
|
122
|
+
globalOptions.assign(options);
|
|
123
|
+
};
|
|
124
|
+
var setGlobalOption = (key, value) => {
|
|
125
|
+
globalOptions.set(key, value);
|
|
126
|
+
};
|
|
127
|
+
var getGlobalOptions = () => {
|
|
128
|
+
return globalOptions.fetch();
|
|
129
|
+
};
|
|
130
|
+
var getGlobalOption = (key, defaultValue) => {
|
|
131
|
+
return globalOptions.get(key, defaultValue);
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// src/options/model-options.ts
|
|
135
|
+
import mongoose from "mongoose";
|
|
136
|
+
import mschema2Jsonschema from "mongoose-schema-jsonschema";
|
|
137
|
+
import { addLeadingSlash, forEach, isArray, isBoolean, isFunction, isNil, isString } from "@web-ts-toolkit/utils";
|
|
138
|
+
|
|
139
|
+
// src/options/default-model-options.ts
|
|
140
|
+
var DEFAULT_QUERY_PATH = "__query";
|
|
141
|
+
var DEFAULT_MUTATION_PATH = "__mutation";
|
|
142
|
+
var defaultModelOptions = new OptionsManager({
|
|
143
|
+
listHardLimit: 1e3,
|
|
144
|
+
documentPermissionField: "_permissions",
|
|
145
|
+
idParam: "id",
|
|
146
|
+
identifier: "_id",
|
|
147
|
+
parentPath: "/",
|
|
148
|
+
queryPath: DEFAULT_QUERY_PATH,
|
|
149
|
+
mutationPath: DEFAULT_MUTATION_PATH,
|
|
150
|
+
routeGuard: false,
|
|
151
|
+
modelPermissionPrefix: ""
|
|
152
|
+
}).build();
|
|
153
|
+
var setDefaultModelOptions = (options) => {
|
|
154
|
+
defaultModelOptions.assign(options);
|
|
155
|
+
};
|
|
156
|
+
var setDefaultModelOption = (key, value) => {
|
|
157
|
+
defaultModelOptions.set(key, value);
|
|
158
|
+
};
|
|
159
|
+
var getDefaultModelOptions = () => {
|
|
160
|
+
return defaultModelOptions.fetch();
|
|
161
|
+
};
|
|
162
|
+
var getDefaultModelOption = (key, defaultValue) => {
|
|
163
|
+
return defaultModelOptions.get(key, defaultValue);
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
// src/options/model-options.ts
|
|
167
|
+
mschema2Jsonschema(mongoose);
|
|
168
|
+
var pluralize = mongoose.pluralize();
|
|
169
|
+
var defaultModelOptions2 = {
|
|
170
|
+
basePath: null,
|
|
171
|
+
mandatoryFields: []
|
|
172
|
+
};
|
|
173
|
+
var modelOptions = {};
|
|
174
|
+
var modelJsonSchemas = {};
|
|
175
|
+
var normalizeBasePath = (name, value) => {
|
|
176
|
+
if (isNil(value)) {
|
|
177
|
+
return `/${pluralize(name)}`;
|
|
178
|
+
}
|
|
179
|
+
return isString(value) ? addLeadingSlash(value) : "";
|
|
180
|
+
};
|
|
181
|
+
var hasModelPermission = (value, modelPermissionPrefix) => {
|
|
182
|
+
if (!modelPermissionPrefix) {
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
185
|
+
if (isString(value)) {
|
|
186
|
+
return value.trim().split(" ").some((item) => item.startsWith(modelPermissionPrefix));
|
|
187
|
+
}
|
|
188
|
+
if (isArray(value)) {
|
|
189
|
+
return value.some((item) => {
|
|
190
|
+
if (isString(item) || isArray(item)) {
|
|
191
|
+
return hasModelPermission(item, modelPermissionPrefix);
|
|
192
|
+
}
|
|
193
|
+
return true;
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
return isFunction(value);
|
|
197
|
+
};
|
|
198
|
+
var classifyPermissionSchema = (permissionSchema, modelPermissionPrefix) => {
|
|
199
|
+
const schemaKeys = Object.keys(permissionSchema);
|
|
200
|
+
const globalPermissionKeys = {};
|
|
201
|
+
const modelPermissionKeys = {};
|
|
202
|
+
forEach(schemaKeys, (schemaKey) => {
|
|
203
|
+
forEach(permissionSchema[schemaKey], (value, accessKey) => {
|
|
204
|
+
if (!isArray(globalPermissionKeys[accessKey])) {
|
|
205
|
+
globalPermissionKeys[accessKey] = [];
|
|
206
|
+
}
|
|
207
|
+
if (!isArray(modelPermissionKeys[accessKey])) {
|
|
208
|
+
modelPermissionKeys[accessKey] = [];
|
|
209
|
+
}
|
|
210
|
+
if (isBoolean(value)) {
|
|
211
|
+
globalPermissionKeys[accessKey].push(schemaKey);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
if (hasModelPermission(value, modelPermissionPrefix)) {
|
|
215
|
+
modelPermissionKeys[accessKey].push(schemaKey);
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
globalPermissionKeys[accessKey].push(schemaKey);
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
return {
|
|
222
|
+
schemaKeys,
|
|
223
|
+
globalPermissionKeys,
|
|
224
|
+
modelPermissionKeys
|
|
225
|
+
};
|
|
226
|
+
};
|
|
227
|
+
var createModelOptions = (modelName) => {
|
|
228
|
+
const model = mongoose.model(modelName);
|
|
229
|
+
const manager = new OptionsManager({
|
|
230
|
+
...defaultModelOptions2,
|
|
231
|
+
modelName
|
|
232
|
+
});
|
|
233
|
+
manager.onchange("permissionSchema", function(newval, key, target, oldval) {
|
|
234
|
+
const permissionSchema = newval ?? {};
|
|
235
|
+
const { schemaKeys, globalPermissionKeys, modelPermissionKeys } = classifyPermissionSchema(
|
|
236
|
+
permissionSchema,
|
|
237
|
+
target.modelPermissionPrefix ?? ""
|
|
238
|
+
);
|
|
239
|
+
target._permissionSchemaKeys = schemaKeys;
|
|
240
|
+
target._globalPermissionKeys = globalPermissionKeys;
|
|
241
|
+
target._modelPermissionKeys = modelPermissionKeys;
|
|
242
|
+
}).onchange("basePath", function(newval, key, target, oldval) {
|
|
243
|
+
target[key] = normalizeBasePath(
|
|
244
|
+
modelName,
|
|
245
|
+
isString(newval) || isNil(newval) ? newval : void 0
|
|
246
|
+
);
|
|
247
|
+
}).build();
|
|
248
|
+
modelJsonSchemas[modelName] = model.jsonSchema();
|
|
249
|
+
return manager;
|
|
250
|
+
};
|
|
251
|
+
var getOrCreateModelOptions = (modelName) => {
|
|
252
|
+
let manager = modelOptions[modelName];
|
|
253
|
+
if (!manager) {
|
|
254
|
+
manager = createModelOptions(modelName);
|
|
255
|
+
modelOptions[modelName] = manager;
|
|
256
|
+
}
|
|
257
|
+
return manager;
|
|
258
|
+
};
|
|
259
|
+
var setModelOptions = (modelName, options) => {
|
|
260
|
+
const manager = getOrCreateModelOptions(modelName);
|
|
261
|
+
const defaultOptions = getDefaultModelOptions();
|
|
262
|
+
const modelOptions2 = manager.fetch();
|
|
263
|
+
manager.assign({ ...defaultOptions, ...modelOptions2, ...options });
|
|
264
|
+
};
|
|
265
|
+
var setModelOption = (modelName, key, value) => {
|
|
266
|
+
const manager = getOrCreateModelOptions(modelName);
|
|
267
|
+
manager.set(key, value);
|
|
268
|
+
};
|
|
269
|
+
var getModelOptions = (modelName) => {
|
|
270
|
+
const manager = getOrCreateModelOptions(modelName);
|
|
271
|
+
return manager.fetch();
|
|
272
|
+
};
|
|
273
|
+
var getModelOption = (modelName, key, defaultValue) => {
|
|
274
|
+
const manager = getOrCreateModelOptions(modelName);
|
|
275
|
+
const defaultModelValue = getDefaultModelOption(
|
|
276
|
+
key,
|
|
277
|
+
defaultValue
|
|
278
|
+
);
|
|
279
|
+
return getNestedOption(manager, key, defaultModelValue);
|
|
280
|
+
};
|
|
281
|
+
var getExactModelOption = (modelName, key) => {
|
|
282
|
+
const manager = getOrCreateModelOptions(modelName);
|
|
283
|
+
const defaultModelValue = getDefaultModelOption(key);
|
|
284
|
+
return manager.get(key, defaultModelValue);
|
|
285
|
+
};
|
|
286
|
+
var getModelNames = () => {
|
|
287
|
+
return Object.keys(modelOptions);
|
|
288
|
+
};
|
|
289
|
+
var getModelJsonSchema = (modelName) => {
|
|
290
|
+
return modelJsonSchemas[modelName];
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
// src/options/data-options.ts
|
|
294
|
+
import mongoose2 from "mongoose";
|
|
295
|
+
import { addLeadingSlash as addLeadingSlash2, isNil as isNil2, isString as isString2 } from "@web-ts-toolkit/utils";
|
|
296
|
+
var pluralize2 = mongoose2.pluralize();
|
|
297
|
+
var dataOptions = {};
|
|
298
|
+
var defaultDataOptions = {
|
|
299
|
+
basePath: null,
|
|
300
|
+
queryPath: "__query"
|
|
301
|
+
};
|
|
302
|
+
var normalizeBasePath2 = (name, value) => {
|
|
303
|
+
if (isNil2(value)) {
|
|
304
|
+
return `/${pluralize2(name)}`;
|
|
305
|
+
}
|
|
306
|
+
return isString2(value) ? addLeadingSlash2(value) : "";
|
|
307
|
+
};
|
|
308
|
+
var createDataOptions = (dataName) => {
|
|
309
|
+
const manager = new OptionsManager({
|
|
310
|
+
...defaultDataOptions,
|
|
311
|
+
dataName
|
|
312
|
+
});
|
|
313
|
+
manager.onchange("basePath", function(newval, key, target, oldval) {
|
|
314
|
+
target[key] = normalizeBasePath2(
|
|
315
|
+
dataName,
|
|
316
|
+
isString2(newval) || isNil2(newval) ? newval : void 0
|
|
317
|
+
);
|
|
318
|
+
}).build();
|
|
319
|
+
return manager;
|
|
320
|
+
};
|
|
321
|
+
var getOrCreateDataOptions = (dataName) => {
|
|
322
|
+
let manager = dataOptions[dataName];
|
|
323
|
+
if (!manager) {
|
|
324
|
+
manager = createDataOptions(dataName);
|
|
325
|
+
dataOptions[dataName] = manager;
|
|
326
|
+
}
|
|
327
|
+
return manager;
|
|
328
|
+
};
|
|
329
|
+
var setDataOptions = (dataName, options) => {
|
|
330
|
+
const manager = getOrCreateDataOptions(dataName);
|
|
331
|
+
const dataOptions2 = manager.fetch();
|
|
332
|
+
manager.assign({ ...dataOptions2, ...options });
|
|
333
|
+
};
|
|
334
|
+
var setDataOption = (dataName, key, value) => {
|
|
335
|
+
const manager = getOrCreateDataOptions(dataName);
|
|
336
|
+
manager.set(key, value);
|
|
337
|
+
};
|
|
338
|
+
var getDataOptions = (dataName) => {
|
|
339
|
+
const manager = getOrCreateDataOptions(dataName);
|
|
340
|
+
return manager.fetch();
|
|
341
|
+
};
|
|
342
|
+
var getDataOption = (dataName, key, defaultValue) => {
|
|
343
|
+
const manager = getOrCreateDataOptions(dataName);
|
|
344
|
+
return getNestedOption(manager, key, defaultValue);
|
|
345
|
+
};
|
|
346
|
+
var getExactDataOption = (dataName, key) => {
|
|
347
|
+
const manager = getOrCreateDataOptions(dataName);
|
|
348
|
+
return manager.get(key);
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
// src/meta.ts
|
|
352
|
+
import mongoose3 from "mongoose";
|
|
353
|
+
import { get as get3, keys } from "@web-ts-toolkit/utils";
|
|
354
|
+
|
|
355
|
+
// src/helpers/collection.ts
|
|
356
|
+
import sift from "sift";
|
|
357
|
+
import { filter, find } from "@web-ts-toolkit/utils";
|
|
358
|
+
var filterCollection = (collection, predicate) => {
|
|
359
|
+
return filter(collection, sift(predicate));
|
|
360
|
+
};
|
|
361
|
+
var findElement = (collection, predicate) => {
|
|
362
|
+
return find(collection, sift(predicate));
|
|
363
|
+
};
|
|
364
|
+
var matchElement = (element, predicate) => {
|
|
365
|
+
return sift(predicate)(element);
|
|
366
|
+
};
|
|
367
|
+
var findElementById = (collection, id) => {
|
|
368
|
+
return findElement(collection, { _id: id });
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
// src/helpers/document.ts
|
|
372
|
+
import { get as get2, isArray as isArray2, isPlainObject as isPlainObject3, isPromise as isPromise2, isString as isString4, pick, set as set2 } from "@web-ts-toolkit/utils";
|
|
373
|
+
|
|
374
|
+
// src/lib.ts
|
|
375
|
+
import { Document, Schema } from "mongoose";
|
|
376
|
+
import { addLeadingSlash as addLeadingSlash3, isPlainObject, isPromise } from "@web-ts-toolkit/utils";
|
|
377
|
+
var isSchema = (val) => val instanceof Schema;
|
|
378
|
+
var isObjectIdType = (val) => val === "ObjectId" || val === Schema.Types.ObjectId;
|
|
379
|
+
var isReference = (val) => isPlainObject(val) && !!val.ref && isObjectIdType(val.type);
|
|
380
|
+
var isDocument = function isDocument2(doc) {
|
|
381
|
+
return doc instanceof Document;
|
|
382
|
+
};
|
|
383
|
+
var mapValuesAsync = async function mapValuesAsync2(object, asyncFn) {
|
|
384
|
+
return Object.fromEntries(
|
|
385
|
+
await Promise.all(Object.entries(object).map(async ([key, value]) => [key, await asyncFn(value, key, object)]))
|
|
386
|
+
);
|
|
387
|
+
};
|
|
388
|
+
var arrToObj = (arr) => {
|
|
389
|
+
const obj = {};
|
|
390
|
+
for (let x = 0; x < arr.length; x++) {
|
|
391
|
+
obj[arr[x]] = true;
|
|
392
|
+
}
|
|
393
|
+
return obj;
|
|
394
|
+
};
|
|
395
|
+
var removeConsecutiveSlashesFromUrl = (url) => url.replace(/\/{2,}/g, "/");
|
|
396
|
+
var processUrl = (url) => addLeadingSlash3(removeConsecutiveSlashesFromUrl(url));
|
|
397
|
+
|
|
398
|
+
// src/helpers/query.ts
|
|
399
|
+
import { flattenDeep, isNaN, isNil as isNil3, isPlainObject as isPlainObject2, isString as isString3, reduce } from "@web-ts-toolkit/utils";
|
|
400
|
+
function genPagination({
|
|
401
|
+
skip,
|
|
402
|
+
limit,
|
|
403
|
+
page,
|
|
404
|
+
pageSize
|
|
405
|
+
}, hardLimit) {
|
|
406
|
+
let _skip = 0;
|
|
407
|
+
let _limit = Number(limit ?? pageSize);
|
|
408
|
+
if (isNaN(_limit) || _limit > hardLimit) _limit = hardLimit;
|
|
409
|
+
if (!isNil3(skip)) {
|
|
410
|
+
_skip = Number(skip);
|
|
411
|
+
} else if (!isNil3(page)) {
|
|
412
|
+
const npage = Number(page);
|
|
413
|
+
if (npage > 1) _skip = (npage - 1) * _limit;
|
|
414
|
+
}
|
|
415
|
+
return { skip: _skip, limit: _limit };
|
|
416
|
+
}
|
|
417
|
+
function parseSortString(sortString) {
|
|
418
|
+
if (!sortString) return { sortKey: "", sortOrder: "asc" };
|
|
419
|
+
if (sortString.startsWith("-")) {
|
|
420
|
+
return { sortKey: sortString.substring(1), sortOrder: "desc" };
|
|
421
|
+
} else {
|
|
422
|
+
return { sortKey: sortString, sortOrder: "asc" };
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
function normalizeSelect(select) {
|
|
426
|
+
if (Array.isArray(select)) return flattenDeep(select.map(normalizeSelect));
|
|
427
|
+
if (isPlainObject2(select)) {
|
|
428
|
+
return reduce(
|
|
429
|
+
select,
|
|
430
|
+
(ret, val, key) => {
|
|
431
|
+
if (val === 1) ret.push(key);
|
|
432
|
+
else if (val === -1) ret.push(`-${key}`);
|
|
433
|
+
return ret;
|
|
434
|
+
},
|
|
435
|
+
[]
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
if (isString3(select)) return select.split(" ").map((v) => v.trim());
|
|
439
|
+
return [];
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// src/helpers/document.ts
|
|
443
|
+
function getDocValue(doc, path, defalutValue) {
|
|
444
|
+
if (isDocument(doc)) {
|
|
445
|
+
return get2(doc._doc, path, defalutValue);
|
|
446
|
+
} else if (isPlainObject3(doc)) {
|
|
447
|
+
return get2(doc, path, defalutValue);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
function setDocValue(doc, path, value) {
|
|
451
|
+
if (isDocument(doc)) {
|
|
452
|
+
set2(doc._doc, path, value);
|
|
453
|
+
} else if (isPlainObject3(doc)) {
|
|
454
|
+
set2(doc, path, value);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
function getDocPermissions(modelName, doc) {
|
|
458
|
+
const docPermissionField = getModelOption(modelName, "documentPermissionField");
|
|
459
|
+
return getDocValue(doc, docPermissionField, {});
|
|
460
|
+
}
|
|
461
|
+
function toObject(doc) {
|
|
462
|
+
return isDocument(doc) ? doc.toObject() : doc;
|
|
463
|
+
}
|
|
464
|
+
function pickDocFields(doc, fields = []) {
|
|
465
|
+
if (isDocument(doc)) {
|
|
466
|
+
doc._doc = pick(doc._doc, fields);
|
|
467
|
+
return doc;
|
|
468
|
+
} else {
|
|
469
|
+
return pick(doc, fields);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
async function populateDoc(doc, target) {
|
|
473
|
+
let p = doc.populate(target);
|
|
474
|
+
if (isPromise2(p)) return p;
|
|
475
|
+
return "execPopulate" in p && p.execPopulate?.();
|
|
476
|
+
}
|
|
477
|
+
var genSubPopulate = (sub, popul) => {
|
|
478
|
+
if (!popul) return [];
|
|
479
|
+
let populate = isArray2(popul) ? popul : [popul];
|
|
480
|
+
populate = populate.map((p) => {
|
|
481
|
+
const ret = isString4(p) ? { path: `${sub}.${p}` } : {
|
|
482
|
+
path: `${sub}.${p.path}`,
|
|
483
|
+
select: normalizeSelect(p.select)
|
|
484
|
+
};
|
|
485
|
+
return ret;
|
|
486
|
+
});
|
|
487
|
+
return populate;
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
// src/helpers/errors.ts
|
|
491
|
+
import JsonRouter from "@web-ts-toolkit/express-json-router";
|
|
492
|
+
|
|
493
|
+
// src/enums.ts
|
|
494
|
+
var StatusCodes = /* @__PURE__ */ ((StatusCodes3) => {
|
|
495
|
+
StatusCodes3[StatusCodes3["OK"] = 200] = "OK";
|
|
496
|
+
StatusCodes3[StatusCodes3["Created"] = 201] = "Created";
|
|
497
|
+
StatusCodes3[StatusCodes3["BadRequest"] = 400] = "BadRequest";
|
|
498
|
+
StatusCodes3[StatusCodes3["Unauthorized"] = 401] = "Unauthorized";
|
|
499
|
+
StatusCodes3[StatusCodes3["Forbidden"] = 403] = "Forbidden";
|
|
500
|
+
StatusCodes3[StatusCodes3["NotFound"] = 404] = "NotFound";
|
|
501
|
+
StatusCodes3[StatusCodes3["UnprocessableContent"] = 422] = "UnprocessableContent";
|
|
502
|
+
return StatusCodes3;
|
|
503
|
+
})(StatusCodes || {});
|
|
504
|
+
var Codes = /* @__PURE__ */ ((Codes2) => {
|
|
505
|
+
Codes2["Success"] = "success";
|
|
506
|
+
Codes2["Created"] = "created";
|
|
507
|
+
Codes2["BadRequest"] = "bad_request";
|
|
508
|
+
Codes2["Unauthorized"] = "unauthorized";
|
|
509
|
+
Codes2["Forbidden"] = "forbidden";
|
|
510
|
+
Codes2["NotFound"] = "not_found";
|
|
511
|
+
return Codes2;
|
|
512
|
+
})(Codes || {});
|
|
513
|
+
var CustomHeaders = /* @__PURE__ */ ((CustomHeaders2) => {
|
|
514
|
+
CustomHeaders2["TotalCount"] = "wtt-total-count";
|
|
515
|
+
CustomHeaders2["ReturnedCount"] = "wtt-returned-count";
|
|
516
|
+
CustomHeaders2["Page"] = "wtt-page";
|
|
517
|
+
CustomHeaders2["PageSize"] = "wtt-page-size";
|
|
518
|
+
CustomHeaders2["TotalPages"] = "wtt-total-pages";
|
|
519
|
+
CustomHeaders2["HasNextPage"] = "wtt-has-next-page";
|
|
520
|
+
CustomHeaders2["HasPreviousPage"] = "wtt-has-previous-page";
|
|
521
|
+
return CustomHeaders2;
|
|
522
|
+
})(CustomHeaders || {});
|
|
523
|
+
var FilterOperator = /* @__PURE__ */ ((FilterOperator2) => {
|
|
524
|
+
FilterOperator2[FilterOperator2["SubQuery"] = 0] = "SubQuery";
|
|
525
|
+
FilterOperator2[FilterOperator2["Date"] = 1] = "Date";
|
|
526
|
+
return FilterOperator2;
|
|
527
|
+
})(FilterOperator || {});
|
|
528
|
+
|
|
529
|
+
// src/helpers/errors.ts
|
|
530
|
+
var { BadRequestError, ForbiddenError, NotFoundError, UnprocessableEntityError } = JsonRouter.clientErrors;
|
|
531
|
+
function mapCodeToMessage(code) {
|
|
532
|
+
switch (code) {
|
|
533
|
+
case "success" /* Success */:
|
|
534
|
+
return "OK";
|
|
535
|
+
case "created" /* Created */:
|
|
536
|
+
return "Created";
|
|
537
|
+
case "bad_request" /* BadRequest */:
|
|
538
|
+
return "Bad Request";
|
|
539
|
+
case "forbidden" /* Forbidden */:
|
|
540
|
+
return "Forbidden";
|
|
541
|
+
case "not_found" /* NotFound */:
|
|
542
|
+
return "Not Found";
|
|
543
|
+
default:
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
function mapCodeToStatusCode(code) {
|
|
548
|
+
switch (code) {
|
|
549
|
+
case "success" /* Success */:
|
|
550
|
+
return 200 /* OK */;
|
|
551
|
+
case "created" /* Created */:
|
|
552
|
+
return 201 /* Created */;
|
|
553
|
+
case "bad_request" /* BadRequest */:
|
|
554
|
+
return 400 /* BadRequest */;
|
|
555
|
+
case "forbidden" /* Forbidden */:
|
|
556
|
+
return 403 /* Forbidden */;
|
|
557
|
+
case "not_found" /* NotFound */:
|
|
558
|
+
return 404 /* NotFound */;
|
|
559
|
+
default:
|
|
560
|
+
return 422 /* UnprocessableContent */;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
function handleResultError(result) {
|
|
564
|
+
if (result.success) return;
|
|
565
|
+
const { code, errors = [] } = result;
|
|
566
|
+
const errorOptions = { errors };
|
|
567
|
+
switch (code) {
|
|
568
|
+
case "bad_request" /* BadRequest */:
|
|
569
|
+
throw new BadRequestError("Bad Request", errorOptions);
|
|
570
|
+
case "forbidden" /* Forbidden */:
|
|
571
|
+
throw new ForbiddenError("Forbidden", errorOptions);
|
|
572
|
+
case "not_found" /* NotFound */:
|
|
573
|
+
throw new NotFoundError("Not Found", errorOptions);
|
|
574
|
+
default:
|
|
575
|
+
throw new UnprocessableEntityError("Unprocessable Content", errorOptions);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// src/helpers/index.ts
|
|
580
|
+
import { forEach as forEach2, isArray as isArray3, isEmpty, isObject, isPlainObject as isPlainObject4, isString as isString5, noop } from "@web-ts-toolkit/utils";
|
|
581
|
+
function recurseObject(obj) {
|
|
582
|
+
if (isSchema(obj)) {
|
|
583
|
+
return buildRefs(obj.tree);
|
|
584
|
+
}
|
|
585
|
+
if (!isObject(obj)) return null;
|
|
586
|
+
if (isReference(obj)) {
|
|
587
|
+
return obj.ref;
|
|
588
|
+
}
|
|
589
|
+
let ret = null;
|
|
590
|
+
forEach2(obj, (val) => {
|
|
591
|
+
ret = recurseObject(val);
|
|
592
|
+
if (!isEmpty(ret)) {
|
|
593
|
+
return false;
|
|
594
|
+
}
|
|
595
|
+
});
|
|
596
|
+
return ret;
|
|
597
|
+
}
|
|
598
|
+
function buildRefs(schema) {
|
|
599
|
+
if (!isObject(schema)) return {};
|
|
600
|
+
const references = {};
|
|
601
|
+
forEach2(schema, (val, key) => {
|
|
602
|
+
const paths = recurseObject(val);
|
|
603
|
+
if (!isEmpty(paths)) {
|
|
604
|
+
references[key] = paths;
|
|
605
|
+
}
|
|
606
|
+
const target = isObject(val) && "type" in val ? val.type ?? val : val;
|
|
607
|
+
if (isArray3(target) && target.length > 0) {
|
|
608
|
+
if (isSchema(target[0]) || isPlainObject4(target[0])) {
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
});
|
|
613
|
+
return references;
|
|
614
|
+
}
|
|
615
|
+
function buildSubPaths(schema) {
|
|
616
|
+
if (!isObject(schema)) return [];
|
|
617
|
+
const subPaths = [];
|
|
618
|
+
forEach2(schema, (val, key) => {
|
|
619
|
+
const target = isObject(val) && "type" in val ? val.type ?? val : val;
|
|
620
|
+
if (isArray3(target) && target.length > 0) {
|
|
621
|
+
if (isSchema(target[0]) || isPlainObject4(target[0]) && !isReference(target[0])) {
|
|
622
|
+
subPaths.push(key);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
});
|
|
626
|
+
return subPaths;
|
|
627
|
+
}
|
|
628
|
+
async function iterateQuery(query, handler) {
|
|
629
|
+
if (!isPlainObject4(query)) return query;
|
|
630
|
+
if (!handler) return noop;
|
|
631
|
+
const queryObject = query;
|
|
632
|
+
return mapValuesAsync(queryObject, async (val, key) => {
|
|
633
|
+
if (isPlainObject4(val)) {
|
|
634
|
+
const plainValue = val;
|
|
635
|
+
if (plainValue.$$sq) {
|
|
636
|
+
return handler(0 /* SubQuery */, plainValue.$$sq, key);
|
|
637
|
+
} else if (plainValue.$$date) {
|
|
638
|
+
return handler(1 /* Date */, plainValue.$$date, key);
|
|
639
|
+
} else {
|
|
640
|
+
return iterateQuery(val, handler);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
if (isArray3(val)) {
|
|
644
|
+
return Promise.all(val.map((v) => iterateQuery(v, handler)));
|
|
645
|
+
}
|
|
646
|
+
return val;
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
var createValidator = (fn) => {
|
|
650
|
+
const stringHandler = (key) => key.trim().split(" ").every((v) => fn(v));
|
|
651
|
+
const arrayHandler = (arr) => arr.some((item) => {
|
|
652
|
+
if (isString5(item)) return stringHandler(item);
|
|
653
|
+
else if (isArray3(item)) return arrayHandler(item);
|
|
654
|
+
else return false;
|
|
655
|
+
});
|
|
656
|
+
return { stringHandler, arrayHandler };
|
|
657
|
+
};
|
|
658
|
+
|
|
659
|
+
// src/meta.ts
|
|
660
|
+
var modelRefs = {};
|
|
661
|
+
var modelSubs = {};
|
|
662
|
+
var modelAtts = {};
|
|
663
|
+
var ensureModelMeta = (modelName) => {
|
|
664
|
+
if (modelName in modelRefs && modelName in modelSubs && modelName in modelAtts) {
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
const model = mongoose3.models[modelName];
|
|
668
|
+
if (!model) {
|
|
669
|
+
modelRefs[modelName] = modelRefs[modelName] ?? {};
|
|
670
|
+
modelSubs[modelName] = modelSubs[modelName] ?? [];
|
|
671
|
+
modelAtts[modelName] = modelAtts[modelName] ?? [];
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
const schema = model.schema;
|
|
675
|
+
modelRefs[modelName] = buildRefs(schema.tree);
|
|
676
|
+
modelSubs[modelName] = buildSubPaths(schema.tree);
|
|
677
|
+
modelAtts[modelName] = keys(schema.obj);
|
|
678
|
+
};
|
|
679
|
+
var getModelRef = (modelName, refPath) => {
|
|
680
|
+
ensureModelMeta(modelName);
|
|
681
|
+
return get3(modelRefs, `${modelName}.${refPath}`, null);
|
|
682
|
+
};
|
|
683
|
+
var getModelSub = (modelName) => {
|
|
684
|
+
ensureModelMeta(modelName);
|
|
685
|
+
return get3(modelSubs, modelName, []);
|
|
686
|
+
};
|
|
687
|
+
|
|
688
|
+
// src/services/base.ts
|
|
689
|
+
import {
|
|
690
|
+
castArray,
|
|
691
|
+
compact,
|
|
692
|
+
flatten,
|
|
693
|
+
forEach as forEach3,
|
|
694
|
+
get as get4,
|
|
695
|
+
intersectionBy,
|
|
696
|
+
isArray as isArray4,
|
|
697
|
+
isPlainObject as isPlainObject5,
|
|
698
|
+
uniq
|
|
699
|
+
} from "@web-ts-toolkit/utils";
|
|
700
|
+
function validateClientFilter(filter2) {
|
|
701
|
+
const errors = [];
|
|
702
|
+
const blockedOperators = /* @__PURE__ */ new Set(["$where", "$expr", "$function", "$accumulator"]);
|
|
703
|
+
const visit = (value, path) => {
|
|
704
|
+
if (isArray4(value)) {
|
|
705
|
+
value.forEach((item, index) => visit(item, `${path}[${index}]`));
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
if (!isPlainObject5(value)) return;
|
|
709
|
+
Object.entries(value).forEach(([key, child]) => {
|
|
710
|
+
const nextPath = path ? `${path}.${key}` : key;
|
|
711
|
+
if (blockedOperators.has(key)) {
|
|
712
|
+
errors.push(`Unsupported filter operator: ${nextPath}`);
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
visit(child, nextPath);
|
|
716
|
+
});
|
|
717
|
+
};
|
|
718
|
+
visit(filter2, "filter");
|
|
719
|
+
return errors;
|
|
720
|
+
}
|
|
721
|
+
var Base = class {
|
|
722
|
+
constructor(req, modelName) {
|
|
723
|
+
this.req = req;
|
|
724
|
+
this.modelName = modelName;
|
|
725
|
+
}
|
|
726
|
+
decorate(doc, access, context) {
|
|
727
|
+
return this.req.macl.decorate(this.modelName, doc, access, context);
|
|
728
|
+
}
|
|
729
|
+
decorateAll(docs, access, context) {
|
|
730
|
+
return this.req.macl.decorateAll(this.modelName, docs, access, context);
|
|
731
|
+
}
|
|
732
|
+
genAllowedFields(doc, access, baseFields) {
|
|
733
|
+
return this.req.macl.genAllowedFields(this.modelName, doc, access, baseFields);
|
|
734
|
+
}
|
|
735
|
+
genDocPermissions(doc, access, context) {
|
|
736
|
+
return this.req.macl.genDocPermissions(this.modelName, doc, access, context);
|
|
737
|
+
}
|
|
738
|
+
genFilter(access, filter2) {
|
|
739
|
+
return this.req.macl.genFilter(this.modelName, access, filter2);
|
|
740
|
+
}
|
|
741
|
+
getIdentifier() {
|
|
742
|
+
return this.req.macl.getIdentifier(this.modelName);
|
|
743
|
+
}
|
|
744
|
+
genIDFilter(id) {
|
|
745
|
+
return this.req.macl.genIDFilter(this.modelName, id);
|
|
746
|
+
}
|
|
747
|
+
genPopulate(access, populate) {
|
|
748
|
+
return this.req.macl.genPopulate(this.modelName, access, populate);
|
|
749
|
+
}
|
|
750
|
+
genSelect(access, targetFields, skipChecks, subPaths) {
|
|
751
|
+
return this.req.macl.genSelect(this.modelName, access, targetFields, skipChecks, subPaths);
|
|
752
|
+
}
|
|
753
|
+
genQuerySelect(access, targetFields, skipChecks, subPaths) {
|
|
754
|
+
return this.genSelect(access, targetFields, skipChecks, subPaths);
|
|
755
|
+
}
|
|
756
|
+
addEmptyPermissions(doc) {
|
|
757
|
+
return this.req.macl.addEmptyPermissions(this.modelName, doc);
|
|
758
|
+
}
|
|
759
|
+
addDocPermissions(doc, access, context) {
|
|
760
|
+
return this.req.macl.addDocPermissions(this.modelName, doc, access, context);
|
|
761
|
+
}
|
|
762
|
+
addFieldPermissions(doc, access, context) {
|
|
763
|
+
return this.req.macl.addFieldPermissions(this.modelName, doc, access, context);
|
|
764
|
+
}
|
|
765
|
+
pickAllowedFields(doc, access, baseFields) {
|
|
766
|
+
return this.req.macl.pickAllowedFields(this.modelName, doc, access, baseFields);
|
|
767
|
+
}
|
|
768
|
+
trimOutputFields(doc, access, baseFields) {
|
|
769
|
+
return this.pickAllowedFields(doc, access, baseFields);
|
|
770
|
+
}
|
|
771
|
+
prepare(allowedData, access, context) {
|
|
772
|
+
return this.req.macl.prepare(this.modelName, allowedData, access, context);
|
|
773
|
+
}
|
|
774
|
+
runTasks(docObject, tasks) {
|
|
775
|
+
return this.req.macl.runTasks(this.modelName, docObject, tasks);
|
|
776
|
+
}
|
|
777
|
+
transform(doc, access, context) {
|
|
778
|
+
return this.req.macl.transform(this.modelName, doc, access, context);
|
|
779
|
+
}
|
|
780
|
+
finalize(doc, access, context) {
|
|
781
|
+
return this.req.macl.finalize(this.modelName, doc, access, context);
|
|
782
|
+
}
|
|
783
|
+
changes(doc, context) {
|
|
784
|
+
return this.req.macl.changes(this.modelName, doc, context);
|
|
785
|
+
}
|
|
786
|
+
validate(allowedData, access, context) {
|
|
787
|
+
return this.req.macl.validate(this.modelName, allowedData, access, context);
|
|
788
|
+
}
|
|
789
|
+
checkIfModelPermissionExists(accesses) {
|
|
790
|
+
const modelPermissionKeys = getModelOption(this.modelName, "_modelPermissionKeys");
|
|
791
|
+
return accesses.some((access) => modelPermissionKeys[access]?.length > 0);
|
|
792
|
+
}
|
|
793
|
+
validateClientFilter(filter2) {
|
|
794
|
+
return validateClientFilter(filter2);
|
|
795
|
+
}
|
|
796
|
+
processInclude(include) {
|
|
797
|
+
const includes = compact(castArray(include)).filter(({ model, op, path, localField, foreignField }) => {
|
|
798
|
+
return model && op && path && localField && foreignField;
|
|
799
|
+
});
|
|
800
|
+
let includeLocalFields = [];
|
|
801
|
+
let includePaths = [];
|
|
802
|
+
forEach3(includes, (inc) => {
|
|
803
|
+
includeLocalFields.push(inc.localField);
|
|
804
|
+
includePaths.push(inc.path);
|
|
805
|
+
});
|
|
806
|
+
includeLocalFields = uniq(compact(includeLocalFields));
|
|
807
|
+
includePaths = uniq(compact(includePaths));
|
|
808
|
+
return {
|
|
809
|
+
includes,
|
|
810
|
+
includeLocalFields,
|
|
811
|
+
includePaths
|
|
812
|
+
};
|
|
813
|
+
}
|
|
814
|
+
async includeDocs(docs, include) {
|
|
815
|
+
if (!include) return docs;
|
|
816
|
+
const includes = compact(castArray(include));
|
|
817
|
+
if (includes.length === 0) return docs;
|
|
818
|
+
const isSingle = !isArray4(docs);
|
|
819
|
+
if (isSingle) docs = [docs];
|
|
820
|
+
for (let x = 0; x < includes.length; x++) {
|
|
821
|
+
const include2 = includes[x];
|
|
822
|
+
if (include2.op === "count") {
|
|
823
|
+
docs = await this.includeDocsCount(docs, include2);
|
|
824
|
+
} else {
|
|
825
|
+
docs = await this.includeDocsList(docs, include2);
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
return isSingle ? docs[0] : docs;
|
|
829
|
+
}
|
|
830
|
+
async includeDocsList(docs, include) {
|
|
831
|
+
const { model, op, path, localField, foreignField, filter: _filters, args = {}, options = {} } = include;
|
|
832
|
+
const svc = this.req.macl.getPublicService(model);
|
|
833
|
+
if (!svc) return docs;
|
|
834
|
+
const includeLocalValues = [];
|
|
835
|
+
forEach3(docs, (doc, i) => {
|
|
836
|
+
includeLocalValues.push(get4(doc, localField));
|
|
837
|
+
});
|
|
838
|
+
const filter2 = { ..._filters ?? {}, [foreignField]: { $in: flatten(includeLocalValues) } };
|
|
839
|
+
const result = await svc.find(filter2, args, {
|
|
840
|
+
...options,
|
|
841
|
+
lean: true,
|
|
842
|
+
includePermissions: false,
|
|
843
|
+
includeCount: false
|
|
844
|
+
});
|
|
845
|
+
if (!result.success) return docs;
|
|
846
|
+
for (let y = 0; y < docs.length; y++) {
|
|
847
|
+
const doc = docs[y];
|
|
848
|
+
const localValue = get4(doc, localField);
|
|
849
|
+
const filterFn = (row) => intersectionBy(castArray(localValue), castArray(get4(row, foreignField)), String).length > 0;
|
|
850
|
+
const matches = result.data.filter(filterFn);
|
|
851
|
+
setDocValue(doc, path, op === "list" ? matches : matches[0]);
|
|
852
|
+
}
|
|
853
|
+
return docs;
|
|
854
|
+
}
|
|
855
|
+
async includeDocsCount(docs, include) {
|
|
856
|
+
const { model, path, localField, foreignField, filter: _filters } = include;
|
|
857
|
+
const svc = this.req.macl.getPublicService(model);
|
|
858
|
+
if (!svc) return docs;
|
|
859
|
+
for (let y = 0; y < docs.length; y++) {
|
|
860
|
+
const doc = docs[y];
|
|
861
|
+
const localValue = get4(doc, localField);
|
|
862
|
+
const filter2 = { ..._filters ?? {}, [foreignField]: localValue };
|
|
863
|
+
const result = await svc.count(filter2);
|
|
864
|
+
if (!result.success) continue;
|
|
865
|
+
setDocValue(doc, path, result.data);
|
|
866
|
+
}
|
|
867
|
+
return docs;
|
|
868
|
+
}
|
|
869
|
+
async parseClientData(filter2) {
|
|
870
|
+
const result = await iterateQuery(filter2, async (fo, val, key) => {
|
|
871
|
+
switch (fo) {
|
|
872
|
+
case 0 /* SubQuery */:
|
|
873
|
+
return this.handleSubQuery(val, key);
|
|
874
|
+
case 1 /* Date */:
|
|
875
|
+
return this.handleDate(val, key);
|
|
876
|
+
default:
|
|
877
|
+
return null;
|
|
878
|
+
}
|
|
879
|
+
});
|
|
880
|
+
return result;
|
|
881
|
+
}
|
|
882
|
+
async handleSubQuery(sq, key) {
|
|
883
|
+
const { model, op, id, filter: filter2, args, options, sqOptions = {} } = sq;
|
|
884
|
+
const svc = this.req.macl.getPublicService(model);
|
|
885
|
+
if (!svc) return null;
|
|
886
|
+
let result;
|
|
887
|
+
if (op === "list") {
|
|
888
|
+
result = await svc.find(filter2, args, options);
|
|
889
|
+
} else if (op === "read") {
|
|
890
|
+
if (id) {
|
|
891
|
+
result = await svc.findById(id, args, options);
|
|
892
|
+
} else if (filter2) {
|
|
893
|
+
result = await svc.findOne(filter2, args, options);
|
|
894
|
+
} else {
|
|
895
|
+
return null;
|
|
896
|
+
}
|
|
897
|
+
} else {
|
|
898
|
+
return null;
|
|
899
|
+
}
|
|
900
|
+
if (!result.success) return null;
|
|
901
|
+
let ret = result.data;
|
|
902
|
+
if (sqOptions.path) {
|
|
903
|
+
ret = isArray4(ret) ? flatten(ret.map((v) => get4(v, sqOptions.path))) : get4(ret, sqOptions.path);
|
|
904
|
+
}
|
|
905
|
+
if (sqOptions.compact) {
|
|
906
|
+
ret = compact(castArray(ret));
|
|
907
|
+
}
|
|
908
|
+
return ret;
|
|
909
|
+
}
|
|
910
|
+
async handleDate(val, key) {
|
|
911
|
+
return /* @__PURE__ */ new Date();
|
|
912
|
+
}
|
|
913
|
+
};
|
|
914
|
+
|
|
915
|
+
// src/services/service.ts
|
|
916
|
+
import {
|
|
917
|
+
castArray as castArray2,
|
|
918
|
+
forEach as forEach4,
|
|
919
|
+
get as get5,
|
|
920
|
+
isArray as isArray5,
|
|
921
|
+
isBoolean as isBoolean3,
|
|
922
|
+
isFunction as isFunction3,
|
|
923
|
+
omit,
|
|
924
|
+
pick as pick3,
|
|
925
|
+
uniq as uniq2
|
|
926
|
+
} from "@web-ts-toolkit/utils";
|
|
927
|
+
import diff from "deep-diff";
|
|
928
|
+
|
|
929
|
+
// src/model.ts
|
|
930
|
+
import mongoose4 from "mongoose";
|
|
931
|
+
var Model = class {
|
|
932
|
+
constructor(modelName) {
|
|
933
|
+
this.modelName = modelName;
|
|
934
|
+
this.model = mongoose4.model(modelName);
|
|
935
|
+
if (!this.model) return;
|
|
936
|
+
this.model.schema.set("optimisticConcurrency", true);
|
|
937
|
+
const currVersionKey = this.model.schema.get("versionKey");
|
|
938
|
+
if (!currVersionKey) this.model.schema.set("versionKey", "__v");
|
|
939
|
+
}
|
|
940
|
+
new() {
|
|
941
|
+
const doc = new this.model();
|
|
942
|
+
return doc;
|
|
943
|
+
}
|
|
944
|
+
create(data) {
|
|
945
|
+
return this.model.create(data);
|
|
946
|
+
}
|
|
947
|
+
find({ filter: filter2, select, sort, populate, limit, skip, lean }) {
|
|
948
|
+
if (!this.validateSort(sort)) {
|
|
949
|
+
sort = null;
|
|
950
|
+
}
|
|
951
|
+
const builder = this.model.find(filter2);
|
|
952
|
+
if (select) builder.select(select);
|
|
953
|
+
if (skip) builder.skip(Number(skip));
|
|
954
|
+
if (limit) builder.limit(Number(limit));
|
|
955
|
+
if (sort) builder.sort(sort);
|
|
956
|
+
if (populate) builder.populate(populate);
|
|
957
|
+
if (lean) builder.lean();
|
|
958
|
+
return builder;
|
|
959
|
+
}
|
|
960
|
+
// See https://github.com/Automattic/mongoose/blob/65b2d12a8f85f86136cfaf32947f338ba0c5f451/lib/query.js#L3011
|
|
961
|
+
validateSort(sort, logError = console.error) {
|
|
962
|
+
const validSortOrders = [1, -1, "asc", "ascending", "desc", "descending"];
|
|
963
|
+
if (sort === null || sort === void 0) return true;
|
|
964
|
+
if (typeof sort === "string") {
|
|
965
|
+
if (!/^[a-zA-Z0-9\s-]+/.test(sort)) {
|
|
966
|
+
logError("Invalid sort string:", sort);
|
|
967
|
+
return false;
|
|
968
|
+
}
|
|
969
|
+
return true;
|
|
970
|
+
}
|
|
971
|
+
if (Array.isArray(sort)) {
|
|
972
|
+
const isValid = sort.every((pair) => {
|
|
973
|
+
if (!Array.isArray(pair) || pair.length !== 2) {
|
|
974
|
+
logError("Invalid sort array element: must be [key, order]", pair);
|
|
975
|
+
return false;
|
|
976
|
+
}
|
|
977
|
+
const [key, order] = pair;
|
|
978
|
+
if (typeof key !== "string") {
|
|
979
|
+
logError("Invalid sort array key: must be string", key);
|
|
980
|
+
return false;
|
|
981
|
+
}
|
|
982
|
+
if (!validSortOrders.includes(order)) {
|
|
983
|
+
logError('Invalid sort array order: must be 1, -1, "asc", or "desc"', order);
|
|
984
|
+
return false;
|
|
985
|
+
}
|
|
986
|
+
return true;
|
|
987
|
+
});
|
|
988
|
+
return isValid;
|
|
989
|
+
}
|
|
990
|
+
if (typeof sort === "object" && !(sort instanceof Map)) {
|
|
991
|
+
const isValid = Object.values(sort).every((order) => {
|
|
992
|
+
if (!validSortOrders.includes(order)) {
|
|
993
|
+
logError('Invalid sort object value: must be 1, -1, "asc", or "desc"', order);
|
|
994
|
+
return false;
|
|
995
|
+
}
|
|
996
|
+
return true;
|
|
997
|
+
});
|
|
998
|
+
return isValid;
|
|
999
|
+
}
|
|
1000
|
+
if (sort instanceof Map) {
|
|
1001
|
+
const isValid = Array.from(sort.values()).every((order) => {
|
|
1002
|
+
if (!validSortOrders.includes(order)) {
|
|
1003
|
+
logError('Invalid sort Map value: must be 1, -1, "asc", or "desc"', order);
|
|
1004
|
+
return false;
|
|
1005
|
+
}
|
|
1006
|
+
return true;
|
|
1007
|
+
});
|
|
1008
|
+
return isValid;
|
|
1009
|
+
}
|
|
1010
|
+
logError("Invalid sort type: must be string, array, object, or Map", sort);
|
|
1011
|
+
return false;
|
|
1012
|
+
}
|
|
1013
|
+
findOne({ filter: filter2, select, sort, populate, lean }) {
|
|
1014
|
+
if (!this.validateSort(sort)) {
|
|
1015
|
+
sort = null;
|
|
1016
|
+
}
|
|
1017
|
+
const builder = this.model.findOne(filter2);
|
|
1018
|
+
if (select) builder.select(select);
|
|
1019
|
+
if (sort) builder.sort(sort);
|
|
1020
|
+
if (populate) builder.populate(populate);
|
|
1021
|
+
if (lean) builder.lean();
|
|
1022
|
+
return builder;
|
|
1023
|
+
}
|
|
1024
|
+
findOneAndDelete(filter2) {
|
|
1025
|
+
return this.model.findOneAndDelete(filter2);
|
|
1026
|
+
}
|
|
1027
|
+
exists(filter2) {
|
|
1028
|
+
if (!filter2) return null;
|
|
1029
|
+
return this.findOne(filter2).select("_id").lean();
|
|
1030
|
+
}
|
|
1031
|
+
// see https://mongoosejs.com/docs/api.html#query_Query-countDocuments
|
|
1032
|
+
countDocuments(filter2 = {}) {
|
|
1033
|
+
return this.model.countDocuments(filter2);
|
|
1034
|
+
}
|
|
1035
|
+
// see https://mongoosejs.com/docs/api.html#model_Model.estimatedDocumentCount
|
|
1036
|
+
estimatedDocumentCount() {
|
|
1037
|
+
return this.model.estimatedDocumentCount();
|
|
1038
|
+
}
|
|
1039
|
+
// see https://mongoosejs.com/docs/api.html#model_Model.distinct
|
|
1040
|
+
distinct(field, conditions = {}) {
|
|
1041
|
+
return this.model.distinct(field, conditions);
|
|
1042
|
+
}
|
|
1043
|
+
};
|
|
1044
|
+
var model_default = Model;
|
|
1045
|
+
|
|
1046
|
+
// src/services/service.ts
|
|
1047
|
+
var Service = class extends Base {
|
|
1048
|
+
constructor(req, modelName) {
|
|
1049
|
+
super(req, modelName);
|
|
1050
|
+
this.model = new model_default(modelName);
|
|
1051
|
+
this.options = getModelOptions(modelName);
|
|
1052
|
+
this.defaults = this.options.defaults || {};
|
|
1053
|
+
this.baseFields = ["_id"];
|
|
1054
|
+
this.baseFieldsExt = this.baseFields.concat(this.options.documentPermissionField);
|
|
1055
|
+
}
|
|
1056
|
+
async findOne(filter2, args, options) {
|
|
1057
|
+
const filterErrors = this.validateClientFilter(filter2);
|
|
1058
|
+
if (filterErrors.length > 0) return { success: false, code: "bad_request" /* BadRequest */, errors: filterErrors };
|
|
1059
|
+
const {
|
|
1060
|
+
select = this.defaults.findOneArgs?.select,
|
|
1061
|
+
sort = this.defaults.findOneArgs?.sort,
|
|
1062
|
+
populate = this.defaults.findOneArgs?.populate,
|
|
1063
|
+
include = this.defaults.findOneArgs?.include,
|
|
1064
|
+
overrides = {}
|
|
1065
|
+
} = args ?? {};
|
|
1066
|
+
const {
|
|
1067
|
+
skim = this.defaults.findOneOptions?.skim ?? false,
|
|
1068
|
+
includePermissions = this.defaults.findOneOptions?.includePermissions ?? true,
|
|
1069
|
+
access = this.defaults.findOneOptions?.access ?? "read",
|
|
1070
|
+
populateAccess = this.defaults.findOneOptions?.populateAccess,
|
|
1071
|
+
lean = this.defaults.findOneOptions?.lean ?? false
|
|
1072
|
+
} = options ?? {};
|
|
1073
|
+
const { filter: overrideFilter, select: overrideSelect, populate: overridePopulate } = overrides ?? {};
|
|
1074
|
+
let [_filter, _select, _populate] = await Promise.all([
|
|
1075
|
+
overrideFilter || this.genFilter(access, await this.parseClientData(filter2)),
|
|
1076
|
+
overrideSelect || this.genQuerySelect(access, select),
|
|
1077
|
+
overridePopulate || this.genPopulate(populateAccess || access, populate)
|
|
1078
|
+
]);
|
|
1079
|
+
const { includes, includeLocalFields, includePaths } = this.processInclude(include);
|
|
1080
|
+
const finalSelect = normalizeSelect(_select).concat(includeLocalFields);
|
|
1081
|
+
const query = {
|
|
1082
|
+
filter: _filter,
|
|
1083
|
+
select: finalSelect,
|
|
1084
|
+
sort,
|
|
1085
|
+
populate: _populate
|
|
1086
|
+
};
|
|
1087
|
+
logger.debug(JSON.stringify({ op: "findOne", query }));
|
|
1088
|
+
if (_filter === false) return { success: false, code: "forbidden" /* Forbidden */, query };
|
|
1089
|
+
let doc = await this.model.findOne({ ...query, lean });
|
|
1090
|
+
if (!doc) return { success: false, code: "not_found" /* NotFound */, query };
|
|
1091
|
+
const context = {
|
|
1092
|
+
model: this.model.model,
|
|
1093
|
+
modelName: this.modelName,
|
|
1094
|
+
originalDocObject: toObject(doc)
|
|
1095
|
+
};
|
|
1096
|
+
doc = await this.includeDocs(doc, includes);
|
|
1097
|
+
let includeDocPermissions = includePermissions;
|
|
1098
|
+
if (!includeDocPermissions && !skim) {
|
|
1099
|
+
includeDocPermissions = this.checkIfModelPermissionExists([access, "read", "update"]);
|
|
1100
|
+
}
|
|
1101
|
+
if (includeDocPermissions) doc = await this.addDocPermissions(doc, access, context);
|
|
1102
|
+
if (includePermissions) doc = await this.addFieldPermissions(doc, access, context);
|
|
1103
|
+
doc = await this.trimOutputFields(
|
|
1104
|
+
doc,
|
|
1105
|
+
access,
|
|
1106
|
+
this.baseFieldsExt.concat(includePaths, normalizeSelect(overrideSelect))
|
|
1107
|
+
);
|
|
1108
|
+
if (!includePermissions) doc = this.addEmptyPermissions(doc);
|
|
1109
|
+
return { success: true, kind: "single", code: "success" /* Success */, data: doc, query, context };
|
|
1110
|
+
}
|
|
1111
|
+
async findById(id, args, options) {
|
|
1112
|
+
const {
|
|
1113
|
+
select = this.defaults.findByIdArgs?.select,
|
|
1114
|
+
populate = this.defaults.findByIdArgs?.populate,
|
|
1115
|
+
include = this.defaults.findByIdArgs?.include,
|
|
1116
|
+
overrides = {}
|
|
1117
|
+
} = args ?? {};
|
|
1118
|
+
const {
|
|
1119
|
+
skim = this.defaults.findOneOptions?.skim ?? false,
|
|
1120
|
+
includePermissions = this.defaults.findOneOptions?.includePermissions ?? true,
|
|
1121
|
+
access = this.defaults.findOneOptions?.access ?? "read",
|
|
1122
|
+
populateAccess = this.defaults.findOneOptions?.populateAccess,
|
|
1123
|
+
lean = this.defaults.findOneOptions?.lean ?? false
|
|
1124
|
+
} = options ?? {};
|
|
1125
|
+
const { select: overrideSelect, populate: overridePopulate, idFilter: overrideIdFilter } = overrides ?? {};
|
|
1126
|
+
const filter2 = overrideIdFilter || await this.genIDFilter(id);
|
|
1127
|
+
return this.findOne(
|
|
1128
|
+
filter2,
|
|
1129
|
+
{
|
|
1130
|
+
select,
|
|
1131
|
+
populate,
|
|
1132
|
+
include,
|
|
1133
|
+
overrides: {
|
|
1134
|
+
select: overrideSelect,
|
|
1135
|
+
populate: overridePopulate
|
|
1136
|
+
}
|
|
1137
|
+
},
|
|
1138
|
+
{ skim, includePermissions, access, populateAccess, lean }
|
|
1139
|
+
);
|
|
1140
|
+
}
|
|
1141
|
+
async find(filter2, args, options, decorate) {
|
|
1142
|
+
const filterErrors = this.validateClientFilter(filter2);
|
|
1143
|
+
if (filterErrors.length > 0) return { success: false, code: "bad_request" /* BadRequest */, errors: filterErrors };
|
|
1144
|
+
const {
|
|
1145
|
+
select = this.defaults.findArgs?.select,
|
|
1146
|
+
populate = this.defaults.findArgs?.populate,
|
|
1147
|
+
include = this.defaults.findArgs?.include,
|
|
1148
|
+
sort = this.defaults.findArgs?.sort,
|
|
1149
|
+
skip = this.defaults.findArgs?.skip,
|
|
1150
|
+
limit = this.defaults.findArgs?.limit,
|
|
1151
|
+
page = this.defaults.findArgs?.page,
|
|
1152
|
+
pageSize = this.defaults.findArgs?.pageSize,
|
|
1153
|
+
overrides = {}
|
|
1154
|
+
} = args ?? {};
|
|
1155
|
+
const {
|
|
1156
|
+
skim = this.defaults.findOptions?.skim ?? false,
|
|
1157
|
+
includePermissions = this.defaults.findOptions?.includePermissions ?? true,
|
|
1158
|
+
includeCount = this.defaults.findOptions?.includeCount ?? false,
|
|
1159
|
+
populateAccess = this.defaults.findOptions?.populateAccess ?? "read",
|
|
1160
|
+
lean = this.defaults.findOptions?.lean ?? false
|
|
1161
|
+
} = options ?? {};
|
|
1162
|
+
const { filter: overrideFilter, select: overrideSelect, populate: overridePopulate } = overrides ?? {};
|
|
1163
|
+
const [_filter, _select, _populate, pagination] = await Promise.all([
|
|
1164
|
+
overrideFilter || this.genFilter("list", await this.parseClientData(filter2)),
|
|
1165
|
+
overrideSelect || this.genQuerySelect("list", select),
|
|
1166
|
+
overridePopulate || this.genPopulate(populateAccess, populate),
|
|
1167
|
+
genPagination({ skip, limit, page, pageSize }, this.options.listHardLimit)
|
|
1168
|
+
]);
|
|
1169
|
+
const finalSelect = normalizeSelect(_select);
|
|
1170
|
+
const filteredPopulate = isArray5(finalSelect) && isArray5(_populate) ? _populate.filter((p) => finalSelect.includes(p.path.split(".")[0])) : _populate;
|
|
1171
|
+
const { includes, includeLocalFields, includePaths } = this.processInclude(include);
|
|
1172
|
+
const query = {
|
|
1173
|
+
filter: _filter,
|
|
1174
|
+
select: finalSelect.concat(includeLocalFields),
|
|
1175
|
+
populate: filteredPopulate,
|
|
1176
|
+
sort,
|
|
1177
|
+
...pagination
|
|
1178
|
+
};
|
|
1179
|
+
logger.debug(JSON.stringify({ op: "find", query }));
|
|
1180
|
+
if (_filter === false) return { success: false, code: "forbidden" /* Forbidden */, query };
|
|
1181
|
+
let docs = await this.model.find({
|
|
1182
|
+
...query,
|
|
1183
|
+
lean
|
|
1184
|
+
});
|
|
1185
|
+
const contexts = docs.map((doc) => ({
|
|
1186
|
+
model: this.model.model,
|
|
1187
|
+
modelName: this.modelName,
|
|
1188
|
+
originalDocObject: toObject(doc)
|
|
1189
|
+
}));
|
|
1190
|
+
const _decorate = isFunction3(decorate) ? decorate : (v) => v;
|
|
1191
|
+
docs = await this.includeDocs(docs, includes);
|
|
1192
|
+
const fieldPermissionAccess = includePermissions ? await this.getFieldPermissionAccess(docs.map((doc) => doc._id)) : void 0;
|
|
1193
|
+
docs = await Promise.all(
|
|
1194
|
+
docs.map(async (doc, i) => {
|
|
1195
|
+
contexts[i].fieldPermissionAccess = fieldPermissionAccess;
|
|
1196
|
+
let includeDocPermissions = includePermissions;
|
|
1197
|
+
if (!includeDocPermissions && !skim) {
|
|
1198
|
+
includeDocPermissions = this.checkIfModelPermissionExists(["list", "read", "update"]);
|
|
1199
|
+
}
|
|
1200
|
+
if (includeDocPermissions) doc = await this.addDocPermissions(doc, "list", contexts[i]);
|
|
1201
|
+
if (includePermissions) doc = await this.addFieldPermissions(doc, "list", contexts[i]);
|
|
1202
|
+
doc = await this.trimOutputFields(
|
|
1203
|
+
doc,
|
|
1204
|
+
"list",
|
|
1205
|
+
this.baseFieldsExt.concat(includePaths, normalizeSelect(overrideSelect))
|
|
1206
|
+
);
|
|
1207
|
+
doc = await _decorate(doc, contexts[i]);
|
|
1208
|
+
if (!includePermissions) doc = this.addEmptyPermissions(doc);
|
|
1209
|
+
return doc;
|
|
1210
|
+
})
|
|
1211
|
+
);
|
|
1212
|
+
return {
|
|
1213
|
+
success: true,
|
|
1214
|
+
kind: "list",
|
|
1215
|
+
code: "success" /* Success */,
|
|
1216
|
+
data: docs,
|
|
1217
|
+
count: docs.length,
|
|
1218
|
+
totalCount: includeCount ? await this.model.countDocuments(_filter) : null,
|
|
1219
|
+
query,
|
|
1220
|
+
contexts
|
|
1221
|
+
};
|
|
1222
|
+
}
|
|
1223
|
+
async create(data, args, options, decorate) {
|
|
1224
|
+
const { populate = this.defaults.createArgs?.populate } = args ?? {};
|
|
1225
|
+
const {
|
|
1226
|
+
skim = this.defaults.createOptions?.skim ?? false,
|
|
1227
|
+
includePermissions = this.defaults.createOptions?.includePermissions ?? true,
|
|
1228
|
+
populateAccess = this.defaults.createOptions?.populateAccess ?? "read"
|
|
1229
|
+
} = options ?? {};
|
|
1230
|
+
const isArr = Array.isArray(data);
|
|
1231
|
+
let dataArr = isArr ? data : [data];
|
|
1232
|
+
dataArr = await Promise.all(dataArr.map((d) => this.parseClientData(d)));
|
|
1233
|
+
const contexts = [];
|
|
1234
|
+
let validationError = null;
|
|
1235
|
+
const items = await Promise.all(
|
|
1236
|
+
dataArr.map(async (item, index) => {
|
|
1237
|
+
const context = { model: this.model.model, modelName: this.modelName, originalData: item };
|
|
1238
|
+
const allowedFields = await this.genAllowedFields(item, "create");
|
|
1239
|
+
const allowedData = pick3(item, allowedFields);
|
|
1240
|
+
const validated = await this.validate(allowedData, "create", context);
|
|
1241
|
+
if (isBoolean3(validated)) {
|
|
1242
|
+
if (!validated) {
|
|
1243
|
+
validationError = { success: false, code: "bad_request" /* BadRequest */ };
|
|
1244
|
+
return;
|
|
1245
|
+
}
|
|
1246
|
+
} else if (isArray5(validated)) {
|
|
1247
|
+
if (validated.length > 0) {
|
|
1248
|
+
validationError = { success: false, code: "bad_request" /* BadRequest */, errors: validated };
|
|
1249
|
+
return;
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
const preparedData = await this.prepare(allowedData, "create", context);
|
|
1253
|
+
context.preparedData = preparedData;
|
|
1254
|
+
contexts.push(context);
|
|
1255
|
+
return preparedData;
|
|
1256
|
+
})
|
|
1257
|
+
);
|
|
1258
|
+
if (validationError) return validationError;
|
|
1259
|
+
const _decorate = isFunction3(decorate) ? decorate : (v) => v;
|
|
1260
|
+
let docs = await this.model.create(items);
|
|
1261
|
+
docs = await Promise.all(
|
|
1262
|
+
docs.map(async (doc, index) => {
|
|
1263
|
+
doc = await this.finalize(doc, "create", contexts[index]);
|
|
1264
|
+
contexts[index].finalDocObject = doc.toObject({ virtuals: false });
|
|
1265
|
+
let includeDocPermissions = includePermissions;
|
|
1266
|
+
if (!includeDocPermissions && !skim) {
|
|
1267
|
+
includeDocPermissions = this.checkIfModelPermissionExists(["create", "read", "update"]);
|
|
1268
|
+
}
|
|
1269
|
+
if (includeDocPermissions) doc = await this.addDocPermissions(doc, "create", contexts[index]);
|
|
1270
|
+
if (includePermissions) doc = await this.addFieldPermissions(doc, "read", contexts[index]);
|
|
1271
|
+
if (populate) await populateDoc(doc, await this.genPopulate(populateAccess, populate));
|
|
1272
|
+
doc = await this.trimOutputFields(doc, "read", this.baseFieldsExt);
|
|
1273
|
+
doc = await _decorate(doc, contexts[index]);
|
|
1274
|
+
if (!includePermissions) doc = this.addEmptyPermissions(doc);
|
|
1275
|
+
return doc;
|
|
1276
|
+
})
|
|
1277
|
+
);
|
|
1278
|
+
return {
|
|
1279
|
+
success: true,
|
|
1280
|
+
kind: "list",
|
|
1281
|
+
code: "created" /* Created */,
|
|
1282
|
+
data: docs,
|
|
1283
|
+
input: items,
|
|
1284
|
+
count: docs.length
|
|
1285
|
+
};
|
|
1286
|
+
}
|
|
1287
|
+
async new() {
|
|
1288
|
+
const data = await this.model.new();
|
|
1289
|
+
return {
|
|
1290
|
+
success: true,
|
|
1291
|
+
kind: "single",
|
|
1292
|
+
code: "success" /* Success */,
|
|
1293
|
+
data
|
|
1294
|
+
};
|
|
1295
|
+
}
|
|
1296
|
+
async updateOne(filter2, data, args, options, decorate) {
|
|
1297
|
+
const filterErrors = this.validateClientFilter(filter2);
|
|
1298
|
+
if (filterErrors.length > 0) return { success: false, code: "bad_request" /* BadRequest */, errors: filterErrors };
|
|
1299
|
+
const { populate = this.defaults.updateOneArgs?.populate, overrides = {} } = args ?? {};
|
|
1300
|
+
const {
|
|
1301
|
+
skim = this.defaults.updateOneOptions?.skim ?? false,
|
|
1302
|
+
includePermissions = this.defaults.updateOneOptions?.includePermissions ?? true,
|
|
1303
|
+
populateAccess = this.defaults.updateOneOptions?.populateAccess ?? "read"
|
|
1304
|
+
} = options ?? {};
|
|
1305
|
+
const { filter: overrideFilter, populate: overridePopulate } = overrides ?? {};
|
|
1306
|
+
const [_filter, _populate] = await Promise.all([
|
|
1307
|
+
overrideFilter || this.genFilter("update", filter2),
|
|
1308
|
+
overridePopulate || this.genPopulate(populateAccess, populate)
|
|
1309
|
+
]);
|
|
1310
|
+
const query = { filter: _filter, populate: _populate };
|
|
1311
|
+
logger.debug(JSON.stringify({ op: "updateOne", query }));
|
|
1312
|
+
if (_filter === false) return { success: false, code: "forbidden" /* Forbidden */, query };
|
|
1313
|
+
let doc = await this.model.findOne({ filter: _filter });
|
|
1314
|
+
if (!doc) return { success: false, code: "not_found" /* NotFound */, query };
|
|
1315
|
+
const context = { model: this.model.model, modelName: this.modelName };
|
|
1316
|
+
data = await this.parseClientData(data);
|
|
1317
|
+
context.originalDocObject = doc.toObject({ virtuals: false });
|
|
1318
|
+
context.originalData = data;
|
|
1319
|
+
doc = await this.addDocPermissions(doc, "update", context);
|
|
1320
|
+
context.docPermissions = this.getDocPermissions(doc);
|
|
1321
|
+
context.currentDoc = doc;
|
|
1322
|
+
const allowedFields = await this.genAllowedFields(doc, "update");
|
|
1323
|
+
const allowedData = pick3(data, allowedFields);
|
|
1324
|
+
const validated = await this.validate(allowedData, "update", context);
|
|
1325
|
+
if (isBoolean3(validated)) {
|
|
1326
|
+
if (!validated) return { success: false, code: "bad_request" /* BadRequest */ };
|
|
1327
|
+
} else if (isArray5(validated)) {
|
|
1328
|
+
if (validated.length > 0) return { success: false, code: "bad_request" /* BadRequest */, errors: validated };
|
|
1329
|
+
}
|
|
1330
|
+
const prepared = await this.prepare(allowedData, "update", context);
|
|
1331
|
+
context.preparedData = prepared;
|
|
1332
|
+
Object.assign(doc, prepared);
|
|
1333
|
+
context.modifiedPaths = doc.modifiedPaths();
|
|
1334
|
+
doc = await this.transform(doc, "update", context);
|
|
1335
|
+
doc = await doc.save();
|
|
1336
|
+
const diffExcludeFields = [this.options.documentPermissionField, "__v"];
|
|
1337
|
+
context.diff = (d) => {
|
|
1338
|
+
context.changes = diff(
|
|
1339
|
+
omit(context.originalDocObject, diffExcludeFields),
|
|
1340
|
+
omit(d.toObject({ virtuals: false }), diffExcludeFields)
|
|
1341
|
+
) || [];
|
|
1342
|
+
context.modifiedPaths = uniq2(context.changes.map((di) => di.path.length > 0 ? di.path[0] : ""));
|
|
1343
|
+
};
|
|
1344
|
+
doc = await this.finalize(doc, "update", context);
|
|
1345
|
+
context.finalDocObject = doc.toObject({ virtuals: false });
|
|
1346
|
+
context.diff(doc);
|
|
1347
|
+
await this.changes(doc, context);
|
|
1348
|
+
let includeDocPermissions = includePermissions;
|
|
1349
|
+
if (!includeDocPermissions && !skim) {
|
|
1350
|
+
includeDocPermissions = this.checkIfModelPermissionExists(["read", "update"]);
|
|
1351
|
+
}
|
|
1352
|
+
if (includeDocPermissions) doc = await this.addDocPermissions(doc, "update", context);
|
|
1353
|
+
if (includePermissions) doc = await this.addFieldPermissions(doc, "update", context);
|
|
1354
|
+
if (_populate) await populateDoc(doc, _populate);
|
|
1355
|
+
doc = await this.trimOutputFields(doc, "read", this.baseFieldsExt);
|
|
1356
|
+
if (isFunction3(decorate)) doc = await decorate(doc, context);
|
|
1357
|
+
if (!includePermissions) doc = this.addEmptyPermissions(doc);
|
|
1358
|
+
return { success: true, kind: "single", code: "success" /* Success */, data: doc, input: prepared };
|
|
1359
|
+
}
|
|
1360
|
+
async updateById(id, data, { populate = this.defaults.updateByIdArgs?.populate, overrides = {} } = {}, {
|
|
1361
|
+
skim = this.defaults.updateByIdOptions?.skim ?? false,
|
|
1362
|
+
includePermissions = this.defaults.updateByIdOptions?.includePermissions ?? true,
|
|
1363
|
+
populateAccess = this.defaults.updateByIdOptions?.populateAccess ?? "read"
|
|
1364
|
+
} = {}, decorate) {
|
|
1365
|
+
const { populate: overridePopulate, idFilter: overrideIdFilter } = overrides;
|
|
1366
|
+
const filter2 = overrideIdFilter || await this.genIDFilter(id);
|
|
1367
|
+
return this.updateOne(
|
|
1368
|
+
filter2,
|
|
1369
|
+
data,
|
|
1370
|
+
{
|
|
1371
|
+
populate,
|
|
1372
|
+
overrides: {
|
|
1373
|
+
populate: overridePopulate
|
|
1374
|
+
}
|
|
1375
|
+
},
|
|
1376
|
+
{ skim, includePermissions, populateAccess },
|
|
1377
|
+
decorate
|
|
1378
|
+
);
|
|
1379
|
+
}
|
|
1380
|
+
async upsert(filter2, data, args, options, decorate) {
|
|
1381
|
+
const filterErrors = this.validateClientFilter(filter2);
|
|
1382
|
+
if (filterErrors.length > 0) return { success: false, code: "bad_request" /* BadRequest */, errors: filterErrors };
|
|
1383
|
+
const { populate = this.defaults.upsertArgs?.populate, overrides = {} } = args ?? {};
|
|
1384
|
+
const {
|
|
1385
|
+
skim = this.defaults.upsertOptions?.skim ?? false,
|
|
1386
|
+
includePermissions = this.defaults.upsertOptions?.includePermissions ?? true,
|
|
1387
|
+
populateAccess = this.defaults.upsertOptions?.populateAccess ?? "read"
|
|
1388
|
+
} = options ?? {};
|
|
1389
|
+
const { filter: overrideFilter, populate: overridePopulate } = overrides ?? {};
|
|
1390
|
+
const theone = await this.model.findOne({ filter: filter2 });
|
|
1391
|
+
if (theone) {
|
|
1392
|
+
const _filter = await (overrideFilter || this.genFilter("update", filter2));
|
|
1393
|
+
const query = { filter: _filter };
|
|
1394
|
+
logger.debug(JSON.stringify({ op: "upsert", query }));
|
|
1395
|
+
if (_filter === false) return { success: false, code: "forbidden" /* Forbidden */, query };
|
|
1396
|
+
const matched = matchElement(theone, _filter);
|
|
1397
|
+
if (!matched) return { success: false, code: "forbidden" /* Forbidden */, query };
|
|
1398
|
+
return this.updateOne(
|
|
1399
|
+
null,
|
|
1400
|
+
data,
|
|
1401
|
+
{
|
|
1402
|
+
populate,
|
|
1403
|
+
overrides: {
|
|
1404
|
+
filter: _filter,
|
|
1405
|
+
populate: overridePopulate
|
|
1406
|
+
}
|
|
1407
|
+
},
|
|
1408
|
+
{ skim, includePermissions, populateAccess },
|
|
1409
|
+
decorate
|
|
1410
|
+
);
|
|
1411
|
+
} else {
|
|
1412
|
+
return this.create(
|
|
1413
|
+
data,
|
|
1414
|
+
{ populate },
|
|
1415
|
+
{
|
|
1416
|
+
skim,
|
|
1417
|
+
includePermissions,
|
|
1418
|
+
populateAccess
|
|
1419
|
+
},
|
|
1420
|
+
decorate
|
|
1421
|
+
);
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
async delete(id) {
|
|
1425
|
+
const filter2 = await this.genFilter("delete", await this.genIDFilter(id));
|
|
1426
|
+
const query = { filter: filter2 };
|
|
1427
|
+
logger.debug(JSON.stringify({ op: "delete", query }));
|
|
1428
|
+
if (filter2 === false) return { success: false, code: "forbidden" /* Forbidden */, query };
|
|
1429
|
+
let doc = await this.model.findOne({ filter: filter2 });
|
|
1430
|
+
if (!doc) return { success: false, code: "not_found" /* NotFound */, query };
|
|
1431
|
+
await ("deleteOne" in doc ? doc.deleteOne() : doc.remove());
|
|
1432
|
+
return { success: true, kind: "single", code: "success" /* Success */, data: doc._id, query };
|
|
1433
|
+
}
|
|
1434
|
+
async exists(filter2, options) {
|
|
1435
|
+
const filterErrors = this.validateClientFilter(filter2);
|
|
1436
|
+
if (filterErrors.length > 0) return { success: false, code: "bad_request" /* BadRequest */, errors: filterErrors };
|
|
1437
|
+
const {
|
|
1438
|
+
access = this.defaults.existsOptions?.access ?? "read",
|
|
1439
|
+
includeId = this.defaults.existsOptions?.includeId ?? false
|
|
1440
|
+
} = options ?? {};
|
|
1441
|
+
filter2 = await this.genFilter(access, filter2);
|
|
1442
|
+
const result = await this.model.exists(filter2);
|
|
1443
|
+
return {
|
|
1444
|
+
success: true,
|
|
1445
|
+
kind: "single",
|
|
1446
|
+
code: "success" /* Success */,
|
|
1447
|
+
data: includeId ? result : !!result,
|
|
1448
|
+
query: { filter: filter2 }
|
|
1449
|
+
};
|
|
1450
|
+
}
|
|
1451
|
+
async distinct(field, args) {
|
|
1452
|
+
let { filter: filter2 } = args ?? {};
|
|
1453
|
+
const filterErrors = this.validateClientFilter(filter2);
|
|
1454
|
+
if (filterErrors.length > 0) return { success: false, code: "bad_request" /* BadRequest */, errors: filterErrors };
|
|
1455
|
+
filter2 = await this.genFilter("read", filter2);
|
|
1456
|
+
const query = { filter: filter2 };
|
|
1457
|
+
if (filter2 === false) return { success: false, code: "forbidden" /* Forbidden */, query };
|
|
1458
|
+
const result = await this.model.distinct(field, filter2);
|
|
1459
|
+
return { success: true, kind: "list", code: "success" /* Success */, data: result, count: result.length, query };
|
|
1460
|
+
}
|
|
1461
|
+
async count(filter2, access = "list") {
|
|
1462
|
+
const filterErrors = this.validateClientFilter(filter2);
|
|
1463
|
+
if (filterErrors.length > 0) return { success: false, code: "bad_request" /* BadRequest */, errors: filterErrors };
|
|
1464
|
+
filter2 = await this.genFilter(access, filter2);
|
|
1465
|
+
const query = { filter: filter2 };
|
|
1466
|
+
if (filter2 === false) return { success: false, code: "forbidden" /* Forbidden */, query };
|
|
1467
|
+
return { success: true, kind: "single", code: "success" /* Success */, data: await this.model.countDocuments(filter2), query };
|
|
1468
|
+
}
|
|
1469
|
+
getDocPermissions(doc) {
|
|
1470
|
+
return getDocPermissions(this.modelName, doc);
|
|
1471
|
+
}
|
|
1472
|
+
async getFieldPermissionAccess(ids) {
|
|
1473
|
+
const uniqueIds = uniq2(ids.map((id) => String(id)).filter(Boolean));
|
|
1474
|
+
if (uniqueIds.length === 0) {
|
|
1475
|
+
return {
|
|
1476
|
+
readIds: /* @__PURE__ */ new Set(),
|
|
1477
|
+
updateIds: /* @__PURE__ */ new Set()
|
|
1478
|
+
};
|
|
1479
|
+
}
|
|
1480
|
+
const [readIds, updateIds] = await Promise.all([
|
|
1481
|
+
this.getAccessibleIdSet(uniqueIds, "read"),
|
|
1482
|
+
this.getAccessibleIdSet(uniqueIds, "update")
|
|
1483
|
+
]);
|
|
1484
|
+
return { readIds, updateIds };
|
|
1485
|
+
}
|
|
1486
|
+
async getAccessibleIdSet(ids, access) {
|
|
1487
|
+
const filter2 = await this.genFilter(access, { _id: { $in: ids } });
|
|
1488
|
+
if (filter2 === false) return /* @__PURE__ */ new Set();
|
|
1489
|
+
const docs = await this.model.find({ filter: filter2, select: "_id", lean: true });
|
|
1490
|
+
return new Set(docs.map((doc) => String(doc._id)));
|
|
1491
|
+
}
|
|
1492
|
+
async listSub(id, sub, options) {
|
|
1493
|
+
let { filter: ft, fields } = options ?? {};
|
|
1494
|
+
const parentDoc = await this.getParentDoc(id, sub, null, { access: "read" });
|
|
1495
|
+
if (!parentDoc) return { success: false, code: "not_found" /* NotFound */ };
|
|
1496
|
+
let result = get5(parentDoc, sub);
|
|
1497
|
+
const [subFilter, subSelect] = await Promise.all([
|
|
1498
|
+
this.genFilter(`subs.${sub}.list`, ft),
|
|
1499
|
+
this.genQuerySelect("list", fields, false, [sub, "sub"])
|
|
1500
|
+
]);
|
|
1501
|
+
if (subFilter === false) return { success: false, code: "forbidden" /* Forbidden */ };
|
|
1502
|
+
result = filterCollection(result, subFilter);
|
|
1503
|
+
if (subSelect) result = result.map((v) => pick3(toObject(v), subSelect.concat("_id")));
|
|
1504
|
+
return { success: true, kind: "list", code: "success" /* Success */, data: result, count: result.length };
|
|
1505
|
+
}
|
|
1506
|
+
async readSub(id, sub, subId, options) {
|
|
1507
|
+
let { fields, populate } = options ?? {};
|
|
1508
|
+
const parentDoc = await this.getParentDoc(id, sub, { populate }, { access: "read" });
|
|
1509
|
+
if (!parentDoc) return { success: false, code: "not_found" /* NotFound */ };
|
|
1510
|
+
const result = get5(parentDoc, sub);
|
|
1511
|
+
const [subFilter, subSelect] = await Promise.all([
|
|
1512
|
+
this.genFilter(`subs.${sub}.read`, { _id: subId }),
|
|
1513
|
+
this.genQuerySelect("read", fields, false, [sub, "sub"])
|
|
1514
|
+
]);
|
|
1515
|
+
if (subFilter === false) return { success: false, code: "forbidden" /* Forbidden */ };
|
|
1516
|
+
let subdoc = findElement(result, subFilter);
|
|
1517
|
+
if (!subdoc) return { success: false, code: "not_found" /* NotFound */ };
|
|
1518
|
+
if (subSelect) subdoc = pick3(toObject(subdoc), subSelect.concat(["_id"]));
|
|
1519
|
+
return { success: true, kind: "single", code: "success" /* Success */, data: subdoc };
|
|
1520
|
+
}
|
|
1521
|
+
async updateSub(id, sub, subId, data) {
|
|
1522
|
+
const parentDoc = await this.getParentDoc(id, sub, null, { access: "update" });
|
|
1523
|
+
if (!parentDoc) return { success: false, code: "not_found" /* NotFound */ };
|
|
1524
|
+
const result = get5(parentDoc, sub);
|
|
1525
|
+
const [subFilter, subReadSelect, subUpdateSelect] = await Promise.all([
|
|
1526
|
+
this.genFilter(`subs.${sub}.update`, { _id: subId }),
|
|
1527
|
+
this.genQuerySelect("read", null, false, [sub, "sub"]),
|
|
1528
|
+
this.genQuerySelect("update", null, false, [sub, "sub"])
|
|
1529
|
+
]);
|
|
1530
|
+
if (subFilter === false) return { success: false, code: "forbidden" /* Forbidden */ };
|
|
1531
|
+
let subdoc = findElement(result, subFilter);
|
|
1532
|
+
if (!subdoc) return { success: false, code: "not_found" /* NotFound */ };
|
|
1533
|
+
const allowedData = pick3(data, subUpdateSelect);
|
|
1534
|
+
Object.assign(subdoc, allowedData);
|
|
1535
|
+
await parentDoc.save();
|
|
1536
|
+
if (subReadSelect) subdoc = pick3(toObject(subdoc), subReadSelect.concat(["_id"]));
|
|
1537
|
+
return { success: true, kind: "single", code: "success" /* Success */, data: subdoc };
|
|
1538
|
+
}
|
|
1539
|
+
async bulkUpdateSub(id, sub, data) {
|
|
1540
|
+
const parentDoc = await this.getParentDoc(id, sub, null, { access: "update" });
|
|
1541
|
+
if (!parentDoc) return { success: false, code: "not_found" /* NotFound */ };
|
|
1542
|
+
let result = get5(parentDoc, sub);
|
|
1543
|
+
data = castArray2(data);
|
|
1544
|
+
const [subFilter, subReadSelect, subUpdateSelect] = await Promise.all([
|
|
1545
|
+
this.genFilter(`subs.${sub}.update`, { _id: { $in: data.map((v) => v._id) } }),
|
|
1546
|
+
this.genQuerySelect("read", null, false, [sub, "sub"]),
|
|
1547
|
+
this.genQuerySelect("update", null, false, [sub, "sub"])
|
|
1548
|
+
]);
|
|
1549
|
+
if (subFilter === false) return { success: false, code: "forbidden" /* Forbidden */ };
|
|
1550
|
+
result = filterCollection(result, subFilter);
|
|
1551
|
+
forEach4(result, (subdoc) => {
|
|
1552
|
+
const tdata = findElementById(data, subdoc._id);
|
|
1553
|
+
if (!tdata) return;
|
|
1554
|
+
const allowedData = pick3(tdata, subUpdateSelect);
|
|
1555
|
+
Object.assign(subdoc, allowedData);
|
|
1556
|
+
});
|
|
1557
|
+
await parentDoc.save();
|
|
1558
|
+
if (subReadSelect) result = result.map((v) => pick3(toObject(v), subReadSelect.concat(["_id"])));
|
|
1559
|
+
return { success: true, kind: "list", code: "success" /* Success */, data: result, count: result.length };
|
|
1560
|
+
}
|
|
1561
|
+
async createSub(id, sub, data, options) {
|
|
1562
|
+
const { addFirst } = options ?? {};
|
|
1563
|
+
const parentDoc = await this.getParentDoc(id, sub, null, { access: "update" });
|
|
1564
|
+
if (!parentDoc) return { success: false, code: "not_found" /* NotFound */ };
|
|
1565
|
+
let result = get5(parentDoc, sub);
|
|
1566
|
+
const [subCreateSelect, subReadSelect] = await Promise.all([
|
|
1567
|
+
this.genQuerySelect("create", null, false, [sub, "sub"]),
|
|
1568
|
+
this.genQuerySelect("read", null, false, [sub, "sub"])
|
|
1569
|
+
]);
|
|
1570
|
+
const allowedData = pick3(data, subCreateSelect);
|
|
1571
|
+
addFirst === true ? result.unshift(allowedData) : result.push(allowedData);
|
|
1572
|
+
await parentDoc.save();
|
|
1573
|
+
if (subReadSelect) result = result.map((v) => pick3(toObject(v), subReadSelect.concat(["_id"])));
|
|
1574
|
+
return { success: true, kind: "list", code: "created" /* Created */, data: result, count: result.length };
|
|
1575
|
+
}
|
|
1576
|
+
async deleteSub(id, sub, subId) {
|
|
1577
|
+
const parentDoc = await this.getParentDoc(id, sub, null, { access: "update" });
|
|
1578
|
+
if (!parentDoc) return { success: false, code: "not_found" /* NotFound */ };
|
|
1579
|
+
const result = get5(parentDoc, sub);
|
|
1580
|
+
const subFilter = await this.genFilter(`subs.${sub}.delete`, { _id: subId });
|
|
1581
|
+
if (subFilter === false) return { success: false, code: "forbidden" /* Forbidden */ };
|
|
1582
|
+
const subdoc = findElement(result, subFilter);
|
|
1583
|
+
if (!subdoc) return { success: false, code: "not_found" /* NotFound */ };
|
|
1584
|
+
await ("deleteOne" in subdoc ? subdoc.deleteOne?.() : subdoc.remove?.());
|
|
1585
|
+
await parentDoc.save();
|
|
1586
|
+
return { success: true, kind: "single", code: "success" /* Success */, data: subdoc._id };
|
|
1587
|
+
}
|
|
1588
|
+
async getParentDoc(id, sub, args, options) {
|
|
1589
|
+
const { populate } = args ?? {};
|
|
1590
|
+
const { access = "read", lean = false } = options ?? {};
|
|
1591
|
+
const parentFilter = await this.genFilter(access, await this.genIDFilter(id));
|
|
1592
|
+
if (parentFilter === false) return null;
|
|
1593
|
+
return this.model.findOne({ filter: parentFilter, select: sub, populate: genSubPopulate(sub, populate), lean });
|
|
1594
|
+
}
|
|
1595
|
+
};
|
|
1596
|
+
|
|
1597
|
+
// src/services/public-service.ts
|
|
1598
|
+
import { pick as pick4 } from "@web-ts-toolkit/utils";
|
|
1599
|
+
var PublicService = class extends Service {
|
|
1600
|
+
async _list(filter2, args, options) {
|
|
1601
|
+
const {
|
|
1602
|
+
select = this.defaults.publicListArgs?.select,
|
|
1603
|
+
populate = this.defaults.publicListArgs?.populate,
|
|
1604
|
+
include = this.defaults.publicListArgs?.include,
|
|
1605
|
+
sort = this.defaults.publicListArgs?.sort,
|
|
1606
|
+
skip = this.defaults.publicListArgs?.skip,
|
|
1607
|
+
limit = this.defaults.publicListArgs?.limit,
|
|
1608
|
+
page = this.defaults.publicListArgs?.page,
|
|
1609
|
+
pageSize = this.defaults.publicListArgs?.pageSize,
|
|
1610
|
+
tasks = this.defaults.publicListArgs?.tasks ?? []
|
|
1611
|
+
} = args ?? {};
|
|
1612
|
+
const {
|
|
1613
|
+
skim = this.defaults.publicListOptions?.skim ?? true,
|
|
1614
|
+
includePermissions = this.defaults.publicListOptions?.includePermissions ?? false,
|
|
1615
|
+
includeCount = this.defaults.publicListOptions?.includeCount ?? false,
|
|
1616
|
+
populateAccess = this.defaults.publicListOptions?.populateAccess ?? "read",
|
|
1617
|
+
lean = this.defaults.publicListOptions?.lean ?? true
|
|
1618
|
+
} = options ?? {};
|
|
1619
|
+
const result = await this.find(
|
|
1620
|
+
filter2,
|
|
1621
|
+
{ select, populate, include, sort, skip, limit, page, pageSize },
|
|
1622
|
+
{ skim, includePermissions, includeCount, populateAccess, lean },
|
|
1623
|
+
async (doc, context) => {
|
|
1624
|
+
doc = toObject(doc);
|
|
1625
|
+
return this.decorate(doc, "list", context);
|
|
1626
|
+
}
|
|
1627
|
+
);
|
|
1628
|
+
if (!result.success) {
|
|
1629
|
+
return result;
|
|
1630
|
+
}
|
|
1631
|
+
let docs = result.data;
|
|
1632
|
+
docs = await this.decorateAll(docs, "list", { model: this.model.model, modelName: this.modelName });
|
|
1633
|
+
docs = docs.map((row) => this.runTasks(row, tasks));
|
|
1634
|
+
result.data = docs;
|
|
1635
|
+
return result;
|
|
1636
|
+
}
|
|
1637
|
+
async _create(data, args, options) {
|
|
1638
|
+
const {
|
|
1639
|
+
select = this.defaults.publicCreateArgs?.select,
|
|
1640
|
+
populate = this.defaults.publicCreateArgs?.populate,
|
|
1641
|
+
tasks = this.defaults.publicCreateArgs?.tasks ?? []
|
|
1642
|
+
} = args ?? {};
|
|
1643
|
+
const {
|
|
1644
|
+
skim = this.defaults.publicCreateOptions?.skim ?? false,
|
|
1645
|
+
includePermissions = this.defaults.publicCreateOptions?.includePermissions ?? true,
|
|
1646
|
+
populateAccess = this.defaults.publicCreateOptions?.populateAccess ?? "read"
|
|
1647
|
+
} = options ?? {};
|
|
1648
|
+
const result = await this.create(
|
|
1649
|
+
data,
|
|
1650
|
+
{ populate },
|
|
1651
|
+
{ skim, includePermissions, populateAccess },
|
|
1652
|
+
async (doc, context) => {
|
|
1653
|
+
doc = toObject(doc);
|
|
1654
|
+
doc = await this.decorate(doc, "create", context);
|
|
1655
|
+
doc = this.runTasks(doc, tasks);
|
|
1656
|
+
if (select) doc = pick4(doc, [...normalizeSelect(select), ...this.baseFieldsExt]);
|
|
1657
|
+
return doc;
|
|
1658
|
+
}
|
|
1659
|
+
);
|
|
1660
|
+
return result;
|
|
1661
|
+
}
|
|
1662
|
+
async _new() {
|
|
1663
|
+
return this.new();
|
|
1664
|
+
}
|
|
1665
|
+
async _read(id, args, options) {
|
|
1666
|
+
const {
|
|
1667
|
+
select = this.defaults.publicReadArgs?.select,
|
|
1668
|
+
populate = this.defaults.publicReadArgs?.populate,
|
|
1669
|
+
include = this.defaults.publicReadArgs?.include,
|
|
1670
|
+
tasks = this.defaults.publicReadArgs?.tasks ?? []
|
|
1671
|
+
} = args ?? {};
|
|
1672
|
+
const {
|
|
1673
|
+
skim = this.defaults.publicReadOptions?.skim ?? false,
|
|
1674
|
+
includePermissions = this.defaults.publicReadOptions?.includePermissions ?? true,
|
|
1675
|
+
tryList = this.defaults.publicReadOptions?.tryList ?? true,
|
|
1676
|
+
populateAccess = this.defaults.publicReadOptions?.populateAccess,
|
|
1677
|
+
lean = this.defaults.publicReadOptions?.lean ?? false
|
|
1678
|
+
} = options ?? {};
|
|
1679
|
+
let access = "read";
|
|
1680
|
+
const idFilter = await this.genIDFilter(id);
|
|
1681
|
+
let result = await this.findById(
|
|
1682
|
+
id,
|
|
1683
|
+
{
|
|
1684
|
+
select,
|
|
1685
|
+
populate,
|
|
1686
|
+
include,
|
|
1687
|
+
overrides: { idFilter }
|
|
1688
|
+
},
|
|
1689
|
+
{ skim, includePermissions, access, populateAccess, lean }
|
|
1690
|
+
);
|
|
1691
|
+
if (tryList && (!result.success || !result.data)) {
|
|
1692
|
+
access = "list";
|
|
1693
|
+
result = await this.findById(
|
|
1694
|
+
id,
|
|
1695
|
+
{
|
|
1696
|
+
select,
|
|
1697
|
+
populate,
|
|
1698
|
+
overrides: { idFilter }
|
|
1699
|
+
},
|
|
1700
|
+
{ skim, includePermissions, access, populateAccess, lean }
|
|
1701
|
+
);
|
|
1702
|
+
}
|
|
1703
|
+
if (!result.success) {
|
|
1704
|
+
return result;
|
|
1705
|
+
}
|
|
1706
|
+
let doc = toObject(result.data);
|
|
1707
|
+
doc = await this.decorate(doc, access, result.context);
|
|
1708
|
+
doc = this.runTasks(doc, tasks);
|
|
1709
|
+
result.data = doc;
|
|
1710
|
+
return result;
|
|
1711
|
+
}
|
|
1712
|
+
async _readFilter(filter2, args, options) {
|
|
1713
|
+
const {
|
|
1714
|
+
select = this.defaults.publicReadArgs?.select,
|
|
1715
|
+
sort = this.defaults.publicListArgs?.sort,
|
|
1716
|
+
populate = this.defaults.publicReadArgs?.populate,
|
|
1717
|
+
include = this.defaults.publicReadArgs?.include,
|
|
1718
|
+
tasks = this.defaults.publicReadArgs?.tasks ?? []
|
|
1719
|
+
} = args ?? {};
|
|
1720
|
+
const {
|
|
1721
|
+
skim = this.defaults.publicReadOptions?.skim ?? false,
|
|
1722
|
+
includePermissions = this.defaults.publicReadOptions?.includePermissions ?? true,
|
|
1723
|
+
tryList = this.defaults.publicReadOptions?.tryList ?? true,
|
|
1724
|
+
populateAccess = this.defaults.publicReadOptions?.populateAccess,
|
|
1725
|
+
lean = this.defaults.publicReadOptions?.lean ?? false
|
|
1726
|
+
} = options ?? {};
|
|
1727
|
+
let access = "read";
|
|
1728
|
+
let result = await this.findOne(
|
|
1729
|
+
filter2,
|
|
1730
|
+
{
|
|
1731
|
+
select,
|
|
1732
|
+
sort,
|
|
1733
|
+
populate,
|
|
1734
|
+
include,
|
|
1735
|
+
overrides: {}
|
|
1736
|
+
},
|
|
1737
|
+
{ skim, includePermissions, access, populateAccess, lean }
|
|
1738
|
+
);
|
|
1739
|
+
if (tryList && (!result.success || !result.data)) {
|
|
1740
|
+
access = "list";
|
|
1741
|
+
result = await this.findOne(
|
|
1742
|
+
filter2,
|
|
1743
|
+
{
|
|
1744
|
+
select,
|
|
1745
|
+
sort,
|
|
1746
|
+
populate,
|
|
1747
|
+
overrides: {}
|
|
1748
|
+
},
|
|
1749
|
+
{ skim, includePermissions, access, populateAccess, lean }
|
|
1750
|
+
);
|
|
1751
|
+
}
|
|
1752
|
+
if (!result.success) {
|
|
1753
|
+
return result;
|
|
1754
|
+
}
|
|
1755
|
+
let doc = toObject(result.data);
|
|
1756
|
+
doc = await this.decorate(doc, access, result.context);
|
|
1757
|
+
doc = this.runTasks(doc, tasks);
|
|
1758
|
+
result.data = doc;
|
|
1759
|
+
return result;
|
|
1760
|
+
}
|
|
1761
|
+
async _update(id, data, args, options) {
|
|
1762
|
+
const {
|
|
1763
|
+
select = this.defaults.publicUpdateArgs?.select,
|
|
1764
|
+
populate = this.defaults.publicUpdateArgs?.populate,
|
|
1765
|
+
tasks = this.defaults.publicUpdateArgs?.tasks ?? []
|
|
1766
|
+
} = args ?? {};
|
|
1767
|
+
const {
|
|
1768
|
+
skim = this.defaults.publicUpdateOptions?.skim ?? false,
|
|
1769
|
+
returningAll = this.defaults.publicUpdateOptions?.returningAll ?? true,
|
|
1770
|
+
includePermissions = this.defaults.publicUpdateOptions?.includePermissions ?? true,
|
|
1771
|
+
populateAccess = this.defaults.publicUpdateOptions?.populateAccess ?? "read"
|
|
1772
|
+
} = options ?? {};
|
|
1773
|
+
const result = await this.updateById(
|
|
1774
|
+
id,
|
|
1775
|
+
data,
|
|
1776
|
+
{ populate },
|
|
1777
|
+
{ skim, includePermissions, populateAccess },
|
|
1778
|
+
async (doc, context) => {
|
|
1779
|
+
doc = toObject(doc);
|
|
1780
|
+
doc = await this.decorate(doc, "update", context);
|
|
1781
|
+
doc = this.runTasks(doc, tasks);
|
|
1782
|
+
if (select) doc = pick4(doc, [...normalizeSelect(select), ...this.baseFieldsExt]);
|
|
1783
|
+
else if (!returningAll) doc = pick4(doc, [...Object.keys(data), "_id"]);
|
|
1784
|
+
return doc;
|
|
1785
|
+
}
|
|
1786
|
+
);
|
|
1787
|
+
return result;
|
|
1788
|
+
}
|
|
1789
|
+
async _delete(id) {
|
|
1790
|
+
const result = await this.delete(id);
|
|
1791
|
+
return result;
|
|
1792
|
+
}
|
|
1793
|
+
async _distinct(field, options = {}) {
|
|
1794
|
+
const result = await this.distinct(field, options);
|
|
1795
|
+
return result;
|
|
1796
|
+
}
|
|
1797
|
+
async _count(filter2, access = "list") {
|
|
1798
|
+
const result = await this.count(filter2, access);
|
|
1799
|
+
return result;
|
|
1800
|
+
}
|
|
1801
|
+
};
|
|
1802
|
+
|
|
1803
|
+
// src/services/data-service.ts
|
|
1804
|
+
import { orderBy, pick as pick5 } from "@web-ts-toolkit/utils";
|
|
1805
|
+
var DataService = class {
|
|
1806
|
+
constructor(req, dataName) {
|
|
1807
|
+
this.req = req;
|
|
1808
|
+
this.dataName = dataName;
|
|
1809
|
+
this.options = getDataOptions(dataName);
|
|
1810
|
+
this.data = this.options.data ?? [];
|
|
1811
|
+
}
|
|
1812
|
+
async findOne(filter2, args, options) {
|
|
1813
|
+
const filterErrors = validateClientFilter(filter2);
|
|
1814
|
+
if (filterErrors.length > 0) return { success: false, code: "bad_request" /* BadRequest */, errors: filterErrors };
|
|
1815
|
+
const { select } = args ?? {};
|
|
1816
|
+
const { access = "read" } = options ?? {};
|
|
1817
|
+
let [_filter, _select] = await Promise.all([this.genFilter(access, filter2), this.genQuerySelect(access, select)]);
|
|
1818
|
+
const query = {
|
|
1819
|
+
filter: _filter,
|
|
1820
|
+
select: _select
|
|
1821
|
+
};
|
|
1822
|
+
if (_filter === false) return { success: false, code: "forbidden" /* Forbidden */, query };
|
|
1823
|
+
let doc = await findElement(this.data, _filter);
|
|
1824
|
+
if (!doc) return { success: false, code: "not_found" /* NotFound */, query };
|
|
1825
|
+
doc = await this.trimOutputFields(doc, access);
|
|
1826
|
+
if (_select.length > 0) doc = pick5(doc, _select);
|
|
1827
|
+
return { success: true, kind: "single", code: "success" /* Success */, data: doc, query };
|
|
1828
|
+
}
|
|
1829
|
+
async findById(id, args, options) {
|
|
1830
|
+
const { select } = args ?? {};
|
|
1831
|
+
const { access = "read" } = options ?? {};
|
|
1832
|
+
const filter2 = await this.genIDFilter(id);
|
|
1833
|
+
return this.findOne(
|
|
1834
|
+
filter2,
|
|
1835
|
+
{
|
|
1836
|
+
select
|
|
1837
|
+
},
|
|
1838
|
+
{ access }
|
|
1839
|
+
);
|
|
1840
|
+
}
|
|
1841
|
+
async find(filter2, args, options) {
|
|
1842
|
+
const filterErrors = validateClientFilter(filter2);
|
|
1843
|
+
if (filterErrors.length > 0) return { success: false, code: "bad_request" /* BadRequest */, errors: filterErrors };
|
|
1844
|
+
const { select, sort, skip, limit, page, pageSize } = args ?? {};
|
|
1845
|
+
const [_filter, _select, pagination] = await Promise.all([
|
|
1846
|
+
this.genFilter("list", filter2),
|
|
1847
|
+
this.genQuerySelect("list", select),
|
|
1848
|
+
genPagination({ skip, limit, page, pageSize }, this.options.listHardLimit)
|
|
1849
|
+
]);
|
|
1850
|
+
const query = {
|
|
1851
|
+
filter: _filter,
|
|
1852
|
+
select: _select,
|
|
1853
|
+
sort,
|
|
1854
|
+
...pagination
|
|
1855
|
+
};
|
|
1856
|
+
if (_filter === false) return { success: false, code: "forbidden" /* Forbidden */, query };
|
|
1857
|
+
let docs = await filterCollection(this.data, _filter);
|
|
1858
|
+
const totalCount = docs.length;
|
|
1859
|
+
docs = await Promise.all(
|
|
1860
|
+
docs.map(async (doc) => {
|
|
1861
|
+
doc = await this.trimOutputFields(doc, "list");
|
|
1862
|
+
if (_select.length > 0) doc = pick5(doc, _select);
|
|
1863
|
+
return doc;
|
|
1864
|
+
})
|
|
1865
|
+
);
|
|
1866
|
+
if (sort) {
|
|
1867
|
+
const { sortKey, sortOrder } = parseSortString(sort);
|
|
1868
|
+
docs = orderBy(docs, [sortKey], [sortOrder]);
|
|
1869
|
+
}
|
|
1870
|
+
docs = docs.slice(query.skip, query.limit && query.skip + query.limit);
|
|
1871
|
+
return {
|
|
1872
|
+
success: true,
|
|
1873
|
+
kind: "list",
|
|
1874
|
+
code: "success" /* Success */,
|
|
1875
|
+
data: docs,
|
|
1876
|
+
count: docs.length,
|
|
1877
|
+
totalCount,
|
|
1878
|
+
query
|
|
1879
|
+
};
|
|
1880
|
+
}
|
|
1881
|
+
decorate(doc, access, context) {
|
|
1882
|
+
return this.req.dacl.decorate(this.dataName, doc, access, context);
|
|
1883
|
+
}
|
|
1884
|
+
decorateAll(docs, access) {
|
|
1885
|
+
return this.req.dacl.decorateAll(this.dataName, docs, access);
|
|
1886
|
+
}
|
|
1887
|
+
genAllowedFields(doc, access, baseFields) {
|
|
1888
|
+
return this.req.dacl.genAllowedFields(this.dataName, doc, access, baseFields);
|
|
1889
|
+
}
|
|
1890
|
+
genFilter(access, filter2) {
|
|
1891
|
+
return this.req.dacl.genFilter(this.dataName, access, filter2);
|
|
1892
|
+
}
|
|
1893
|
+
genIDFilter(id) {
|
|
1894
|
+
return this.req.dacl.genIDFilter(this.dataName, id);
|
|
1895
|
+
}
|
|
1896
|
+
genSelect(access, targetFields, skipChecks, subPaths) {
|
|
1897
|
+
return this.req.dacl.genSelect(this.dataName, access, targetFields, skipChecks, subPaths);
|
|
1898
|
+
}
|
|
1899
|
+
genQuerySelect(access, targetFields, skipChecks, subPaths) {
|
|
1900
|
+
return this.genSelect(access, targetFields, skipChecks, subPaths);
|
|
1901
|
+
}
|
|
1902
|
+
pickAllowedFields(doc, access, baseFields) {
|
|
1903
|
+
return this.req.dacl.pickAllowedFields(this.dataName, doc, access, baseFields);
|
|
1904
|
+
}
|
|
1905
|
+
trimOutputFields(doc, access, baseFields) {
|
|
1906
|
+
return this.pickAllowedFields(doc, access, baseFields);
|
|
1907
|
+
}
|
|
1908
|
+
};
|
|
1909
|
+
|
|
1910
|
+
// src/processors.ts
|
|
1911
|
+
import { castArray as castArray3, cloneDeep, forEach as forEach5, get as get6, isArray as isArray6, map as map3, set as set5 } from "@web-ts-toolkit/utils";
|
|
1912
|
+
var copyAndDepopulate = (docObject, operations, options = { mutable: true, idField: "_id" }) => {
|
|
1913
|
+
const obj = get6(options, "mutable", true) ? docObject : cloneDeep(docObject);
|
|
1914
|
+
const idField = get6(options, "idField", "_id");
|
|
1915
|
+
forEach5(castArray3(operations), (op) => {
|
|
1916
|
+
if (!op.src || !op.dest) return;
|
|
1917
|
+
let targets = [obj];
|
|
1918
|
+
const segs = op.src.split(".");
|
|
1919
|
+
forEach5(segs, (seg, ind) => {
|
|
1920
|
+
if (segs.length === ind + 1) {
|
|
1921
|
+
forEach5(targets, (target) => {
|
|
1922
|
+
const targetObject = target;
|
|
1923
|
+
set5(targetObject, op.dest, get6(targetObject, seg));
|
|
1924
|
+
set5(
|
|
1925
|
+
targetObject,
|
|
1926
|
+
seg,
|
|
1927
|
+
isArray6(targetObject[seg]) ? map3(targetObject[seg], idField) : get6(targetObject, `${seg}.${idField}`)
|
|
1928
|
+
);
|
|
1929
|
+
});
|
|
1930
|
+
} else {
|
|
1931
|
+
targets = targets.reduce((ret, target) => {
|
|
1932
|
+
const targetObject = target;
|
|
1933
|
+
if (isArray6(targetObject[seg])) ret.push(...targetObject[seg]);
|
|
1934
|
+
else ret.push(targetObject[seg]);
|
|
1935
|
+
return ret;
|
|
1936
|
+
}, []);
|
|
1937
|
+
}
|
|
1938
|
+
});
|
|
1939
|
+
});
|
|
1940
|
+
return obj;
|
|
1941
|
+
};
|
|
1942
|
+
|
|
1943
|
+
// src/symbols.ts
|
|
1944
|
+
var MIDDLEWARE = /* @__PURE__ */ Symbol("middleware");
|
|
1945
|
+
var DATA_MIDDLEWARE = /* @__PURE__ */ Symbol("data-middleware");
|
|
1946
|
+
var CORE = /* @__PURE__ */ Symbol("core");
|
|
1947
|
+
var PERMISSIONS = /* @__PURE__ */ Symbol("permissions");
|
|
1948
|
+
var PERMISSION_KEYS = /* @__PURE__ */ Symbol("permission-keys");
|
|
1949
|
+
|
|
1950
|
+
// src/cache.ts
|
|
1951
|
+
var Cache = class {
|
|
1952
|
+
constructor() {
|
|
1953
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
1954
|
+
}
|
|
1955
|
+
set(key, value) {
|
|
1956
|
+
this.cache.set(key, value);
|
|
1957
|
+
}
|
|
1958
|
+
get(key) {
|
|
1959
|
+
return this.cache.get(key);
|
|
1960
|
+
}
|
|
1961
|
+
delete(key) {
|
|
1962
|
+
this.cache.delete(key);
|
|
1963
|
+
}
|
|
1964
|
+
clear() {
|
|
1965
|
+
this.cache.clear();
|
|
1966
|
+
}
|
|
1967
|
+
has(key) {
|
|
1968
|
+
return this.cache.has(key);
|
|
1969
|
+
}
|
|
1970
|
+
keys() {
|
|
1971
|
+
return Array.from(this.cache.keys());
|
|
1972
|
+
}
|
|
1973
|
+
values() {
|
|
1974
|
+
return Array.from(this.cache.values());
|
|
1975
|
+
}
|
|
1976
|
+
size() {
|
|
1977
|
+
return this.cache.size;
|
|
1978
|
+
}
|
|
1979
|
+
};
|
|
1980
|
+
|
|
1981
|
+
// src/core-shared.ts
|
|
1982
|
+
import {
|
|
1983
|
+
castArray as castArray4,
|
|
1984
|
+
isArray as isArray7,
|
|
1985
|
+
isBoolean as isBoolean4,
|
|
1986
|
+
isEmpty as isEmpty2,
|
|
1987
|
+
isEqual,
|
|
1988
|
+
isFunction as isFunction4,
|
|
1989
|
+
isPlainObject as isPlainObject6,
|
|
1990
|
+
isString as isString6
|
|
1991
|
+
} from "@web-ts-toolkit/utils";
|
|
1992
|
+
|
|
1993
|
+
// src/permission.ts
|
|
1994
|
+
var Permission = class {
|
|
1995
|
+
constructor(permissions) {
|
|
1996
|
+
this.$_permissions = permissions;
|
|
1997
|
+
this.$_permissionKeys = Object.keys(permissions);
|
|
1998
|
+
for (let x = 0; x < this.$_permissionKeys.length; x++) {
|
|
1999
|
+
const key = this.$_permissionKeys[x];
|
|
2000
|
+
Object.defineProperty(this, key, {
|
|
2001
|
+
enumerable: true,
|
|
2002
|
+
get: function() {
|
|
2003
|
+
return this.has(key);
|
|
2004
|
+
}
|
|
2005
|
+
});
|
|
2006
|
+
}
|
|
2007
|
+
}
|
|
2008
|
+
prop(permission) {
|
|
2009
|
+
return this.$_permissions.hasOwnProperty(permission);
|
|
2010
|
+
}
|
|
2011
|
+
has(permission) {
|
|
2012
|
+
return this.$_permissions[permission] || false;
|
|
2013
|
+
}
|
|
2014
|
+
hasAny(permissions) {
|
|
2015
|
+
return permissions.some((permission) => {
|
|
2016
|
+
return this.has(permission);
|
|
2017
|
+
});
|
|
2018
|
+
}
|
|
2019
|
+
hasAll(permissions) {
|
|
2020
|
+
return permissions.every((permission) => {
|
|
2021
|
+
return this.has(permission);
|
|
2022
|
+
});
|
|
2023
|
+
}
|
|
2024
|
+
any(permissions) {
|
|
2025
|
+
return this.hasAny(permissions);
|
|
2026
|
+
}
|
|
2027
|
+
all(permissions) {
|
|
2028
|
+
return this.hasAll(permissions);
|
|
2029
|
+
}
|
|
2030
|
+
};
|
|
2031
|
+
var permission_default = Permission;
|
|
2032
|
+
|
|
2033
|
+
// src/core-shared.ts
|
|
2034
|
+
function isAndFilter(filter2) {
|
|
2035
|
+
return !!filter2 && isPlainObject6(filter2) && Object.keys(filter2).length === 1 && isArray7(filter2.$and);
|
|
2036
|
+
}
|
|
2037
|
+
function isMergeableClause(filter2) {
|
|
2038
|
+
return !!filter2 && isPlainObject6(filter2) && Object.keys(filter2).every((key) => !key.startsWith("$"));
|
|
2039
|
+
}
|
|
2040
|
+
function optimizeAndFilter(clausesInput) {
|
|
2041
|
+
const clauses = [];
|
|
2042
|
+
for (let x = 0; x < clausesInput.length; x++) {
|
|
2043
|
+
const clause = normalizeFilter(clausesInput[x]);
|
|
2044
|
+
if (clause === false) return false;
|
|
2045
|
+
if (!clause) continue;
|
|
2046
|
+
if (isAndFilter(clause)) {
|
|
2047
|
+
clauses.push(...clause.$and);
|
|
2048
|
+
} else {
|
|
2049
|
+
clauses.push(clause);
|
|
2050
|
+
}
|
|
2051
|
+
}
|
|
2052
|
+
const dedupedClauses = clauses.filter(
|
|
2053
|
+
(clause, index) => clauses.findIndex((item) => isEqual(item, clause)) === index
|
|
2054
|
+
);
|
|
2055
|
+
const mergedClause = {};
|
|
2056
|
+
const remainingClauses = [];
|
|
2057
|
+
for (let x = 0; x < dedupedClauses.length; x++) {
|
|
2058
|
+
const clause = dedupedClauses[x];
|
|
2059
|
+
if (!isMergeableClause(clause)) {
|
|
2060
|
+
remainingClauses.push(clause);
|
|
2061
|
+
continue;
|
|
2062
|
+
}
|
|
2063
|
+
let canMerge = true;
|
|
2064
|
+
for (const [key, value] of Object.entries(clause)) {
|
|
2065
|
+
if (Object.prototype.hasOwnProperty.call(mergedClause, key) && !isEqual(mergedClause[key], value)) {
|
|
2066
|
+
canMerge = false;
|
|
2067
|
+
break;
|
|
2068
|
+
}
|
|
2069
|
+
}
|
|
2070
|
+
if (!canMerge) {
|
|
2071
|
+
remainingClauses.push(clause);
|
|
2072
|
+
continue;
|
|
2073
|
+
}
|
|
2074
|
+
Object.assign(mergedClause, clause);
|
|
2075
|
+
}
|
|
2076
|
+
const finalClauses = isEmpty2(mergedClause) ? remainingClauses : [mergedClause, ...remainingClauses];
|
|
2077
|
+
if (finalClauses.length === 0) return null;
|
|
2078
|
+
if (finalClauses.length === 1) return finalClauses[0];
|
|
2079
|
+
return { $and: finalClauses };
|
|
2080
|
+
}
|
|
2081
|
+
function normalizeFilter(filter2) {
|
|
2082
|
+
if (filter2 === false) return false;
|
|
2083
|
+
if (!filter2 || !isPlainObject6(filter2) || isEmpty2(filter2)) return null;
|
|
2084
|
+
if (!isAndFilter(filter2)) {
|
|
2085
|
+
return filter2;
|
|
2086
|
+
}
|
|
2087
|
+
return optimizeAndFilter(filter2.$and);
|
|
2088
|
+
}
|
|
2089
|
+
async function resolveIdentifierFilter(req, identifier, id) {
|
|
2090
|
+
if (isString6(identifier)) {
|
|
2091
|
+
return { [identifier]: id };
|
|
2092
|
+
}
|
|
2093
|
+
if (isFunction4(identifier)) {
|
|
2094
|
+
return identifier.call(req, id);
|
|
2095
|
+
}
|
|
2096
|
+
return { _id: id };
|
|
2097
|
+
}
|
|
2098
|
+
async function resolveAccessFilter({
|
|
2099
|
+
req,
|
|
2100
|
+
permissions,
|
|
2101
|
+
cache,
|
|
2102
|
+
cacheKey,
|
|
2103
|
+
access = "read",
|
|
2104
|
+
filter: filter2 = null,
|
|
2105
|
+
getOption
|
|
2106
|
+
}) {
|
|
2107
|
+
let nextFilter = normalizeFilter(filter2);
|
|
2108
|
+
const overrideFilterFn = getOption(`overrideFilter.${access}`, null);
|
|
2109
|
+
if (isFunction4(overrideFilterFn)) {
|
|
2110
|
+
nextFilter = normalizeFilter(await overrideFilterFn.call(req, nextFilter, permissions));
|
|
2111
|
+
}
|
|
2112
|
+
const baseFilterFn = getOption(`baseFilter.${access}`, null);
|
|
2113
|
+
if (!isFunction4(baseFilterFn)) return nextFilter || {};
|
|
2114
|
+
const baseFilter = normalizeFilter(
|
|
2115
|
+
cache.has(cacheKey) ? cache.get(cacheKey) : await baseFilterFn.call(req, permissions)
|
|
2116
|
+
);
|
|
2117
|
+
if (!cache.has(cacheKey)) {
|
|
2118
|
+
cache.set(cacheKey, baseFilter);
|
|
2119
|
+
}
|
|
2120
|
+
if (baseFilter === false) return false;
|
|
2121
|
+
if (!baseFilter) return nextFilter || {};
|
|
2122
|
+
if (!nextFilter) return baseFilter;
|
|
2123
|
+
return optimizeAndFilter([baseFilter, nextFilter]);
|
|
2124
|
+
}
|
|
2125
|
+
function getRequestPermissions(req) {
|
|
2126
|
+
const requestPermissionField = getGlobalOption("requestPermissionField");
|
|
2127
|
+
return new permission_default(req[requestPermissionField] || {});
|
|
2128
|
+
}
|
|
2129
|
+
async function setRequestPermissions(req) {
|
|
2130
|
+
const requestPermissionField = getGlobalOption("requestPermissionField");
|
|
2131
|
+
if (req[requestPermissionField]) return;
|
|
2132
|
+
const globalPermissions = getGlobalOption("globalPermissions");
|
|
2133
|
+
if (!isFunction4(globalPermissions)) return;
|
|
2134
|
+
const permissions = await globalPermissions.call(req, req);
|
|
2135
|
+
if (isPlainObject6(permissions)) req[requestPermissionField] = permissions;
|
|
2136
|
+
else if (isArray7(permissions)) req[requestPermissionField] = arrToObj(permissions);
|
|
2137
|
+
else if (isString6(permissions)) req[requestPermissionField] = { [permissions]: true };
|
|
2138
|
+
}
|
|
2139
|
+
async function evaluateRouteGuard(req, permissions, routeGuard) {
|
|
2140
|
+
const phas = (key) => permissions.has(key);
|
|
2141
|
+
const { stringHandler, arrayHandler } = createValidator(phas);
|
|
2142
|
+
if (isBoolean4(routeGuard)) {
|
|
2143
|
+
return routeGuard === true;
|
|
2144
|
+
}
|
|
2145
|
+
if (isString6(routeGuard)) {
|
|
2146
|
+
return stringHandler(routeGuard);
|
|
2147
|
+
}
|
|
2148
|
+
if (isArray7(routeGuard)) {
|
|
2149
|
+
return arrayHandler(routeGuard);
|
|
2150
|
+
}
|
|
2151
|
+
if (isFunction4(routeGuard)) {
|
|
2152
|
+
return routeGuard.call(req, permissions);
|
|
2153
|
+
}
|
|
2154
|
+
return false;
|
|
2155
|
+
}
|
|
2156
|
+
async function callMiddlewareChain(req, middleware, doc, permissions, context) {
|
|
2157
|
+
const middlewares = castArray4(middleware);
|
|
2158
|
+
for (let x = 0; x < middlewares.length; x++) {
|
|
2159
|
+
if (isFunction4(middlewares[x])) {
|
|
2160
|
+
doc = await middlewares[x].call(req, doc, permissions, context);
|
|
2161
|
+
}
|
|
2162
|
+
}
|
|
2163
|
+
return doc;
|
|
2164
|
+
}
|
|
2165
|
+
async function collectSchemaFields({
|
|
2166
|
+
req,
|
|
2167
|
+
permissionSchema,
|
|
2168
|
+
access,
|
|
2169
|
+
baseFields = [],
|
|
2170
|
+
hasPermission,
|
|
2171
|
+
functionArgs = []
|
|
2172
|
+
}) {
|
|
2173
|
+
const fields = [...baseFields ?? []];
|
|
2174
|
+
if (!permissionSchema) return fields;
|
|
2175
|
+
const { stringHandler, arrayHandler } = createValidator(hasPermission);
|
|
2176
|
+
const keys2 = Object.keys(permissionSchema);
|
|
2177
|
+
for (let x = 0; x < keys2.length; x++) {
|
|
2178
|
+
const key = keys2[x];
|
|
2179
|
+
if (baseFields.includes(key)) continue;
|
|
2180
|
+
const val = permissionSchema[key];
|
|
2181
|
+
const value = val && val[access] || val;
|
|
2182
|
+
if (isBoolean4(value)) {
|
|
2183
|
+
if (value) fields.push(key);
|
|
2184
|
+
} else if (isString6(value)) {
|
|
2185
|
+
if (stringHandler(value)) fields.push(key);
|
|
2186
|
+
} else if (isArray7(value)) {
|
|
2187
|
+
if (arrayHandler(value)) fields.push(key);
|
|
2188
|
+
} else if (isFunction4(value)) {
|
|
2189
|
+
if (await value.call(req, ...functionArgs)) fields.push(key);
|
|
2190
|
+
}
|
|
2191
|
+
}
|
|
2192
|
+
return fields;
|
|
2193
|
+
}
|
|
2194
|
+
|
|
2195
|
+
// src/core.ts
|
|
2196
|
+
var Core = class {
|
|
2197
|
+
constructor(req) {
|
|
2198
|
+
this.req = req;
|
|
2199
|
+
this.caches = {
|
|
2200
|
+
baseFilter: new Cache()
|
|
2201
|
+
};
|
|
2202
|
+
}
|
|
2203
|
+
getIdentifier(modelName) {
|
|
2204
|
+
const identifier = getModelOption(modelName, "identifier");
|
|
2205
|
+
if (isFunction5(identifier)) {
|
|
2206
|
+
return null;
|
|
2207
|
+
}
|
|
2208
|
+
if (isString7(identifier)) {
|
|
2209
|
+
return identifier;
|
|
2210
|
+
}
|
|
2211
|
+
return "_id";
|
|
2212
|
+
}
|
|
2213
|
+
async genIDFilter(modelName, id) {
|
|
2214
|
+
const identifier = getModelOption(modelName, "identifier");
|
|
2215
|
+
return resolveIdentifierFilter(this.req, identifier, id);
|
|
2216
|
+
}
|
|
2217
|
+
async genFilter(modelName, access = "read", _filter = null) {
|
|
2218
|
+
const permissions = this.getGlobalPermissions();
|
|
2219
|
+
const cacheKey = `${modelName}_baseFilter_${access}`;
|
|
2220
|
+
return resolveAccessFilter({
|
|
2221
|
+
req: this.req,
|
|
2222
|
+
permissions,
|
|
2223
|
+
cache: this.caches.baseFilter,
|
|
2224
|
+
cacheKey,
|
|
2225
|
+
access,
|
|
2226
|
+
filter: _filter,
|
|
2227
|
+
getOption: (key, defaultValue) => getModelOption(modelName, key, defaultValue)
|
|
2228
|
+
});
|
|
2229
|
+
}
|
|
2230
|
+
removePrefix(str, prefix) {
|
|
2231
|
+
if (!prefix) return str;
|
|
2232
|
+
if (str.startsWith(prefix)) {
|
|
2233
|
+
return str.substring(prefix.length);
|
|
2234
|
+
}
|
|
2235
|
+
return str;
|
|
2236
|
+
}
|
|
2237
|
+
async genAllowedFields(modelName, doc, access, baseFields = []) {
|
|
2238
|
+
const permissionSchema = getModelOption(modelName, "permissionSchema");
|
|
2239
|
+
const modelPermissionPrefix = getModelOption(modelName, "modelPermissionPrefix", "");
|
|
2240
|
+
const permissions = this.getGlobalPermissions();
|
|
2241
|
+
const docPermissions = getDocPermissions(modelName, doc);
|
|
2242
|
+
return collectSchemaFields({
|
|
2243
|
+
req: this.req,
|
|
2244
|
+
permissionSchema,
|
|
2245
|
+
access,
|
|
2246
|
+
baseFields,
|
|
2247
|
+
hasPermission: (key) => permissions.has(key) || docPermissions[this.removePrefix(key, modelPermissionPrefix)],
|
|
2248
|
+
functionArgs: [permissions, docPermissions]
|
|
2249
|
+
});
|
|
2250
|
+
}
|
|
2251
|
+
async pickAllowedFields(modelName, doc, access, baseFields = []) {
|
|
2252
|
+
const allowed = await this.genAllowedFields(modelName, doc, access, baseFields);
|
|
2253
|
+
return pickDocFields(doc, allowed);
|
|
2254
|
+
}
|
|
2255
|
+
async genSelect(modelName, access, targetFields = null, skipChecks = true, subPaths = []) {
|
|
2256
|
+
let normalizedSelect = normalizeSelect(targetFields);
|
|
2257
|
+
const permissionSchema = getModelOption(modelName, ["permissionSchema"].concat(subPaths).join("."));
|
|
2258
|
+
if (!permissionSchema) return [];
|
|
2259
|
+
const permissions = this.getGlobalPermissions();
|
|
2260
|
+
let fields = await collectSchemaFields({
|
|
2261
|
+
req: this.req,
|
|
2262
|
+
permissionSchema,
|
|
2263
|
+
access,
|
|
2264
|
+
hasPermission: (key) => {
|
|
2265
|
+
if (permissions.prop(key)) {
|
|
2266
|
+
return permissions.has(key);
|
|
2267
|
+
}
|
|
2268
|
+
return !!skipChecks;
|
|
2269
|
+
},
|
|
2270
|
+
functionArgs: [permissions]
|
|
2271
|
+
});
|
|
2272
|
+
if (normalizedSelect.length > 0) {
|
|
2273
|
+
const excludeid = normalizedSelect.includes("-_id");
|
|
2274
|
+
const excludeall = normalizedSelect.every((v) => v.startsWith("-"));
|
|
2275
|
+
if (excludeall) {
|
|
2276
|
+
normalizedSelect = normalizedSelect.map((v) => v.substring(1));
|
|
2277
|
+
fields = difference(fields, normalizedSelect);
|
|
2278
|
+
if (excludeid) fields.push("-_id");
|
|
2279
|
+
} else {
|
|
2280
|
+
fields = intersection(normalizedSelect, fields.concat(excludeid ? "-_id" : "_id"));
|
|
2281
|
+
}
|
|
2282
|
+
}
|
|
2283
|
+
const mandatoryFields = subPaths.length > 0 ? [] : getModelOption(modelName, `mandatoryFields.${access}`, []);
|
|
2284
|
+
return fields.concat(mandatoryFields);
|
|
2285
|
+
}
|
|
2286
|
+
async genPopulate(modelName, access = "read", _populate = null) {
|
|
2287
|
+
if (!_populate) return [];
|
|
2288
|
+
let populate = Array.isArray(_populate) ? _populate : [_populate];
|
|
2289
|
+
populate = compact3(
|
|
2290
|
+
await Promise.all(
|
|
2291
|
+
populate.map(async (p) => {
|
|
2292
|
+
const populateAccess = !isString7(p) && p.access ? p.access : access;
|
|
2293
|
+
const ret = isString7(p) ? { path: p } : {
|
|
2294
|
+
path: p.path,
|
|
2295
|
+
select: normalizeSelect(p.select)
|
|
2296
|
+
};
|
|
2297
|
+
const allowedParentPaths = await this.genSelect(modelName, populateAccess, [ret.path], false);
|
|
2298
|
+
if (!allowedParentPaths.includes(ret.path)) return null;
|
|
2299
|
+
const refModelName = getModelRef(modelName, ret.path);
|
|
2300
|
+
if (!refModelName) return null;
|
|
2301
|
+
ret.select = await this.genSelect(refModelName, populateAccess, ret.select, false);
|
|
2302
|
+
const filter2 = await this.genFilter(refModelName, populateAccess, null);
|
|
2303
|
+
if (filter2 === false) return null;
|
|
2304
|
+
ret.match = filter2;
|
|
2305
|
+
return ret;
|
|
2306
|
+
})
|
|
2307
|
+
)
|
|
2308
|
+
);
|
|
2309
|
+
return populate;
|
|
2310
|
+
}
|
|
2311
|
+
async validate(modelName, allowedData, access, context) {
|
|
2312
|
+
const validate = getModelOption(modelName, `validate.${access}`, null);
|
|
2313
|
+
if (isFunction5(validate)) {
|
|
2314
|
+
const permissions = this.getGlobalPermissions();
|
|
2315
|
+
return validate.call(this.req, allowedData, permissions, context);
|
|
2316
|
+
} else if (isBoolean5(validate) || isArray8(validate)) {
|
|
2317
|
+
return validate;
|
|
2318
|
+
} else {
|
|
2319
|
+
return true;
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
2322
|
+
async prepare(modelName, allowedData, access, context) {
|
|
2323
|
+
const prepare = getModelOption(modelName, `prepare.${access}`, null);
|
|
2324
|
+
const permissions = this.getGlobalPermissions();
|
|
2325
|
+
return callMiddlewareChain(this.req, prepare, allowedData, permissions, context);
|
|
2326
|
+
}
|
|
2327
|
+
async transform(modelName, doc, access, context) {
|
|
2328
|
+
const transform = getModelOption(modelName, `transform.${access}`, null);
|
|
2329
|
+
const permissions = this.getGlobalPermissions();
|
|
2330
|
+
return callMiddlewareChain(this.req, transform, doc, permissions, context);
|
|
2331
|
+
}
|
|
2332
|
+
async finalize(modelName, doc, access, context) {
|
|
2333
|
+
const finalize = getModelOption(modelName, `finalize.${access}`, null);
|
|
2334
|
+
const permissions = this.getGlobalPermissions();
|
|
2335
|
+
return callMiddlewareChain(this.req, finalize, doc, permissions, context);
|
|
2336
|
+
}
|
|
2337
|
+
async changes(modelName, doc, context) {
|
|
2338
|
+
const changeOptions = getModelOption(modelName, `change`, {});
|
|
2339
|
+
for (let x = 0; x < context.modifiedPaths.length; x++) {
|
|
2340
|
+
const mpath = context.modifiedPaths[x];
|
|
2341
|
+
if (isFunction5(changeOptions[mpath])) {
|
|
2342
|
+
await changeOptions[mpath].call(
|
|
2343
|
+
this.req,
|
|
2344
|
+
context.originalDocObject[mpath],
|
|
2345
|
+
doc[mpath],
|
|
2346
|
+
context.changes.filter((di) => di.path.length > 0 && di.path[0] === mpath),
|
|
2347
|
+
context
|
|
2348
|
+
);
|
|
2349
|
+
}
|
|
2350
|
+
}
|
|
2351
|
+
}
|
|
2352
|
+
async genDocPermissions(modelName, doc, access, context) {
|
|
2353
|
+
const docPermissionsFn = getModelOption(modelName, `docPermissions.${access}`, null);
|
|
2354
|
+
let docPermissions = {};
|
|
2355
|
+
if (isFunction5(docPermissionsFn)) {
|
|
2356
|
+
const permissions = this.getGlobalPermissions();
|
|
2357
|
+
try {
|
|
2358
|
+
docPermissions = await docPermissionsFn.call(this.req, doc, permissions, context);
|
|
2359
|
+
} catch (error) {
|
|
2360
|
+
logger.warn(
|
|
2361
|
+
`docPermissions hook failed for model=${modelName} access=${access}: ${error instanceof Error ? error.message : String(error)}`
|
|
2362
|
+
);
|
|
2363
|
+
}
|
|
2364
|
+
}
|
|
2365
|
+
return docPermissions;
|
|
2366
|
+
}
|
|
2367
|
+
addEmptyPermissions(modelName, doc) {
|
|
2368
|
+
const docPermissionField = getModelOption(modelName, "documentPermissionField");
|
|
2369
|
+
setDocValue(doc, docPermissionField, { _view: { $: "_" }, _edit: { $: "_" } });
|
|
2370
|
+
return doc;
|
|
2371
|
+
}
|
|
2372
|
+
async addDocPermissions(modelName, doc, access, context) {
|
|
2373
|
+
const docPermissionField = getModelOption(modelName, "documentPermissionField");
|
|
2374
|
+
const docPermissions = await this.genDocPermissions(modelName, doc, access, context);
|
|
2375
|
+
setDocValue(doc, docPermissionField, docPermissions);
|
|
2376
|
+
return doc;
|
|
2377
|
+
}
|
|
2378
|
+
async addFieldPermissions(modelName, doc, access, context) {
|
|
2379
|
+
const docPermissionField = getModelOption(modelName, "documentPermissionField");
|
|
2380
|
+
const docId = String(doc._id);
|
|
2381
|
+
let readExists = true;
|
|
2382
|
+
let updateExists = true;
|
|
2383
|
+
if (access !== "read") {
|
|
2384
|
+
if (context.fieldPermissionAccess?.readIds) {
|
|
2385
|
+
readExists = context.fieldPermissionAccess.readIds.has(docId);
|
|
2386
|
+
} else {
|
|
2387
|
+
const existsResult = await this.req.macl.getService(modelName).exists({ _id: doc._id }, { access: "read" });
|
|
2388
|
+
readExists = existsResult.success ? !!existsResult.data : false;
|
|
2389
|
+
}
|
|
2390
|
+
}
|
|
2391
|
+
if (access !== "update") {
|
|
2392
|
+
if (context.fieldPermissionAccess?.updateIds) {
|
|
2393
|
+
updateExists = context.fieldPermissionAccess.updateIds.has(docId);
|
|
2394
|
+
} else {
|
|
2395
|
+
const existsResult = await this.req.macl.getService(modelName).exists({ _id: doc._id }, { access: "update" });
|
|
2396
|
+
updateExists = existsResult.success ? !!existsResult.data : false;
|
|
2397
|
+
}
|
|
2398
|
+
}
|
|
2399
|
+
const [views, edits] = await Promise.all([
|
|
2400
|
+
readExists ? this.genAllowedFields(modelName, doc, "read") : [],
|
|
2401
|
+
updateExists ? this.genAllowedFields(modelName, doc, "update") : []
|
|
2402
|
+
]);
|
|
2403
|
+
const viewObj = reduce2(
|
|
2404
|
+
views,
|
|
2405
|
+
(ret, view) => {
|
|
2406
|
+
ret[view] = true;
|
|
2407
|
+
return ret;
|
|
2408
|
+
},
|
|
2409
|
+
{}
|
|
2410
|
+
);
|
|
2411
|
+
const editObj = reduce2(
|
|
2412
|
+
edits,
|
|
2413
|
+
(ret, view) => {
|
|
2414
|
+
ret[view] = true;
|
|
2415
|
+
return ret;
|
|
2416
|
+
},
|
|
2417
|
+
{}
|
|
2418
|
+
);
|
|
2419
|
+
setDocValue(doc, `${docPermissionField}._view`, viewObj);
|
|
2420
|
+
setDocValue(doc, `${docPermissionField}._edit`, editObj);
|
|
2421
|
+
return doc;
|
|
2422
|
+
}
|
|
2423
|
+
async decorate(modelName, doc, access, context) {
|
|
2424
|
+
const decorate = getModelOption(modelName, `decorate.${access}`, null);
|
|
2425
|
+
const permissions = this.getGlobalPermissions();
|
|
2426
|
+
context.docPermissions = getDocPermissions(modelName, doc);
|
|
2427
|
+
return callMiddlewareChain(this.req, decorate, doc, permissions, context);
|
|
2428
|
+
}
|
|
2429
|
+
async decorateAll(modelName, docs, access, context) {
|
|
2430
|
+
const decorateAll = getModelOption(modelName, `decorateAll.${access}`, null);
|
|
2431
|
+
const permissions = this.getGlobalPermissions();
|
|
2432
|
+
return callMiddlewareChain(this.req, decorateAll, docs, permissions, context);
|
|
2433
|
+
}
|
|
2434
|
+
runTasks(modelName, docObject, task) {
|
|
2435
|
+
const tasks = compact3(castArray5(task));
|
|
2436
|
+
if (tasks.length === 0) return docObject;
|
|
2437
|
+
forEach6(tasks, (task2) => {
|
|
2438
|
+
const { type, args, options } = task2;
|
|
2439
|
+
switch (type) {
|
|
2440
|
+
case "COPY_AND_DEPOPULATE":
|
|
2441
|
+
docObject = copyAndDepopulate(
|
|
2442
|
+
docObject,
|
|
2443
|
+
args,
|
|
2444
|
+
options
|
|
2445
|
+
);
|
|
2446
|
+
break;
|
|
2447
|
+
}
|
|
2448
|
+
});
|
|
2449
|
+
return docObject;
|
|
2450
|
+
}
|
|
2451
|
+
getPermissions() {
|
|
2452
|
+
return getRequestPermissions(this.req);
|
|
2453
|
+
}
|
|
2454
|
+
async setPermissions() {
|
|
2455
|
+
await setRequestPermissions(this.req);
|
|
2456
|
+
}
|
|
2457
|
+
async canActivate(routeGuard) {
|
|
2458
|
+
const permissions = this.getGlobalPermissions();
|
|
2459
|
+
return evaluateRouteGuard(this.req, permissions, routeGuard);
|
|
2460
|
+
}
|
|
2461
|
+
async isAllowed(modelName, access) {
|
|
2462
|
+
if (access.startsWith("subs")) {
|
|
2463
|
+
const keys2 = access.split(".");
|
|
2464
|
+
if (keys2.length < 3) {
|
|
2465
|
+
return false;
|
|
2466
|
+
}
|
|
2467
|
+
const [, field, op] = keys2;
|
|
2468
|
+
const subOption = getExactModelOption(modelName, `routeGuard.${access}`);
|
|
2469
|
+
if (isUndefined(subOption)) {
|
|
2470
|
+
const subFieldOption = getExactModelOption(modelName, `routeGuard.subs.${field}`);
|
|
2471
|
+
if (isUndefined(subFieldOption)) {
|
|
2472
|
+
const opOption = getModelOption(modelName, `routeGuard.${op}`);
|
|
2473
|
+
return this.canActivate(opOption);
|
|
2474
|
+
}
|
|
2475
|
+
return this.canActivate(subFieldOption);
|
|
2476
|
+
}
|
|
2477
|
+
return this.canActivate(subOption);
|
|
2478
|
+
}
|
|
2479
|
+
const routeGuard = getModelOption(modelName, `routeGuard.${access}`);
|
|
2480
|
+
return this.canActivate(routeGuard);
|
|
2481
|
+
}
|
|
2482
|
+
getService(modelName) {
|
|
2483
|
+
return new Service(this.req, modelName);
|
|
2484
|
+
}
|
|
2485
|
+
getPublicService(modelName) {
|
|
2486
|
+
return new PublicService(this.req, modelName);
|
|
2487
|
+
}
|
|
2488
|
+
service(modelName) {
|
|
2489
|
+
return this.getPublicService(modelName);
|
|
2490
|
+
}
|
|
2491
|
+
svc(modelName) {
|
|
2492
|
+
return this.getPublicService(modelName);
|
|
2493
|
+
}
|
|
2494
|
+
getGlobalPermissions() {
|
|
2495
|
+
return this.req[PERMISSIONS];
|
|
2496
|
+
}
|
|
2497
|
+
};
|
|
2498
|
+
async function setCore(req, res, next) {
|
|
2499
|
+
if (req[MIDDLEWARE]) return next();
|
|
2500
|
+
const core = new Core(req);
|
|
2501
|
+
await core.setPermissions();
|
|
2502
|
+
req.macl = core;
|
|
2503
|
+
req[PERMISSIONS] = core.getPermissions();
|
|
2504
|
+
req[PERMISSION_KEYS] = req[PERMISSIONS].$_permissionKeys;
|
|
2505
|
+
req[MIDDLEWARE] = true;
|
|
2506
|
+
next();
|
|
2507
|
+
}
|
|
2508
|
+
|
|
2509
|
+
// src/middleware.ts
|
|
2510
|
+
function macl() {
|
|
2511
|
+
return async function(req, res, next) {
|
|
2512
|
+
await setCore(req, res, next);
|
|
2513
|
+
};
|
|
2514
|
+
}
|
|
2515
|
+
function guard(condition) {
|
|
2516
|
+
return async (req, _res, next) => {
|
|
2517
|
+
const permissions = req[PERMISSIONS];
|
|
2518
|
+
let cond = condition;
|
|
2519
|
+
let phas = (key) => permissions.has(key);
|
|
2520
|
+
if (isPlainObject7(condition)) {
|
|
2521
|
+
const { modelName, id, condition: _cond } = condition;
|
|
2522
|
+
const svc = req.macl.getPublicService(modelName);
|
|
2523
|
+
const select = getModelOption(modelName, `mandatoryFields.read`, void 0);
|
|
2524
|
+
let _id = isString8(id) ? id : null;
|
|
2525
|
+
if (isPlainObject7(id)) {
|
|
2526
|
+
const { type = "param", key } = id;
|
|
2527
|
+
if (type === "param") {
|
|
2528
|
+
const paramValue = req.params[key];
|
|
2529
|
+
_id = isArray9(paramValue) ? paramValue[0] ?? null : paramValue ?? null;
|
|
2530
|
+
} else if (type === "query") {
|
|
2531
|
+
const _qval = req.query[key];
|
|
2532
|
+
if (isArray9(_qval)) _id = _qval.length > 0 ? _qval[0] : null;
|
|
2533
|
+
else _id = _qval;
|
|
2534
|
+
} else {
|
|
2535
|
+
_id = null;
|
|
2536
|
+
}
|
|
2537
|
+
}
|
|
2538
|
+
if (!isString8(_id) || !_id) {
|
|
2539
|
+
return next(new JsonRouter2.clientErrors.BadRequestError());
|
|
2540
|
+
}
|
|
2541
|
+
const result = await svc._read(_id, { select });
|
|
2542
|
+
if (!result.success) {
|
|
2543
|
+
return next(new JsonRouter2.clientErrors.UnauthorizedError());
|
|
2544
|
+
}
|
|
2545
|
+
const docPermissions = getDocPermissions(modelName, result.data);
|
|
2546
|
+
phas = (key) => permissions.has(key) || docPermissions[key];
|
|
2547
|
+
cond = _cond;
|
|
2548
|
+
}
|
|
2549
|
+
const { stringHandler, arrayHandler } = createValidator(phas);
|
|
2550
|
+
if (isString8(cond)) {
|
|
2551
|
+
if (stringHandler(cond)) return next();
|
|
2552
|
+
} else if (isArray9(cond)) {
|
|
2553
|
+
if (arrayHandler(cond)) return next();
|
|
2554
|
+
} else if (isFunction6(cond)) {
|
|
2555
|
+
const result = await cond.call(req, permissions);
|
|
2556
|
+
if (!!result) return next();
|
|
2557
|
+
}
|
|
2558
|
+
next(new JsonRouter2.clientErrors.UnauthorizedError());
|
|
2559
|
+
};
|
|
2560
|
+
}
|
|
2561
|
+
|
|
2562
|
+
// src/routers/model-router.ts
|
|
2563
|
+
import JsonRouter4 from "@web-ts-toolkit/express-json-router";
|
|
2564
|
+
import { forEach as forEach7, isPlainObject as isPlainObject8, isString as isString9, isUndefined as isUndefined2, padEnd } from "@web-ts-toolkit/utils";
|
|
2565
|
+
|
|
2566
|
+
// src/routers/shared.ts
|
|
2567
|
+
var parseBooleanString = (str, defaultValue) => str ? str === "true" : defaultValue;
|
|
2568
|
+
var formatListResponse = (req, result, includeCount, includeExtraHeaders) => {
|
|
2569
|
+
const { data, count: returnedCount, totalCount } = result;
|
|
2570
|
+
const query = result.query ?? {};
|
|
2571
|
+
const rawLimit = Number(query.limit ?? 0);
|
|
2572
|
+
const skip = Number(query.skip ?? 0);
|
|
2573
|
+
const limit = rawLimit > 0 ? rawLimit : returnedCount;
|
|
2574
|
+
const pageSize = limit;
|
|
2575
|
+
const page = rawLimit > 0 ? Math.floor(skip / limit) + 1 : 1;
|
|
2576
|
+
const hasPreviousPage = skip > 0;
|
|
2577
|
+
const meta = {
|
|
2578
|
+
returnedCount,
|
|
2579
|
+
skip,
|
|
2580
|
+
limit,
|
|
2581
|
+
page,
|
|
2582
|
+
pageSize,
|
|
2583
|
+
hasPreviousPage,
|
|
2584
|
+
...includeCount && totalCount != null ? {
|
|
2585
|
+
totalCount,
|
|
2586
|
+
totalPages: limit > 0 ? Math.ceil(totalCount / limit) : 1,
|
|
2587
|
+
hasNextPage: skip + returnedCount < totalCount
|
|
2588
|
+
} : {}
|
|
2589
|
+
};
|
|
2590
|
+
if (includeExtraHeaders) {
|
|
2591
|
+
req.res.setHeader("wtt-returned-count" /* ReturnedCount */, returnedCount);
|
|
2592
|
+
req.res.setHeader("wtt-page" /* Page */, page);
|
|
2593
|
+
req.res.setHeader("wtt-page-size" /* PageSize */, pageSize);
|
|
2594
|
+
req.res.setHeader("wtt-has-previous-page" /* HasPreviousPage */, String(hasPreviousPage));
|
|
2595
|
+
if (includeCount && totalCount != null) {
|
|
2596
|
+
req.res.setHeader("wtt-total-count" /* TotalCount */, totalCount);
|
|
2597
|
+
req.res.setHeader("wtt-total-pages" /* TotalPages */, meta.totalPages);
|
|
2598
|
+
req.res.setHeader("wtt-has-next-page" /* HasNextPage */, String(meta.hasNextPage));
|
|
2599
|
+
}
|
|
2600
|
+
}
|
|
2601
|
+
return { data, meta };
|
|
2602
|
+
};
|
|
2603
|
+
var formatCreatedData = (result) => {
|
|
2604
|
+
return result.count === 1 ? result.data[0] : result.data;
|
|
2605
|
+
};
|
|
2606
|
+
var formatUpsertCreatedData = (result) => {
|
|
2607
|
+
return result.data.length > 0 ? result.data[0] : null;
|
|
2608
|
+
};
|
|
2609
|
+
|
|
2610
|
+
// src/routers/validation.ts
|
|
2611
|
+
import JsonRouter3 from "@web-ts-toolkit/express-json-router";
|
|
2612
|
+
import { z } from "zod";
|
|
2613
|
+
var clientErrors = JsonRouter3.clientErrors;
|
|
2614
|
+
var stringOrStringArray = z.union([z.string(), z.array(z.string())]);
|
|
2615
|
+
var queryBooleanString = z.enum(["true", "false"]);
|
|
2616
|
+
var positiveIntegerString = z.string().regex(/^\d+$/, "Expected a non-negative integer");
|
|
2617
|
+
var unknownRecord = z.record(z.string(), z.unknown());
|
|
2618
|
+
var projectionObjectSchema = z.record(z.string(), z.union([z.literal(1), z.literal(-1)]));
|
|
2619
|
+
var projectionSchema = z.union([z.string(), z.array(z.string()), projectionObjectSchema]);
|
|
2620
|
+
var sortOrderSchema = z.union([
|
|
2621
|
+
z.literal(-1),
|
|
2622
|
+
z.literal(1),
|
|
2623
|
+
z.literal("asc"),
|
|
2624
|
+
z.literal("ascending"),
|
|
2625
|
+
z.literal("desc"),
|
|
2626
|
+
z.literal("descending")
|
|
2627
|
+
]);
|
|
2628
|
+
var sortSchema = z.union([
|
|
2629
|
+
z.string(),
|
|
2630
|
+
z.record(z.string(), sortOrderSchema),
|
|
2631
|
+
z.array(z.tuple([z.string(), sortOrderSchema])),
|
|
2632
|
+
z.null()
|
|
2633
|
+
]);
|
|
2634
|
+
var populateSchema = z.union([
|
|
2635
|
+
z.string(),
|
|
2636
|
+
z.array(
|
|
2637
|
+
z.union([
|
|
2638
|
+
z.string(),
|
|
2639
|
+
z.object({
|
|
2640
|
+
path: z.string().min(1),
|
|
2641
|
+
select: projectionSchema.optional(),
|
|
2642
|
+
match: z.unknown().optional(),
|
|
2643
|
+
access: z.enum(["list", "read"]).optional()
|
|
2644
|
+
}).passthrough()
|
|
2645
|
+
])
|
|
2646
|
+
),
|
|
2647
|
+
z.object({
|
|
2648
|
+
path: z.string().min(1),
|
|
2649
|
+
select: projectionSchema.optional(),
|
|
2650
|
+
match: z.unknown().optional(),
|
|
2651
|
+
access: z.enum(["list", "read"]).optional()
|
|
2652
|
+
}).passthrough()
|
|
2653
|
+
]);
|
|
2654
|
+
var subPopulateSchema = z.union([
|
|
2655
|
+
z.string(),
|
|
2656
|
+
z.array(
|
|
2657
|
+
z.union([
|
|
2658
|
+
z.string(),
|
|
2659
|
+
z.object({
|
|
2660
|
+
path: z.string().min(1),
|
|
2661
|
+
select: projectionSchema.optional()
|
|
2662
|
+
}).passthrough()
|
|
2663
|
+
])
|
|
2664
|
+
),
|
|
2665
|
+
z.object({
|
|
2666
|
+
path: z.string().min(1),
|
|
2667
|
+
select: projectionSchema.optional()
|
|
2668
|
+
}).passthrough()
|
|
2669
|
+
]);
|
|
2670
|
+
var includeItemSchema = z.object({
|
|
2671
|
+
model: z.string().min(1),
|
|
2672
|
+
op: z.enum(["list", "read", "count"]),
|
|
2673
|
+
path: z.string().min(1),
|
|
2674
|
+
filter: z.record(z.string(), z.unknown()).optional(),
|
|
2675
|
+
localField: z.string().min(1),
|
|
2676
|
+
foreignField: z.string().min(1),
|
|
2677
|
+
args: z.unknown().optional(),
|
|
2678
|
+
options: z.unknown().optional()
|
|
2679
|
+
}).passthrough();
|
|
2680
|
+
var includeSchema = z.union([includeItemSchema, z.array(includeItemSchema)]);
|
|
2681
|
+
var fieldsSchema = z.array(z.string().min(1));
|
|
2682
|
+
var taskSchema = z.object({
|
|
2683
|
+
type: z.string().min(1),
|
|
2684
|
+
args: z.unknown(),
|
|
2685
|
+
options: unknownRecord.optional()
|
|
2686
|
+
});
|
|
2687
|
+
var tasksSchema = z.union([taskSchema, z.array(taskSchema)]);
|
|
2688
|
+
var objectOrArraySchema = z.union([z.record(z.string(), z.unknown()), z.array(z.unknown())]);
|
|
2689
|
+
var listQuerySchema = z.object({
|
|
2690
|
+
skip: positiveIntegerString.optional(),
|
|
2691
|
+
limit: positiveIntegerString.optional(),
|
|
2692
|
+
page: positiveIntegerString.optional(),
|
|
2693
|
+
page_size: positiveIntegerString.optional(),
|
|
2694
|
+
skim: queryBooleanString.optional(),
|
|
2695
|
+
include_permissions: queryBooleanString.optional(),
|
|
2696
|
+
include_count: queryBooleanString.optional(),
|
|
2697
|
+
include_extra_headers: queryBooleanString.optional()
|
|
2698
|
+
}).passthrough();
|
|
2699
|
+
var createQuerySchema = z.object({
|
|
2700
|
+
include_permissions: queryBooleanString.optional()
|
|
2701
|
+
}).passthrough();
|
|
2702
|
+
var readQuerySchema = z.object({
|
|
2703
|
+
include_permissions: queryBooleanString.optional(),
|
|
2704
|
+
try_list: queryBooleanString.optional()
|
|
2705
|
+
}).passthrough();
|
|
2706
|
+
var updateQuerySchema = z.object({
|
|
2707
|
+
returning_all: queryBooleanString.optional()
|
|
2708
|
+
}).passthrough();
|
|
2709
|
+
var upsertQuerySchema = z.object({
|
|
2710
|
+
returning_all: queryBooleanString.optional(),
|
|
2711
|
+
include_permissions: queryBooleanString.optional()
|
|
2712
|
+
}).passthrough();
|
|
2713
|
+
var rootQueryEntrySchema = z.object({
|
|
2714
|
+
model: z.string().min(1),
|
|
2715
|
+
op: z.string().min(1),
|
|
2716
|
+
id: z.string().min(1).optional(),
|
|
2717
|
+
field: z.string().min(1).optional(),
|
|
2718
|
+
filter: objectOrArraySchema.optional(),
|
|
2719
|
+
data: z.unknown().optional(),
|
|
2720
|
+
args: z.unknown().optional(),
|
|
2721
|
+
options: z.unknown().optional(),
|
|
2722
|
+
order: z.number().int().optional()
|
|
2723
|
+
}).passthrough();
|
|
2724
|
+
var rootQuerySchema = z.array(rootQueryEntrySchema);
|
|
2725
|
+
var listBodySchema = z.object({
|
|
2726
|
+
query: objectOrArraySchema.optional(),
|
|
2727
|
+
filter: objectOrArraySchema.optional(),
|
|
2728
|
+
select: projectionSchema.optional(),
|
|
2729
|
+
sort: sortSchema.optional(),
|
|
2730
|
+
populate: populateSchema.optional(),
|
|
2731
|
+
include: includeSchema.optional(),
|
|
2732
|
+
tasks: tasksSchema.optional(),
|
|
2733
|
+
skip: z.union([z.number(), positiveIntegerString]).optional(),
|
|
2734
|
+
limit: z.union([z.number(), positiveIntegerString]).optional(),
|
|
2735
|
+
page: z.union([z.number(), positiveIntegerString]).optional(),
|
|
2736
|
+
pageSize: z.union([z.number(), positiveIntegerString]).optional(),
|
|
2737
|
+
options: z.object({
|
|
2738
|
+
skim: z.boolean().optional(),
|
|
2739
|
+
includePermissions: z.boolean().optional(),
|
|
2740
|
+
includeCount: z.boolean().optional(),
|
|
2741
|
+
includeExtraHeaders: z.boolean().optional(),
|
|
2742
|
+
populateAccess: z.unknown().optional()
|
|
2743
|
+
}).passthrough().optional()
|
|
2744
|
+
}).passthrough();
|
|
2745
|
+
var dataListBodySchema = z.object({
|
|
2746
|
+
filter: objectOrArraySchema.optional(),
|
|
2747
|
+
select: projectionSchema.optional(),
|
|
2748
|
+
sort: sortSchema.optional(),
|
|
2749
|
+
skip: z.union([z.number(), positiveIntegerString]).optional(),
|
|
2750
|
+
limit: z.union([z.number(), positiveIntegerString]).optional(),
|
|
2751
|
+
page: z.union([z.number(), positiveIntegerString]).optional(),
|
|
2752
|
+
pageSize: z.union([z.number(), positiveIntegerString]).optional(),
|
|
2753
|
+
options: z.object({
|
|
2754
|
+
includeCount: z.boolean().optional(),
|
|
2755
|
+
includeExtraHeaders: z.boolean().optional()
|
|
2756
|
+
}).passthrough().optional()
|
|
2757
|
+
}).passthrough();
|
|
2758
|
+
var countBodySchema = z.object({
|
|
2759
|
+
query: objectOrArraySchema.optional(),
|
|
2760
|
+
filter: objectOrArraySchema.optional(),
|
|
2761
|
+
access: z.unknown().optional()
|
|
2762
|
+
}).passthrough();
|
|
2763
|
+
var readFilterBodySchema = z.object({
|
|
2764
|
+
filter: objectOrArraySchema.optional(),
|
|
2765
|
+
select: projectionSchema.optional(),
|
|
2766
|
+
sort: sortSchema.optional(),
|
|
2767
|
+
populate: populateSchema.optional(),
|
|
2768
|
+
include: includeSchema.optional(),
|
|
2769
|
+
tasks: tasksSchema.optional(),
|
|
2770
|
+
options: z.object({
|
|
2771
|
+
skim: z.boolean().optional(),
|
|
2772
|
+
includePermissions: z.boolean().optional(),
|
|
2773
|
+
tryList: z.boolean().optional(),
|
|
2774
|
+
populateAccess: z.unknown().optional()
|
|
2775
|
+
}).passthrough().optional()
|
|
2776
|
+
}).passthrough();
|
|
2777
|
+
var dataReadFilterBodySchema = z.object({
|
|
2778
|
+
filter: objectOrArraySchema.optional(),
|
|
2779
|
+
select: projectionSchema.optional(),
|
|
2780
|
+
options: z.object({}).passthrough().optional()
|
|
2781
|
+
}).passthrough();
|
|
2782
|
+
var readByIdBodySchema = z.object({
|
|
2783
|
+
select: projectionSchema.optional(),
|
|
2784
|
+
populate: populateSchema.optional(),
|
|
2785
|
+
include: includeSchema.optional(),
|
|
2786
|
+
tasks: tasksSchema.optional(),
|
|
2787
|
+
options: z.object({
|
|
2788
|
+
skim: z.boolean().optional(),
|
|
2789
|
+
includePermissions: z.boolean().optional(),
|
|
2790
|
+
tryList: z.boolean().optional(),
|
|
2791
|
+
populateAccess: z.unknown().optional()
|
|
2792
|
+
}).passthrough().optional()
|
|
2793
|
+
}).passthrough();
|
|
2794
|
+
var dataReadByIdBodySchema = z.object({
|
|
2795
|
+
select: projectionSchema.optional(),
|
|
2796
|
+
options: z.object({}).passthrough().optional()
|
|
2797
|
+
}).passthrough();
|
|
2798
|
+
var advancedCreateBodySchema = z.object({
|
|
2799
|
+
data: z.unknown(),
|
|
2800
|
+
select: projectionSchema.optional(),
|
|
2801
|
+
populate: populateSchema.optional(),
|
|
2802
|
+
tasks: tasksSchema.optional(),
|
|
2803
|
+
options: z.object({
|
|
2804
|
+
includePermissions: z.boolean().optional(),
|
|
2805
|
+
populateAccess: z.unknown().optional()
|
|
2806
|
+
}).passthrough().optional()
|
|
2807
|
+
}).passthrough();
|
|
2808
|
+
var createBodySchema = z.union([
|
|
2809
|
+
z.record(z.string(), z.unknown()),
|
|
2810
|
+
z.array(z.record(z.string(), z.unknown()))
|
|
2811
|
+
]);
|
|
2812
|
+
var updateBodySchema = z.record(z.string(), z.unknown());
|
|
2813
|
+
var advancedUpdateBodySchema = z.object({
|
|
2814
|
+
data: z.unknown(),
|
|
2815
|
+
select: projectionSchema.optional(),
|
|
2816
|
+
populate: populateSchema.optional(),
|
|
2817
|
+
tasks: tasksSchema.optional(),
|
|
2818
|
+
options: z.object({
|
|
2819
|
+
returningAll: z.boolean().optional(),
|
|
2820
|
+
includePermissions: z.boolean().optional(),
|
|
2821
|
+
populateAccess: z.unknown().optional()
|
|
2822
|
+
}).passthrough().optional()
|
|
2823
|
+
}).passthrough();
|
|
2824
|
+
var upsertBodySchema = z.record(z.string(), z.unknown());
|
|
2825
|
+
var advancedUpsertBodySchema = z.object({
|
|
2826
|
+
data: z.record(z.string(), z.unknown()),
|
|
2827
|
+
select: projectionSchema.optional(),
|
|
2828
|
+
populate: populateSchema.optional(),
|
|
2829
|
+
tasks: tasksSchema.optional(),
|
|
2830
|
+
options: z.object({
|
|
2831
|
+
returningAll: z.boolean().optional(),
|
|
2832
|
+
includePermissions: z.boolean().optional(),
|
|
2833
|
+
populateAccess: z.unknown().optional()
|
|
2834
|
+
}).passthrough().optional()
|
|
2835
|
+
}).passthrough();
|
|
2836
|
+
var distinctBodySchema = z.object({
|
|
2837
|
+
query: objectOrArraySchema.optional(),
|
|
2838
|
+
filter: objectOrArraySchema.optional()
|
|
2839
|
+
}).passthrough();
|
|
2840
|
+
var subListBodySchema = z.object({
|
|
2841
|
+
filter: z.record(z.string(), z.unknown()).optional(),
|
|
2842
|
+
fields: fieldsSchema.optional()
|
|
2843
|
+
}).passthrough();
|
|
2844
|
+
var subMutationBodySchema = z.union([
|
|
2845
|
+
z.record(z.string(), z.unknown()),
|
|
2846
|
+
z.array(z.record(z.string(), z.unknown()))
|
|
2847
|
+
]);
|
|
2848
|
+
var subReadBodySchema = z.object({
|
|
2849
|
+
fields: fieldsSchema.optional(),
|
|
2850
|
+
populate: subPopulateSchema.optional()
|
|
2851
|
+
}).passthrough();
|
|
2852
|
+
function parsePathParam(value, parameter) {
|
|
2853
|
+
const result = stringOrStringArray.safeParse(value);
|
|
2854
|
+
if (!result.success) {
|
|
2855
|
+
throwValidationError(result.error.issues, parameter, "parameter");
|
|
2856
|
+
}
|
|
2857
|
+
const param = Array.isArray(result.data) ? result.data[0] : result.data;
|
|
2858
|
+
if (!param) {
|
|
2859
|
+
throw new clientErrors.BadRequestError("Bad Request", {
|
|
2860
|
+
errors: [{ detail: "Required", parameter }]
|
|
2861
|
+
});
|
|
2862
|
+
}
|
|
2863
|
+
return param;
|
|
2864
|
+
}
|
|
2865
|
+
function parseQuery(schema, value) {
|
|
2866
|
+
const result = schema.safeParse(value);
|
|
2867
|
+
if (!result.success) {
|
|
2868
|
+
throwValidationError(result.error.issues, void 0, "parameter");
|
|
2869
|
+
}
|
|
2870
|
+
return result.data;
|
|
2871
|
+
}
|
|
2872
|
+
function parseBody(schema, value) {
|
|
2873
|
+
const result = schema.safeParse(value ?? {});
|
|
2874
|
+
if (!result.success) {
|
|
2875
|
+
throwValidationError(result.error.issues, void 0, "pointer");
|
|
2876
|
+
}
|
|
2877
|
+
return result.data;
|
|
2878
|
+
}
|
|
2879
|
+
function parseBodyWithSchema(schema, value, userSchema) {
|
|
2880
|
+
const body = parseBody(schema, value);
|
|
2881
|
+
return isZodSchema(userSchema) ? parseUserSchema(userSchema, body) : body;
|
|
2882
|
+
}
|
|
2883
|
+
function parseNestedBodyWithSchema(schema, value, nestedKey, userSchema) {
|
|
2884
|
+
const body = parseBody(schema, value);
|
|
2885
|
+
if (!isZodSchema(userSchema)) return body;
|
|
2886
|
+
return {
|
|
2887
|
+
...body,
|
|
2888
|
+
[nestedKey]: parseUserSchema(userSchema, body?.[nestedKey], [nestedKey])
|
|
2889
|
+
};
|
|
2890
|
+
}
|
|
2891
|
+
function throwValidationError(issues, key, location = "pointer") {
|
|
2892
|
+
const errors = issues.map((issue) => formatIssue(issue, key, location));
|
|
2893
|
+
throw new clientErrors.BadRequestError("Bad Request", { errors });
|
|
2894
|
+
}
|
|
2895
|
+
function parseUserSchema(schema, value, prefix = []) {
|
|
2896
|
+
const result = schema.safeParse(value);
|
|
2897
|
+
if (!result.success) {
|
|
2898
|
+
const issues = result.error.issues.map((issue) => ({
|
|
2899
|
+
...issue,
|
|
2900
|
+
path: prefix.concat(issue.path.map(String))
|
|
2901
|
+
}));
|
|
2902
|
+
throwValidationError(issues, void 0, "pointer");
|
|
2903
|
+
}
|
|
2904
|
+
return result.data;
|
|
2905
|
+
}
|
|
2906
|
+
function isZodSchema(schema) {
|
|
2907
|
+
return typeof schema === "object" && schema !== null && "safeParse" in schema && typeof schema.safeParse === "function";
|
|
2908
|
+
}
|
|
2909
|
+
function formatIssue(issue, key, location = "pointer") {
|
|
2910
|
+
const path = issue.path.map(String);
|
|
2911
|
+
const joinedPath = path.join(".");
|
|
2912
|
+
if (location === "parameter") {
|
|
2913
|
+
return {
|
|
2914
|
+
detail: issue.message,
|
|
2915
|
+
parameter: (key ?? joinedPath) || void 0
|
|
2916
|
+
};
|
|
2917
|
+
}
|
|
2918
|
+
return {
|
|
2919
|
+
detail: issue.message,
|
|
2920
|
+
pointer: buildPointer(path)
|
|
2921
|
+
};
|
|
2922
|
+
}
|
|
2923
|
+
function buildPointer(path) {
|
|
2924
|
+
return path.length === 0 ? "#" : `#/${path.join("/")}`;
|
|
2925
|
+
}
|
|
2926
|
+
var requestSchemas = {
|
|
2927
|
+
listQuery: listQuerySchema,
|
|
2928
|
+
createQuery: createQuerySchema,
|
|
2929
|
+
readQuery: readQuerySchema,
|
|
2930
|
+
updateQuery: updateQuerySchema,
|
|
2931
|
+
upsertQuery: upsertQuerySchema
|
|
2932
|
+
};
|
|
2933
|
+
|
|
2934
|
+
// src/routers/model-router.ts
|
|
2935
|
+
var clientErrors2 = JsonRouter4.clientErrors;
|
|
2936
|
+
var success = JsonRouter4.success;
|
|
2937
|
+
function setOption(parentKey, optionKey, option) {
|
|
2938
|
+
const key = isUndefined2(option) ? parentKey : `${parentKey}.${optionKey}`;
|
|
2939
|
+
const value = isUndefined2(option) ? optionKey : option;
|
|
2940
|
+
setModelOption(this.modelName, key, value);
|
|
2941
|
+
return this;
|
|
2942
|
+
}
|
|
2943
|
+
var ModelRouter = class {
|
|
2944
|
+
constructor(modelName, initialOptions) {
|
|
2945
|
+
/**
|
|
2946
|
+
* The maximum limit of the number of documents returned from the `list` operation.
|
|
2947
|
+
*/
|
|
2948
|
+
this.listHardLimit = setOption.bind(this, "listHardLimit");
|
|
2949
|
+
/**
|
|
2950
|
+
* The object schema to define the access control policy for each model field.
|
|
2951
|
+
*/
|
|
2952
|
+
this.permissionSchema = setOption.bind(this, "permissionSchema");
|
|
2953
|
+
/**
|
|
2954
|
+
* The object field to store the document permissions.
|
|
2955
|
+
*/
|
|
2956
|
+
this.documentPermissionField = setOption.bind(this, "documentPermissionField");
|
|
2957
|
+
/**
|
|
2958
|
+
* The essential model fields involved in generating document permissions.
|
|
2959
|
+
*/
|
|
2960
|
+
this.mandatoryFields = setOption.bind(this, "mandatoryFields");
|
|
2961
|
+
/**
|
|
2962
|
+
* The function called in the process of generating document permissions.
|
|
2963
|
+
*/
|
|
2964
|
+
this.docPermissions = setOption.bind(this, "docPermissions");
|
|
2965
|
+
/**
|
|
2966
|
+
* The access control policy for CRUDL endpoints.
|
|
2967
|
+
* @operation `create`, `list`, `read`, `update`, `delete`
|
|
2968
|
+
*/
|
|
2969
|
+
this.routeGuard = setOption.bind(this, "routeGuard");
|
|
2970
|
+
/**
|
|
2971
|
+
* The base filter definitions applied in every query transaction.
|
|
2972
|
+
* @operation `list`, `read`, `update`, `delete`
|
|
2973
|
+
*/
|
|
2974
|
+
this.baseFilter = setOption.bind(this, "baseFilter");
|
|
2975
|
+
/**
|
|
2976
|
+
* The override filter definitions applied in every query transaction.
|
|
2977
|
+
* @operation `list`, `read`, `update`, `delete`
|
|
2978
|
+
*/
|
|
2979
|
+
this.overrideFilter = setOption.bind(this, "overrideFilter");
|
|
2980
|
+
/**
|
|
2981
|
+
* Middleware
|
|
2982
|
+
*
|
|
2983
|
+
* The function called before a new/update document data is processed in `prepare` hooks. This method is used to validate `write data` and throw an error if not valid.
|
|
2984
|
+
* @operation `create`, `update`
|
|
2985
|
+
*/
|
|
2986
|
+
this.validate = setOption.bind(this, "validate");
|
|
2987
|
+
/**
|
|
2988
|
+
* Middleware
|
|
2989
|
+
*
|
|
2990
|
+
* The function called before a new document is created or an existing document is updated. This method is used to process raw data passed into the API endpoints.
|
|
2991
|
+
* @operation `create`, `update`
|
|
2992
|
+
*/
|
|
2993
|
+
this.prepare = setOption.bind(this, "prepare");
|
|
2994
|
+
/**
|
|
2995
|
+
* Middleware
|
|
2996
|
+
*
|
|
2997
|
+
* The function called before an updated document is saved.
|
|
2998
|
+
* @operation `update`
|
|
2999
|
+
*/
|
|
3000
|
+
this.transform = setOption.bind(this, "transform");
|
|
3001
|
+
/**
|
|
3002
|
+
* Middleware
|
|
3003
|
+
*
|
|
3004
|
+
* The function called after a new document is created or an updated document is saved.
|
|
3005
|
+
* @operation `create`, `update`
|
|
3006
|
+
*/
|
|
3007
|
+
this.finalize = setOption.bind(this, "finalize");
|
|
3008
|
+
/**
|
|
3009
|
+
* Middleware
|
|
3010
|
+
*
|
|
3011
|
+
* The function called after a updated document finalized
|
|
3012
|
+
* @operation `update`
|
|
3013
|
+
*/
|
|
3014
|
+
this.change = setOption.bind(this, "change");
|
|
3015
|
+
/**
|
|
3016
|
+
* Middleware
|
|
3017
|
+
*
|
|
3018
|
+
* The function called before response data is sent. This method is used to process raw data to apply custom logic before sending the result.
|
|
3019
|
+
* @operation `list`, `read`, `create`, `update`
|
|
3020
|
+
*/
|
|
3021
|
+
this.decorate = setOption.bind(this, "decorate");
|
|
3022
|
+
/**
|
|
3023
|
+
* Middleware
|
|
3024
|
+
*
|
|
3025
|
+
* The function are called before response data is sent and after `decorate` middleware runs. This method is used to process and filter multiple document objects before sending the result.
|
|
3026
|
+
* @operation `list`
|
|
3027
|
+
*/
|
|
3028
|
+
this.decorateAll = setOption.bind(this, "decorateAll");
|
|
3029
|
+
/**
|
|
3030
|
+
* The document selector definition with the `id` param.
|
|
3031
|
+
* @option `string` | `Function`
|
|
3032
|
+
* @operation `read`, `update`, `delete`
|
|
3033
|
+
*/
|
|
3034
|
+
this.identifier = setOption.bind(this, "identifier");
|
|
3035
|
+
/**
|
|
3036
|
+
* The default values used when missing in the operations.
|
|
3037
|
+
*/
|
|
3038
|
+
this.defaults = setOption.bind(this, "defaults");
|
|
3039
|
+
setModelOptions(modelName, initialOptions);
|
|
3040
|
+
this.options = getModelOptions(modelName);
|
|
3041
|
+
this.fullBasePath = processUrl(this.options.parentPath + this.options.basePath);
|
|
3042
|
+
this.modelName = modelName;
|
|
3043
|
+
this.router = new JsonRouter4(this.options.basePath, setCore, accessRouterResponseHandler);
|
|
3044
|
+
this.model = new model_default(modelName);
|
|
3045
|
+
this.setCollectionRoutes();
|
|
3046
|
+
this.setDocumentRoutes();
|
|
3047
|
+
this.setSubDocumentRoutes();
|
|
3048
|
+
this.logEndpoints();
|
|
3049
|
+
}
|
|
3050
|
+
getRequestSchema(key) {
|
|
3051
|
+
return getExactModelOption(this.modelName, key);
|
|
3052
|
+
}
|
|
3053
|
+
async assertAllowed(req, access) {
|
|
3054
|
+
const allowed = await req.macl.isAllowed(this.modelName, access);
|
|
3055
|
+
if (!allowed) throw new clientErrors2.UnauthorizedError();
|
|
3056
|
+
}
|
|
3057
|
+
///////////////////////
|
|
3058
|
+
// Collection Routes //
|
|
3059
|
+
///////////////////////
|
|
3060
|
+
setCollectionRoutes() {
|
|
3061
|
+
this.router.get("", async (req) => {
|
|
3062
|
+
await this.assertAllowed(req, "list");
|
|
3063
|
+
const { skip, limit, page, page_size, skim, include_permissions, include_count, include_extra_headers } = parseQuery(requestSchemas.listQuery, req.query);
|
|
3064
|
+
const svc = req.macl.getPublicService(this.modelName);
|
|
3065
|
+
const includeCount = parseBooleanString(include_count);
|
|
3066
|
+
const includeExtraHeaders = parseBooleanString(include_extra_headers);
|
|
3067
|
+
const result = await svc._list(
|
|
3068
|
+
{},
|
|
3069
|
+
{ skip, limit, page, pageSize: page_size },
|
|
3070
|
+
{
|
|
3071
|
+
skim: parseBooleanString(skim),
|
|
3072
|
+
includePermissions: parseBooleanString(include_permissions),
|
|
3073
|
+
includeCount
|
|
3074
|
+
}
|
|
3075
|
+
);
|
|
3076
|
+
handleResultError(result);
|
|
3077
|
+
return formatListResponse(req, result, includeCount, includeExtraHeaders);
|
|
3078
|
+
});
|
|
3079
|
+
this.router.post(`/${this.options.queryPath}`, async (req) => {
|
|
3080
|
+
await this.assertAllowed(req, "list");
|
|
3081
|
+
const body = parseBodyWithSchema(
|
|
3082
|
+
listBodySchema,
|
|
3083
|
+
req.body,
|
|
3084
|
+
this.getRequestSchema("requestSchemas.advancedList")
|
|
3085
|
+
);
|
|
3086
|
+
let { query, filter: filter2, select, sort, populate, include, tasks, skip, limit, page, pageSize } = body;
|
|
3087
|
+
const options = body.options ?? {};
|
|
3088
|
+
const { skim, includePermissions, includeCount, includeExtraHeaders, populateAccess } = options;
|
|
3089
|
+
const svc = req.macl.getPublicService(this.modelName);
|
|
3090
|
+
const listFilter = filter2 ?? query ?? {};
|
|
3091
|
+
const result = await svc._list(
|
|
3092
|
+
listFilter,
|
|
3093
|
+
{ select, sort, populate, include, tasks, skip, limit, page, pageSize },
|
|
3094
|
+
{
|
|
3095
|
+
skim,
|
|
3096
|
+
includePermissions,
|
|
3097
|
+
includeCount,
|
|
3098
|
+
populateAccess
|
|
3099
|
+
}
|
|
3100
|
+
);
|
|
3101
|
+
handleResultError(result);
|
|
3102
|
+
return formatListResponse(req, result, includeCount, includeExtraHeaders);
|
|
3103
|
+
});
|
|
3104
|
+
this.router.post("", async (req, res) => {
|
|
3105
|
+
await this.assertAllowed(req, "create");
|
|
3106
|
+
const { include_permissions } = parseQuery(requestSchemas.createQuery, req.query);
|
|
3107
|
+
const data = parseBodyWithSchema(createBodySchema, req.body, this.getRequestSchema("requestSchemas.create"));
|
|
3108
|
+
const svc = req.macl.getPublicService(this.modelName);
|
|
3109
|
+
const result = await svc._create(data, {}, { includePermissions: parseBooleanString(include_permissions) });
|
|
3110
|
+
handleResultError(result);
|
|
3111
|
+
return new success.Created(formatCreatedData(result));
|
|
3112
|
+
});
|
|
3113
|
+
this.router.post(`/${this.options.mutationPath}`, async (req, res) => {
|
|
3114
|
+
await this.assertAllowed(req, "create");
|
|
3115
|
+
const { include_permissions } = parseQuery(requestSchemas.createQuery, req.query);
|
|
3116
|
+
const body = parseNestedBodyWithSchema(
|
|
3117
|
+
advancedCreateBodySchema,
|
|
3118
|
+
req.body,
|
|
3119
|
+
"data",
|
|
3120
|
+
this.getRequestSchema("requestSchemas.advancedCreate.data")
|
|
3121
|
+
);
|
|
3122
|
+
const { data, select, populate, tasks } = body;
|
|
3123
|
+
const options = body.options ?? {};
|
|
3124
|
+
parseBodyWithSchema(
|
|
3125
|
+
advancedCreateBodySchema,
|
|
3126
|
+
{ data, select, populate, tasks, options },
|
|
3127
|
+
this.getRequestSchema("requestSchemas.advancedCreate.default") ?? this.getRequestSchema("requestSchemas.advancedCreate")
|
|
3128
|
+
);
|
|
3129
|
+
const { includePermissions, populateAccess } = options;
|
|
3130
|
+
const svc = req.macl.getPublicService(this.modelName);
|
|
3131
|
+
const result = await svc._create(
|
|
3132
|
+
data,
|
|
3133
|
+
{ select, populate, tasks },
|
|
3134
|
+
{
|
|
3135
|
+
includePermissions: includePermissions ?? parseBooleanString(include_permissions),
|
|
3136
|
+
populateAccess
|
|
3137
|
+
}
|
|
3138
|
+
);
|
|
3139
|
+
handleResultError(result);
|
|
3140
|
+
return new success.Created(formatCreatedData(result));
|
|
3141
|
+
});
|
|
3142
|
+
this.router.get("/new", async (req) => {
|
|
3143
|
+
const svc = req.macl.getPublicService(this.modelName);
|
|
3144
|
+
const result = await svc._new();
|
|
3145
|
+
handleResultError(result);
|
|
3146
|
+
return result.data;
|
|
3147
|
+
});
|
|
3148
|
+
}
|
|
3149
|
+
/////////////////////
|
|
3150
|
+
// Document Routes //
|
|
3151
|
+
/////////////////////
|
|
3152
|
+
setDocumentRoutes() {
|
|
3153
|
+
this.router.get("/count", async (req) => {
|
|
3154
|
+
await this.assertAllowed(req, "count");
|
|
3155
|
+
const svc = req.macl.getPublicService(this.modelName);
|
|
3156
|
+
const result = await svc._count({});
|
|
3157
|
+
handleResultError(result);
|
|
3158
|
+
return result.data;
|
|
3159
|
+
});
|
|
3160
|
+
this.router.post("/count", async (req) => {
|
|
3161
|
+
await this.assertAllowed(req, "count");
|
|
3162
|
+
const { query, filter: filter2, access } = parseBodyWithSchema(
|
|
3163
|
+
countBodySchema,
|
|
3164
|
+
req.body,
|
|
3165
|
+
this.getRequestSchema("requestSchemas.count")
|
|
3166
|
+
);
|
|
3167
|
+
const svc = req.macl.getPublicService(this.modelName);
|
|
3168
|
+
const countFilter = filter2 ?? query ?? {};
|
|
3169
|
+
const result = await svc._count(countFilter, access);
|
|
3170
|
+
handleResultError(result);
|
|
3171
|
+
return result.data;
|
|
3172
|
+
});
|
|
3173
|
+
this.router.get(`/:${this.options.idParam}`, async (req) => {
|
|
3174
|
+
await this.assertAllowed(req, "read");
|
|
3175
|
+
const id = parsePathParam(req.params[this.options.idParam], this.options.idParam);
|
|
3176
|
+
const { include_permissions, try_list } = parseQuery(requestSchemas.readQuery, req.query);
|
|
3177
|
+
const svc = req.macl.getPublicService(this.modelName);
|
|
3178
|
+
const result = await svc._read(
|
|
3179
|
+
id,
|
|
3180
|
+
{},
|
|
3181
|
+
{
|
|
3182
|
+
includePermissions: parseBooleanString(include_permissions),
|
|
3183
|
+
tryList: parseBooleanString(try_list)
|
|
3184
|
+
}
|
|
3185
|
+
);
|
|
3186
|
+
handleResultError(result);
|
|
3187
|
+
return result.data;
|
|
3188
|
+
});
|
|
3189
|
+
this.router.post(`/${this.options.queryPath}/__filter`, async (req) => {
|
|
3190
|
+
await this.assertAllowed(req, "read");
|
|
3191
|
+
const body = parseBodyWithSchema(
|
|
3192
|
+
readFilterBodySchema,
|
|
3193
|
+
req.body,
|
|
3194
|
+
this.getRequestSchema("requestSchemas.advancedReadFilter")
|
|
3195
|
+
);
|
|
3196
|
+
let { filter: filter2, select, sort, populate, include, tasks } = body;
|
|
3197
|
+
const options = body.options ?? {};
|
|
3198
|
+
const { skim, includePermissions, tryList, populateAccess } = options;
|
|
3199
|
+
const svc = req.macl.getPublicService(this.modelName);
|
|
3200
|
+
const result = await svc._readFilter(
|
|
3201
|
+
filter2 ?? {},
|
|
3202
|
+
{
|
|
3203
|
+
select,
|
|
3204
|
+
sort,
|
|
3205
|
+
populate,
|
|
3206
|
+
include,
|
|
3207
|
+
tasks
|
|
3208
|
+
},
|
|
3209
|
+
{ skim, includePermissions, tryList, populateAccess }
|
|
3210
|
+
);
|
|
3211
|
+
handleResultError(result);
|
|
3212
|
+
return result.data;
|
|
3213
|
+
});
|
|
3214
|
+
this.router.post(`/${this.options.queryPath}/:${this.options.idParam}`, async (req) => {
|
|
3215
|
+
await this.assertAllowed(req, "read");
|
|
3216
|
+
const id = parsePathParam(req.params[this.options.idParam], this.options.idParam);
|
|
3217
|
+
const body = parseBodyWithSchema(
|
|
3218
|
+
readByIdBodySchema,
|
|
3219
|
+
req.body,
|
|
3220
|
+
this.getRequestSchema("requestSchemas.advancedRead")
|
|
3221
|
+
);
|
|
3222
|
+
let { select, populate, include, tasks } = body;
|
|
3223
|
+
const options = body.options ?? {};
|
|
3224
|
+
const { skim, includePermissions, tryList, populateAccess } = options;
|
|
3225
|
+
const svc = req.macl.getPublicService(this.modelName);
|
|
3226
|
+
const result = await svc._read(
|
|
3227
|
+
id,
|
|
3228
|
+
{
|
|
3229
|
+
select,
|
|
3230
|
+
populate,
|
|
3231
|
+
include,
|
|
3232
|
+
tasks
|
|
3233
|
+
},
|
|
3234
|
+
{ skim, includePermissions, tryList, populateAccess }
|
|
3235
|
+
);
|
|
3236
|
+
handleResultError(result);
|
|
3237
|
+
return result.data;
|
|
3238
|
+
});
|
|
3239
|
+
this.router.patch(`/:${this.options.idParam}`, async (req) => {
|
|
3240
|
+
await this.assertAllowed(req, "update");
|
|
3241
|
+
const id = parsePathParam(req.params[this.options.idParam], this.options.idParam);
|
|
3242
|
+
const { returning_all } = parseQuery(requestSchemas.updateQuery, req.query);
|
|
3243
|
+
const data = parseBodyWithSchema(updateBodySchema, req.body, this.getRequestSchema("requestSchemas.update"));
|
|
3244
|
+
const svc = req.macl.getPublicService(this.modelName);
|
|
3245
|
+
const result = await svc._update(id, data, {}, { returningAll: parseBooleanString(returning_all) });
|
|
3246
|
+
handleResultError(result);
|
|
3247
|
+
return result.data;
|
|
3248
|
+
});
|
|
3249
|
+
this.router.patch(`/${this.options.mutationPath}/:${this.options.idParam}`, async (req) => {
|
|
3250
|
+
await this.assertAllowed(req, "update");
|
|
3251
|
+
const id = parsePathParam(req.params[this.options.idParam], this.options.idParam);
|
|
3252
|
+
const { returning_all } = parseQuery(requestSchemas.updateQuery, req.query);
|
|
3253
|
+
const body = parseNestedBodyWithSchema(
|
|
3254
|
+
advancedUpdateBodySchema,
|
|
3255
|
+
req.body,
|
|
3256
|
+
"data",
|
|
3257
|
+
this.getRequestSchema("requestSchemas.advancedUpdate.data")
|
|
3258
|
+
);
|
|
3259
|
+
const { data, select, populate, tasks } = body;
|
|
3260
|
+
const options = body.options ?? {};
|
|
3261
|
+
parseBodyWithSchema(
|
|
3262
|
+
advancedUpdateBodySchema,
|
|
3263
|
+
{ data, select, populate, tasks, options },
|
|
3264
|
+
this.getRequestSchema("requestSchemas.advancedUpdate.default") ?? this.getRequestSchema("requestSchemas.advancedUpdate")
|
|
3265
|
+
);
|
|
3266
|
+
const { returningAll, includePermissions, populateAccess } = options;
|
|
3267
|
+
const svc = req.macl.getPublicService(this.modelName);
|
|
3268
|
+
const result = await svc._update(
|
|
3269
|
+
id,
|
|
3270
|
+
data,
|
|
3271
|
+
{ select, populate, tasks },
|
|
3272
|
+
{
|
|
3273
|
+
returningAll: returningAll ?? parseBooleanString(returning_all),
|
|
3274
|
+
includePermissions,
|
|
3275
|
+
populateAccess
|
|
3276
|
+
}
|
|
3277
|
+
);
|
|
3278
|
+
handleResultError(result);
|
|
3279
|
+
return result.data;
|
|
3280
|
+
});
|
|
3281
|
+
this.router.put(`/`, async (req) => {
|
|
3282
|
+
await this.assertAllowed(req, "upsert");
|
|
3283
|
+
const svc = req.macl.getPublicService(this.modelName);
|
|
3284
|
+
const idKey = svc.getIdentifier();
|
|
3285
|
+
if (idKey !== "_id") throw new clientErrors2.BadRequestError("not supported identifier");
|
|
3286
|
+
const { returning_all, include_permissions } = parseQuery(requestSchemas.upsertQuery, req.query);
|
|
3287
|
+
const body = parseBodyWithSchema(upsertBodySchema, req.body, this.getRequestSchema("requestSchemas.upsert"));
|
|
3288
|
+
const { [idKey]: idVal, ...data } = body;
|
|
3289
|
+
const upsertId = idVal;
|
|
3290
|
+
if (upsertId) {
|
|
3291
|
+
const existing = await svc.exists({ [idKey]: upsertId }, { access: "update" });
|
|
3292
|
+
handleResultError(existing);
|
|
3293
|
+
if (!existing.data) throw new clientErrors2.UnauthorizedError();
|
|
3294
|
+
const result = await svc._update(upsertId, data, {}, { returningAll: parseBooleanString(returning_all) });
|
|
3295
|
+
handleResultError(result);
|
|
3296
|
+
return result.data;
|
|
3297
|
+
} else {
|
|
3298
|
+
const result = await svc._create(data, {}, { includePermissions: parseBooleanString(include_permissions) });
|
|
3299
|
+
handleResultError(result);
|
|
3300
|
+
return new success.Created(formatUpsertCreatedData(result));
|
|
3301
|
+
}
|
|
3302
|
+
});
|
|
3303
|
+
this.router.put(`/${this.options.mutationPath}`, async (req) => {
|
|
3304
|
+
await this.assertAllowed(req, "upsert");
|
|
3305
|
+
const svc = req.macl.getPublicService(this.modelName);
|
|
3306
|
+
const idKey = svc.getIdentifier();
|
|
3307
|
+
if (idKey !== "_id") throw new clientErrors2.BadRequestError("not supported identifier");
|
|
3308
|
+
const { returning_all, include_permissions } = parseQuery(requestSchemas.upsertQuery, req.query);
|
|
3309
|
+
const body = parseNestedBodyWithSchema(
|
|
3310
|
+
advancedUpsertBodySchema,
|
|
3311
|
+
req.body,
|
|
3312
|
+
"data",
|
|
3313
|
+
this.getRequestSchema("requestSchemas.advancedUpsert.data")
|
|
3314
|
+
);
|
|
3315
|
+
const { data, select, populate, tasks } = body;
|
|
3316
|
+
const options = body.options ?? {};
|
|
3317
|
+
parseBodyWithSchema(
|
|
3318
|
+
advancedUpsertBodySchema,
|
|
3319
|
+
{ data, select, populate, tasks, options },
|
|
3320
|
+
this.getRequestSchema("requestSchemas.advancedUpsert.default") ?? this.getRequestSchema("requestSchemas.advancedUpsert")
|
|
3321
|
+
);
|
|
3322
|
+
const { returningAll, includePermissions, populateAccess } = options;
|
|
3323
|
+
const { [idKey]: idVal, ...otherData } = data;
|
|
3324
|
+
const upsertId = idVal;
|
|
3325
|
+
if (upsertId) {
|
|
3326
|
+
const existing = await svc.exists({ [idKey]: upsertId }, { access: "update" });
|
|
3327
|
+
handleResultError(existing);
|
|
3328
|
+
if (!existing.data) throw new clientErrors2.UnauthorizedError();
|
|
3329
|
+
const result = await svc._update(
|
|
3330
|
+
upsertId,
|
|
3331
|
+
otherData,
|
|
3332
|
+
{ select, populate, tasks },
|
|
3333
|
+
{
|
|
3334
|
+
returningAll: returningAll ?? parseBooleanString(returning_all),
|
|
3335
|
+
includePermissions: includePermissions ?? parseBooleanString(include_permissions),
|
|
3336
|
+
populateAccess
|
|
3337
|
+
}
|
|
3338
|
+
);
|
|
3339
|
+
handleResultError(result);
|
|
3340
|
+
return result.data;
|
|
3341
|
+
} else {
|
|
3342
|
+
const result = await svc._create(
|
|
3343
|
+
otherData,
|
|
3344
|
+
{ select, populate, tasks },
|
|
3345
|
+
{
|
|
3346
|
+
includePermissions: includePermissions ?? parseBooleanString(include_permissions),
|
|
3347
|
+
populateAccess
|
|
3348
|
+
}
|
|
3349
|
+
);
|
|
3350
|
+
handleResultError(result);
|
|
3351
|
+
return new success.Created(formatUpsertCreatedData(result));
|
|
3352
|
+
}
|
|
3353
|
+
});
|
|
3354
|
+
this.router.delete(`/:${this.options.idParam}`, async (req) => {
|
|
3355
|
+
await this.assertAllowed(req, "delete");
|
|
3356
|
+
const id = parsePathParam(req.params[this.options.idParam], this.options.idParam);
|
|
3357
|
+
const svc = req.macl.getPublicService(this.modelName);
|
|
3358
|
+
const result = await svc._delete(id);
|
|
3359
|
+
handleResultError(result);
|
|
3360
|
+
return result.data;
|
|
3361
|
+
});
|
|
3362
|
+
this.router.get("/distinct/:field", async (req) => {
|
|
3363
|
+
await this.assertAllowed(req, "distinct");
|
|
3364
|
+
const field = parsePathParam(req.params.field, "field");
|
|
3365
|
+
const svc = req.macl.getPublicService(this.modelName);
|
|
3366
|
+
const result = await svc._distinct(field);
|
|
3367
|
+
handleResultError(result);
|
|
3368
|
+
return result.data;
|
|
3369
|
+
});
|
|
3370
|
+
this.router.post("/distinct/:field", async (req) => {
|
|
3371
|
+
await this.assertAllowed(req, "distinct");
|
|
3372
|
+
const field = parsePathParam(req.params.field, "field");
|
|
3373
|
+
const { query, filter: filter2 } = parseBodyWithSchema(
|
|
3374
|
+
distinctBodySchema,
|
|
3375
|
+
req.body,
|
|
3376
|
+
this.getRequestSchema("requestSchemas.distinct")
|
|
3377
|
+
);
|
|
3378
|
+
const svc = req.macl.getPublicService(this.modelName);
|
|
3379
|
+
const result = await svc._distinct(field, { filter: filter2 ?? query ?? {} });
|
|
3380
|
+
handleResultError(result);
|
|
3381
|
+
return result.data;
|
|
3382
|
+
});
|
|
3383
|
+
}
|
|
3384
|
+
/////////////////////////
|
|
3385
|
+
// Sub-Document Routes //
|
|
3386
|
+
/////////////////////////
|
|
3387
|
+
setSubDocumentRoutes() {
|
|
3388
|
+
const subs = getModelSub(this.modelName);
|
|
3389
|
+
for (let x = 0; x < subs.length; x++) {
|
|
3390
|
+
const sub = subs[x];
|
|
3391
|
+
this.router.get(`/:${this.options.idParam}/${sub}`, async (req) => {
|
|
3392
|
+
await this.assertAllowed(req, `subs.${sub}.list`);
|
|
3393
|
+
const id = parsePathParam(req.params[this.options.idParam], this.options.idParam);
|
|
3394
|
+
const svc = req.macl.getPublicService(this.modelName);
|
|
3395
|
+
const result = await svc.listSub(id, sub);
|
|
3396
|
+
handleResultError(result);
|
|
3397
|
+
return result.data;
|
|
3398
|
+
});
|
|
3399
|
+
this.router.post(`/:${this.options.idParam}/${sub}/${this.options.queryPath}`, async (req) => {
|
|
3400
|
+
await this.assertAllowed(req, `subs.${sub}.list`);
|
|
3401
|
+
const id = parsePathParam(req.params[this.options.idParam], this.options.idParam);
|
|
3402
|
+
const body = parseBodyWithSchema(
|
|
3403
|
+
subListBodySchema,
|
|
3404
|
+
req.body,
|
|
3405
|
+
this.getRequestSchema("requestSchemas.subList")
|
|
3406
|
+
);
|
|
3407
|
+
const svc = req.macl.getPublicService(this.modelName);
|
|
3408
|
+
const result = await svc.listSub(id, sub, { filter: body.filter ?? {}, fields: body.fields ?? [] });
|
|
3409
|
+
handleResultError(result);
|
|
3410
|
+
return result.data;
|
|
3411
|
+
});
|
|
3412
|
+
this.router.patch(`/:${this.options.idParam}/${sub}`, async (req) => {
|
|
3413
|
+
await this.assertAllowed(req, `subs.${sub}.update`);
|
|
3414
|
+
const id = parsePathParam(req.params[this.options.idParam], this.options.idParam);
|
|
3415
|
+
const data = parseBodyWithSchema(
|
|
3416
|
+
subMutationBodySchema,
|
|
3417
|
+
req.body,
|
|
3418
|
+
this.getRequestSchema("requestSchemas.subBulkUpdate")
|
|
3419
|
+
);
|
|
3420
|
+
const svc = req.macl.getPublicService(this.modelName);
|
|
3421
|
+
const result = await svc.bulkUpdateSub(id, sub, data);
|
|
3422
|
+
handleResultError(result);
|
|
3423
|
+
return result.data;
|
|
3424
|
+
});
|
|
3425
|
+
this.router.get(`/:${this.options.idParam}/${sub}/:subId`, async (req) => {
|
|
3426
|
+
await this.assertAllowed(req, `subs.${sub}.read`);
|
|
3427
|
+
const id = parsePathParam(req.params[this.options.idParam], this.options.idParam);
|
|
3428
|
+
const subId = parsePathParam(req.params.subId, "subId");
|
|
3429
|
+
const svc = req.macl.getPublicService(this.modelName);
|
|
3430
|
+
const result = await svc.readSub(id, sub, subId);
|
|
3431
|
+
handleResultError(result);
|
|
3432
|
+
return result.data;
|
|
3433
|
+
});
|
|
3434
|
+
this.router.post(`/:${this.options.idParam}/${sub}/:subId/${this.options.queryPath}`, async (req) => {
|
|
3435
|
+
await this.assertAllowed(req, `subs.${sub}.read`);
|
|
3436
|
+
const id = parsePathParam(req.params[this.options.idParam], this.options.idParam);
|
|
3437
|
+
const subId = parsePathParam(req.params.subId, "subId");
|
|
3438
|
+
const body = parseBodyWithSchema(
|
|
3439
|
+
subReadBodySchema,
|
|
3440
|
+
req.body,
|
|
3441
|
+
this.getRequestSchema("requestSchemas.subRead")
|
|
3442
|
+
);
|
|
3443
|
+
const populate = body.populate;
|
|
3444
|
+
const normalizedPopulate = Array.isArray(populate) ? populate.map((item) => isString9(item) ? { path: item } : item) : isString9(populate) ? { path: populate } : populate ?? [];
|
|
3445
|
+
const svc = req.macl.getPublicService(this.modelName);
|
|
3446
|
+
const result = await svc.readSub(id, sub, subId, { fields: body.fields ?? [], populate: normalizedPopulate });
|
|
3447
|
+
handleResultError(result);
|
|
3448
|
+
return result.data;
|
|
3449
|
+
});
|
|
3450
|
+
this.router.patch(`/:${this.options.idParam}/${sub}/:subId`, async (req) => {
|
|
3451
|
+
await this.assertAllowed(req, `subs.${sub}.update`);
|
|
3452
|
+
const id = parsePathParam(req.params[this.options.idParam], this.options.idParam);
|
|
3453
|
+
const subId = parsePathParam(req.params.subId, "subId");
|
|
3454
|
+
const data = parseBodyWithSchema(
|
|
3455
|
+
subMutationBodySchema,
|
|
3456
|
+
req.body,
|
|
3457
|
+
this.getRequestSchema("requestSchemas.subUpdate")
|
|
3458
|
+
);
|
|
3459
|
+
const svc = req.macl.getPublicService(this.modelName);
|
|
3460
|
+
const result = await svc.updateSub(id, sub, subId, data);
|
|
3461
|
+
handleResultError(result);
|
|
3462
|
+
return result.data;
|
|
3463
|
+
});
|
|
3464
|
+
this.router.post(`/:${this.options.idParam}/${sub}`, async (req) => {
|
|
3465
|
+
await this.assertAllowed(req, `subs.${sub}.create`);
|
|
3466
|
+
const id = parsePathParam(req.params[this.options.idParam], this.options.idParam);
|
|
3467
|
+
const data = parseBodyWithSchema(
|
|
3468
|
+
subMutationBodySchema,
|
|
3469
|
+
req.body,
|
|
3470
|
+
this.getRequestSchema("requestSchemas.subCreate")
|
|
3471
|
+
);
|
|
3472
|
+
const svc = req.macl.getPublicService(this.modelName);
|
|
3473
|
+
const result = await svc.createSub(id, sub, data);
|
|
3474
|
+
handleResultError(result);
|
|
3475
|
+
return new success.Created(result.data);
|
|
3476
|
+
});
|
|
3477
|
+
this.router.delete(`/:${this.options.idParam}/${sub}/:subId`, async (req) => {
|
|
3478
|
+
await this.assertAllowed(req, `subs.${sub}.delete`);
|
|
3479
|
+
const id = parsePathParam(req.params[this.options.idParam], this.options.idParam);
|
|
3480
|
+
const subId = parsePathParam(req.params.subId, "subId");
|
|
3481
|
+
const svc = req.macl.getPublicService(this.modelName);
|
|
3482
|
+
const result = await svc.deleteSub(id, sub, subId);
|
|
3483
|
+
handleResultError(result);
|
|
3484
|
+
return result.data;
|
|
3485
|
+
});
|
|
3486
|
+
}
|
|
3487
|
+
}
|
|
3488
|
+
logEndpoints() {
|
|
3489
|
+
forEach7(this.router.endpoints, ({ method, path }) => {
|
|
3490
|
+
logger.info(`${padEnd(method, 6)} ${processUrl(this.options.parentPath + path)}`);
|
|
3491
|
+
});
|
|
3492
|
+
}
|
|
3493
|
+
set(keyOrOptions, value) {
|
|
3494
|
+
if (arguments.length === 2 && isString9(keyOrOptions)) {
|
|
3495
|
+
setModelOption(this.modelName, keyOrOptions, value);
|
|
3496
|
+
}
|
|
3497
|
+
if (arguments.length === 1 && isPlainObject8(keyOrOptions)) {
|
|
3498
|
+
setModelOptions(this.modelName, keyOrOptions);
|
|
3499
|
+
}
|
|
3500
|
+
return this;
|
|
3501
|
+
}
|
|
3502
|
+
setOption(key, option) {
|
|
3503
|
+
setModelOption(this.modelName, key, option);
|
|
3504
|
+
return this;
|
|
3505
|
+
}
|
|
3506
|
+
setOptions(options) {
|
|
3507
|
+
setModelOptions(this.modelName, options);
|
|
3508
|
+
return this;
|
|
3509
|
+
}
|
|
3510
|
+
get routes() {
|
|
3511
|
+
return this.router.original;
|
|
3512
|
+
}
|
|
3513
|
+
};
|
|
3514
|
+
|
|
3515
|
+
// src/routers/root-router.ts
|
|
3516
|
+
import JsonRouter5 from "@web-ts-toolkit/express-json-router";
|
|
3517
|
+
import { isNumber as _isNumber, orderBy as _orderBy } from "@web-ts-toolkit/utils";
|
|
3518
|
+
var clientErrors3 = JsonRouter5.clientErrors;
|
|
3519
|
+
var ALL_ROUTES = ["new", "list", "read", "update", "delete", "create", "distinct", "count"];
|
|
3520
|
+
var RootRouter = class {
|
|
3521
|
+
constructor(options = { basePath: "", routeGuard: true }) {
|
|
3522
|
+
const { basePath, routeGuard } = options;
|
|
3523
|
+
this.basename = basePath || "";
|
|
3524
|
+
this.routeGuard = routeGuard;
|
|
3525
|
+
this.router = new JsonRouter5(this.basename, setCore, accessRouterResponseHandler);
|
|
3526
|
+
this.setRoutes();
|
|
3527
|
+
}
|
|
3528
|
+
processResult(op, result) {
|
|
3529
|
+
const message = mapCodeToMessage(result.code);
|
|
3530
|
+
const statusCode = mapCodeToStatusCode(result.code);
|
|
3531
|
+
if (!result.success) {
|
|
3532
|
+
const { success: success3, code: code2, errors } = result;
|
|
3533
|
+
return { success: success3, code: code2, errors, message, statusCode, op };
|
|
3534
|
+
}
|
|
3535
|
+
if (result.kind === "list") {
|
|
3536
|
+
const { success: success3, code: code2, data: data2, count, totalCount } = result;
|
|
3537
|
+
return { success: success3, code: code2, data: data2, count, totalCount, message, statusCode, op };
|
|
3538
|
+
}
|
|
3539
|
+
const { success: success2, code, data } = result;
|
|
3540
|
+
return { success: success2, code, data, message, statusCode, op };
|
|
3541
|
+
}
|
|
3542
|
+
async processOp(req, item) {
|
|
3543
|
+
const svc = req.macl.getPublicService(item.model);
|
|
3544
|
+
if (!svc) return { success: false, code: "bad_request" /* BadRequest */, data: null, message: `Model ${item.model} not found` };
|
|
3545
|
+
if (!ALL_ROUTES.includes(item.op))
|
|
3546
|
+
return { success: false, code: "bad_request" /* BadRequest */, data: null, message: `Operation ${item.op} not found` };
|
|
3547
|
+
const routeGuard = getModelOption(item.model, `routeGuard.${item.op}`);
|
|
3548
|
+
const allowed = await req.macl.canActivate(routeGuard);
|
|
3549
|
+
if (!allowed) return { success: false, code: "unauthorized" /* Unauthorized */, data: null, message: "Unauthorized" };
|
|
3550
|
+
if (item.op === "list") {
|
|
3551
|
+
return this.processResult(item.op, await svc._list(item.filter, item.args, item.options));
|
|
3552
|
+
} else if (item.op === "create") {
|
|
3553
|
+
return this.processResult(item.op, await svc._create(item.data, item.args));
|
|
3554
|
+
} else if (item.op === "new") {
|
|
3555
|
+
return this.processResult(item.op, await svc._new());
|
|
3556
|
+
} else if (item.op === "read") {
|
|
3557
|
+
if (item.id) {
|
|
3558
|
+
return this.processResult(item.op, await svc._read(item.id, item.args, item.options));
|
|
3559
|
+
}
|
|
3560
|
+
if (item.filter) {
|
|
3561
|
+
return this.processResult(item.op, await svc._readFilter(item.filter, item.args, item.options));
|
|
3562
|
+
}
|
|
3563
|
+
return { success: false, code: "bad_request" /* BadRequest */, data: null, message: `Operation ${item.op} invalid` };
|
|
3564
|
+
} else if (item.op === "update") {
|
|
3565
|
+
return this.processResult(item.op, await svc._update(item.id, item.data, item.args, item.options));
|
|
3566
|
+
} else if (item.op === "delete") {
|
|
3567
|
+
return this.processResult(item.op, await svc._delete(item.id));
|
|
3568
|
+
} else if (item.op === "distinct") {
|
|
3569
|
+
return this.processResult(item.op, await svc._distinct(item.field, { filter: item.filter }));
|
|
3570
|
+
} else if (item.op === "count") {
|
|
3571
|
+
return this.processResult(item.op, await svc._count(item.filter));
|
|
3572
|
+
} else {
|
|
3573
|
+
return { success: false, code: "bad_request" /* BadRequest */, data: null, message: `operation ${item.op} not found` };
|
|
3574
|
+
}
|
|
3575
|
+
}
|
|
3576
|
+
groupItemsByOrder(items) {
|
|
3577
|
+
return items.reduce((acc, item, index) => {
|
|
3578
|
+
let order = 0;
|
|
3579
|
+
if (_isNumber(item.order)) {
|
|
3580
|
+
order = item.order < 0 ? 0 : item.order;
|
|
3581
|
+
}
|
|
3582
|
+
if (!acc[order]) {
|
|
3583
|
+
acc[order] = [];
|
|
3584
|
+
}
|
|
3585
|
+
acc[order].push({ index, item });
|
|
3586
|
+
return acc;
|
|
3587
|
+
}, []);
|
|
3588
|
+
}
|
|
3589
|
+
async assertAllowed(req) {
|
|
3590
|
+
const allowed = await req.macl.canActivate(this.routeGuard);
|
|
3591
|
+
if (!allowed) throw new clientErrors3.UnauthorizedError();
|
|
3592
|
+
}
|
|
3593
|
+
setRoutes() {
|
|
3594
|
+
this.router.post("", async (req) => {
|
|
3595
|
+
await this.assertAllowed(req);
|
|
3596
|
+
const items = parseBody(rootQuerySchema, req.body).map((item) => ({
|
|
3597
|
+
...item,
|
|
3598
|
+
filter: item.filter ?? {}
|
|
3599
|
+
}));
|
|
3600
|
+
const groupedItems = this.groupItemsByOrder(items);
|
|
3601
|
+
const results = [];
|
|
3602
|
+
for (let x = 0; x < groupedItems.length; x++) {
|
|
3603
|
+
const arrResult = await Promise.all(
|
|
3604
|
+
groupedItems[x].map(async ({ item, index }) => {
|
|
3605
|
+
const ret = await this.processOp(req, item);
|
|
3606
|
+
return { ret, index };
|
|
3607
|
+
})
|
|
3608
|
+
);
|
|
3609
|
+
results.push(...arrResult);
|
|
3610
|
+
}
|
|
3611
|
+
return _orderBy(results, ["index"], ["asc"]).map(({ ret }) => ret);
|
|
3612
|
+
});
|
|
3613
|
+
}
|
|
3614
|
+
get routes() {
|
|
3615
|
+
return this.router.original;
|
|
3616
|
+
}
|
|
3617
|
+
};
|
|
3618
|
+
|
|
3619
|
+
// src/routers/data-router.ts
|
|
3620
|
+
import JsonRouter6 from "@web-ts-toolkit/express-json-router";
|
|
3621
|
+
import { isPlainObject as isPlainObject9, isString as isString10, isUndefined as isUndefined3 } from "@web-ts-toolkit/utils";
|
|
3622
|
+
|
|
3623
|
+
// src/core-data.ts
|
|
3624
|
+
import { intersection as intersection2 } from "@web-ts-toolkit/utils";
|
|
3625
|
+
var DataCore = class {
|
|
3626
|
+
constructor(req) {
|
|
3627
|
+
this.req = req;
|
|
3628
|
+
this.caches = {
|
|
3629
|
+
baseFilter: new Cache()
|
|
3630
|
+
};
|
|
3631
|
+
}
|
|
3632
|
+
async genIDFilter(dataName, id) {
|
|
3633
|
+
const identifier = getDataOption(dataName, "identifier");
|
|
3634
|
+
return resolveIdentifierFilter(this.req, identifier, id);
|
|
3635
|
+
}
|
|
3636
|
+
async genFilter(dataName, access = "read", _filter = null) {
|
|
3637
|
+
const permissions = this.getGlobalPermissions();
|
|
3638
|
+
const cacheKey = `${dataName}_baseFilter_${access}`;
|
|
3639
|
+
return resolveAccessFilter({
|
|
3640
|
+
req: this.req,
|
|
3641
|
+
permissions,
|
|
3642
|
+
cache: this.caches.baseFilter,
|
|
3643
|
+
cacheKey,
|
|
3644
|
+
access,
|
|
3645
|
+
filter: _filter,
|
|
3646
|
+
getOption: (key, defaultValue) => getDataOption(dataName, key, defaultValue)
|
|
3647
|
+
});
|
|
3648
|
+
}
|
|
3649
|
+
async genAllowedFields(dataName, _doc, access, baseFields = []) {
|
|
3650
|
+
const permissionSchema = getDataOption(dataName, "permissionSchema");
|
|
3651
|
+
const permissions = this.getGlobalPermissions();
|
|
3652
|
+
return collectSchemaFields({
|
|
3653
|
+
req: this.req,
|
|
3654
|
+
permissionSchema,
|
|
3655
|
+
access,
|
|
3656
|
+
baseFields,
|
|
3657
|
+
hasPermission: (key) => permissions.has(key),
|
|
3658
|
+
functionArgs: [permissions]
|
|
3659
|
+
});
|
|
3660
|
+
}
|
|
3661
|
+
async pickAllowedFields(dataName, doc, access, baseFields = []) {
|
|
3662
|
+
const allowed = await this.genAllowedFields(dataName, doc, access, baseFields);
|
|
3663
|
+
return pickDocFields(doc, allowed);
|
|
3664
|
+
}
|
|
3665
|
+
async genSelect(dataName, access, targetFields = null, skipChecks = true, subPaths = []) {
|
|
3666
|
+
let normalizedSelect = normalizeSelect(targetFields);
|
|
3667
|
+
const permissionSchema = getDataOption(dataName, ["permissionSchema"].concat(subPaths).join("."));
|
|
3668
|
+
if (!permissionSchema) return [];
|
|
3669
|
+
const permissions = this.getGlobalPermissions();
|
|
3670
|
+
let fields = await collectSchemaFields({
|
|
3671
|
+
req: this.req,
|
|
3672
|
+
permissionSchema,
|
|
3673
|
+
access,
|
|
3674
|
+
hasPermission: (key) => {
|
|
3675
|
+
if (permissions.prop(key)) {
|
|
3676
|
+
return permissions.has(key);
|
|
3677
|
+
}
|
|
3678
|
+
return !!skipChecks;
|
|
3679
|
+
},
|
|
3680
|
+
functionArgs: [permissions]
|
|
3681
|
+
});
|
|
3682
|
+
fields = intersection2(normalizedSelect, fields);
|
|
3683
|
+
return fields;
|
|
3684
|
+
}
|
|
3685
|
+
async decorate(dataName, doc, access, context = {}) {
|
|
3686
|
+
const decorate = getDataOption(dataName, `decorate.${access}`, null);
|
|
3687
|
+
const permissions = this.getGlobalPermissions();
|
|
3688
|
+
return callMiddlewareChain(this.req, decorate, doc, permissions, context);
|
|
3689
|
+
}
|
|
3690
|
+
async decorateAll(dataName, docs, access) {
|
|
3691
|
+
const decorateAll = getDataOption(dataName, `decorateAll.${access}`, null);
|
|
3692
|
+
const permissions = this.getGlobalPermissions();
|
|
3693
|
+
return callMiddlewareChain(this.req, decorateAll, docs, permissions, {});
|
|
3694
|
+
}
|
|
3695
|
+
getPermissions() {
|
|
3696
|
+
return getRequestPermissions(this.req);
|
|
3697
|
+
}
|
|
3698
|
+
async setPermissions() {
|
|
3699
|
+
await setRequestPermissions(this.req);
|
|
3700
|
+
}
|
|
3701
|
+
async canActivate(routeGuard) {
|
|
3702
|
+
const permissions = this.getGlobalPermissions();
|
|
3703
|
+
return evaluateRouteGuard(this.req, permissions, routeGuard);
|
|
3704
|
+
}
|
|
3705
|
+
async isAllowed(dataName, access) {
|
|
3706
|
+
const routeGuard = getDataOption(dataName, `routeGuard.${access}`);
|
|
3707
|
+
return this.canActivate(routeGuard);
|
|
3708
|
+
}
|
|
3709
|
+
getService(dataName) {
|
|
3710
|
+
return new DataService(this.req, dataName);
|
|
3711
|
+
}
|
|
3712
|
+
service(dataName) {
|
|
3713
|
+
return this.getService(dataName);
|
|
3714
|
+
}
|
|
3715
|
+
svc(dataName) {
|
|
3716
|
+
return this.getService(dataName);
|
|
3717
|
+
}
|
|
3718
|
+
getGlobalPermissions() {
|
|
3719
|
+
return this.req[PERMISSIONS];
|
|
3720
|
+
}
|
|
3721
|
+
};
|
|
3722
|
+
async function setDataCore(req, _res, next) {
|
|
3723
|
+
if (req[DATA_MIDDLEWARE]) return next();
|
|
3724
|
+
const core = new DataCore(req);
|
|
3725
|
+
await core.setPermissions();
|
|
3726
|
+
req.dacl = core;
|
|
3727
|
+
req[PERMISSIONS] = core.getPermissions();
|
|
3728
|
+
req[PERMISSION_KEYS] = req[PERMISSIONS].$_permissionKeys;
|
|
3729
|
+
req[DATA_MIDDLEWARE] = true;
|
|
3730
|
+
next();
|
|
3731
|
+
}
|
|
3732
|
+
|
|
3733
|
+
// src/routers/data-router.ts
|
|
3734
|
+
var clientErrors4 = JsonRouter6.clientErrors;
|
|
3735
|
+
function setOption2(parentKey, optionKey, option) {
|
|
3736
|
+
const key = isUndefined3(option) ? parentKey : `${parentKey}.${optionKey}`;
|
|
3737
|
+
const value = isUndefined3(option) ? optionKey : option;
|
|
3738
|
+
setDataOption(this.dataName, key, value);
|
|
3739
|
+
return this;
|
|
3740
|
+
}
|
|
3741
|
+
var DataRouter = class {
|
|
3742
|
+
constructor(dataName, initialOptions) {
|
|
3743
|
+
this.data = setOption2.bind(this, "data");
|
|
3744
|
+
this.listHardLimit = setOption2.bind(this, "listHardLimit");
|
|
3745
|
+
this.permissionSchema = setOption2.bind(this, "permissionSchema");
|
|
3746
|
+
this.routeGuard = setOption2.bind(this, "routeGuard");
|
|
3747
|
+
this.baseFilter = setOption2.bind(this, "baseFilter");
|
|
3748
|
+
this.overrideFilter = setOption2.bind(this, "overrideFilter");
|
|
3749
|
+
this.decorate = setOption2.bind(this, "decorate");
|
|
3750
|
+
this.decorateAll = setOption2.bind(this, "decorateAll");
|
|
3751
|
+
this.identifier = setOption2.bind(this, "identifier");
|
|
3752
|
+
setDataOptions(dataName, initialOptions);
|
|
3753
|
+
this.options = getDataOptions(dataName);
|
|
3754
|
+
this.fullBasePath = processUrl(this.options.parentPath + this.options.basePath);
|
|
3755
|
+
this.dataName = dataName;
|
|
3756
|
+
this.router = new JsonRouter6(this.options.basePath, setDataCore, accessRouterResponseHandler);
|
|
3757
|
+
this.setCollectionRoutes();
|
|
3758
|
+
this.setDocumentRoutes();
|
|
3759
|
+
}
|
|
3760
|
+
getRequestSchema(key) {
|
|
3761
|
+
return getExactDataOption(this.dataName, key);
|
|
3762
|
+
}
|
|
3763
|
+
async assertAllowed(req, access) {
|
|
3764
|
+
const allowed = await req.dacl.isAllowed(this.dataName, access);
|
|
3765
|
+
if (!allowed) throw new clientErrors4.UnauthorizedError();
|
|
3766
|
+
}
|
|
3767
|
+
///////////////////////
|
|
3768
|
+
// Collection Routes //
|
|
3769
|
+
///////////////////////
|
|
3770
|
+
setCollectionRoutes() {
|
|
3771
|
+
this.router.get("", async (req) => {
|
|
3772
|
+
await this.assertAllowed(req, "list");
|
|
3773
|
+
const { skip, limit, page, page_size, include_count, include_extra_headers } = parseQuery(
|
|
3774
|
+
requestSchemas.listQuery,
|
|
3775
|
+
req.query
|
|
3776
|
+
);
|
|
3777
|
+
const svc = req.dacl.getService(this.dataName);
|
|
3778
|
+
const includeCount = parseBooleanString(include_count);
|
|
3779
|
+
const includeExtraHeaders = parseBooleanString(include_extra_headers);
|
|
3780
|
+
const result = await svc.find(
|
|
3781
|
+
{},
|
|
3782
|
+
{ skip, limit, page, pageSize: page_size },
|
|
3783
|
+
{
|
|
3784
|
+
includeCount
|
|
3785
|
+
}
|
|
3786
|
+
);
|
|
3787
|
+
handleResultError(result);
|
|
3788
|
+
return formatListResponse(req, result, includeCount, includeExtraHeaders);
|
|
3789
|
+
});
|
|
3790
|
+
this.router.post(`/${this.options.queryPath}`, async (req) => {
|
|
3791
|
+
await this.assertAllowed(req, "list");
|
|
3792
|
+
let {
|
|
3793
|
+
filter: filter2,
|
|
3794
|
+
select,
|
|
3795
|
+
sort,
|
|
3796
|
+
skip,
|
|
3797
|
+
limit,
|
|
3798
|
+
page,
|
|
3799
|
+
pageSize,
|
|
3800
|
+
options = {}
|
|
3801
|
+
} = parseBodyWithSchema(dataListBodySchema, req.body, this.getRequestSchema("requestSchemas.advancedList"));
|
|
3802
|
+
const { includeCount, includeExtraHeaders } = options;
|
|
3803
|
+
const svc = req.dacl.getService(this.dataName);
|
|
3804
|
+
const result = await svc.find(
|
|
3805
|
+
filter2 ?? {},
|
|
3806
|
+
{ select, sort: typeof sort === "string" ? sort : void 0, skip, limit, page, pageSize },
|
|
3807
|
+
{ includeCount }
|
|
3808
|
+
);
|
|
3809
|
+
handleResultError(result);
|
|
3810
|
+
return formatListResponse(req, result, includeCount, includeExtraHeaders);
|
|
3811
|
+
});
|
|
3812
|
+
}
|
|
3813
|
+
/////////////////////
|
|
3814
|
+
// Document Routes //
|
|
3815
|
+
/////////////////////
|
|
3816
|
+
setDocumentRoutes() {
|
|
3817
|
+
this.router.get(`/:${this.options.idParam}`, async (req) => {
|
|
3818
|
+
await this.assertAllowed(req, "read");
|
|
3819
|
+
const id = parsePathParam(req.params[this.options.idParam], this.options.idParam);
|
|
3820
|
+
const svc = req.dacl.getService(this.dataName);
|
|
3821
|
+
const result = await svc.findById(id, {}, {});
|
|
3822
|
+
handleResultError(result);
|
|
3823
|
+
return result.data;
|
|
3824
|
+
});
|
|
3825
|
+
this.router.post(`/${this.options.queryPath}/__filter`, async (req) => {
|
|
3826
|
+
await this.assertAllowed(req, "read");
|
|
3827
|
+
let { filter: filter2, select } = parseBodyWithSchema(
|
|
3828
|
+
dataReadFilterBodySchema,
|
|
3829
|
+
req.body,
|
|
3830
|
+
this.getRequestSchema("requestSchemas.advancedReadFilter")
|
|
3831
|
+
);
|
|
3832
|
+
const svc = req.dacl.getService(this.dataName);
|
|
3833
|
+
const result = await svc.findOne(filter2 ?? {}, { select }, {});
|
|
3834
|
+
handleResultError(result);
|
|
3835
|
+
return result.data;
|
|
3836
|
+
});
|
|
3837
|
+
this.router.post(`/${this.options.queryPath}/:${this.options.idParam}`, async (req) => {
|
|
3838
|
+
await this.assertAllowed(req, "read");
|
|
3839
|
+
const id = parsePathParam(req.params[this.options.idParam], this.options.idParam);
|
|
3840
|
+
let { select } = parseBodyWithSchema(
|
|
3841
|
+
dataReadByIdBodySchema,
|
|
3842
|
+
req.body,
|
|
3843
|
+
this.getRequestSchema("requestSchemas.advancedRead")
|
|
3844
|
+
);
|
|
3845
|
+
const svc = req.dacl.getService(this.dataName);
|
|
3846
|
+
const result = await svc.findById(id, { select }, {});
|
|
3847
|
+
handleResultError(result);
|
|
3848
|
+
return result.data;
|
|
3849
|
+
});
|
|
3850
|
+
}
|
|
3851
|
+
set(keyOrOptions, value) {
|
|
3852
|
+
if (arguments.length === 2 && isString10(keyOrOptions)) {
|
|
3853
|
+
setDataOption(this.dataName, keyOrOptions, value);
|
|
3854
|
+
}
|
|
3855
|
+
if (arguments.length === 1 && isPlainObject9(keyOrOptions)) {
|
|
3856
|
+
setDataOptions(this.dataName, keyOrOptions);
|
|
3857
|
+
}
|
|
3858
|
+
return this;
|
|
3859
|
+
}
|
|
3860
|
+
setOption(key, option) {
|
|
3861
|
+
setDataOption(this.dataName, key, option);
|
|
3862
|
+
return this;
|
|
3863
|
+
}
|
|
3864
|
+
setOptions(options) {
|
|
3865
|
+
setDataOptions(this.dataName, options);
|
|
3866
|
+
return this;
|
|
3867
|
+
}
|
|
3868
|
+
get routes() {
|
|
3869
|
+
return this.router.original;
|
|
3870
|
+
}
|
|
3871
|
+
};
|
|
3872
|
+
|
|
3873
|
+
// src/routers/index.ts
|
|
3874
|
+
import JsonRouter7 from "@web-ts-toolkit/express-json-router";
|
|
3875
|
+
var accessRouterResponseHandler = JsonRouter7.createHandler({
|
|
3876
|
+
errorFormat: JsonRouter7.ErrorFormats.rfc9457,
|
|
3877
|
+
rfc9457ContentType: "application/problem+json"
|
|
3878
|
+
});
|
|
3879
|
+
accessRouterResponseHandler.errorMessageProvider = function(error) {
|
|
3880
|
+
const errorLike = error;
|
|
3881
|
+
console.error(error);
|
|
3882
|
+
return errorLike.message || errorLike._message || String(error);
|
|
3883
|
+
};
|
|
3884
|
+
|
|
3885
|
+
// src/plugins.ts
|
|
3886
|
+
function permissionsPlugin(schema, options) {
|
|
3887
|
+
if (!options?.modelName) return;
|
|
3888
|
+
schema.virtual(options?.virtualPermissionField || "permissions").get(function() {
|
|
3889
|
+
const docPermissionField = getModelOption(options.modelName, "documentPermissionField");
|
|
3890
|
+
return this._doc[docPermissionField];
|
|
3891
|
+
});
|
|
3892
|
+
}
|
|
3893
|
+
|
|
3894
|
+
// src/index.ts
|
|
3895
|
+
var wtt = macl;
|
|
3896
|
+
wtt.createRouter = function(modelName, options) {
|
|
3897
|
+
return isUndefined4(options) ? new RootRouter(modelName) : new ModelRouter(modelName, options);
|
|
3898
|
+
};
|
|
3899
|
+
wtt.createDataRouter = function(dataName, options) {
|
|
3900
|
+
return new DataRouter(dataName, options);
|
|
3901
|
+
};
|
|
3902
|
+
wtt.set = function(keyOrOptions, value) {
|
|
3903
|
+
if (arguments.length === 2 && isString11(keyOrOptions)) {
|
|
3904
|
+
return setGlobalOption(keyOrOptions, value);
|
|
3905
|
+
}
|
|
3906
|
+
if (arguments.length === 1 && isPlainObject10(keyOrOptions)) {
|
|
3907
|
+
return setGlobalOptions(keyOrOptions);
|
|
3908
|
+
}
|
|
3909
|
+
};
|
|
3910
|
+
wtt.setGlobalOptions = setGlobalOptions;
|
|
3911
|
+
wtt.setGlobalOption = setGlobalOption;
|
|
3912
|
+
wtt.getGlobalOptions = getGlobalOptions;
|
|
3913
|
+
wtt.getGlobalOption = getGlobalOption;
|
|
3914
|
+
wtt.setModelOptions = setModelOptions;
|
|
3915
|
+
wtt.setModelOption = setModelOption;
|
|
3916
|
+
wtt.getModelOptions = getModelOptions;
|
|
3917
|
+
wtt.getModelOption = getModelOption;
|
|
3918
|
+
wtt.setDefaultModelOptions = setDefaultModelOptions;
|
|
3919
|
+
wtt.setDefaultModelOption = setDefaultModelOption;
|
|
3920
|
+
wtt.getDefaultModelOptions = getDefaultModelOptions;
|
|
3921
|
+
wtt.getDefaultModelOption = getDefaultModelOption;
|
|
3922
|
+
wtt.RootRouter = RootRouter;
|
|
3923
|
+
wtt.ModelRouter = ModelRouter;
|
|
3924
|
+
wtt.DataRouter = DataRouter;
|
|
3925
|
+
var index_default = wtt;
|
|
3926
|
+
export {
|
|
3927
|
+
CORE,
|
|
3928
|
+
Codes,
|
|
3929
|
+
CustomHeaders,
|
|
3930
|
+
DATA_MIDDLEWARE,
|
|
3931
|
+
FilterOperator,
|
|
3932
|
+
MIDDLEWARE,
|
|
3933
|
+
ModelRouter,
|
|
3934
|
+
PERMISSIONS,
|
|
3935
|
+
PERMISSION_KEYS,
|
|
3936
|
+
RootRouter,
|
|
3937
|
+
StatusCodes,
|
|
3938
|
+
advancedCreateBodySchema,
|
|
3939
|
+
advancedUpdateBodySchema,
|
|
3940
|
+
advancedUpsertBodySchema,
|
|
3941
|
+
countBodySchema,
|
|
3942
|
+
createBodySchema,
|
|
3943
|
+
dataListBodySchema,
|
|
3944
|
+
dataReadByIdBodySchema,
|
|
3945
|
+
dataReadFilterBodySchema,
|
|
3946
|
+
index_default as default,
|
|
3947
|
+
distinctBodySchema,
|
|
3948
|
+
getDefaultModelOption,
|
|
3949
|
+
getDefaultModelOptions,
|
|
3950
|
+
getGlobalOption,
|
|
3951
|
+
getGlobalOptions,
|
|
3952
|
+
getModelJsonSchema,
|
|
3953
|
+
getModelNames,
|
|
3954
|
+
getModelOption,
|
|
3955
|
+
getModelOptions,
|
|
3956
|
+
guard,
|
|
3957
|
+
listBodySchema,
|
|
3958
|
+
parseBody,
|
|
3959
|
+
parseBodyWithSchema,
|
|
3960
|
+
parseNestedBodyWithSchema,
|
|
3961
|
+
parsePathParam,
|
|
3962
|
+
parseQuery,
|
|
3963
|
+
permissionsPlugin,
|
|
3964
|
+
readByIdBodySchema,
|
|
3965
|
+
readFilterBodySchema,
|
|
3966
|
+
requestSchemas,
|
|
3967
|
+
rootQuerySchema,
|
|
3968
|
+
setDefaultModelOption,
|
|
3969
|
+
setDefaultModelOptions,
|
|
3970
|
+
setGlobalOption,
|
|
3971
|
+
setGlobalOptions,
|
|
3972
|
+
setModelOption,
|
|
3973
|
+
setModelOptions,
|
|
3974
|
+
subListBodySchema,
|
|
3975
|
+
subMutationBodySchema,
|
|
3976
|
+
subReadBodySchema,
|
|
3977
|
+
updateBodySchema,
|
|
3978
|
+
upsertBodySchema
|
|
3979
|
+
};
|