@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,84 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "koa-api-starter",
|
|
3
|
+
"version": "2.3.0",
|
|
4
|
+
"description": "Koa api",
|
|
5
|
+
"private": false,
|
|
6
|
+
"main": "src/app.js",
|
|
7
|
+
"author": "Paralect",
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"keywords": [
|
|
10
|
+
"koa",
|
|
11
|
+
"rest api",
|
|
12
|
+
"paralect"
|
|
13
|
+
],
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/paralect/koa-api-starter.git"
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build-assets": "mjml ./src/assets/emails/*.mjml -o ./src/assets/emails/dist/",
|
|
20
|
+
"dev": "nodemon --watch src src/app.js",
|
|
21
|
+
"init-project": "./bin/init-project.sh",
|
|
22
|
+
"start": "node src/app.js",
|
|
23
|
+
"migrate": "node ./src/migrator.js",
|
|
24
|
+
"schedule-dev": "nodemon --watch ./src ./src/scheduler ./src/scheduler.js",
|
|
25
|
+
"schedule": "node ./src/scheduler.js",
|
|
26
|
+
"precommit": "lint-staged",
|
|
27
|
+
"prepare": "husky install"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@koa/cors": "3.1.0",
|
|
31
|
+
"@koa/multer": "3.0.0",
|
|
32
|
+
"@koa/router": "10.1.1",
|
|
33
|
+
"@paralect/node-mongo": "2.1.1",
|
|
34
|
+
"@sendgrid/mail": "7.6.1",
|
|
35
|
+
"@socket.io/redis-adapter": "7.1.0",
|
|
36
|
+
"@socket.io/redis-emitter": "4.1.1",
|
|
37
|
+
"app-module-path": "2.2.0",
|
|
38
|
+
"aws-sdk": "2.1080.0",
|
|
39
|
+
"bcryptjs": "2.4.3",
|
|
40
|
+
"dotenv": "16.0.0",
|
|
41
|
+
"eslint-config-prettier": "8.5.0",
|
|
42
|
+
"handlebars": "4.7.7",
|
|
43
|
+
"joi": "17.6.0",
|
|
44
|
+
"koa": "2.13.4",
|
|
45
|
+
"koa-bodyparser": "4.3.0",
|
|
46
|
+
"koa-helmet": "6.1.0",
|
|
47
|
+
"koa-logger": "3.2.1",
|
|
48
|
+
"koa-mount": "4.0.0",
|
|
49
|
+
"koa-qs": "3.0.0",
|
|
50
|
+
"lodash": "4.17.21",
|
|
51
|
+
"mjml": "4.12.0",
|
|
52
|
+
"mkdirp": "1.0.4",
|
|
53
|
+
"moment": "2.29.1",
|
|
54
|
+
"moment-duration-format": "2.3.2",
|
|
55
|
+
"multer": "1.4.4",
|
|
56
|
+
"node-schedule": "2.1.0",
|
|
57
|
+
"nodemon": "2.0.15",
|
|
58
|
+
"prettier": "2.6.2",
|
|
59
|
+
"psl": "1.8.0",
|
|
60
|
+
"redis": "3.1.2",
|
|
61
|
+
"require-dir": "1.2.0",
|
|
62
|
+
"socket.io": "4.4.1",
|
|
63
|
+
"socket.io-emitter": "3.2.0",
|
|
64
|
+
"tail": "2.2.4",
|
|
65
|
+
"winston": "3.6.0"
|
|
66
|
+
},
|
|
67
|
+
"devDependencies": {
|
|
68
|
+
"eslint": "8.9.0",
|
|
69
|
+
"eslint-config-airbnb-base": "15.0.0",
|
|
70
|
+
"eslint-plugin-import": "2.25.4",
|
|
71
|
+
"husky": "7.0.4",
|
|
72
|
+
"lint-staged": "12.3.4"
|
|
73
|
+
},
|
|
74
|
+
"lint-staged": {
|
|
75
|
+
"*.js": [
|
|
76
|
+
"prettier --write",
|
|
77
|
+
"git add"
|
|
78
|
+
],
|
|
79
|
+
".js": [
|
|
80
|
+
"npm run lint:fix",
|
|
81
|
+
"npm run lint"
|
|
82
|
+
]
|
|
83
|
+
}
|
|
84
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
require("app-module-path").addPath(__dirname);
|
|
2
|
+
|
|
3
|
+
if (process.env.HIVE_SRC) {
|
|
4
|
+
require("app-module-path").addPath(process.env.HIVE_SRC);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const fs = require("fs");
|
|
8
|
+
|
|
9
|
+
const Koa = require("koa");
|
|
10
|
+
const http = require("http");
|
|
11
|
+
const config = require("config");
|
|
12
|
+
const logger = require("logger");
|
|
13
|
+
const cors = require("@koa/cors");
|
|
14
|
+
const helmet = require("koa-helmet");
|
|
15
|
+
const qs = require("koa-qs");
|
|
16
|
+
const bodyParser = require("koa-bodyparser");
|
|
17
|
+
const requestLogger = require("koa-logger");
|
|
18
|
+
const db = require("db");
|
|
19
|
+
|
|
20
|
+
const socketIo = require("socketIo");
|
|
21
|
+
const mount = require("koa-mount");
|
|
22
|
+
|
|
23
|
+
const main = async () => {
|
|
24
|
+
await db.init();
|
|
25
|
+
|
|
26
|
+
const routes = require("routes");
|
|
27
|
+
|
|
28
|
+
process.on("unhandledRejection", (reason, p) => {
|
|
29
|
+
console.trace(reason.stack);
|
|
30
|
+
|
|
31
|
+
logger.error(reason.stack);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const app = new Koa();
|
|
35
|
+
|
|
36
|
+
app.use(cors({ credentials: true }));
|
|
37
|
+
app.use(helmet());
|
|
38
|
+
qs(app);
|
|
39
|
+
app.use(bodyParser({ enableTypes: ["json", "form", "text"] }));
|
|
40
|
+
|
|
41
|
+
app.use(mount("/health", require("resources/health/endpoints/get").handler));
|
|
42
|
+
|
|
43
|
+
app.use(requestLogger());
|
|
44
|
+
|
|
45
|
+
routes(app);
|
|
46
|
+
|
|
47
|
+
const server = http.createServer(app.callback());
|
|
48
|
+
server.listen(config.port, () => {
|
|
49
|
+
console.log(
|
|
50
|
+
`Api server listening on ${config.port}, in ${process.env.NODE_ENV} mode and ${process.env.APP_ENV} environment`
|
|
51
|
+
);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
await socketIo(server);
|
|
55
|
+
|
|
56
|
+
require("scheduler");
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
main();
|
|
60
|
+
|
|
61
|
+
module.exports = main;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<mj-section background-color="#ffffff" padding-bottom="0px" padding-top="0">
|
|
2
|
+
<mj-column vertical-align="top" width="100%">
|
|
3
|
+
<mj-text
|
|
4
|
+
align="center"
|
|
5
|
+
font-size="30px"
|
|
6
|
+
font-weight="bold"
|
|
7
|
+
font-family="Trebuchet MS, Arial, sans-serif"
|
|
8
|
+
color="#55299a"
|
|
9
|
+
>
|
|
10
|
+
Ship
|
|
11
|
+
</mj-text>
|
|
12
|
+
</mj-column>
|
|
13
|
+
</mj-section>
|
|
File without changes
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<mjml>
|
|
2
|
+
<mj-head>
|
|
3
|
+
<mj-include path="./styles/index.mjml" />
|
|
4
|
+
</mj-head>
|
|
5
|
+
|
|
6
|
+
<mj-body>
|
|
7
|
+
<mj-include path="./components/header.mjml" />
|
|
8
|
+
|
|
9
|
+
<mj-section mj-class="main-section">
|
|
10
|
+
<mj-column vertical-align="middle" width="100%">
|
|
11
|
+
<mj-text mj-class="big-text"> Hi, there. </mj-text>
|
|
12
|
+
|
|
13
|
+
<mj-text mj-class="text">
|
|
14
|
+
Thanks for checking out Ship, we hope our products can make your
|
|
15
|
+
routine life a little more enjoyable.
|
|
16
|
+
</mj-text>
|
|
17
|
+
|
|
18
|
+
<mj-button mj-class="btn" href="{{verifyEmailUrl}}">
|
|
19
|
+
Verify Your Email
|
|
20
|
+
</mj-button>
|
|
21
|
+
</mj-column>
|
|
22
|
+
</mj-section>
|
|
23
|
+
|
|
24
|
+
<mj-section mj-class="bottom-section">
|
|
25
|
+
<mj-column vertical-align="middle" width="70%">
|
|
26
|
+
<mj-text mj-class="small-text"> INTERESTING FACT </mj-text>
|
|
27
|
+
|
|
28
|
+
<mj-text mj-class="tiny-text">
|
|
29
|
+
It took more than three years to build the Titanic.
|
|
30
|
+
</mj-text>
|
|
31
|
+
</mj-column>
|
|
32
|
+
</mj-section>
|
|
33
|
+
</mj-body>
|
|
34
|
+
</mjml>
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
<mj-attributes>
|
|
2
|
+
<mj-class
|
|
3
|
+
name="big-text"
|
|
4
|
+
align="center"
|
|
5
|
+
font-weight="bold"
|
|
6
|
+
font-family="Trebuchet MS, Arial, sans-serif"
|
|
7
|
+
color="#3b1d6c"
|
|
8
|
+
font-size="30px"
|
|
9
|
+
padding-left="25px"
|
|
10
|
+
padding-right="25px"
|
|
11
|
+
/>
|
|
12
|
+
|
|
13
|
+
<mj-class
|
|
14
|
+
name="text"
|
|
15
|
+
align="center"
|
|
16
|
+
color="#000"
|
|
17
|
+
font-size="18px"
|
|
18
|
+
font-family="Century Gothic, Arial, sans-serif"
|
|
19
|
+
padding-left="25px"
|
|
20
|
+
padding-right="25px"
|
|
21
|
+
line-height="26px"
|
|
22
|
+
/>
|
|
23
|
+
|
|
24
|
+
<mj-class
|
|
25
|
+
name="small-text"
|
|
26
|
+
align="center"
|
|
27
|
+
font-weight="bold"
|
|
28
|
+
font-family="Trebuchet MS, Arial, sans-serif"
|
|
29
|
+
color="#3b1d6c"
|
|
30
|
+
font-size="15px"
|
|
31
|
+
padding-left="25px"
|
|
32
|
+
padding-right="25px"
|
|
33
|
+
/>
|
|
34
|
+
|
|
35
|
+
<mj-class
|
|
36
|
+
name="tiny-text"
|
|
37
|
+
align="center"
|
|
38
|
+
color="#000"
|
|
39
|
+
font-size="15px"
|
|
40
|
+
font-family="Verdana, Arial, sans-serif"
|
|
41
|
+
line-height="20px"
|
|
42
|
+
/>
|
|
43
|
+
|
|
44
|
+
<mj-class
|
|
45
|
+
name="btn"
|
|
46
|
+
align="center"
|
|
47
|
+
font-size="22px"
|
|
48
|
+
background-color="#4c258b"
|
|
49
|
+
border-radius="4px"
|
|
50
|
+
color="#fff"
|
|
51
|
+
font-family="open Sans Helvetica, Arial, sans-serif"
|
|
52
|
+
/>
|
|
53
|
+
|
|
54
|
+
<mj-class
|
|
55
|
+
name="main-section"
|
|
56
|
+
background-color="#cccccc"
|
|
57
|
+
vertical-align="top"
|
|
58
|
+
padding-bottom="20px"
|
|
59
|
+
padding-top="0"
|
|
60
|
+
/>
|
|
61
|
+
|
|
62
|
+
<mj-class
|
|
63
|
+
name="main-section-image"
|
|
64
|
+
background-color="#cccccc"
|
|
65
|
+
vertical-align="top"
|
|
66
|
+
padding-bottom="0"
|
|
67
|
+
padding-top="0"
|
|
68
|
+
/>
|
|
69
|
+
|
|
70
|
+
<mj-class
|
|
71
|
+
name="bottom-section"
|
|
72
|
+
background-color="#ffffff"
|
|
73
|
+
vertical-align="top"
|
|
74
|
+
padding-bottom="20px"
|
|
75
|
+
padding-top="20px"
|
|
76
|
+
/>
|
|
77
|
+
</mj-attributes>
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
const _ = require("lodash");
|
|
2
|
+
const db = require("db");
|
|
3
|
+
|
|
4
|
+
const ifUpdated = require("helpers/db/ifUpdated");
|
|
5
|
+
|
|
6
|
+
const schemaMappings = require("./schemaMappings");
|
|
7
|
+
|
|
8
|
+
const getDependentFields = (schema, dependentFieldName) => {
|
|
9
|
+
let targetSchema = schema[dependentFieldName];
|
|
10
|
+
|
|
11
|
+
if (targetSchema.type === "array") {
|
|
12
|
+
[targetSchema] = targetSchema.items;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return Object.keys(targetSchema.keys).filter(
|
|
16
|
+
(key) => !_.includes(["_id", "createdOn", "updatedOn"], key)
|
|
17
|
+
);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const updatedSchemaMappings = (() => {
|
|
21
|
+
const result = {};
|
|
22
|
+
|
|
23
|
+
const schemaNames = Object.keys(schemaMappings);
|
|
24
|
+
|
|
25
|
+
schemaNames.forEach((schemaName) => {
|
|
26
|
+
const dependentFieldNames = Object.keys(schemaMappings[schemaName]);
|
|
27
|
+
const schema = db.schemas[schemaName].describe().keys;
|
|
28
|
+
|
|
29
|
+
dependentFieldNames.forEach((dependentFieldName) => {
|
|
30
|
+
const dependentFields = getDependentFields(schema, dependentFieldName);
|
|
31
|
+
|
|
32
|
+
if (!_.isEmpty(dependentFields)) {
|
|
33
|
+
result[schemaName] = result[schemaName] || {};
|
|
34
|
+
|
|
35
|
+
result[schemaName][dependentFieldName] = {
|
|
36
|
+
schema: schemaMappings[schemaName][dependentFieldName].schema,
|
|
37
|
+
dependentFields,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
return result;
|
|
44
|
+
})();
|
|
45
|
+
|
|
46
|
+
const getToUpdate = async ({ doc, schemaName }) => {
|
|
47
|
+
const dependentFieldNames = Object.keys(updatedSchemaMappings[schemaName]);
|
|
48
|
+
|
|
49
|
+
const toUpdate = {};
|
|
50
|
+
|
|
51
|
+
await Promise.all(
|
|
52
|
+
dependentFieldNames.map(async (dependentFieldName) => {
|
|
53
|
+
const { schema: dependentSchemaName } =
|
|
54
|
+
updatedSchemaMappings[schemaName][dependentFieldName];
|
|
55
|
+
|
|
56
|
+
const { dependentFields } =
|
|
57
|
+
updatedSchemaMappings[schemaName][dependentFieldName];
|
|
58
|
+
|
|
59
|
+
if (!_.isEmpty(doc[dependentFieldName])) {
|
|
60
|
+
if (_.isArray(doc[dependentFieldName])) {
|
|
61
|
+
toUpdate[dependentFieldName] = (
|
|
62
|
+
await db.services[dependentSchemaName].find(
|
|
63
|
+
{
|
|
64
|
+
_id: { $in: _.uniq(_.map(doc[dependentFieldName], "_id")) },
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
fields: dependentFields,
|
|
68
|
+
}
|
|
69
|
+
)
|
|
70
|
+
).results;
|
|
71
|
+
} else {
|
|
72
|
+
toUpdate[dependentFieldName] = await db.services[
|
|
73
|
+
dependentSchemaName
|
|
74
|
+
].findOne(
|
|
75
|
+
{
|
|
76
|
+
_id: doc[dependentFieldName]._id,
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
fields: dependentFields,
|
|
80
|
+
}
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
return toUpdate;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const populateOnCreate = ({ schemaName }) => {
|
|
91
|
+
db.services[schemaName]._options.onBeforeCreated = async ({ docs }) => {
|
|
92
|
+
const res = await Promise.all(
|
|
93
|
+
docs.map(async (doc) => {
|
|
94
|
+
const toUpdate = await getToUpdate({ schemaName, doc });
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
...doc,
|
|
98
|
+
...toUpdate,
|
|
99
|
+
};
|
|
100
|
+
})
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
return res;
|
|
104
|
+
};
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// const addOnEntityCreatedHandler = ({ schemaName }) => {
|
|
108
|
+
// db.services[schemaName].on('created', async ({ doc }) => {
|
|
109
|
+
// const toUpdate = await getToUpdate({ schemaName, doc });
|
|
110
|
+
|
|
111
|
+
// if (!_.isEmpty(toUpdate)) {
|
|
112
|
+
// await db.services[schemaName].atomic.update(
|
|
113
|
+
// { _id: doc._id },
|
|
114
|
+
// { $set: toUpdate }
|
|
115
|
+
// );
|
|
116
|
+
// }
|
|
117
|
+
// });
|
|
118
|
+
// };
|
|
119
|
+
|
|
120
|
+
const addOnDependentEntitiesUpdatedHandlers = ({ schemaName }) => {
|
|
121
|
+
const dependentFieldNames = Object.keys(schemaMappings[schemaName]);
|
|
122
|
+
|
|
123
|
+
dependentFieldNames.forEach((dependentFieldName) => {
|
|
124
|
+
const dependentFieldSchemaName =
|
|
125
|
+
schemaMappings[schemaName][dependentFieldName].schema;
|
|
126
|
+
const schema = db.schemas[schemaName].describe().keys;
|
|
127
|
+
|
|
128
|
+
const dependentFields = getDependentFields(schema, dependentFieldName);
|
|
129
|
+
|
|
130
|
+
db.services[dependentFieldSchemaName].on(
|
|
131
|
+
"updated",
|
|
132
|
+
ifUpdated(dependentFields, async ({ doc }) => {
|
|
133
|
+
const toUpdate = _.pick(doc, ["_id", ...dependentFields]);
|
|
134
|
+
|
|
135
|
+
if (schema[dependentFieldName].type === "array") {
|
|
136
|
+
db.services[schemaName].atomic.update(
|
|
137
|
+
{ [`${dependentFieldName}._id`]: doc._id },
|
|
138
|
+
{
|
|
139
|
+
$set: {
|
|
140
|
+
[`${dependentFieldName}.$`]: toUpdate,
|
|
141
|
+
},
|
|
142
|
+
}
|
|
143
|
+
);
|
|
144
|
+
} else {
|
|
145
|
+
db.services[schemaName].atomic.update(
|
|
146
|
+
{ [`${dependentFieldName}._id`]: doc._id },
|
|
147
|
+
{
|
|
148
|
+
$set: {
|
|
149
|
+
[`${dependentFieldName}`]: toUpdate,
|
|
150
|
+
},
|
|
151
|
+
}
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
})
|
|
155
|
+
);
|
|
156
|
+
});
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
module.exports = async () => {
|
|
160
|
+
const schemaNames = Object.keys(updatedSchemaMappings);
|
|
161
|
+
|
|
162
|
+
schemaNames.forEach((schemaName) => {
|
|
163
|
+
// addOnEntityCreatedHandler({ schemaName });
|
|
164
|
+
populateOnCreate({ schemaName });
|
|
165
|
+
addOnDependentEntitiesUpdatedHandlers({ schemaName });
|
|
166
|
+
});
|
|
167
|
+
};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
const _ = require("lodash");
|
|
2
|
+
|
|
3
|
+
const db = require("db");
|
|
4
|
+
const schemaMappings = require("./schemaMappings");
|
|
5
|
+
|
|
6
|
+
const schemaMappingService = db.services.schemaMappings;
|
|
7
|
+
|
|
8
|
+
const getDependentFields = (schema, dependentFieldName) => {
|
|
9
|
+
let targetSchema = schema[dependentFieldName];
|
|
10
|
+
if (targetSchema.type === "array") {
|
|
11
|
+
targetSchema = targetSchema.items[0];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return Object.keys(targetSchema.keys).filter(
|
|
15
|
+
(key) => !_.includes(["_id", "createdOn", "updatedOn"], key)
|
|
16
|
+
);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const joiSchemaToSchemaMappings = () => {
|
|
20
|
+
const newSchemaMappings = {};
|
|
21
|
+
|
|
22
|
+
Object.keys(schemaMappings).forEach((schemaName) => {
|
|
23
|
+
const schema = db.schemas[schemaName].describe().keys;
|
|
24
|
+
|
|
25
|
+
newSchemaMappings[schemaName] = {};
|
|
26
|
+
|
|
27
|
+
Object.keys(schemaMappings[schemaName]).forEach((fieldName) => {
|
|
28
|
+
newSchemaMappings[schemaName][fieldName] = {
|
|
29
|
+
schema: schemaMappings[schemaName][fieldName].schema,
|
|
30
|
+
fields: getDependentFields(schema, fieldName),
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
return newSchemaMappings;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
module.exports = async () => {
|
|
39
|
+
const prevSchema = await schemaMappingService.findOne({});
|
|
40
|
+
const prevSchemaMappings = prevSchema?.mappings;
|
|
41
|
+
|
|
42
|
+
if (!prevSchemaMappings) {
|
|
43
|
+
const initialSchemaMappings = joiSchemaToSchemaMappings();
|
|
44
|
+
|
|
45
|
+
schemaMappingService.create({ mappings: initialSchemaMappings });
|
|
46
|
+
} else {
|
|
47
|
+
const schemaNames = Object.keys(schemaMappings);
|
|
48
|
+
|
|
49
|
+
await Promise.all(
|
|
50
|
+
schemaNames.map(async (schemaName) => {
|
|
51
|
+
const schema = db.schemas[schemaName].describe().keys;
|
|
52
|
+
|
|
53
|
+
const fieldNames = Object.keys(schemaMappings[schemaName]);
|
|
54
|
+
|
|
55
|
+
await Promise.all(
|
|
56
|
+
fieldNames.map(async (fieldName) => {
|
|
57
|
+
const dependentFields = getDependentFields(schema, fieldName);
|
|
58
|
+
|
|
59
|
+
const prevSchema = (prevSchemaMappings[schemaName] &&
|
|
60
|
+
prevSchemaMappings[schemaName][fieldName]) || { fields: [] };
|
|
61
|
+
|
|
62
|
+
const { fields: prevDependentFields } = prevSchema;
|
|
63
|
+
|
|
64
|
+
if (
|
|
65
|
+
_.difference(dependentFields, prevDependentFields).length !== 0
|
|
66
|
+
) {
|
|
67
|
+
console.log(`Mapping schema changes: ${schemaName}.${fieldName}`);
|
|
68
|
+
|
|
69
|
+
const uniqueDependentEntityIds = await db.services[
|
|
70
|
+
schemaName
|
|
71
|
+
].distinct(`${fieldName}._id`);
|
|
72
|
+
|
|
73
|
+
const { results: uniqueDependentEntities } = await db.services[
|
|
74
|
+
schemaMappings[schemaName][fieldName].schema
|
|
75
|
+
].find(
|
|
76
|
+
{
|
|
77
|
+
_id: { $in: uniqueDependentEntityIds },
|
|
78
|
+
},
|
|
79
|
+
{ fields: ["_id", ...dependentFields] }
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
await Promise.all(
|
|
83
|
+
uniqueDependentEntities.map(async (entity) => {
|
|
84
|
+
if (schema[fieldName].type === "array") {
|
|
85
|
+
await db.services[schemaName].atomic.update(
|
|
86
|
+
{ [`${fieldName}._id`]: entity._id },
|
|
87
|
+
{
|
|
88
|
+
$set: { [`${fieldName}.$`]: entity },
|
|
89
|
+
}
|
|
90
|
+
);
|
|
91
|
+
} else {
|
|
92
|
+
await db.services[schemaName].atomic.update(
|
|
93
|
+
{ [`${fieldName}._id`]: entity._id },
|
|
94
|
+
{
|
|
95
|
+
$set: { [`${fieldName}`]: entity },
|
|
96
|
+
}
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
})
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
})
|
|
103
|
+
);
|
|
104
|
+
})
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
schemaMappingService.atomic.update(
|
|
108
|
+
{ _id: prevSchema._id },
|
|
109
|
+
{ $set: { mappings: joiSchemaToSchemaMappings() } }
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
require("dotenv").config({ path: `${__dirname}/.env` });
|
|
2
|
+
require("dotenv").config({ path: `${__dirname}/.env.app` });
|
|
3
|
+
|
|
4
|
+
const appConfig = require("./app");
|
|
5
|
+
|
|
6
|
+
const env = process.env.APP_ENV || "development";
|
|
7
|
+
|
|
8
|
+
const config = {
|
|
9
|
+
env,
|
|
10
|
+
port: process.env.PORT || 3001,
|
|
11
|
+
isDev: env === "development",
|
|
12
|
+
|
|
13
|
+
projectId: process.env.PROJECT_ID,
|
|
14
|
+
|
|
15
|
+
mongoUri: process.env.MONGODB_URI,
|
|
16
|
+
|
|
17
|
+
redis: {
|
|
18
|
+
url: process.env.REDIS_URI,
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
...appConfig,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
module.exports = config;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const _ = require("lodash");
|
|
3
|
+
const requireDir = require("require-dir");
|
|
4
|
+
|
|
5
|
+
const getSchemas = require("helpers/getSchemas");
|
|
6
|
+
const getResources = require("helpers/getResources");
|
|
7
|
+
|
|
8
|
+
const config = require("config");
|
|
9
|
+
const db = require("lib/node-mongo").connect(config.mongoUri);
|
|
10
|
+
|
|
11
|
+
db.services = {};
|
|
12
|
+
db.schemas = {};
|
|
13
|
+
|
|
14
|
+
db.init = async () => {
|
|
15
|
+
const schemaPaths = await getSchemas();
|
|
16
|
+
|
|
17
|
+
_.each(
|
|
18
|
+
schemaPaths,
|
|
19
|
+
({ file: schemaFile, resourceName, name: schemaName }) => {
|
|
20
|
+
const schema = require(schemaFile);
|
|
21
|
+
db.schemas[schemaName] = schema;
|
|
22
|
+
|
|
23
|
+
console.log("Registering service", resourceName);
|
|
24
|
+
|
|
25
|
+
db.services[schemaName] = db.createService(`${resourceName}`, {
|
|
26
|
+
validate: (obj) => schema.validate(obj, { allowUnknown: true }),
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
const resourcePaths = await getResources();
|
|
32
|
+
|
|
33
|
+
console.log('resourcePaths', resourcePaths);
|
|
34
|
+
|
|
35
|
+
_.each(resourcePaths, ({ dir }) => {
|
|
36
|
+
if (fs.existsSync(`${dir}/handlers`)) {
|
|
37
|
+
requireDir(`${dir}/handlers`);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const mapSchema = require("autoMap/mapSchema");
|
|
42
|
+
await mapSchema();
|
|
43
|
+
|
|
44
|
+
const addHandlers = require("autoMap/addHandlers");
|
|
45
|
+
await addHandlers();
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
module.exports = db;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const _ = require("lodash");
|
|
2
|
+
|
|
3
|
+
module.exports = (fieldNames, callback) => {
|
|
4
|
+
return async ({ doc, prevDoc }) => {
|
|
5
|
+
let isFieldChanged = false;
|
|
6
|
+
|
|
7
|
+
_.forEach(fieldNames, (fieldName) => {
|
|
8
|
+
if (!_.isEqual(doc[fieldName], prevDoc[fieldName])) {
|
|
9
|
+
isFieldChanged = true;
|
|
10
|
+
return false; // break loop
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return true;
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
if (isFieldChanged) {
|
|
17
|
+
return callback({ doc, prevDoc });
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return () => {};
|
|
21
|
+
};
|
|
22
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const _ = require("lodash");
|
|
3
|
+
|
|
4
|
+
const {
|
|
5
|
+
promises: { readdir },
|
|
6
|
+
} = fs;
|
|
7
|
+
|
|
8
|
+
module.exports = async (resourceName) => {
|
|
9
|
+
console.log('get resource endpoint', resourceName);
|
|
10
|
+
let isHiveEndpoint;
|
|
11
|
+
|
|
12
|
+
if (fs.existsSync(`${__dirname}/../resources/${resourceName}/endpoints`) || (process.env.HIVE_SRC && (isHiveEndpoint = true) && fs.existsSync(`${process.env.HIVE_SRC}/resources/${resourceName}/endpoints`)) ) {
|
|
13
|
+
const endpointFiles = await readdir(
|
|
14
|
+
isHiveEndpoint ?
|
|
15
|
+
`${process.env.HIVE_SRC}/resources/${resourceName}/endpoints` :
|
|
16
|
+
`${__dirname}/../resources/${resourceName}/endpoints`
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
return endpointFiles.map((f) => ({
|
|
20
|
+
file: isHiveEndpoint ? `${process.env.HIVE_SRC}/resources/${resourceName}/endpoints/${f}`: `${__dirname}/../resources/${resourceName}/endpoints/${f}`,
|
|
21
|
+
name: _.last(f.split("/")).replace(".js", ""),
|
|
22
|
+
}));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return [];
|
|
26
|
+
};
|