@paralect/hive 0.0.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/cli/cli.js +10 -0
- package/package.json +60 -0
- package/starter/Dockerfile +13 -0
- package/starter/Dockerfile.dev +33 -0
- package/starter/Dockerfile.prod +29 -0
- package/starter/README.md +11 -0
- package/starter/bin/init-project.sh +22 -0
- package/starter/bin/start.sh +2 -0
- package/starter/bootstrap-hive.js +118 -0
- package/starter/initial-data.json +176 -0
- package/starter/mongodb-ca-certificate.cer +32 -0
- package/starter/package-lock.json +6711 -0
- package/starter/package.json +84 -0
- package/starter/ship_logo.png +0 -0
- package/starter/src/app.js +61 -0
- package/starter/src/assets/emails/components/header.mjml +13 -0
- package/starter/src/assets/emails/dist/.gitkeep +0 -0
- package/starter/src/assets/emails/signup-welcome.mjml +34 -0
- package/starter/src/assets/emails/styles/index.mjml +77 -0
- package/starter/src/autoMap/addHandlers.js +167 -0
- package/starter/src/autoMap/mapSchema.js +112 -0
- package/starter/src/autoMap/schemaMappings.js +7 -0
- package/starter/src/config/app.js +3 -0
- package/starter/src/config/index.js +24 -0
- package/starter/src/db.js +48 -0
- package/starter/src/helpers/db/ifUpdated.js +22 -0
- package/starter/src/helpers/getResourceEndpoints.js +26 -0
- package/starter/src/helpers/getResources.js +25 -0
- package/starter/src/helpers/getSchemas.js +25 -0
- package/starter/src/helpers/prettierFormat.js +8 -0
- package/starter/src/ioEmitter.js +10 -0
- package/starter/src/jsconfig.json +5 -0
- package/starter/src/lib/node-mongo/.github/workflows/npm-publish.yml +32 -0
- package/starter/src/lib/node-mongo/API.md +654 -0
- package/starter/src/lib/node-mongo/CHANGELOG.md +98 -0
- package/starter/src/lib/node-mongo/README.md +97 -0
- package/starter/src/lib/node-mongo/package.json +74 -0
- package/starter/src/lib/node-mongo/src/index.js +67 -0
- package/starter/src/lib/node-mongo/src/mongo-query-service.js +72 -0
- package/starter/src/lib/node-mongo/src/mongo-service-error.js +15 -0
- package/starter/src/lib/node-mongo/src/mongo-service.js +279 -0
- package/starter/src/logger.js +30 -0
- package/starter/src/middlewares/global/extractUserTokens.js +15 -0
- package/starter/src/middlewares/global/tryToAttachUser.js +32 -0
- package/starter/src/middlewares/isAuthorized.js +9 -0
- package/starter/src/middlewares/shouldExist.js +17 -0
- package/starter/src/middlewares/shouldNotExist.js +19 -0
- package/starter/src/middlewares/uploadFile.js +5 -0
- package/starter/src/middlewares/validate.js +39 -0
- package/starter/src/migrations/migration.js +8 -0
- package/starter/src/migrations/migration.service.js +75 -0
- package/starter/src/migrations/migrations/1.js +22 -0
- package/starter/src/migrations/migrations-log/migration-log.schema.js +15 -0
- package/starter/src/migrations/migrations-log/migration-log.service.js +51 -0
- package/starter/src/migrations/migrations.schema.js +9 -0
- package/starter/src/migrations/migrator.js +77 -0
- package/starter/src/migrator.js +5 -0
- package/starter/src/resources/_dev/endpoints/triggerSchedulerHandler.js +30 -0
- package/starter/src/resources/health/endpoints/get.js +10 -0
- package/starter/src/resources/schemaMappings/schemaMappings.schema.js +9 -0
- package/starter/src/resources/users/endpoints/getCurrentUser.js +13 -0
- package/starter/src/resources/users/endpoints/getUserProfile.js +16 -0
- package/starter/src/resources/users/users.schema.js +14 -0
- package/starter/src/routes/index.js +151 -0
- package/starter/src/routes/middlewares/attachCustomErrors.js +28 -0
- package/starter/src/routes/middlewares/routeErrorHandler.js +27 -0
- package/starter/src/scheduler/handlers/sendDailyReport.example.js +7 -0
- package/starter/src/scheduler.js +21 -0
- package/starter/src/security.util.js +38 -0
- package/starter/src/services/globalTest.js +0 -0
- package/starter/src/socketIo.js +91 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
## v2.1.0 (2020-10-15)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
#### Manager
|
|
6
|
+
|
|
7
|
+
[createService](API.md#createservice)
|
|
8
|
+
|
|
9
|
+
- Add [emitter](API.md#createservice) option.
|
|
10
|
+
|
|
11
|
+
## v2.0.0 (2020-09-29)
|
|
12
|
+
|
|
13
|
+
- Update dependencies.
|
|
14
|
+
|
|
15
|
+
### Breaking Changes
|
|
16
|
+
|
|
17
|
+
#### [Manager](API.md#manager)
|
|
18
|
+
|
|
19
|
+
[createQueryService](API.md#createqueryservice)
|
|
20
|
+
|
|
21
|
+
- Rename `validateSchema` option to `validate`.
|
|
22
|
+
- Change `addCreatedOnField` default to `true`.
|
|
23
|
+
- Change `addUpdatedOnField` default to `true`.
|
|
24
|
+
|
|
25
|
+
[createService](API.md#createservice)
|
|
26
|
+
|
|
27
|
+
- Rename `validateSchema` option to `validate`.
|
|
28
|
+
- Change `addCreatedOnField` default to `true`.
|
|
29
|
+
- Change `addUpdatedOnField` default to `true`.
|
|
30
|
+
|
|
31
|
+
#### [Query Service](API.md#query-service)
|
|
32
|
+
|
|
33
|
+
- Remove `generateId` method.
|
|
34
|
+
- Remove `expectDocument` method.
|
|
35
|
+
|
|
36
|
+
#### [Service](API.md#service)
|
|
37
|
+
|
|
38
|
+
- Remove `update` method. Use [updateOne](API.md#updateone) or [updateMany](API.md#updatemany).
|
|
39
|
+
- Remove `ensureIndex`. Use [atomic.createIndex](API.md#atomiccreateindex).
|
|
40
|
+
- Remove `createOrUpdate`. Use [create](API.md#create) or [updateOne](API.md#updateone) or [updateMany](API.md#updatemany).
|
|
41
|
+
- Remove `findOneAndUpdate`. Use [findOne](API.md#findone) and [updateOne](API.md#updateone).
|
|
42
|
+
|
|
43
|
+
### Features
|
|
44
|
+
|
|
45
|
+
#### Manager
|
|
46
|
+
|
|
47
|
+
[createQueryService](API.md#createqueryservice)
|
|
48
|
+
|
|
49
|
+
- Add `useStringId` option.
|
|
50
|
+
|
|
51
|
+
[createService](API.md#createservice)
|
|
52
|
+
|
|
53
|
+
- Add `useStringId` option.
|
|
54
|
+
|
|
55
|
+
#### [Query Service](API.md#query-service)
|
|
56
|
+
|
|
57
|
+
- Add more monk's methods. [See full list](API.md#query-service)
|
|
58
|
+
|
|
59
|
+
#### [Service](API.md#service)
|
|
60
|
+
|
|
61
|
+
- Add [generateId](API.md#generateid) method.
|
|
62
|
+
- Add [updateOne](API.md#updateone) method.
|
|
63
|
+
- Add [updateMany](API.md#updatemany) method.
|
|
64
|
+
- Add [performTransaction](API.md#performtransaction) method.
|
|
65
|
+
- Add more monk's methods in `atomic` namespace. [See full list](API.md#service)
|
|
66
|
+
|
|
67
|
+
## v1.1.0 (2019-06-25)
|
|
68
|
+
|
|
69
|
+
- Update dependencies.
|
|
70
|
+
- Fix required version of the Node.js.
|
|
71
|
+
|
|
72
|
+
### Breaking Changes
|
|
73
|
+
|
|
74
|
+
- Now `update` function will work via [set](https://docs.mongodb.com/manual/reference/operator/update/set/) operator. It means the new doc will be the result of merge of the old doc and the provided one.
|
|
75
|
+
|
|
76
|
+
## v1.0.0 (2018-05-23)
|
|
77
|
+
|
|
78
|
+
- Update dependencies.
|
|
79
|
+
- Add tests.
|
|
80
|
+
- Fix required version of the Node.js.
|
|
81
|
+
|
|
82
|
+
### Breaking Changes
|
|
83
|
+
|
|
84
|
+
- Now, by default, we do not add the fields `createdOn` and` updatedOn` automatically to the model. If you want to save the current behavior, add the appropriate `addCreatedOnField` and` addUpdatedOnField` options to the service definitions.
|
|
85
|
+
|
|
86
|
+
## v0.3.1 (2017-12-16)
|
|
87
|
+
|
|
88
|
+
- Stop using deprecated method `ensureIndex` of the `monk`.
|
|
89
|
+
|
|
90
|
+
## v0.3.0 (2017-10-24)
|
|
91
|
+
|
|
92
|
+
- Add ability to create custom methods for service and query service.
|
|
93
|
+
- Add tests.
|
|
94
|
+
|
|
95
|
+
## v0.2.0 (2017-10-12)
|
|
96
|
+
|
|
97
|
+
- Add support of the [joi](https://github.com/hapijs/joi) for validating data schema.
|
|
98
|
+
- Add tests for validating of the schema.
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Node Mongo
|
|
2
|
+
|
|
3
|
+
[](https://badge.fury.io/js/%40paralect%2Fnode-mongo)
|
|
4
|
+
|
|
5
|
+
Node Mongo is reactive extension to MongoDB API. It provides few usability improvements to the [monk](https://github.com/Automattic/monk) API.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- ️️**Reactive**. Fires events as document stored, updated or deleted from database
|
|
10
|
+
- **Paging**. Implements high level paging API
|
|
11
|
+
- **Schema validation**. Validates your data before save
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
npm i @paralect/node-mongo
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Documentation
|
|
20
|
+
|
|
21
|
+
[API Reference](API.md).
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
### Connect to MongoDB
|
|
26
|
+
|
|
27
|
+
```javascript
|
|
28
|
+
const connectionString = "mongodb://localhost:27017/home-db";
|
|
29
|
+
const db = require("@paralect/node-mongo").connect(connectionString);
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### CRUD Operations
|
|
33
|
+
|
|
34
|
+
```javascript
|
|
35
|
+
// create a service to work with specific database collection
|
|
36
|
+
const userService = db.createService("users");
|
|
37
|
+
|
|
38
|
+
// create documents
|
|
39
|
+
const users = await userService.create([{ name: "Alex" }, { name: "Bob" }]);
|
|
40
|
+
|
|
41
|
+
// find one document
|
|
42
|
+
const user = await userService.findOne({ name: "Bob" });
|
|
43
|
+
|
|
44
|
+
// find many documents with pagination
|
|
45
|
+
const { results, pagesCount, count } = await userService.find(
|
|
46
|
+
{ name: "Bob" },
|
|
47
|
+
{ page: 1, perPage: 30 }
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
// update document
|
|
51
|
+
const updatedUser = await userService.updateOne({ _id: "1" }, (doc) => ({
|
|
52
|
+
...doc,
|
|
53
|
+
name: "Alex",
|
|
54
|
+
}));
|
|
55
|
+
|
|
56
|
+
// remove document
|
|
57
|
+
const removedUser = await userService.remove({ _id: "1" });
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Event handlers
|
|
61
|
+
|
|
62
|
+
```js
|
|
63
|
+
const userService = db.createService("users");
|
|
64
|
+
|
|
65
|
+
userService.on("created", ({ doc }) => {});
|
|
66
|
+
|
|
67
|
+
userService.on("updated", ({ doc, prevDoc }) => {});
|
|
68
|
+
|
|
69
|
+
userService.onPropertiesUpdated(["email"], ({ doc, prevDoc }) => {});
|
|
70
|
+
|
|
71
|
+
userService.on("removed", ({ doc }) => {});
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Schema validation
|
|
75
|
+
|
|
76
|
+
```javascript
|
|
77
|
+
const Joi = require("Joi");
|
|
78
|
+
|
|
79
|
+
const userSchema = Joi.object({
|
|
80
|
+
_id: Joi.string(),
|
|
81
|
+
createdOn: Joi.date(),
|
|
82
|
+
name: Joi.string(),
|
|
83
|
+
status: Joi.string().valid("active", "inactive"),
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
function validate(obj) {
|
|
87
|
+
return userSchema.validate(obj);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const userService = db.createService("users", { validate });
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Change Log
|
|
94
|
+
|
|
95
|
+
This project adheres to [Semantic Versioning](http://semver.org/).
|
|
96
|
+
|
|
97
|
+
Every release is documented on the Github [Releases](https://github.com/paralect/node-mongo/releases) page.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_args": [
|
|
3
|
+
[
|
|
4
|
+
"@paralect/node-mongo@2.1.1",
|
|
5
|
+
"/Users/igorkrasnik/Documents/work/trojanflix-api"
|
|
6
|
+
]
|
|
7
|
+
],
|
|
8
|
+
"_from": "@paralect/node-mongo@2.1.1",
|
|
9
|
+
"_id": "@paralect/node-mongo@2.1.1",
|
|
10
|
+
"_inBundle": false,
|
|
11
|
+
"_integrity": "sha512-IneYD4qTa2P0St3m3Zlw/nRXejTsrsepkmEy7Qw0KYjEQeoekr6gxC6edOPouupkQA0qBOUl4zQSdkaEIUn36w==",
|
|
12
|
+
"_location": "/@paralect/node-mongo",
|
|
13
|
+
"_phantomChildren": {},
|
|
14
|
+
"_requested": {
|
|
15
|
+
"type": "version",
|
|
16
|
+
"registry": true,
|
|
17
|
+
"raw": "@paralect/node-mongo@2.1.1",
|
|
18
|
+
"name": "@paralect/node-mongo",
|
|
19
|
+
"escapedName": "@paralect%2fnode-mongo",
|
|
20
|
+
"scope": "@paralect",
|
|
21
|
+
"rawSpec": "2.1.1",
|
|
22
|
+
"saveSpec": null,
|
|
23
|
+
"fetchSpec": "2.1.1"
|
|
24
|
+
},
|
|
25
|
+
"_requiredBy": [
|
|
26
|
+
"/"
|
|
27
|
+
],
|
|
28
|
+
"_resolved": "https://registry.npmjs.org/@paralect/node-mongo/-/node-mongo-2.1.1.tgz",
|
|
29
|
+
"_spec": "2.1.1",
|
|
30
|
+
"_where": "/Users/igorkrasnik/Documents/work/trojanflix-api",
|
|
31
|
+
"author": {
|
|
32
|
+
"name": "Paralect"
|
|
33
|
+
},
|
|
34
|
+
"bugs": {
|
|
35
|
+
"url": "https://github.com/paralect/node-mongo/issues"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"lodash": "4.17.20",
|
|
39
|
+
"monk": "7.3.2"
|
|
40
|
+
},
|
|
41
|
+
"description": "MongoDB wrapper for Node.JS 12",
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"chai": "^4.2.0",
|
|
44
|
+
"chai-spies": "^1.0.0",
|
|
45
|
+
"eslint": "^7.10.0",
|
|
46
|
+
"eslint-config-airbnb-base": "^14.2.0",
|
|
47
|
+
"eslint-plugin-import": "^2.22.1",
|
|
48
|
+
"mocha": "^8.1.3",
|
|
49
|
+
"npm-run-all": "^4.1.5"
|
|
50
|
+
},
|
|
51
|
+
"engines": {
|
|
52
|
+
"node": ">=12.0.0"
|
|
53
|
+
},
|
|
54
|
+
"homepage": "https://github.com/paralect/node-mongo#readme",
|
|
55
|
+
"keywords": [
|
|
56
|
+
"mongo",
|
|
57
|
+
"monk",
|
|
58
|
+
"paralect"
|
|
59
|
+
],
|
|
60
|
+
"license": "MIT",
|
|
61
|
+
"main": "src/index.js",
|
|
62
|
+
"name": "@paralect/node-mongo",
|
|
63
|
+
"private": false,
|
|
64
|
+
"repository": {
|
|
65
|
+
"type": "git",
|
|
66
|
+
"url": "git+https://github.com/paralect/node-mongo.git"
|
|
67
|
+
},
|
|
68
|
+
"scripts": {
|
|
69
|
+
"test": "run-s test:*",
|
|
70
|
+
"test:eslint": "eslint ./",
|
|
71
|
+
"test:mocha": "NODE_ENV=test mocha --exit --recursive -c -R spec src/index.test.js"
|
|
72
|
+
},
|
|
73
|
+
"version": "2.1.1"
|
|
74
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
const monk = require("monk");
|
|
2
|
+
const _ = require("lodash");
|
|
3
|
+
|
|
4
|
+
const MongoService = require("./mongo-service");
|
|
5
|
+
const MongoQueryService = require("./mongo-query-service");
|
|
6
|
+
|
|
7
|
+
const logger = global.logger || console;
|
|
8
|
+
|
|
9
|
+
const connect = (connectionString, settings) => {
|
|
10
|
+
const connectionSettings = _.defaults({}, settings, {
|
|
11
|
+
connectTimeoutMS: 20000,
|
|
12
|
+
});
|
|
13
|
+
const db = monk(connectionString, connectionSettings);
|
|
14
|
+
|
|
15
|
+
db.on("error-opening", (err) => {
|
|
16
|
+
logger.error(err, "Failed to connect to the mongodb on start");
|
|
17
|
+
throw err;
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
db.on("open", () => {
|
|
21
|
+
logger.info(`Connected to mongodb: ${connectionString}`);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
db.on("close", (err) => {
|
|
25
|
+
if (err) {
|
|
26
|
+
logger.error(err, `Lost connection with mongodb: ${connectionString}`);
|
|
27
|
+
} else {
|
|
28
|
+
logger.warn(`Closed connection with mongodb: ${connectionString}`);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
db.on("connected", (err) => {
|
|
33
|
+
if (err) {
|
|
34
|
+
logger.error(err);
|
|
35
|
+
} else {
|
|
36
|
+
logger.info(`Connected to mongodb: ${connectionString}`);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
db.createService = (collectionName, options = {}) => {
|
|
41
|
+
const collection = db.get(collectionName, { castIds: false });
|
|
42
|
+
|
|
43
|
+
return new MongoService(collection, options);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
db.setServiceMethod = (name, method) => {
|
|
47
|
+
MongoService.prototype[name] = function customMethod(...args) {
|
|
48
|
+
return method.apply(this, [this, ...args]);
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
db.createQueryService = (collectionName, options = {}) => {
|
|
53
|
+
const collection = db.get(collectionName, { castIds: false });
|
|
54
|
+
|
|
55
|
+
return new MongoQueryService(collection, options);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
db.setQueryServiceMethod = (name, method) => {
|
|
59
|
+
MongoQueryService.prototype[name] = function customMethod(...args) {
|
|
60
|
+
return method.apply(this, [this, ...args]);
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
return db;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
module.exports.connect = connect;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
const _ = require("lodash");
|
|
2
|
+
|
|
3
|
+
const MongoServiceError = require("./mongo-service-error");
|
|
4
|
+
|
|
5
|
+
class MongoQueryService {
|
|
6
|
+
constructor(collection, options = {}) {
|
|
7
|
+
this._collection = collection;
|
|
8
|
+
this._options = options;
|
|
9
|
+
|
|
10
|
+
this.name = collection.name;
|
|
11
|
+
|
|
12
|
+
this.aggregate = collection.aggregate;
|
|
13
|
+
this.count = collection.count;
|
|
14
|
+
this.distinct = collection.distinct;
|
|
15
|
+
this.geoHaystackSearch = collection.geoHaystackSearch;
|
|
16
|
+
this.indexes = collection.indexes;
|
|
17
|
+
this.mapReduce = collection.mapReduce;
|
|
18
|
+
this.stats = collection.stats;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async find(query = {}, opt = { perPage: 100, page: 0 }) {
|
|
22
|
+
const options = _.cloneDeep(opt);
|
|
23
|
+
const { page, perPage } = options;
|
|
24
|
+
const hasPaging = page > 0;
|
|
25
|
+
if (hasPaging) {
|
|
26
|
+
options.skip = (page - 1) * perPage;
|
|
27
|
+
options.limit = perPage;
|
|
28
|
+
}
|
|
29
|
+
delete options.perPage;
|
|
30
|
+
delete options.page;
|
|
31
|
+
|
|
32
|
+
const results = await this._collection.find(query, options);
|
|
33
|
+
if (!hasPaging) {
|
|
34
|
+
return {
|
|
35
|
+
results,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const countOptions = {};
|
|
40
|
+
if (options.session) countOptions.session = options.session;
|
|
41
|
+
const count = await this._collection.count(query, countOptions);
|
|
42
|
+
const pagesCount = Math.ceil(count / perPage) || 1;
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
pagesCount,
|
|
46
|
+
results,
|
|
47
|
+
count,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async findOne(query = {}, options = {}) {
|
|
52
|
+
const { results } = await this.find(query, { limit: 2, ...options });
|
|
53
|
+
|
|
54
|
+
if (results.length > 1) {
|
|
55
|
+
throw new MongoServiceError(
|
|
56
|
+
MongoServiceError.MORE_THAN_ONE,
|
|
57
|
+
`findOne: More than one document return for query ${JSON.stringify(
|
|
58
|
+
query
|
|
59
|
+
)}`
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return results[0] || null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async exists(query, options = {}) {
|
|
67
|
+
const count = await this.count(query, { limit: 1, ...options });
|
|
68
|
+
return count > 0;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
module.exports = MongoQueryService;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
class MongoServiceError extends Error {
|
|
2
|
+
constructor(code, message, error) {
|
|
3
|
+
super(message);
|
|
4
|
+
this.name = "MongoServiceError";
|
|
5
|
+
this.code = code;
|
|
6
|
+
this.error = error;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
MongoServiceError.NOT_FOUND = "NOT_FOUND";
|
|
11
|
+
MongoServiceError.MORE_THAN_ONE = "MORE_THAN_ONE";
|
|
12
|
+
MongoServiceError.INVALID_SCHEMA = "INVALID_SCHEMA";
|
|
13
|
+
MongoServiceError.INVALID_ARGUMENT = "INVALID_ARGUMENT";
|
|
14
|
+
|
|
15
|
+
module.exports = MongoServiceError;
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
const monk = require("monk");
|
|
2
|
+
const { EventEmitter } = require("events");
|
|
3
|
+
const _ = require("lodash");
|
|
4
|
+
|
|
5
|
+
const MongoQueryService = require("./mongo-query-service");
|
|
6
|
+
const MongoServiceError = require("./mongo-service-error");
|
|
7
|
+
|
|
8
|
+
const defaultOptions = {
|
|
9
|
+
addCreatedOnField: true,
|
|
10
|
+
addUpdatedOnField: true,
|
|
11
|
+
useStringId: true,
|
|
12
|
+
validate: undefined,
|
|
13
|
+
emitter: undefined,
|
|
14
|
+
|
|
15
|
+
onBeforeCreated: ({ docs }) => docs,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
class MongoService extends MongoQueryService {
|
|
19
|
+
constructor(collection, options = {}) {
|
|
20
|
+
super(collection, options);
|
|
21
|
+
|
|
22
|
+
_.defaults(this._options, defaultOptions);
|
|
23
|
+
|
|
24
|
+
this._bus = this._options.emitter || new EventEmitter();
|
|
25
|
+
|
|
26
|
+
this.generateId = () => monk.id().toHexString();
|
|
27
|
+
|
|
28
|
+
this.atomic = {
|
|
29
|
+
bulkWrite: collection.bulkWrite,
|
|
30
|
+
createIndex: collection.createIndex,
|
|
31
|
+
drop: collection.drop,
|
|
32
|
+
dropIndex: collection.dropIndex,
|
|
33
|
+
dropIndexes: collection.dropIndexes,
|
|
34
|
+
findOneAndDelete: collection.findOneAndDelete,
|
|
35
|
+
findOneAndUpdate: collection.findOneAndUpdate,
|
|
36
|
+
insert: collection.insert,
|
|
37
|
+
remove: collection.remove,
|
|
38
|
+
update: collection.update,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
collection.manager
|
|
42
|
+
.executeWhenOpened()
|
|
43
|
+
.then(async () => {
|
|
44
|
+
await collection.manager._db.command({ create: collection.name });
|
|
45
|
+
})
|
|
46
|
+
.catch((error) => {
|
|
47
|
+
// a collection already exists
|
|
48
|
+
if (error.code !== 48) {
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
static _deepCompare(data, initialData, properties) {
|
|
55
|
+
let changed = false;
|
|
56
|
+
|
|
57
|
+
if (Array.isArray(properties)) {
|
|
58
|
+
changed =
|
|
59
|
+
_.find(properties, (prop) => {
|
|
60
|
+
const value = _.get(data, prop);
|
|
61
|
+
const initialValue = _.get(initialData, prop);
|
|
62
|
+
|
|
63
|
+
return !_.isEqual(value, initialValue);
|
|
64
|
+
}) !== undefined;
|
|
65
|
+
} else {
|
|
66
|
+
Object.keys(properties).forEach((prop) => {
|
|
67
|
+
if (changed) return;
|
|
68
|
+
|
|
69
|
+
const value = _.get(data, prop);
|
|
70
|
+
const initialValue = _.get(initialData, prop);
|
|
71
|
+
|
|
72
|
+
if (
|
|
73
|
+
_.isEqual(value, properties[prop]) &&
|
|
74
|
+
!_.isEqual(initialValue, properties[prop])
|
|
75
|
+
) {
|
|
76
|
+
changed = true;
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return changed;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async _validate(entity) {
|
|
85
|
+
if (this._options.validate) {
|
|
86
|
+
const { value, error } = await this._options.validate(entity);
|
|
87
|
+
|
|
88
|
+
if (error) {
|
|
89
|
+
console.log("Schema Error", error);
|
|
90
|
+
|
|
91
|
+
throw new MongoServiceError(
|
|
92
|
+
MongoServiceError.INVALID_SCHEMA,
|
|
93
|
+
`Document schema is invalid: ${JSON.stringify(error, null, 2)}`,
|
|
94
|
+
error
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return value;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return entity;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
emit(eventName, event) {
|
|
105
|
+
return this._bus.emit(eventName, event);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
once(eventName, handler) {
|
|
109
|
+
return this._bus.once(eventName, handler);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
on(eventName, handler) {
|
|
113
|
+
return this._bus.on(eventName, handler);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
onPropertiesUpdated(properties, handler) {
|
|
117
|
+
return this.on("updated", (event) => {
|
|
118
|
+
const isChanged = MongoService._deepCompare(
|
|
119
|
+
event.doc,
|
|
120
|
+
event.prevDoc,
|
|
121
|
+
properties
|
|
122
|
+
);
|
|
123
|
+
if (isChanged) handler(event);
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async create(objs, options = {}) {
|
|
128
|
+
const entities = _.isArray(objs) ? objs : [objs];
|
|
129
|
+
|
|
130
|
+
let created = await Promise.all(
|
|
131
|
+
entities.map(async (doc) => {
|
|
132
|
+
const entity = _.cloneDeep(doc);
|
|
133
|
+
|
|
134
|
+
if (this._options.useStringId && !entity._id)
|
|
135
|
+
entity._id = this.generateId();
|
|
136
|
+
if (this._options.addCreatedOnField && !entity.createdOn) {
|
|
137
|
+
entity.createdOn = new Date().toISOString();
|
|
138
|
+
}
|
|
139
|
+
const validated = await this._validate(entity);
|
|
140
|
+
|
|
141
|
+
return validated;
|
|
142
|
+
})
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
created = await this._options.onBeforeCreated({ docs: created });
|
|
146
|
+
|
|
147
|
+
await this._collection.insert(created, options);
|
|
148
|
+
|
|
149
|
+
created.forEach((doc) => {
|
|
150
|
+
this._bus.emit("created", {
|
|
151
|
+
doc,
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
return created.length > 1 ? created : created[0];
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async updateOne(query, updateFn, options = {}) {
|
|
159
|
+
if (!_.isFunction(updateFn)) {
|
|
160
|
+
throw new MongoServiceError(
|
|
161
|
+
MongoServiceError.INVALID_ARGUMENT,
|
|
162
|
+
`updateOne: second argument is invalid. Expected a function but got ${typeof updateFn}`
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const findOptions = {};
|
|
167
|
+
if (options.session) findOptions.session = options.session;
|
|
168
|
+
const doc = await this.findOne(query, findOptions);
|
|
169
|
+
if (!doc) {
|
|
170
|
+
throw new MongoServiceError(
|
|
171
|
+
MongoServiceError.NOT_FOUND,
|
|
172
|
+
`updateOne: document not found. Query: ${JSON.stringify(query)}`
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
let entity = _.cloneDeep(doc);
|
|
177
|
+
|
|
178
|
+
if (this._options.addUpdatedOnField)
|
|
179
|
+
entity.updatedOn = new Date().toISOString();
|
|
180
|
+
entity = await updateFn(entity);
|
|
181
|
+
const updated = await this._validate(entity);
|
|
182
|
+
|
|
183
|
+
await this._collection.update(
|
|
184
|
+
{ ...query, _id: doc._id },
|
|
185
|
+
{ $set: updated },
|
|
186
|
+
options
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
this._bus.emit("updated", {
|
|
190
|
+
doc: updated,
|
|
191
|
+
prevDoc: doc,
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
return updated;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async updateMany(query, updateFn, options = {}) {
|
|
198
|
+
if (!_.isFunction(updateFn)) {
|
|
199
|
+
throw new MongoServiceError(
|
|
200
|
+
MongoServiceError.INVALID_ARGUMENT,
|
|
201
|
+
`updateMany: second argument is invalid. Expected a function but got ${typeof updateFn}`
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const findOptions = {};
|
|
206
|
+
if (options.session) findOptions.session = options.session;
|
|
207
|
+
const { results: docs } = await this.find(query, findOptions);
|
|
208
|
+
if (docs.length === 0) return [];
|
|
209
|
+
|
|
210
|
+
const updated = await Promise.all(
|
|
211
|
+
docs.map(async (doc) => {
|
|
212
|
+
let entity = _.cloneDeep(doc);
|
|
213
|
+
|
|
214
|
+
if (this._options.addUpdatedOnField)
|
|
215
|
+
entity.updatedOn = new Date().toISOString();
|
|
216
|
+
entity = await updateFn(entity);
|
|
217
|
+
const validated = await this._validate(entity);
|
|
218
|
+
|
|
219
|
+
return validated;
|
|
220
|
+
})
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
await Promise.all(
|
|
224
|
+
updated.map((doc) =>
|
|
225
|
+
this._collection.update(
|
|
226
|
+
{ ...query, _id: doc._id },
|
|
227
|
+
{ $set: doc },
|
|
228
|
+
options
|
|
229
|
+
)
|
|
230
|
+
)
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
updated.forEach((doc, index) => {
|
|
234
|
+
this._bus.emit("updated", {
|
|
235
|
+
doc,
|
|
236
|
+
prevDoc: docs[index],
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
return updated;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
async remove(query, options = {}) {
|
|
244
|
+
const findOptions = {};
|
|
245
|
+
if (options.session) findOptions.session = options.session;
|
|
246
|
+
const removed = await this.find(query, findOptions);
|
|
247
|
+
await this._collection.remove(query, options);
|
|
248
|
+
|
|
249
|
+
removed.results.forEach((doc) => {
|
|
250
|
+
this._bus.emit("removed", {
|
|
251
|
+
doc,
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
return removed;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
async performTransaction(transactionFn, options = {}) {
|
|
259
|
+
if (!_.isFunction(transactionFn)) {
|
|
260
|
+
throw new MongoServiceError(
|
|
261
|
+
MongoServiceError.INVALID_ARGUMENT,
|
|
262
|
+
`performTransaction: first argument is invalid. Expected a function but got ${typeof transactionFn}`
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
await this._collection.manager.executeWhenOpened();
|
|
267
|
+
|
|
268
|
+
const session = this._collection.manager._client.startSession(options);
|
|
269
|
+
|
|
270
|
+
try {
|
|
271
|
+
await session.withTransaction(transactionFn);
|
|
272
|
+
} catch (error) {
|
|
273
|
+
session.endSession();
|
|
274
|
+
throw error;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
module.exports = MongoService;
|