jxp 3.0.0 → 4.1.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/.env.sample +43 -6
- package/README.md +27 -13
- package/dist/bin/init_db.d.ts +3 -0
- package/dist/bin/init_db.d.ts.map +1 -0
- package/dist/bin/init_db.js +48 -0
- package/dist/bin/init_db.js.map +1 -0
- package/dist/bin/server.d.ts +4 -0
- package/dist/bin/server.d.ts.map +1 -0
- package/dist/bin/server.js +132 -0
- package/dist/bin/server.js.map +1 -0
- package/dist/bin/setup.d.ts +3 -0
- package/dist/bin/setup.d.ts.map +1 -0
- package/dist/bin/setup.js +185 -0
- package/dist/bin/setup.js.map +1 -0
- package/dist/globals.d.ts +14 -0
- package/dist/globals.d.ts.map +1 -0
- package/dist/globals.js +3 -0
- package/dist/globals.js.map +1 -0
- package/dist/libs/aggregate_guard.d.ts +5 -0
- package/dist/libs/aggregate_guard.d.ts.map +1 -0
- package/dist/libs/aggregate_guard.js +58 -0
- package/dist/libs/aggregate_guard.js.map +1 -0
- package/dist/libs/bulkwrite_guard.d.ts +5 -0
- package/dist/libs/bulkwrite_guard.d.ts.map +1 -0
- package/dist/libs/bulkwrite_guard.js +44 -0
- package/dist/libs/bulkwrite_guard.js.map +1 -0
- package/dist/libs/cache.d.ts +2 -0
- package/dist/libs/cache.d.ts.map +1 -0
- package/dist/libs/cache.js +94 -0
- package/dist/libs/cache.js.map +1 -0
- package/dist/libs/call_guard.d.ts +3 -0
- package/dist/libs/call_guard.d.ts.map +1 -0
- package/dist/libs/call_guard.js +17 -0
- package/dist/libs/call_guard.js.map +1 -0
- package/dist/libs/connection_string.d.ts +3 -0
- package/dist/libs/connection_string.d.ts.map +1 -0
- package/dist/libs/connection_string.js +4 -0
- package/dist/libs/connection_string.js.map +1 -0
- package/dist/libs/datamunging.d.ts +2 -0
- package/dist/libs/datamunging.d.ts.map +1 -0
- package/dist/libs/datamunging.js +320 -0
- package/dist/libs/datamunging.js.map +1 -0
- package/dist/libs/docs-auth.d.ts +25 -0
- package/dist/libs/docs-auth.d.ts.map +1 -0
- package/dist/libs/docs-auth.js +205 -0
- package/dist/libs/docs-auth.js.map +1 -0
- package/dist/libs/docs.d.ts +2 -0
- package/dist/libs/docs.d.ts.map +1 -0
- package/dist/libs/docs.js +328 -0
- package/dist/libs/docs.js.map +1 -0
- package/dist/libs/groups.d.ts +2 -0
- package/dist/libs/groups.d.ts.map +1 -0
- package/dist/libs/groups.js +136 -0
- package/dist/libs/groups.js.map +1 -0
- package/dist/libs/jxp.d.ts +4 -0
- package/dist/libs/jxp.d.ts.map +1 -0
- package/dist/libs/jxp.js +1176 -0
- package/dist/libs/jxp.js.map +1 -0
- package/dist/libs/link_index.d.ts +14 -0
- package/dist/libs/link_index.d.ts.map +1 -0
- package/dist/libs/link_index.js +37 -0
- package/dist/libs/link_index.js.map +1 -0
- package/dist/libs/load-config.d.ts +16 -0
- package/dist/libs/load-config.d.ts.map +1 -0
- package/dist/libs/load-config.js +166 -0
- package/dist/libs/load-config.js.map +1 -0
- package/dist/libs/login.d.ts +2 -0
- package/dist/libs/login.d.ts.map +1 -0
- package/dist/libs/login.js +244 -0
- package/dist/libs/login.js.map +1 -0
- package/dist/libs/login_rate_limit.d.ts +16 -0
- package/dist/libs/login_rate_limit.d.ts.map +1 -0
- package/dist/libs/login_rate_limit.js +49 -0
- package/dist/libs/login_rate_limit.js.map +1 -0
- package/dist/libs/modeldir.d.ts +5 -0
- package/dist/libs/modeldir.d.ts.map +1 -0
- package/dist/libs/modeldir.js +15 -0
- package/dist/libs/modeldir.js.map +1 -0
- package/dist/libs/query_limits.d.ts +2 -0
- package/dist/libs/query_limits.d.ts.map +1 -0
- package/dist/libs/query_limits.js +84 -0
- package/dist/libs/query_limits.js.map +1 -0
- package/dist/libs/query_manipulation.d.ts +3 -0
- package/dist/libs/query_manipulation.d.ts.map +1 -0
- package/{libs → dist/libs}/query_manipulation.js +22 -21
- package/dist/libs/query_manipulation.js.map +1 -0
- package/dist/libs/query_sanitize.d.ts +5 -0
- package/dist/libs/query_sanitize.d.ts.map +1 -0
- package/dist/libs/query_sanitize.js +109 -0
- package/dist/libs/query_sanitize.js.map +1 -0
- package/dist/libs/response_sanitize.d.ts +7 -0
- package/dist/libs/response_sanitize.d.ts.map +1 -0
- package/dist/libs/response_sanitize.js +57 -0
- package/dist/libs/response_sanitize.js.map +1 -0
- package/dist/libs/safe_error.d.ts +3 -0
- package/dist/libs/safe_error.d.ts.map +1 -0
- package/dist/libs/safe_error.js +14 -0
- package/dist/libs/safe_error.js.map +1 -0
- package/dist/libs/schema.d.ts +14 -0
- package/dist/libs/schema.d.ts.map +1 -0
- package/dist/libs/schema.js +98 -0
- package/dist/libs/schema.js.map +1 -0
- package/dist/libs/schema_description.d.ts +3 -0
- package/dist/libs/schema_description.d.ts.map +1 -0
- package/{libs → dist/libs}/schema_description.js +12 -21
- package/dist/libs/schema_description.js.map +1 -0
- package/dist/libs/security.d.ts +2 -0
- package/dist/libs/security.d.ts.map +1 -0
- package/dist/libs/security.js +428 -0
- package/dist/libs/security.js.map +1 -0
- package/dist/libs/setup.d.ts +2 -0
- package/dist/libs/setup.d.ts.map +1 -0
- package/dist/libs/setup.js +87 -0
- package/dist/libs/setup.js.map +1 -0
- package/dist/libs/startup-deprecations.d.ts +2 -0
- package/dist/libs/startup-deprecations.d.ts.map +1 -0
- package/dist/libs/startup-deprecations.js +21 -0
- package/dist/libs/startup-deprecations.js.map +1 -0
- package/dist/libs/startup.d.ts +16 -0
- package/dist/libs/startup.d.ts.map +1 -0
- package/dist/libs/startup.js +63 -0
- package/dist/libs/startup.js.map +1 -0
- package/dist/libs/ws.d.ts +2 -0
- package/dist/libs/ws.d.ts.map +1 -0
- package/{libs → dist/libs}/ws.js +91 -73
- package/dist/libs/ws.js.map +1 -0
- package/dist/models/apikey_model.d.ts +10 -0
- package/dist/models/apikey_model.d.ts.map +1 -0
- package/dist/models/apikey_model.js +14 -0
- package/dist/models/apikey_model.js.map +1 -0
- package/dist/models/link_model.d.ts +9 -0
- package/dist/models/link_model.d.ts.map +1 -0
- package/dist/models/link_model.js +20 -0
- package/dist/models/link_model.js.map +1 -0
- package/dist/models/refreshtoken_model.d.ts +11 -0
- package/dist/models/refreshtoken_model.d.ts.map +1 -0
- package/dist/models/refreshtoken_model.js +22 -0
- package/dist/models/refreshtoken_model.js.map +1 -0
- package/dist/models/test_model.d.ts +24 -0
- package/dist/models/test_model.d.ts.map +1 -0
- package/dist/models/test_model.js +44 -0
- package/dist/models/test_model.js.map +1 -0
- package/dist/models/token_model.d.ts +14 -0
- package/dist/models/token_model.d.ts.map +1 -0
- package/dist/models/token_model.js +25 -0
- package/dist/models/token_model.js.map +1 -0
- package/dist/models/user_model.d.ts +15 -0
- package/dist/models/user_model.d.ts.map +1 -0
- package/dist/models/user_model.js +38 -0
- package/dist/models/user_model.js.map +1 -0
- package/dist/models/usergroups_model.d.ts +9 -0
- package/dist/models/usergroups_model.d.ts.map +1 -0
- package/dist/models/usergroups_model.js +18 -0
- package/dist/models/usergroups_model.js.map +1 -0
- package/dist/types/jxp-config.d.ts +151 -0
- package/dist/types/jxp-config.d.ts.map +1 -0
- package/dist/types/jxp-config.js +3 -0
- package/dist/types/jxp-config.js.map +1 -0
- package/dist/types/schema-fields.d.ts +43 -0
- package/dist/types/schema-fields.d.ts.map +1 -0
- package/dist/types/schema-fields.js +3 -0
- package/dist/types/schema-fields.js.map +1 -0
- package/docs/aggregations.md +40 -21
- package/docs/api.md +55 -15
- package/docs/authentication.md +60 -37
- package/docs/bulk_writes.md +11 -8
- package/docs/caching.md +28 -11
- package/docs/changelog.md +258 -31
- package/docs/configuration.md +126 -115
- package/docs/hooks.md +12 -8
- package/docs/index.md +17 -17
- package/docs/installation.md +23 -18
- package/docs/permissions.md +33 -19
- package/docs/queries.md +9 -5
- package/docs/schemas.md +21 -10
- package/docs/special.md +4 -4
- package/docs/typescript.md +86 -0
- package/docs/websocket.md +99 -1
- package/mkdocs.yml +1 -0
- package/package.json +121 -69
- package/templates/api.pug +32 -0
- package/templates/app-server.js +43 -0
- package/templates/app.env.sample +8 -0
- package/templates/assets/api-console.js +151 -0
- package/templates/assets/docs-login.js +66 -0
- package/templates/assets/docs.css +244 -0
- package/templates/index.pug +67 -2
- package/templates/layout.pug +21 -11
- package/templates/login.pug +39 -0
- package/templates/md.pug +1 -1
- package/templates/model.pug +117 -68
- package/templates/navbar.pug +25 -21
- package/templates/sidebar.pug +30 -0
- package/.env +0 -9
- package/.eslintrc.json +0 -20
- package/.jshintrc +0 -4
- package/.vscode/launch.json +0 -34
- package/.vscode/settings.json +0 -3
- package/Dockerfile +0 -7
- package/bin/init_db.js +0 -54
- package/bin/server.js +0 -131
- package/bin/setup.js +0 -190
- package/config_sample.json +0 -10
- package/docker-compose.yml +0 -32
- package/libs/cache.js +0 -92
- package/libs/connection_string.js +0 -24
- package/libs/datamunging.js +0 -335
- package/libs/docs.js +0 -162
- package/libs/groups.js +0 -133
- package/libs/jxp.js +0 -1208
- package/libs/login.js +0 -222
- package/libs/modeldir.js +0 -14
- package/libs/schema.js +0 -99
- package/libs/security.js +0 -410
- package/libs/setup.js +0 -90
- package/models/apikey_model.js +0 -10
- package/models/link_model.js +0 -17
- package/models/refreshtoken_model.js +0 -20
- package/models/test_model.js +0 -50
- package/models/token_model.js +0 -23
- package/models/user_model.js +0 -38
- package/models/usergroups_model.js +0 -15
- package/setup.sh +0 -3
- package/test/.eslintrc.json +0 -13
- package/test/auth.js +0 -190
- package/test/init.js +0 -92
- package/test/setup.test.js +0 -62
- package/test/test.js +0 -1527
- package/test/wstest.js.old +0 -124
package/dist/libs/jxp.js
ADDED
|
@@ -0,0 +1,1176 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const errors = require("restify-errors");
|
|
3
|
+
const restify = require("restify");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const security = require("./security");
|
|
6
|
+
const datamunging = require("./datamunging");
|
|
7
|
+
const login = require("./login");
|
|
8
|
+
const groups = require("./groups");
|
|
9
|
+
const setup = require("./setup");
|
|
10
|
+
const Docs = require("./docs");
|
|
11
|
+
const docsAuth = require("./docs-auth");
|
|
12
|
+
const loginRateLimit = require("./login_rate_limit");
|
|
13
|
+
const querystring = require("node:querystring");
|
|
14
|
+
const fs = require("fs");
|
|
15
|
+
const morgan = require("morgan");
|
|
16
|
+
const ws = require("./ws");
|
|
17
|
+
const modeldir = require("./modeldir");
|
|
18
|
+
const query_manipulation = require("./query_manipulation");
|
|
19
|
+
const corsMiddleware = require('restify-cors-middleware2');
|
|
20
|
+
const { Parser: CsvParser } = require('@json2csv/plainjs');
|
|
21
|
+
const cache = require("./cache");
|
|
22
|
+
const query_limits = require("./query_limits");
|
|
23
|
+
const query_sanitize = require("./query_sanitize");
|
|
24
|
+
const aggregate_guard = require("./aggregate_guard");
|
|
25
|
+
const bulkwrite_guard = require("./bulkwrite_guard");
|
|
26
|
+
const call_guard = require("./call_guard");
|
|
27
|
+
const response_sanitize = require("./response_sanitize");
|
|
28
|
+
const link_index = require("./link_index");
|
|
29
|
+
const { safeErrorMessage } = require("./safe_error");
|
|
30
|
+
const schemaModule = require("./schema");
|
|
31
|
+
global.JXPSchema = schemaModule.default || schemaModule;
|
|
32
|
+
var models = {};
|
|
33
|
+
var ops = 0;
|
|
34
|
+
var debug = false;
|
|
35
|
+
const USER_PRIVILEGE_FIELDS = ["admin", "password", "groups"];
|
|
36
|
+
function getStripFields(req) {
|
|
37
|
+
return req.config?.security?.strip_fields || ["password"];
|
|
38
|
+
}
|
|
39
|
+
function getSecurityOpts(req) {
|
|
40
|
+
return req.config?.security || {};
|
|
41
|
+
}
|
|
42
|
+
function advancedQueryAllowed(Model, kind) {
|
|
43
|
+
const opts = Model.schema.opts;
|
|
44
|
+
const aq = opts?.advanced_queries;
|
|
45
|
+
if (kind === "bulkwrite") {
|
|
46
|
+
return aq?.bulkwrite === true;
|
|
47
|
+
}
|
|
48
|
+
if (kind === "aggregate") {
|
|
49
|
+
return aq?.aggregate !== false;
|
|
50
|
+
}
|
|
51
|
+
return aq?.query !== false;
|
|
52
|
+
}
|
|
53
|
+
// Middleware
|
|
54
|
+
const middlewareModel = (req, res, next) => {
|
|
55
|
+
const modelname = req.params.modelname;
|
|
56
|
+
req.modelname = modelname;
|
|
57
|
+
req.Model = models[modelname];
|
|
58
|
+
if (!req.Model) {
|
|
59
|
+
throw new errors.NotFoundError(`Model ${modelname} not found`);
|
|
60
|
+
}
|
|
61
|
+
return next();
|
|
62
|
+
};
|
|
63
|
+
const middlewarePasswords = (req, res, next) => {
|
|
64
|
+
if (req.body && req.body.password) {
|
|
65
|
+
if (req.query.password_override) {
|
|
66
|
+
if (!res.user?.admin) {
|
|
67
|
+
throw new errors.ForbiddenError("password_override requires admin");
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
req.body.password = security.encPassword(req.body.password);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
next();
|
|
75
|
+
};
|
|
76
|
+
const middlewareCheckAdmin = (req, res, next) => {
|
|
77
|
+
if (req.modelname !== "user")
|
|
78
|
+
return next();
|
|
79
|
+
const isAdmin = res.user?.admin;
|
|
80
|
+
if (!isAdmin) {
|
|
81
|
+
if (req.params)
|
|
82
|
+
req.params.admin = false;
|
|
83
|
+
if (req.body) {
|
|
84
|
+
for (const field of USER_PRIVILEGE_FIELDS) {
|
|
85
|
+
if (field in req.body) {
|
|
86
|
+
delete req.body[field];
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
next();
|
|
92
|
+
};
|
|
93
|
+
// Outputs whatever is in res.result as JSON
|
|
94
|
+
const outputJSON = async (req, res) => {
|
|
95
|
+
try {
|
|
96
|
+
res.send(res.result);
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
console.error(new Date(), err);
|
|
100
|
+
throw new errors.InternalServerError(safeErrorMessage(err));
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
// Outputs whatever is in res.result as CSV
|
|
104
|
+
const outputCSV = (req, res, next) => {
|
|
105
|
+
const opts = { "flatten": true };
|
|
106
|
+
if (!res.result.data) {
|
|
107
|
+
throw new errors.InternalServerError("Error generating CSV");
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
const data = res.result.data.map(row => row._doc);
|
|
111
|
+
if (!data.length) {
|
|
112
|
+
throw ("");
|
|
113
|
+
}
|
|
114
|
+
res.writeHead(200, {
|
|
115
|
+
'Content-Type': 'text/csv',
|
|
116
|
+
'Content-Disposition': 'attachment; filename=export.csv'
|
|
117
|
+
});
|
|
118
|
+
const csv = new CsvParser(opts).parse(data);
|
|
119
|
+
res.end(csv);
|
|
120
|
+
next();
|
|
121
|
+
}
|
|
122
|
+
catch (err) {
|
|
123
|
+
console.error(err);
|
|
124
|
+
throw new errors.InternalServerError(safeErrorMessage(err));
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
// Actions (verbs)
|
|
128
|
+
const actionGet = async (req, res) => {
|
|
129
|
+
const opname = `get ${req.modelname} ${ops++}`;
|
|
130
|
+
console.time(opname);
|
|
131
|
+
let filters = {};
|
|
132
|
+
try {
|
|
133
|
+
filters = parseFilter(req.query.filter);
|
|
134
|
+
filters = query_sanitize.sanitizeFilter(filters, getSecurityOpts(req));
|
|
135
|
+
}
|
|
136
|
+
catch (err) {
|
|
137
|
+
console.trace(new Date(), err);
|
|
138
|
+
// Preserve BadRequestError, convert others to InternalServerError
|
|
139
|
+
if (err instanceof errors.BadRequestError) {
|
|
140
|
+
throw err;
|
|
141
|
+
}
|
|
142
|
+
if (err instanceof errors.ForbiddenError) {
|
|
143
|
+
throw err;
|
|
144
|
+
}
|
|
145
|
+
throw new errors.InternalServerError(safeErrorMessage(err));
|
|
146
|
+
}
|
|
147
|
+
const search = query_sanitize.parseSearchObject(req.query.search);
|
|
148
|
+
for (const i in search) {
|
|
149
|
+
filters[i] = search[i];
|
|
150
|
+
}
|
|
151
|
+
let countquery = filters;
|
|
152
|
+
let qcount = req.Model.find(filters);
|
|
153
|
+
let q = req.Model.find(filters);
|
|
154
|
+
let checkDeleted = [{ _deleted: false }, { _deleted: null }];
|
|
155
|
+
if (!req.query.showDeleted) {
|
|
156
|
+
countquery = Object.assign({ $or: checkDeleted }, countquery);
|
|
157
|
+
qcount.or(checkDeleted);
|
|
158
|
+
q.or(checkDeleted);
|
|
159
|
+
}
|
|
160
|
+
if (req.query.search) {
|
|
161
|
+
// console.log({ search: req.query.search });
|
|
162
|
+
q = req.Model.find({ $text: { $search: req.query.search } }, { score: { $meta: "textScore" } }).sort({ score: { $meta: "textScore" } });
|
|
163
|
+
countquery = Object.assign({ $text: { $search: req.query.search } }, countquery);
|
|
164
|
+
qcount = req.Model.find({ $text: { $search: req.query.search } });
|
|
165
|
+
}
|
|
166
|
+
if (res.user) {
|
|
167
|
+
q.options = ({ user: res.user });
|
|
168
|
+
}
|
|
169
|
+
try {
|
|
170
|
+
const estimatedCount = await req.Model.estimatedDocumentCount();
|
|
171
|
+
let count = -1;
|
|
172
|
+
if (query_limits.shouldRunCount(req)) {
|
|
173
|
+
if (estimatedCount < 100000 && Object.keys(countquery).length !== 0) {
|
|
174
|
+
count = await qcount.countDocuments();
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
count = estimatedCount;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
const result = {};
|
|
181
|
+
if (count >= 0) {
|
|
182
|
+
result.count = count;
|
|
183
|
+
}
|
|
184
|
+
const effectiveLimit = query_limits.enforceListLimit(req, estimatedCount);
|
|
185
|
+
query_limits.applyListPagination(q, result, req, effectiveLimit, count >= 0 ? count : 0, changeUrlParams);
|
|
186
|
+
if (req.query.sort) {
|
|
187
|
+
q.sort(req.query.sort);
|
|
188
|
+
result.sort = req.query.sort;
|
|
189
|
+
}
|
|
190
|
+
if (req.query.populate) {
|
|
191
|
+
if ((typeof req.query.populate === "object") && !Array.isArray(req.query.populate)) {
|
|
192
|
+
for (let i in req.query.populate) {
|
|
193
|
+
q.populate(i, req.query.populate[i].replace(/,/g, " "));
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
q.populate(req.query.populate);
|
|
198
|
+
}
|
|
199
|
+
result.populate = req.query.populate;
|
|
200
|
+
}
|
|
201
|
+
if (req.query.autopopulate) {
|
|
202
|
+
res.header("jxp-autopopulate-warning", "expensive");
|
|
203
|
+
for (let key in req.Model.schema.paths) {
|
|
204
|
+
const dirpath = req.Model.schema.paths[key];
|
|
205
|
+
if (dirpath.instance == "ObjectID" && dirpath.options.link) {
|
|
206
|
+
q.populate(String(dirpath.options.map_to || dirpath.options.virtual || dirpath.options.link.toLowerCase()));
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
result.autopopulate = true;
|
|
210
|
+
}
|
|
211
|
+
if (req.query.fields) {
|
|
212
|
+
const fields = req.query.fields.split(",");
|
|
213
|
+
const select = {};
|
|
214
|
+
fields.forEach(field => {
|
|
215
|
+
select[field] = 1;
|
|
216
|
+
});
|
|
217
|
+
q.select(select);
|
|
218
|
+
}
|
|
219
|
+
if (req.query.search) {
|
|
220
|
+
result.search = req.query.search;
|
|
221
|
+
}
|
|
222
|
+
result.data = await q.exec();
|
|
223
|
+
response_sanitize.sanitizeListResult(result, getStripFields(req));
|
|
224
|
+
res.result = result;
|
|
225
|
+
if (debug)
|
|
226
|
+
console.timeEnd(opname);
|
|
227
|
+
}
|
|
228
|
+
catch (err) {
|
|
229
|
+
console.error(new Date(), err);
|
|
230
|
+
if (debug)
|
|
231
|
+
console.timeEnd(opname);
|
|
232
|
+
// Preserve BadRequestError, convert others to InternalServerError
|
|
233
|
+
if (err instanceof errors.BadRequestError) {
|
|
234
|
+
throw err;
|
|
235
|
+
}
|
|
236
|
+
if (err instanceof errors.ForbiddenError) {
|
|
237
|
+
throw err;
|
|
238
|
+
}
|
|
239
|
+
if (err.code)
|
|
240
|
+
throw err;
|
|
241
|
+
throw new errors.InternalServerError(safeErrorMessage(err));
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
const actionGetOne = async (req, res) => {
|
|
245
|
+
const opname = `getOne ${req.modelname}/${req.params.item_id} ${ops++}`;
|
|
246
|
+
console.time(opname);
|
|
247
|
+
try {
|
|
248
|
+
const data = await getOne(req.Model, req.params.item_id, req.query, { user: res.user });
|
|
249
|
+
res.result = { data };
|
|
250
|
+
if (debug)
|
|
251
|
+
console.timeEnd(opname);
|
|
252
|
+
}
|
|
253
|
+
catch (err) {
|
|
254
|
+
console.error(new Date(), err);
|
|
255
|
+
if (debug)
|
|
256
|
+
console.timeEnd(opname);
|
|
257
|
+
if (err.code)
|
|
258
|
+
throw err;
|
|
259
|
+
throw new errors.InternalServerError(safeErrorMessage(err));
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
const actionPost = async (req, res) => {
|
|
263
|
+
const opname = `post ${req.modelname} ${ops++}`;
|
|
264
|
+
console.time(opname);
|
|
265
|
+
try {
|
|
266
|
+
let item = new req.Model();
|
|
267
|
+
_populateItem(item, datamunging.deserialize(req.body));
|
|
268
|
+
if (res.user) {
|
|
269
|
+
item._owner_id = res.user._id;
|
|
270
|
+
item.__user = res.user;
|
|
271
|
+
}
|
|
272
|
+
const result = await item.save();
|
|
273
|
+
let silence = req.params._silence;
|
|
274
|
+
if (req.body && req.body._silence)
|
|
275
|
+
silence = true;
|
|
276
|
+
if (!silence) {
|
|
277
|
+
req.config.callbacks.post.call(null, req.modelname, result, res.user);
|
|
278
|
+
ws.postHook.call(null, req.modelname, result, res.user);
|
|
279
|
+
}
|
|
280
|
+
res.json({
|
|
281
|
+
status: "ok",
|
|
282
|
+
message: req.modelname + " created",
|
|
283
|
+
data: item
|
|
284
|
+
});
|
|
285
|
+
if (debug)
|
|
286
|
+
console.timeEnd(opname);
|
|
287
|
+
}
|
|
288
|
+
catch (err) {
|
|
289
|
+
console.error(new Date(), err);
|
|
290
|
+
if (debug)
|
|
291
|
+
console.timeEnd(opname);
|
|
292
|
+
if (err.code)
|
|
293
|
+
throw err;
|
|
294
|
+
throw new errors.InternalServerError(safeErrorMessage(err));
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
const actionPut = async (req, res) => {
|
|
298
|
+
const opname = `put ${req.modelname}/${req.params.item_id} ${ops++}`;
|
|
299
|
+
console.time(opname);
|
|
300
|
+
try {
|
|
301
|
+
let item = await req.Model.findById(req.params.item_id);
|
|
302
|
+
if (!item) {
|
|
303
|
+
console.error(new Date(), "Document not found");
|
|
304
|
+
throw new errors.NotFoundError(`Document ${req.params.item_id} not found on ${req.modelname}`);
|
|
305
|
+
}
|
|
306
|
+
_populateItem(item, datamunging.deserialize(req.body));
|
|
307
|
+
_versionItem(item);
|
|
308
|
+
if (res.user) {
|
|
309
|
+
item.__user = res.user;
|
|
310
|
+
item._updated_by_id = res.user._id;
|
|
311
|
+
}
|
|
312
|
+
const data = await item.save();
|
|
313
|
+
let silence = req.params._silence;
|
|
314
|
+
if (req.body && req.body._silence)
|
|
315
|
+
silence = true;
|
|
316
|
+
if (!silence) {
|
|
317
|
+
req.config.callbacks.put.call(null, req.modelname, item, res.user);
|
|
318
|
+
ws.putHook.call(null, req.modelname, item, res.user);
|
|
319
|
+
}
|
|
320
|
+
res.json({
|
|
321
|
+
status: "ok",
|
|
322
|
+
message: req.modelname + " updated",
|
|
323
|
+
data: data
|
|
324
|
+
});
|
|
325
|
+
if (debug)
|
|
326
|
+
console.timeEnd(opname);
|
|
327
|
+
}
|
|
328
|
+
catch (err) {
|
|
329
|
+
console.error(new Date(), err);
|
|
330
|
+
if (debug)
|
|
331
|
+
console.timeEnd(opname);
|
|
332
|
+
if (err.code)
|
|
333
|
+
throw err;
|
|
334
|
+
throw new errors.InternalServerError(safeErrorMessage(err));
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
const actionUpdate = async (req, res) => {
|
|
338
|
+
const opname = `update ${req.modelname}/${req.params.item_id} ${ops++}`;
|
|
339
|
+
console.time(opname);
|
|
340
|
+
try {
|
|
341
|
+
const body_data = datamunging.deserialize(req.body);
|
|
342
|
+
const item = await req.Model.findById(req.params.item_id);
|
|
343
|
+
if (!item) {
|
|
344
|
+
throw new errors.NotFoundError(`Document ${req.params.item_id} not found on ${req.modelname}`);
|
|
345
|
+
}
|
|
346
|
+
_populateItem(item, body_data);
|
|
347
|
+
_versionItem(item);
|
|
348
|
+
if (res.user) {
|
|
349
|
+
item.__user = res.user;
|
|
350
|
+
item._updated_by_id = res.user._id;
|
|
351
|
+
}
|
|
352
|
+
const data = await item.save();
|
|
353
|
+
let silence = req.params._silence;
|
|
354
|
+
if (req.body && req.body._silence)
|
|
355
|
+
silence = true;
|
|
356
|
+
if (!silence) {
|
|
357
|
+
req.config.callbacks.put.call(null, req.modelname, item, res.user);
|
|
358
|
+
ws.putHook.call(null, req.modelname, item, res.user);
|
|
359
|
+
}
|
|
360
|
+
res.json({
|
|
361
|
+
status: "ok",
|
|
362
|
+
message: req.modelname + " updated",
|
|
363
|
+
data
|
|
364
|
+
});
|
|
365
|
+
if (debug)
|
|
366
|
+
console.timeEnd(opname);
|
|
367
|
+
}
|
|
368
|
+
catch (err) {
|
|
369
|
+
console.error(new Date(), err);
|
|
370
|
+
if (debug)
|
|
371
|
+
console.timeEnd(opname);
|
|
372
|
+
if (err.code)
|
|
373
|
+
throw err;
|
|
374
|
+
throw new errors.InternalServerError(safeErrorMessage(err));
|
|
375
|
+
}
|
|
376
|
+
};
|
|
377
|
+
const actionDelete = async (req, res) => {
|
|
378
|
+
const permaDelete = req.query._permaDelete;
|
|
379
|
+
const cascade = req.query._cascade;
|
|
380
|
+
let silence = req.query._silence || (req.body && req.body._silence);
|
|
381
|
+
const opname = `del ${req.modelname}/${req.params.item_id} ${ops++}`;
|
|
382
|
+
console.time(opname);
|
|
383
|
+
try {
|
|
384
|
+
let item = await req.Model.findById(req.params.item_id);
|
|
385
|
+
if (!item) {
|
|
386
|
+
throw new errors.NotFoundError(`Couldn't find item ${req.params.item_id} for delete on ${req.modelname}`);
|
|
387
|
+
}
|
|
388
|
+
const linked_models = link_index.getReferrers(req.Model.modelName);
|
|
389
|
+
const referrerChecks = linked_models.map(async (linked_model) => {
|
|
390
|
+
const q = {};
|
|
391
|
+
q[linked_model.field] = item._id;
|
|
392
|
+
const check = await models[linked_model.modelname].countDocuments(q);
|
|
393
|
+
if (check) {
|
|
394
|
+
if (cascade) {
|
|
395
|
+
if (permaDelete) {
|
|
396
|
+
await models[linked_model.modelname].deleteMany(q);
|
|
397
|
+
}
|
|
398
|
+
else {
|
|
399
|
+
await models[linked_model.modelname].updateMany(q, { _deleted: true });
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
else {
|
|
403
|
+
throw new errors.ConflictError(`Parent link item exists in ${linked_model.modelname}/${linked_model.field}`);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
await Promise.all(referrerChecks);
|
|
408
|
+
if (res.user) {
|
|
409
|
+
item.__user = res.user;
|
|
410
|
+
}
|
|
411
|
+
if (Object.prototype.hasOwnProperty.call(req.Model.schema.paths, "_deleted") && !(permaDelete)) {
|
|
412
|
+
item._deleted = true;
|
|
413
|
+
_versionItem(item);
|
|
414
|
+
await item.save();
|
|
415
|
+
}
|
|
416
|
+
else {
|
|
417
|
+
// console.log("Hard deleting");
|
|
418
|
+
await req.Model.deleteOne({ _id: item._id });
|
|
419
|
+
}
|
|
420
|
+
if (!silence) {
|
|
421
|
+
req.config.callbacks.delete.call(null, req.modelname, item, res.user, { soft: false });
|
|
422
|
+
ws.delHook.call(null, req.modelname, item, res.user);
|
|
423
|
+
}
|
|
424
|
+
res.json({
|
|
425
|
+
status: "ok",
|
|
426
|
+
message: `${req.modelname}/${req.params.item_id} deleted`
|
|
427
|
+
});
|
|
428
|
+
if (debug)
|
|
429
|
+
console.timeEnd(opname);
|
|
430
|
+
}
|
|
431
|
+
catch (err) {
|
|
432
|
+
console.error(new Date(), err);
|
|
433
|
+
if (debug)
|
|
434
|
+
console.timeEnd(opname);
|
|
435
|
+
if (err.code)
|
|
436
|
+
throw err;
|
|
437
|
+
throw new errors.InternalServerError(safeErrorMessage(err));
|
|
438
|
+
}
|
|
439
|
+
};
|
|
440
|
+
const actionCount = async (req, res) => {
|
|
441
|
+
const opname = `count ${req.modelname} ${ops++}`;
|
|
442
|
+
console.time(opname);
|
|
443
|
+
let filters = {};
|
|
444
|
+
try {
|
|
445
|
+
filters = parseFilter(req.query.filter);
|
|
446
|
+
filters = query_sanitize.sanitizeFilter(filters, getSecurityOpts(req));
|
|
447
|
+
}
|
|
448
|
+
catch (err) {
|
|
449
|
+
console.trace(new Date(), err);
|
|
450
|
+
if (err instanceof errors.BadRequestError || err instanceof errors.ForbiddenError) {
|
|
451
|
+
throw err;
|
|
452
|
+
}
|
|
453
|
+
throw new errors.InternalServerError(safeErrorMessage(err));
|
|
454
|
+
}
|
|
455
|
+
const search = query_sanitize.parseSearchObject(req.query.search);
|
|
456
|
+
for (const i in search) {
|
|
457
|
+
filters[i] = search[i];
|
|
458
|
+
}
|
|
459
|
+
if (!req.query.showDeleted) {
|
|
460
|
+
filters = Object.assign({ $or: [{ _deleted: false }, { _deleted: null }] }, filters);
|
|
461
|
+
}
|
|
462
|
+
try {
|
|
463
|
+
const count = await req.Model.countDocuments(filters).exec();
|
|
464
|
+
res.result = { count };
|
|
465
|
+
if (debug)
|
|
466
|
+
console.timeEnd(opname);
|
|
467
|
+
}
|
|
468
|
+
catch (err) {
|
|
469
|
+
console.error(new Date(), err);
|
|
470
|
+
if (debug)
|
|
471
|
+
console.timeEnd(opname);
|
|
472
|
+
if (err.code)
|
|
473
|
+
throw err;
|
|
474
|
+
throw new errors.InternalServerError(safeErrorMessage(err));
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
const actionCall = async (req, res) => {
|
|
478
|
+
req.body = req.body || {};
|
|
479
|
+
req.body.__user = res.user || null;
|
|
480
|
+
try {
|
|
481
|
+
call_guard.assertCallableStatic(req.Model, req.params.method_name);
|
|
482
|
+
const result = await req.Model[req.params.method_name](req.body);
|
|
483
|
+
res.json(result);
|
|
484
|
+
}
|
|
485
|
+
catch (err) {
|
|
486
|
+
console.error(new Date(), err);
|
|
487
|
+
if (err.code)
|
|
488
|
+
throw err;
|
|
489
|
+
if (err instanceof errors.ForbiddenError || err instanceof errors.NotFoundError) {
|
|
490
|
+
throw err;
|
|
491
|
+
}
|
|
492
|
+
throw new errors.InternalServerError(safeErrorMessage(err));
|
|
493
|
+
}
|
|
494
|
+
};
|
|
495
|
+
const actionCallItem = async (req, res) => {
|
|
496
|
+
try {
|
|
497
|
+
call_guard.assertCallableStatic(req.Model, req.params.method_name);
|
|
498
|
+
const item = await req.Model.findById(req.params.item_id);
|
|
499
|
+
if (!item) {
|
|
500
|
+
throw new errors.NotFoundError(`Couldn't find item ${req.params.item_id} on ${req.modelname} for call`);
|
|
501
|
+
}
|
|
502
|
+
if (item._deleted && !req.query.showDeleted) {
|
|
503
|
+
throw new errors.NotFoundError(`Document ${req.params.item_id} is deleted on ${req.modelname}`);
|
|
504
|
+
}
|
|
505
|
+
const body = req.body || {};
|
|
506
|
+
body.__user = res.user || null;
|
|
507
|
+
const result = await req.Model[req.params.method_name](item, body);
|
|
508
|
+
res.json(result);
|
|
509
|
+
}
|
|
510
|
+
catch (err) {
|
|
511
|
+
console.trace(err);
|
|
512
|
+
if (err.code)
|
|
513
|
+
throw err;
|
|
514
|
+
if (err instanceof errors.ForbiddenError || err instanceof errors.NotFoundError) {
|
|
515
|
+
throw err;
|
|
516
|
+
}
|
|
517
|
+
throw new errors.InternalServerError(safeErrorMessage(err));
|
|
518
|
+
}
|
|
519
|
+
};
|
|
520
|
+
// Actions (verbs)
|
|
521
|
+
const actionQuery = async (req, res) => {
|
|
522
|
+
if (!advancedQueryAllowed(req.Model, "query")) {
|
|
523
|
+
throw new errors.ForbiddenError(`POST /query is disabled for model ${req.modelname}`);
|
|
524
|
+
}
|
|
525
|
+
if (!req.body || !req.body.query || typeof req.body.query !== "object") {
|
|
526
|
+
throw new errors.BadRequestError("Query missing or not of type object");
|
|
527
|
+
}
|
|
528
|
+
const opname = `query ${req.modelname} ${ops++}`;
|
|
529
|
+
console.time(opname);
|
|
530
|
+
const sanitizedQuery = query_sanitize.sanitizeFilter(req.body.query, getSecurityOpts(req));
|
|
531
|
+
let query = [sanitizedQuery];
|
|
532
|
+
let checkDeleted = { "$or": [{ _deleted: false }, { _deleted: null }] };
|
|
533
|
+
if (!req.query.showDeleted) {
|
|
534
|
+
query.push(checkDeleted);
|
|
535
|
+
}
|
|
536
|
+
let qcount = req.Model.find({ "$and": query });
|
|
537
|
+
let q = req.Model.find({ "$and": query });
|
|
538
|
+
try {
|
|
539
|
+
let count = -1;
|
|
540
|
+
if (query_limits.shouldRunCount(req)) {
|
|
541
|
+
count = await qcount.countDocuments();
|
|
542
|
+
}
|
|
543
|
+
const result = {};
|
|
544
|
+
if (count >= 0) {
|
|
545
|
+
result.count = count;
|
|
546
|
+
}
|
|
547
|
+
const estimatedCount = await req.Model.estimatedDocumentCount();
|
|
548
|
+
const effectiveLimit = query_limits.enforceListLimit(req, estimatedCount);
|
|
549
|
+
query_limits.applyListPagination(q, result, req, effectiveLimit, count >= 0 ? count : 0, changeUrlParams);
|
|
550
|
+
if (req.query.sort) {
|
|
551
|
+
q.sort(req.query.sort);
|
|
552
|
+
result.sort = req.query.sort;
|
|
553
|
+
}
|
|
554
|
+
if (req.query.populate) {
|
|
555
|
+
if ((typeof req.query.populate === "object") && !Array.isArray(req.query.populate)) {
|
|
556
|
+
for (let i in req.query.populate) {
|
|
557
|
+
q.populate(i, req.query.populate[i].replace(/,/g, " "));
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
else {
|
|
561
|
+
q.populate(req.query.populate);
|
|
562
|
+
}
|
|
563
|
+
result.populate = req.query.populate;
|
|
564
|
+
}
|
|
565
|
+
if (req.query.autopopulate) {
|
|
566
|
+
for (let key in req.Model.schema.paths) {
|
|
567
|
+
const dirpath = req.Model.schema.paths[key];
|
|
568
|
+
if (dirpath.instance == "ObjectID" && dirpath.options.link) {
|
|
569
|
+
q.populate(String(dirpath.options.map_to || dirpath.options.virtual || dirpath.options.link));
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
result.autopopulate = true;
|
|
573
|
+
}
|
|
574
|
+
if (req.query.fields) {
|
|
575
|
+
const fields = req.query.fields.split(",");
|
|
576
|
+
const select = {};
|
|
577
|
+
fields.forEach(field => {
|
|
578
|
+
select[field] = 1;
|
|
579
|
+
});
|
|
580
|
+
q.select(select);
|
|
581
|
+
}
|
|
582
|
+
result.data = await q.exec();
|
|
583
|
+
response_sanitize.sanitizeListResult(result, getStripFields(req));
|
|
584
|
+
res.result = result;
|
|
585
|
+
if (debug)
|
|
586
|
+
console.timeEnd(opname);
|
|
587
|
+
res.json(result);
|
|
588
|
+
}
|
|
589
|
+
catch (err) {
|
|
590
|
+
console.error(new Date(), err);
|
|
591
|
+
if (debug)
|
|
592
|
+
console.timeEnd(opname);
|
|
593
|
+
if (err instanceof errors.BadRequestError || err instanceof errors.ForbiddenError) {
|
|
594
|
+
throw err;
|
|
595
|
+
}
|
|
596
|
+
if (err.code)
|
|
597
|
+
throw err;
|
|
598
|
+
throw new errors.InternalServerError(safeErrorMessage(err));
|
|
599
|
+
}
|
|
600
|
+
};
|
|
601
|
+
// Actions (verbs)
|
|
602
|
+
const actionAggregate = async (req, res) => {
|
|
603
|
+
if (!advancedQueryAllowed(req.Model, "aggregate")) {
|
|
604
|
+
throw new errors.ForbiddenError(`POST /aggregate is disabled for model ${req.modelname}`);
|
|
605
|
+
}
|
|
606
|
+
let query = (req.body.query) ? req.body.query : req.body; // Don't require to embed in query anymore
|
|
607
|
+
if (!query || !Array.isArray(query)) {
|
|
608
|
+
console.error("query missing or not of type array");
|
|
609
|
+
throw new errors.BadRequestError("Query missing or not of type array");
|
|
610
|
+
}
|
|
611
|
+
query = query_manipulation.fix_query(query);
|
|
612
|
+
aggregate_guard.validatePipeline(query, {
|
|
613
|
+
aggregate_stages_allow: getSecurityOpts(req).aggregate_stages_allow,
|
|
614
|
+
isAdmin: res.user?.admin,
|
|
615
|
+
});
|
|
616
|
+
const opname = `aggregate ${req.modelname} ${ops++}`;
|
|
617
|
+
console.time(opname);
|
|
618
|
+
try {
|
|
619
|
+
let result = {};
|
|
620
|
+
if (req.query.allowDiskUse) {
|
|
621
|
+
result.data = await req.Model.aggregate(query).allowDiskUse(true).exec();
|
|
622
|
+
}
|
|
623
|
+
else {
|
|
624
|
+
result.data = await req.Model.aggregate(query);
|
|
625
|
+
}
|
|
626
|
+
response_sanitize.sanitizeResponse(result, getStripFields(req));
|
|
627
|
+
res.result = result;
|
|
628
|
+
if (debug)
|
|
629
|
+
console.timeEnd(opname);
|
|
630
|
+
res.json(result);
|
|
631
|
+
}
|
|
632
|
+
catch (err) {
|
|
633
|
+
console.error(new Date(), err);
|
|
634
|
+
if (debug)
|
|
635
|
+
console.timeEnd(opname);
|
|
636
|
+
if (err instanceof errors.BadRequestError || err instanceof errors.ForbiddenError) {
|
|
637
|
+
throw err;
|
|
638
|
+
}
|
|
639
|
+
throw new errors.InternalServerError(safeErrorMessage(err));
|
|
640
|
+
}
|
|
641
|
+
};
|
|
642
|
+
// Actions (verbs)
|
|
643
|
+
const actionBulkWrite = async (req, res) => {
|
|
644
|
+
if (!advancedQueryAllowed(req.Model, "bulkwrite")) {
|
|
645
|
+
throw new errors.ForbiddenError(`POST /bulkwrite is disabled for model ${req.modelname}`);
|
|
646
|
+
}
|
|
647
|
+
if (!req.body || !Array.isArray(req.body)) {
|
|
648
|
+
console.error("query missing or not of type array");
|
|
649
|
+
throw new errors.BadRequestError("Query missing or not of type array");
|
|
650
|
+
}
|
|
651
|
+
const opname = `bulkwrite ${req.modelname} ${ops++}`;
|
|
652
|
+
console.time(opname);
|
|
653
|
+
const query = req.body;
|
|
654
|
+
bulkwrite_guard.validateBulkOps(query, {
|
|
655
|
+
bulk_operations_allow: getSecurityOpts(req).bulk_operations_allow,
|
|
656
|
+
isAdmin: res.user?.admin,
|
|
657
|
+
});
|
|
658
|
+
try {
|
|
659
|
+
let result = {};
|
|
660
|
+
result.data = await req.Model.bulkWrite(query);
|
|
661
|
+
res.result = result;
|
|
662
|
+
if (debug)
|
|
663
|
+
console.timeEnd(opname);
|
|
664
|
+
res.json(result);
|
|
665
|
+
}
|
|
666
|
+
catch (err) {
|
|
667
|
+
console.error(new Date(), err);
|
|
668
|
+
if (debug)
|
|
669
|
+
console.timeEnd(opname);
|
|
670
|
+
if (err instanceof errors.BadRequestError || err instanceof errors.ForbiddenError) {
|
|
671
|
+
throw err;
|
|
672
|
+
}
|
|
673
|
+
throw new errors.InternalServerError(safeErrorMessage(err));
|
|
674
|
+
}
|
|
675
|
+
};
|
|
676
|
+
// var actionBatch = (req, res, next) => {
|
|
677
|
+
// console.time("BATCH " + req.modelname);
|
|
678
|
+
// var items = [];
|
|
679
|
+
// data = JSON.parse(req.params.json);
|
|
680
|
+
// data.forEach(function(data) {
|
|
681
|
+
// var item = new req.Model();
|
|
682
|
+
// if (res.user) {
|
|
683
|
+
// item.__user = res.user;
|
|
684
|
+
// }
|
|
685
|
+
// _populateItem(item, data);
|
|
686
|
+
// _versionItem(item);
|
|
687
|
+
// if (res.user) {
|
|
688
|
+
// item._owner_id = res.user._id;
|
|
689
|
+
// }
|
|
690
|
+
// items.push(item);
|
|
691
|
+
// });
|
|
692
|
+
// req.Model.create(items, function(err, docs) {
|
|
693
|
+
// if (err) {
|
|
694
|
+
// console.error(err);
|
|
695
|
+
// res.status(500).send(err.toString());
|
|
696
|
+
// } else {
|
|
697
|
+
// // websocket.emit(modelname, { method: "post", _id: result._id });
|
|
698
|
+
// console.log({ action_id: 8, action: "Batch insert", type: req.modelname, count: items.length, user: filterLogUser(res.user) });
|
|
699
|
+
// res.send({ message: req.modelname + " created ", data: items.length });
|
|
700
|
+
// if (debug) console.timeEnd("BATCH " + req.modelname);
|
|
701
|
+
// return;
|
|
702
|
+
// }
|
|
703
|
+
// });
|
|
704
|
+
// };
|
|
705
|
+
// Utitlities
|
|
706
|
+
const getOne = async (Model, item_id, params, options) => {
|
|
707
|
+
const query = Model.findById(item_id, {}, options);
|
|
708
|
+
if (params.populate) {
|
|
709
|
+
if ((typeof params.populate === "object") && !Array.isArray(params.populate)) {
|
|
710
|
+
for (let i in params.populate) {
|
|
711
|
+
query.populate(i, params.populate[i].replace(/,/g, " "));
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
else {
|
|
715
|
+
query.populate(params.populate);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
if (params.autopopulate) {
|
|
719
|
+
for (let key in Model.schema.paths) {
|
|
720
|
+
var dirpath = Model.schema.paths[key];
|
|
721
|
+
if (dirpath.instance == "ObjectID" && dirpath.options.link) {
|
|
722
|
+
query.populate(String(dirpath.options.map_to || dirpath.options.virtual || dirpath.options.link.toLowerCase()));
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
try {
|
|
727
|
+
var item = await query.exec();
|
|
728
|
+
if (!item) {
|
|
729
|
+
// console.error("Could not find document");
|
|
730
|
+
throw new errors.NotFoundError(`Could not find document ${item_id} on ${Model.modelName}`);
|
|
731
|
+
}
|
|
732
|
+
if (item._deleted && !params.showDeleted) {
|
|
733
|
+
// console.error("Document is deleted");
|
|
734
|
+
throw new errors.NotFoundError(`Document ${item_id} is deleted on ${Model.modelName}`);
|
|
735
|
+
}
|
|
736
|
+
return response_sanitize.sanitizeDocument(item);
|
|
737
|
+
}
|
|
738
|
+
catch (err) {
|
|
739
|
+
console.error(err);
|
|
740
|
+
if (err.code)
|
|
741
|
+
throw err;
|
|
742
|
+
throw new errors.InternalServerError(safeErrorMessage(err));
|
|
743
|
+
}
|
|
744
|
+
};
|
|
745
|
+
// Helper function to check if a string is an ISO date string
|
|
746
|
+
function isISODateString(str) {
|
|
747
|
+
if (typeof str !== 'string')
|
|
748
|
+
return false;
|
|
749
|
+
const isoDateRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,3})?Z$/;
|
|
750
|
+
if (!isoDateRegex.test(str))
|
|
751
|
+
return false;
|
|
752
|
+
const date = new Date(str);
|
|
753
|
+
if (isNaN(date.getTime())) {
|
|
754
|
+
throw new errors.BadRequestError("Invalid date format");
|
|
755
|
+
}
|
|
756
|
+
return true;
|
|
757
|
+
}
|
|
758
|
+
const parseFilter = (filter, depth = 0) => {
|
|
759
|
+
const MAX_DEPTH = 10;
|
|
760
|
+
if (!filter)
|
|
761
|
+
return {};
|
|
762
|
+
if (depth > MAX_DEPTH) {
|
|
763
|
+
throw new errors.BadRequestError("Maximum filter depth exceeded");
|
|
764
|
+
}
|
|
765
|
+
if (typeof filter !== "object" || filter === null)
|
|
766
|
+
return filter;
|
|
767
|
+
// Handle arrays by merging their operators
|
|
768
|
+
if (Array.isArray(filter)) {
|
|
769
|
+
const result = {};
|
|
770
|
+
filter.forEach(item => {
|
|
771
|
+
if (typeof item === "string" && item.includes(":")) {
|
|
772
|
+
const parts = item.split(":");
|
|
773
|
+
const key = parts[0];
|
|
774
|
+
const value = parts.slice(1).join(":");
|
|
775
|
+
if (key.startsWith("$")) {
|
|
776
|
+
try {
|
|
777
|
+
if (isISODateString(value)) {
|
|
778
|
+
result[key] = new Date(value);
|
|
779
|
+
}
|
|
780
|
+
else {
|
|
781
|
+
result[key] = value;
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
catch (err) {
|
|
785
|
+
if (err instanceof errors.BadRequestError) {
|
|
786
|
+
throw err;
|
|
787
|
+
}
|
|
788
|
+
throw new errors.BadRequestError("Invalid date format");
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
});
|
|
793
|
+
return result;
|
|
794
|
+
}
|
|
795
|
+
// Create a new object to avoid modifying the input
|
|
796
|
+
const parsedFilter = {};
|
|
797
|
+
for (let i in filter) {
|
|
798
|
+
if (filter[i] === "false") {
|
|
799
|
+
parsedFilter[i] = false;
|
|
800
|
+
continue;
|
|
801
|
+
}
|
|
802
|
+
if (filter[i] === "true") {
|
|
803
|
+
parsedFilter[i] = true;
|
|
804
|
+
continue;
|
|
805
|
+
}
|
|
806
|
+
if (typeof filter[i] === "string") {
|
|
807
|
+
try {
|
|
808
|
+
if (isISODateString(filter[i])) {
|
|
809
|
+
parsedFilter[i] = new Date(filter[i]);
|
|
810
|
+
continue;
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
catch (err) {
|
|
814
|
+
if (err instanceof errors.BadRequestError) {
|
|
815
|
+
throw err;
|
|
816
|
+
}
|
|
817
|
+
throw new errors.BadRequestError("Invalid date format");
|
|
818
|
+
}
|
|
819
|
+
if (filter[i].includes(":")) {
|
|
820
|
+
const parts = filter[i].split(":");
|
|
821
|
+
const key = parts[0];
|
|
822
|
+
const value = parts.slice(1).join(":");
|
|
823
|
+
if (key.startsWith("$")) {
|
|
824
|
+
try {
|
|
825
|
+
if (isISODateString(value)) {
|
|
826
|
+
if (!parsedFilter[i])
|
|
827
|
+
parsedFilter[i] = {};
|
|
828
|
+
parsedFilter[i][key] = new Date(value);
|
|
829
|
+
}
|
|
830
|
+
else if (value.startsWith("[") && value.endsWith("]")) {
|
|
831
|
+
if (!parsedFilter[i])
|
|
832
|
+
parsedFilter[i] = {};
|
|
833
|
+
parsedFilter[i][key] = value.slice(1, -1).split(",");
|
|
834
|
+
}
|
|
835
|
+
else if (key === "$regex" && value.startsWith("/")) {
|
|
836
|
+
if (!parsedFilter[i])
|
|
837
|
+
parsedFilter[i] = {};
|
|
838
|
+
const match = value.match(/^\/(.+?)\/([gimy]*)$/);
|
|
839
|
+
if (match) {
|
|
840
|
+
parsedFilter[i][key] = new RegExp(match[1], match[2]);
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
else {
|
|
844
|
+
if (!parsedFilter[i])
|
|
845
|
+
parsedFilter[i] = {};
|
|
846
|
+
parsedFilter[i][key] = value;
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
catch (err) {
|
|
850
|
+
if (err instanceof errors.BadRequestError) {
|
|
851
|
+
throw err;
|
|
852
|
+
}
|
|
853
|
+
throw new errors.BadRequestError("Invalid date format");
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
else {
|
|
857
|
+
parsedFilter[i] = filter[i];
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
else {
|
|
861
|
+
parsedFilter[i] = filter[i];
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
else if (Array.isArray(filter[i])) {
|
|
865
|
+
parsedFilter[i] = parseFilter(filter[i], depth + 1);
|
|
866
|
+
}
|
|
867
|
+
else if (typeof filter[i] === "object") {
|
|
868
|
+
parsedFilter[i] = parseFilter(filter[i], depth + 1);
|
|
869
|
+
}
|
|
870
|
+
else {
|
|
871
|
+
parsedFilter[i] = filter[i];
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
return parsedFilter;
|
|
875
|
+
};
|
|
876
|
+
const _deSerialize = (data) => {
|
|
877
|
+
function assign(obj, keyPath, value) {
|
|
878
|
+
const MAX_DEPTH = 20; // Prevent excessive nesting
|
|
879
|
+
const lastKeyIndex = keyPath.length - 1;
|
|
880
|
+
if (lastKeyIndex >= MAX_DEPTH) {
|
|
881
|
+
console.warn('Maximum nesting depth exceeded in _deSerialize');
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
for (let i = 0; i < lastKeyIndex; ++i) {
|
|
885
|
+
let key = keyPath[i];
|
|
886
|
+
if (!(key in obj))
|
|
887
|
+
obj[key] = {};
|
|
888
|
+
obj = obj[key];
|
|
889
|
+
}
|
|
890
|
+
obj[keyPath[lastKeyIndex]] = value;
|
|
891
|
+
}
|
|
892
|
+
if (!data || typeof data !== 'object')
|
|
893
|
+
return;
|
|
894
|
+
for (let datum in data) {
|
|
895
|
+
const matches = datum.match(/\[(.+?)\]/g);
|
|
896
|
+
if (matches) {
|
|
897
|
+
const params = matches.map(function (match) {
|
|
898
|
+
return match.replace(/[[\]]/g, "");
|
|
899
|
+
});
|
|
900
|
+
if (isNaN(Number(params[0]))) {
|
|
901
|
+
params.unshift(datum.match(/(.+?)\[/)[1]);
|
|
902
|
+
assign(data, params, data[datum]);
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
};
|
|
907
|
+
const _populateItem = (item, data) => {
|
|
908
|
+
_deSerialize(data);
|
|
909
|
+
for (let prop in item) {
|
|
910
|
+
if (typeof data[prop] != "undefined") {
|
|
911
|
+
item[prop] = data[prop];
|
|
912
|
+
// Unset any blank values - essentially 'deleting' values on editing
|
|
913
|
+
if (data[prop] === "") {
|
|
914
|
+
item[prop] = null;
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
//Check for arrays that come in like param[1]=blah, param[2]=yack
|
|
918
|
+
if (data[prop + "[0]"]) {
|
|
919
|
+
var x = 0;
|
|
920
|
+
var tmp = [];
|
|
921
|
+
while (data[prop + "[" + x + "]"]) {
|
|
922
|
+
tmp.push(data[prop + "[" + x + "]"]);
|
|
923
|
+
x++;
|
|
924
|
+
}
|
|
925
|
+
item[prop] = tmp;
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
};
|
|
929
|
+
const _versionItem = (item) => {
|
|
930
|
+
if (item._version || item._version === 0) {
|
|
931
|
+
item._version++;
|
|
932
|
+
}
|
|
933
|
+
else {
|
|
934
|
+
item._version = 0;
|
|
935
|
+
}
|
|
936
|
+
};
|
|
937
|
+
const _fixArrays = (req, res, next) => {
|
|
938
|
+
if (req.body) {
|
|
939
|
+
for (var i in req.body) {
|
|
940
|
+
if (i.search(/\[\d+\]/) > -1) {
|
|
941
|
+
var parts = i.match(/(^[A-Za-z]+)(\[)/);
|
|
942
|
+
var el = parts[1];
|
|
943
|
+
if (!req.body[el]) {
|
|
944
|
+
req.body[el] = [];
|
|
945
|
+
}
|
|
946
|
+
req.body[el].push(req.body[i]);
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
next();
|
|
951
|
+
};
|
|
952
|
+
const changeUrlParams = (req, key, val) => {
|
|
953
|
+
var q = req.query;
|
|
954
|
+
q[key] = val;
|
|
955
|
+
return req.config.url + req.path() + "?" + querystring.stringify(q);
|
|
956
|
+
};
|
|
957
|
+
const JXP = function (options) {
|
|
958
|
+
const server = restify.createServer();
|
|
959
|
+
const model_dir = options.model_dir || modeldir.findModelDir(path.dirname(process.argv[1]));
|
|
960
|
+
//Set up config with default
|
|
961
|
+
var config = {
|
|
962
|
+
model_dir: path.join(model_dir),
|
|
963
|
+
mongo: options.mongo,
|
|
964
|
+
callbacks: {
|
|
965
|
+
put: function () { },
|
|
966
|
+
post: function () { },
|
|
967
|
+
delete: function () { },
|
|
968
|
+
get: function () { },
|
|
969
|
+
getOne: function () { },
|
|
970
|
+
update: function () { },
|
|
971
|
+
},
|
|
972
|
+
log: "access.log",
|
|
973
|
+
pre_hooks: {
|
|
974
|
+
login: (req, res, next) => {
|
|
975
|
+
next();
|
|
976
|
+
},
|
|
977
|
+
get: (req, res, next) => {
|
|
978
|
+
next();
|
|
979
|
+
},
|
|
980
|
+
getOne: (req, res, next) => {
|
|
981
|
+
next();
|
|
982
|
+
},
|
|
983
|
+
post: (req, res, next) => {
|
|
984
|
+
next();
|
|
985
|
+
},
|
|
986
|
+
put: (req, res, next) => {
|
|
987
|
+
next();
|
|
988
|
+
},
|
|
989
|
+
update: (req, res, next) => {
|
|
990
|
+
next();
|
|
991
|
+
},
|
|
992
|
+
delete: (req, res, next) => {
|
|
993
|
+
next();
|
|
994
|
+
}
|
|
995
|
+
},
|
|
996
|
+
post_hooks: {
|
|
997
|
+
login: async () => {
|
|
998
|
+
},
|
|
999
|
+
},
|
|
1000
|
+
cache_timeout: "5 minutes",
|
|
1001
|
+
query_limits: {
|
|
1002
|
+
enabled: true,
|
|
1003
|
+
large_collection_threshold: 10000,
|
|
1004
|
+
max: 1000,
|
|
1005
|
+
default: 100,
|
|
1006
|
+
require_limit_always: true,
|
|
1007
|
+
skip_count_unless_paginated: true,
|
|
1008
|
+
},
|
|
1009
|
+
security: {
|
|
1010
|
+
strip_fields: ["password"],
|
|
1011
|
+
},
|
|
1012
|
+
cors: {
|
|
1013
|
+
origins: ["*"],
|
|
1014
|
+
},
|
|
1015
|
+
};
|
|
1016
|
+
//Override config with passed in options
|
|
1017
|
+
for (let i in options) {
|
|
1018
|
+
if (typeof config[i] === "object" && !Array.isArray(config[i])) {
|
|
1019
|
+
if (typeof options[i] === "object" && !Array.isArray(options[i])) {
|
|
1020
|
+
for (let j in options[i]) {
|
|
1021
|
+
config[i][j] = options[i][j]; // Second level object copy
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
else {
|
|
1026
|
+
config[i] = options[i];
|
|
1027
|
+
}
|
|
1028
|
+
if (i === "model_dir" || i === "log") {
|
|
1029
|
+
const value = String(options[i]);
|
|
1030
|
+
// Absolute paths unchanged; relative paths resolve from project cwd (npm scripts)
|
|
1031
|
+
config[i] = value.charAt(0) === "/" ? value : path.resolve(process.cwd(), value);
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
if (config.debug)
|
|
1035
|
+
debug = true;
|
|
1036
|
+
// Set apikey and server globally to inject into schemas
|
|
1037
|
+
global.apikey = config.apikey;
|
|
1038
|
+
global.server = config.server;
|
|
1039
|
+
global.model_dir = config.model_dir;
|
|
1040
|
+
// Pre-load models
|
|
1041
|
+
var files = fs.readdirSync(config.model_dir);
|
|
1042
|
+
let modelnames = files.filter(function (fname) {
|
|
1043
|
+
return fname.endsWith("_model.js");
|
|
1044
|
+
});
|
|
1045
|
+
modelnames.forEach(function (fname) {
|
|
1046
|
+
var modelname = fname.replace("_model.js", "");
|
|
1047
|
+
const mod = require(path.join(config.model_dir, fname));
|
|
1048
|
+
models[modelname] = mod.default || mod;
|
|
1049
|
+
});
|
|
1050
|
+
link_index.buildLinkIndex(models);
|
|
1051
|
+
setup.init(config);
|
|
1052
|
+
security.init(config);
|
|
1053
|
+
login.init(config);
|
|
1054
|
+
groups.init(config);
|
|
1055
|
+
ws.init({ models });
|
|
1056
|
+
cache.init(config);
|
|
1057
|
+
const docs = new Docs({ config, models });
|
|
1058
|
+
docsAuth.init(config);
|
|
1059
|
+
docsAuth.logDocsAccessMode(config);
|
|
1060
|
+
const loginThrottle = loginRateLimit.createLoginThrottle(config);
|
|
1061
|
+
loginRateLimit.logLoginRateLimit(config);
|
|
1062
|
+
// Set up our API server
|
|
1063
|
+
// Rate limitting
|
|
1064
|
+
if (config.throttle) {
|
|
1065
|
+
server.use(restify.plugins.throttle(config.throttle));
|
|
1066
|
+
}
|
|
1067
|
+
// Logging
|
|
1068
|
+
if (!config.quiet_startup) {
|
|
1069
|
+
console.log("Logging to", config.log);
|
|
1070
|
+
}
|
|
1071
|
+
var accessLogStream = fs.createWriteStream(config.log, { flags: "a" });
|
|
1072
|
+
server.use(morgan("combined", { stream: accessLogStream }));
|
|
1073
|
+
// CORS
|
|
1074
|
+
const corsOrigins = config.cors?.origins?.length ? config.cors.origins : ["*"];
|
|
1075
|
+
const cors = corsMiddleware({
|
|
1076
|
+
preflightMaxAge: 5, //Optional
|
|
1077
|
+
origins: corsOrigins,
|
|
1078
|
+
allowHeaders: ['X-Requested-With', 'Authorization'],
|
|
1079
|
+
exposeHeaders: ['Authorization']
|
|
1080
|
+
});
|
|
1081
|
+
server.pre(cors.preflight);
|
|
1082
|
+
server.use(cors.actual);
|
|
1083
|
+
// Parse data
|
|
1084
|
+
server.use(restify.plugins.queryParser());
|
|
1085
|
+
server.use(restify.plugins.bodyParser());
|
|
1086
|
+
// Bind our config to req.config
|
|
1087
|
+
server.use((req, res, next) => {
|
|
1088
|
+
req.config = config;
|
|
1089
|
+
next();
|
|
1090
|
+
});
|
|
1091
|
+
// Set req.username = "anonymous" if not logged in
|
|
1092
|
+
server.use((req, res, next) => {
|
|
1093
|
+
if (!req.username)
|
|
1094
|
+
req.username = "anonymous";
|
|
1095
|
+
next();
|
|
1096
|
+
});
|
|
1097
|
+
// Define our endpoints
|
|
1098
|
+
/* Our API endpoints */
|
|
1099
|
+
server.get("/api/:modelname", middlewareModel, security.login, security.auth, config.pre_hooks.get, cache.get, actionGet, cache.set, outputJSON);
|
|
1100
|
+
server.get("/api/:modelname/:item_id", middlewareModel, security.login, security.auth, config.pre_hooks.getOne, cache.get, actionGetOne, cache.set, outputJSON);
|
|
1101
|
+
server.post("/api/:modelname", middlewareModel, security.login, security.auth, middlewarePasswords, config.pre_hooks.post, actionPost, cache.clearAll, (req, res, next) => {
|
|
1102
|
+
next();
|
|
1103
|
+
});
|
|
1104
|
+
server.put("/api/:modelname/:item_id", middlewareModel, security.login, security.auth, middlewarePasswords, middlewareCheckAdmin, config.pre_hooks.put, actionPut, cache.clearAll, (req, res, next) => {
|
|
1105
|
+
next();
|
|
1106
|
+
});
|
|
1107
|
+
server.del("/api/:modelname/:item_id", middlewareModel, security.login, security.auth, config.pre_hooks.delete, actionDelete, cache.clearAll);
|
|
1108
|
+
// Count
|
|
1109
|
+
server.get("/count/:modelname", middlewareModel, security.login, security.auth, config.pre_hooks.get,
|
|
1110
|
+
// cache.get,
|
|
1111
|
+
actionCount,
|
|
1112
|
+
// cache.set,
|
|
1113
|
+
outputJSON);
|
|
1114
|
+
// CSV endpoints
|
|
1115
|
+
server.get("/csv/:modelname", middlewareModel, security.login, security.auth, config.pre_hooks.get, actionGet, outputCSV);
|
|
1116
|
+
// Query endpoints
|
|
1117
|
+
server.post("/query/:modelname", middlewareModel, security.login, security.auth, config.pre_hooks.get, actionQuery);
|
|
1118
|
+
server.post("/aggregate/:modelname", middlewareModel, security.login, security.auth, config.pre_hooks.get, actionAggregate);
|
|
1119
|
+
server.post("/bulkwrite/:modelname", middlewareModel, security.login, security.bulkAuth, config.pre_hooks.get, actionBulkWrite);
|
|
1120
|
+
server.post("/update/:modelname/:item_id", middlewareModel, security.login, security.auth, middlewarePasswords, middlewareCheckAdmin, config.pre_hooks.update, actionUpdate, cache.clearAll);
|
|
1121
|
+
/* Batch routes - ROLLED BACK FOR NOW */
|
|
1122
|
+
// server.post('/batch/create/:modelname', middlewareModel, security.login, security.auth, actionBatch);
|
|
1123
|
+
/* Call Methods in our models */
|
|
1124
|
+
server.get("/call/:modelname/:method_name", middlewareModel, security.login, security.auth, actionCall, cache.clearAll);
|
|
1125
|
+
server.post("/call/:modelname/:method_name", middlewareModel, security.login, security.auth, actionCall, cache.clearAll);
|
|
1126
|
+
server.get("/call/:modelname/:item_id/:method_name", middlewareModel, security.login, security.auth, actionCallItem, cache.clearAll);
|
|
1127
|
+
/* Login and authentication */
|
|
1128
|
+
server.post("/login/recover", login.recover);
|
|
1129
|
+
server.post("/login/getjwt", security.login, login.getJWT);
|
|
1130
|
+
server.get("/login/logout", security.login, login.logout);
|
|
1131
|
+
server.get("/logout", security.login, login.logout);
|
|
1132
|
+
server.get("/login/oauth/:provider", login.oauth);
|
|
1133
|
+
server.get("/login/oauth/callback/:provider", login.oauth_callback);
|
|
1134
|
+
const loginChain = [
|
|
1135
|
+
...(loginThrottle ? [loginThrottle] : []),
|
|
1136
|
+
config.pre_hooks.login,
|
|
1137
|
+
login.login,
|
|
1138
|
+
config.post_hooks.login,
|
|
1139
|
+
outputJSON,
|
|
1140
|
+
];
|
|
1141
|
+
server.post("/login", ...loginChain);
|
|
1142
|
+
server.post("/refresh", security.refresh);
|
|
1143
|
+
server.post("/login/refresh", security.refresh);
|
|
1144
|
+
/* Groups */
|
|
1145
|
+
server.put("/groups/:user_id", security.login, security.admin_only, _fixArrays, groups.actionPut);
|
|
1146
|
+
server.post("/groups/:user_id", security.login, security.admin_only, _fixArrays, groups.actionPost);
|
|
1147
|
+
server.get("/groups/:user_id", security.login, groups.actionGet);
|
|
1148
|
+
server.del("/groups/:user_id", security.login, security.admin_only, groups.actionDelete);
|
|
1149
|
+
/* Meta */
|
|
1150
|
+
server.get("/model/:modelname", middlewareModel, docs.metaModel.bind(docs));
|
|
1151
|
+
server.get("/model", docs.metaModels.bind(docs));
|
|
1152
|
+
// server.get("/docs/_design", docs.dbDiagram.bind(docs));
|
|
1153
|
+
server.get("/docs/login", async (req, res) => {
|
|
1154
|
+
await docsAuth.loginPage(req, res, docs.renderLogin.bind(docs));
|
|
1155
|
+
});
|
|
1156
|
+
server.post("/docs/session", ...(loginThrottle ? [loginThrottle] : []), docsAuth.establishSession);
|
|
1157
|
+
server.get("/docs/session", docsAuth.getSession);
|
|
1158
|
+
server.post("/docs/logout", docsAuth.logout);
|
|
1159
|
+
server.get("/docs/assets/:file", docs.serveAsset.bind(docs));
|
|
1160
|
+
server.get("/docs/api", docsAuth.docsAccessMiddleware, docs.apiIndex.bind(docs));
|
|
1161
|
+
server.get("/docs/md/:md_doc", docs.md.bind(docs));
|
|
1162
|
+
server.get("/docs/model/:modelname", docsAuth.docsAccessMiddleware, docs.model.bind(docs));
|
|
1163
|
+
server.get("/", docs.frontPage.bind(docs));
|
|
1164
|
+
/* Setup */
|
|
1165
|
+
server.get("/setup", setup.checkUserDoesNotExist, setup.setup);
|
|
1166
|
+
server.post("/setup", setup.checkUserDoesNotExist, setup.setup);
|
|
1167
|
+
server.post("/setup/data", setup.checkUserDoesNotExist, setup.data_setup);
|
|
1168
|
+
/* Websocket */
|
|
1169
|
+
server.on("upgrade", ws.upgrade);
|
|
1170
|
+
/* Cache */
|
|
1171
|
+
server.get("/cache/stats", security.login, security.admin_only, cache.stats, outputJSON);
|
|
1172
|
+
server.get("/cache/clear", security.login, security.admin_only, cache.clearAll, outputJSON);
|
|
1173
|
+
return server;
|
|
1174
|
+
};
|
|
1175
|
+
module.exports = JXP;
|
|
1176
|
+
//# sourceMappingURL=jxp.js.map
|