jxp 2.8.3 → 2.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env +8 -0
- package/.env.sample +6 -0
- package/Dockerfile +7 -0
- package/bin/server.js +9 -5
- package/docker-compose.yml +32 -0
- package/docs/configuration.md +2 -0
- package/docs/installation.md +5 -1
- package/libs/connection_string.js +14 -0
- package/libs/jxp.js +62 -1
- package/libs/login.js +1 -4
- package/libs/schema.js +7 -7
- package/libs/setup.js +1 -2
- package/models/test_model.js +4 -1
- package/package.json +13 -12
- package/test/init.js +0 -8
- package/test/test.js +15 -0
package/.env
ADDED
package/.env.sample
ADDED
package/Dockerfile
ADDED
package/bin/server.js
CHANGED
|
@@ -195,6 +195,7 @@ Add the parameter `_silence` to supress callbacks. Useful to avoid infinite loop
|
|
|
195
195
|
const mongoose = require("mongoose");
|
|
196
196
|
const JXP = require("../libs/jxp");
|
|
197
197
|
const config = require("config");
|
|
198
|
+
require("dotenv").config();
|
|
198
199
|
|
|
199
200
|
config.callbacks = {
|
|
200
201
|
post: function(modelname, item, user) {
|
|
@@ -232,15 +233,16 @@ config.pre_hooks = {
|
|
|
232
233
|
//DB connection
|
|
233
234
|
// ES6 promises
|
|
234
235
|
mongoose.Promise = Promise;
|
|
236
|
+
if (!config.mongo) config.mongo = {};
|
|
235
237
|
if (!config.mongo.options) config.mongo.options = {};
|
|
236
238
|
const mongo_options = Object.assign(config.mongo.options, {
|
|
237
|
-
promiseLibrary: global.Promise,
|
|
238
239
|
useNewUrlParser: true,
|
|
239
|
-
|
|
240
|
+
useUnifiedTopology: true
|
|
240
241
|
});
|
|
241
242
|
|
|
242
|
-
|
|
243
|
-
|
|
243
|
+
const connection_string = require("../libs/connection_string");
|
|
244
|
+
console.log(`Connecting to ${connection_string}`);
|
|
245
|
+
mongoose.connect(connection_string, mongo_options);
|
|
244
246
|
|
|
245
247
|
const db = mongoose.connection;
|
|
246
248
|
|
|
@@ -254,7 +256,9 @@ db.once('open', () => {
|
|
|
254
256
|
|
|
255
257
|
var server = new JXP(config);
|
|
256
258
|
|
|
257
|
-
|
|
259
|
+
let port = process.env.NODE_DOCKER_PORT || process.env.PORT || config.port || 4001;
|
|
260
|
+
if (process.env.NODE_ENV === "test") port = 4005;
|
|
261
|
+
server.listen(port, function() {
|
|
258
262
|
console.log('%s listening at %s', server.name, server.url);
|
|
259
263
|
});
|
|
260
264
|
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
version: "3.9"
|
|
2
|
+
|
|
3
|
+
services:
|
|
4
|
+
mongodb:
|
|
5
|
+
image: mongo
|
|
6
|
+
restart: always
|
|
7
|
+
environment:
|
|
8
|
+
MONGO_INITDB_ROOT_USERNAME: root
|
|
9
|
+
MONGO_INITDB_ROOT_PASSWORD: password
|
|
10
|
+
ports:
|
|
11
|
+
- $MONGODB_LOCAL_PORT:$MONGODB_DOCKER_PORT
|
|
12
|
+
volumes:
|
|
13
|
+
- db:/data/db
|
|
14
|
+
|
|
15
|
+
jxp:
|
|
16
|
+
depends_on:
|
|
17
|
+
- mongodb
|
|
18
|
+
build: ./
|
|
19
|
+
restart: unless-stopped
|
|
20
|
+
env_file: ./.env
|
|
21
|
+
ports:
|
|
22
|
+
- $NODE_LOCAL_PORT:$NODE_DOCKER_PORT
|
|
23
|
+
environment:
|
|
24
|
+
- DB_HOST=mongodb
|
|
25
|
+
- DB_USER=$MONGODB_USER
|
|
26
|
+
- DB_PASSWORD=$MONGODB_PASSWORD
|
|
27
|
+
- DB_NAME=$MONGODB_DATABASE
|
|
28
|
+
- DB_PORT=$MONGODB_DOCKER_PORT
|
|
29
|
+
stdin_open: true
|
|
30
|
+
tty: true
|
|
31
|
+
volumes:
|
|
32
|
+
db:
|
package/docs/configuration.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Configuring JXP
|
|
2
2
|
|
|
3
|
+
### NB: The `config` methodology will be deprecated in future versions in favour of dotenv. See the .env.sample for an example.
|
|
4
|
+
|
|
3
5
|
The config files are stored in `/config` in strict JSON format. (Remember to enclose your keys in inverted commas!) Typically, you'd use `/config/default.json` for a simple configuration, or `/config/development.json` and `/config/production.json` for separating development and production.
|
|
4
6
|
|
|
5
7
|
If you used the `jxp-setup` command for setting everything up, you should have a basic `/config/default.json` file ready to go.
|
package/docs/installation.md
CHANGED
|
@@ -4,7 +4,7 @@ JXP would typically run as a stand-alone server, although you can include it as
|
|
|
4
4
|
|
|
5
5
|
## Requirements
|
|
6
6
|
|
|
7
|
-
JXP runs on [Node JS](https://nodejs.org/en/). We support Node v10 and above, and recommend Node
|
|
7
|
+
JXP runs on [Node JS](https://nodejs.org/en/). We support Node v10 and above, and recommend Node v16. It has been tested up to Node v17.
|
|
8
8
|
|
|
9
9
|
JXP requires a [Mongo](https://www.mongodb.com/) database server to connect to. You can host your own, or we also support connecting to [MongoDB Atlas](https://www.mongodb.com/cloud/atlas), which has a free tier if you don't want to have Mongo running locally.
|
|
10
10
|
|
|
@@ -12,6 +12,10 @@ JXP will also take advantage of Memcache if you have it installed, although it's
|
|
|
12
12
|
|
|
13
13
|
If you want to send out forgotten password links, you'll need an SMTP server you can connect to.
|
|
14
14
|
|
|
15
|
+
## Running on Docker
|
|
16
|
+
|
|
17
|
+
You can run JXP on Docker using Docker Compose. Just run `docker-compose up -d` and it should Just Work (tm).
|
|
18
|
+
|
|
15
19
|
## Installing the easy way
|
|
16
20
|
|
|
17
21
|
JXP has a helper that will set up and configure an instance for you.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const config = require("config");
|
|
2
|
+
require("dotenv").config();
|
|
3
|
+
|
|
4
|
+
// mongodb connection
|
|
5
|
+
let connection_string = `mongodb://localhost:27017/jxp`;
|
|
6
|
+
if (config.mongo && config.mongo.connection_string) {
|
|
7
|
+
connection_string = config.mongo.connection_string;
|
|
8
|
+
console.warn("`config` to be deprecated in favour of dotenv in future versions");
|
|
9
|
+
}
|
|
10
|
+
if (process.env.DB_HOST) {
|
|
11
|
+
connection_string = `mongodb://${process.env.DB_USER}:${process.env.DB_PASSWORD}@${process.env.DB_HOST}:${process.env.DB_PORT}/${process.env.NODE_ENV === "test" ? "jxp-test" : process.env.DB_NAME}?authSource=admin`
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
module.exports = connection_string;
|
package/libs/jxp.js
CHANGED
|
@@ -272,6 +272,47 @@ const actionPut = async (req, res, next) => {
|
|
|
272
272
|
}
|
|
273
273
|
};
|
|
274
274
|
|
|
275
|
+
const actionUpdate = async (req, res, next) => {
|
|
276
|
+
const opname = `update ${req.modelname}/${req.params.item_id} ${ops++}`;
|
|
277
|
+
console.time(opname);
|
|
278
|
+
try {
|
|
279
|
+
// let item = await req.Model.findById(req.params.item_id);
|
|
280
|
+
// if (!item) {
|
|
281
|
+
// console.error(new Date(), "Document not found");
|
|
282
|
+
// res.send(404, { status: "error", message: "Document not found" });
|
|
283
|
+
// return;
|
|
284
|
+
// }
|
|
285
|
+
// _populateItem(item, datamunging.deserialize(req.body));
|
|
286
|
+
// _versionItem(item);
|
|
287
|
+
// if (res.user) {
|
|
288
|
+
// item.__user = res.user;
|
|
289
|
+
// item._updated_by_id = res.user._id;
|
|
290
|
+
// }
|
|
291
|
+
// const data = await item.save();
|
|
292
|
+
console.log({ body: req.body });
|
|
293
|
+
let body_data = datamunging.deserialize(req.body);
|
|
294
|
+
const data = await req.Model.update({ _id: req.params.item_id }, body_data);
|
|
295
|
+
let silence = req.params._silence;
|
|
296
|
+
if (req.body && req.body._silence) silence = true;
|
|
297
|
+
if (!silence) {
|
|
298
|
+
req.config.callbacks.put.call(null, req.modelname, data, res.user );
|
|
299
|
+
ws.putHook.call(null, req.modelname, data, res.user);
|
|
300
|
+
}
|
|
301
|
+
res.json({
|
|
302
|
+
status: "ok",
|
|
303
|
+
message: req.modelname + " updateed",
|
|
304
|
+
data
|
|
305
|
+
});
|
|
306
|
+
if (debug) console.timeEnd(opname);
|
|
307
|
+
next();
|
|
308
|
+
} catch (err) {
|
|
309
|
+
console.error(new Date(), err);
|
|
310
|
+
if (debug) console.timeEnd(opname);
|
|
311
|
+
res.send(500, { status: "error", message: err.toString() });
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
|
|
275
316
|
const actionDelete = async (req, res, next) => {
|
|
276
317
|
const permaDelete = req.query._permaDelete;
|
|
277
318
|
const cascade = req.query._cascade;
|
|
@@ -759,7 +800,8 @@ const JXP = function(options) {
|
|
|
759
800
|
post: function() {},
|
|
760
801
|
delete: function() {},
|
|
761
802
|
get: function() {},
|
|
762
|
-
getOne: function() {}
|
|
803
|
+
getOne: function() {},
|
|
804
|
+
update: function() {},
|
|
763
805
|
},
|
|
764
806
|
log: "access.log",
|
|
765
807
|
pre_hooks: {
|
|
@@ -778,6 +820,9 @@ const JXP = function(options) {
|
|
|
778
820
|
put: (req, res, next) => {
|
|
779
821
|
next();
|
|
780
822
|
},
|
|
823
|
+
update: (req, res, next) => {
|
|
824
|
+
next();
|
|
825
|
+
},
|
|
781
826
|
delete: (req, res, next) => {
|
|
782
827
|
next();
|
|
783
828
|
}
|
|
@@ -816,6 +861,11 @@ const JXP = function(options) {
|
|
|
816
861
|
|
|
817
862
|
if (config.debug) debug = true;
|
|
818
863
|
|
|
864
|
+
// Set apikey and server globally to inject into schemas
|
|
865
|
+
global.apikey = config.apikey;
|
|
866
|
+
global.server = config.server;
|
|
867
|
+
global.model_dir = model_dir;
|
|
868
|
+
|
|
819
869
|
// Pre-load models
|
|
820
870
|
var files = fs.readdirSync(config.model_dir);
|
|
821
871
|
let modelnames = files.filter(function(fname) {
|
|
@@ -970,6 +1020,17 @@ const JXP = function(options) {
|
|
|
970
1020
|
actionBulkWrite,
|
|
971
1021
|
);
|
|
972
1022
|
|
|
1023
|
+
server.post(
|
|
1024
|
+
"/update/:modelname/:item_id",
|
|
1025
|
+
middlewareModel,
|
|
1026
|
+
security.login,
|
|
1027
|
+
security.auth,
|
|
1028
|
+
middlewarePasswords,
|
|
1029
|
+
middlewareCheckAdmin,
|
|
1030
|
+
config.pre_hooks.update,
|
|
1031
|
+
actionUpdate,
|
|
1032
|
+
);
|
|
1033
|
+
|
|
973
1034
|
/* Batch routes - ROLLED BACK FOR NOW */
|
|
974
1035
|
// server.post('/batch/create/:modelname', middlewareModel, security.login, security.auth, actionBatch);
|
|
975
1036
|
|
package/libs/login.js
CHANGED
|
@@ -122,10 +122,7 @@ const oauth_callback = async (req, res, next) => {
|
|
|
122
122
|
}
|
|
123
123
|
user[provider] = data;
|
|
124
124
|
await user.save();
|
|
125
|
-
|
|
126
|
-
apikey.user_id = user._id;
|
|
127
|
-
apikey.apikey = require('rand-token').generate(16);
|
|
128
|
-
await apikey.save();
|
|
125
|
+
const apikey = await security.generateApiKey(user._id)
|
|
129
126
|
var jwt_token = jwt.sign({ apikey: apikey.apikey, user: user }, req.config.shared_secret, {
|
|
130
127
|
expiresIn: "1m"
|
|
131
128
|
});
|
package/libs/schema.js
CHANGED
|
@@ -7,13 +7,6 @@ const config = require("config");
|
|
|
7
7
|
const JXPHelper = require('jxp-helper');
|
|
8
8
|
const modeldir = require("./modeldir");
|
|
9
9
|
|
|
10
|
-
// Set up our jxp-helper so that we call call the API from within the API (if we've set config.apikey)
|
|
11
|
-
const jxp_settings = {};
|
|
12
|
-
if (config.apikey) jxp_settings.apikey = config.apikey;
|
|
13
|
-
if (config.server) jxp_settings.server = config.server;
|
|
14
|
-
if (jxp_settings.apikey && jxp_settings.server) global.jxphelper = new JXPHelper(jxp_settings);
|
|
15
|
-
|
|
16
|
-
|
|
17
10
|
// Set some global types
|
|
18
11
|
global.ObjectId = mongoose.Schema.Types.ObjectId;
|
|
19
12
|
global.Mixed = mongoose.Schema.Types.Mixed;
|
|
@@ -39,6 +32,13 @@ class Schema extends mongoose.Schema {
|
|
|
39
32
|
_owner_id: { type: ObjectId, link: "User", map_to: "_owner", index: true },
|
|
40
33
|
_updated_by_id: { type: ObjectId, link: "User", map_to: "_updated_by", index: true },
|
|
41
34
|
}, definition);
|
|
35
|
+
// Set up our jxp-helper so that we call call the API from within the API (if we've set config.apikey)
|
|
36
|
+
if (!global.jxphelper) {
|
|
37
|
+
const jxp_settings = {};
|
|
38
|
+
if (global.apikey) jxp_settings.apikey = global.apikey;
|
|
39
|
+
if (global.server) jxp_settings.server = global.server;
|
|
40
|
+
if (jxp_settings.apikey && jxp_settings.server) global.jxphelper = new JXPHelper(jxp_settings);
|
|
41
|
+
}
|
|
42
42
|
// construct our parent
|
|
43
43
|
super(definition, opts);
|
|
44
44
|
// Some properties
|
package/libs/setup.js
CHANGED
|
@@ -3,11 +3,10 @@ const path = require("path");
|
|
|
3
3
|
const security = require("./security");
|
|
4
4
|
const ObjectID = require('mongodb').ObjectID;
|
|
5
5
|
var User = null;
|
|
6
|
-
|
|
6
|
+
const connection_string = require("./connection_string");
|
|
7
7
|
|
|
8
8
|
const init = config => {
|
|
9
9
|
User = require(path.join(config.model_dir, "user_model"));
|
|
10
|
-
connection_string = config.mongo.connection_string;
|
|
11
10
|
}
|
|
12
11
|
|
|
13
12
|
const checkUserDoesNotExist = async (req, res, next) => {
|
package/models/test_model.js
CHANGED
|
@@ -10,7 +10,10 @@ const TestSchema = new JXPSchema({
|
|
|
10
10
|
fulltext: { type: String, index: { text: true } },
|
|
11
11
|
link_id: { type: ObjectId, link: "Link", }, // We can populate these links during a query
|
|
12
12
|
other_link_id: { type: ObjectId, link: "Link", map_to: "other_link" },
|
|
13
|
-
array_link_id: [{ type: ObjectId, link: "Link", map_to: "array_link", justOne: false } ]
|
|
13
|
+
array_link_id: [{ type: ObjectId, link: "Link", map_to: "array_link", justOne: false } ],
|
|
14
|
+
composite_array: [
|
|
15
|
+
{ afoo: String, abar: Number }
|
|
16
|
+
]
|
|
14
17
|
},
|
|
15
18
|
{
|
|
16
19
|
perms: {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jxp",
|
|
3
3
|
"description:": "An opinionated RESTful API library based on Mongoose and Restify. Make an API by just writing Mongoose models.",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.10.1",
|
|
5
5
|
"private": false,
|
|
6
6
|
"main": "libs/jxp.js",
|
|
7
7
|
"scripts": {
|
|
@@ -27,37 +27,38 @@
|
|
|
27
27
|
"url": "https://github.com/j-norwood-young/jexpress-2/issues"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"axios": "^0.
|
|
30
|
+
"axios": "^0.24.0",
|
|
31
31
|
"bcryptjs": "^2.4.3",
|
|
32
|
-
"commander": "^
|
|
32
|
+
"commander": "^8.3.0",
|
|
33
33
|
"config": "^3.3.6",
|
|
34
|
-
"
|
|
35
|
-
"
|
|
34
|
+
"dotenv": "^10.0.0",
|
|
35
|
+
"glob": "7.2.0",
|
|
36
|
+
"js-yaml": "4.1.0",
|
|
36
37
|
"json2csv": "^5.0.6",
|
|
37
38
|
"jsonwebtoken": "^8.5.1",
|
|
38
39
|
"jstransformer-markdown-it": "^2.1.0",
|
|
39
|
-
"jxp-helper": "^1.3.
|
|
40
|
+
"jxp-helper": "^1.3.14",
|
|
40
41
|
"mkdirp": "^1.0.4",
|
|
41
|
-
"mongoose": "
|
|
42
|
+
"mongoose": "6.0.13",
|
|
42
43
|
"mongoose-friendly": "^0.1.4",
|
|
43
44
|
"morgan": "^1.10.0",
|
|
44
|
-
"nodemailer": "^6.
|
|
45
|
+
"nodemailer": "^6.7.1",
|
|
45
46
|
"nodemailer-smtp-transport": "^2.7.4",
|
|
46
47
|
"path": "^0.12.7",
|
|
47
48
|
"pug": "^3.0.2",
|
|
48
49
|
"querystring": "^0.2.1",
|
|
49
50
|
"rand-token": "^1.0.1",
|
|
50
51
|
"readline-sync": "^1.4.10",
|
|
51
|
-
"restify": "^8.
|
|
52
|
+
"restify": "^8.6.0",
|
|
52
53
|
"restify-cors-middleware": "^1.1.1",
|
|
53
54
|
"traverse": "^0.6.6",
|
|
54
|
-
"underscore": "^1.
|
|
55
|
-
"ws": "^
|
|
55
|
+
"underscore": "^1.13.1",
|
|
56
|
+
"ws": "^8.2.3"
|
|
56
57
|
},
|
|
57
58
|
"devDependencies": {
|
|
58
59
|
"chai": "^4.3.4",
|
|
59
60
|
"chai-http": "^4.3.0",
|
|
60
|
-
"mocha": "^
|
|
61
|
+
"mocha": "^9.1.3",
|
|
61
62
|
"should": "^13.2.3"
|
|
62
63
|
}
|
|
63
64
|
}
|
package/test/init.js
CHANGED
|
@@ -11,14 +11,6 @@ const Test = require(path.join(model_dir, "test_model"));
|
|
|
11
11
|
|
|
12
12
|
const security = require("../libs/security");
|
|
13
13
|
|
|
14
|
-
const chai = require('chai');
|
|
15
|
-
const chaiHttp = require('chai-http');
|
|
16
|
-
const should = chai.should();
|
|
17
|
-
|
|
18
|
-
const server = require(path.join(__dirname, "../bin/server.js"));
|
|
19
|
-
|
|
20
|
-
chai.use(chaiHttp);
|
|
21
|
-
|
|
22
14
|
const empty = async model => {
|
|
23
15
|
try {
|
|
24
16
|
const result = await model.deleteMany({});
|
package/test/test.js
CHANGED
|
@@ -832,6 +832,21 @@ describe('Test', () => {
|
|
|
832
832
|
});
|
|
833
833
|
});
|
|
834
834
|
});
|
|
835
|
+
it("should $push to an array", done => {
|
|
836
|
+
chai.request(server)
|
|
837
|
+
.patch("/api/test/" + post_id)
|
|
838
|
+
.auth(init.email, init.password)
|
|
839
|
+
.send({ $push: { shmack: "fah" } })
|
|
840
|
+
.end((err, res) => {
|
|
841
|
+
console.log(res.body.data);
|
|
842
|
+
res.should.have.status(200);
|
|
843
|
+
res.body.data.should.be.an('object');
|
|
844
|
+
res.body.data.should.have.property("shmack")
|
|
845
|
+
res.body.data.shmack.should.be.an("array");
|
|
846
|
+
res.body.data.shmack.should.have.length(4);
|
|
847
|
+
done();
|
|
848
|
+
});
|
|
849
|
+
});
|
|
835
850
|
});
|
|
836
851
|
|
|
837
852
|
describe("Models", () => {
|