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