@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,151 @@
|
|
|
1
|
+
const _ = require("lodash");
|
|
2
|
+
|
|
3
|
+
const getResources = require("helpers/getResources");
|
|
4
|
+
const getResourceEndpoints = require("helpers/getResourceEndpoints");
|
|
5
|
+
|
|
6
|
+
const mount = require("koa-mount");
|
|
7
|
+
const Router = require("@koa/router");
|
|
8
|
+
const validate = require("middlewares/validate");
|
|
9
|
+
|
|
10
|
+
const requestLogService = require("db").createService("_request_logs");
|
|
11
|
+
|
|
12
|
+
const tryToAttachUser = require("middlewares/global/tryToAttachUser");
|
|
13
|
+
|
|
14
|
+
const attachCustomErrors = require("./middlewares/attachCustomErrors");
|
|
15
|
+
const routeErrorHandler = require("./middlewares/routeErrorHandler");
|
|
16
|
+
|
|
17
|
+
const logRequestToMongo = async (ctx, next) => {
|
|
18
|
+
const startedOn = new Date();
|
|
19
|
+
|
|
20
|
+
const saveLog = async ({ error = null } = {}) => {
|
|
21
|
+
if (ctx.state.resourceName && ctx.state.endpoint) {
|
|
22
|
+
const requestLog = {
|
|
23
|
+
isSuccess: true,
|
|
24
|
+
|
|
25
|
+
request: {
|
|
26
|
+
url: ctx.originalUrl,
|
|
27
|
+
method: ctx.request.method,
|
|
28
|
+
|
|
29
|
+
query: ctx.query,
|
|
30
|
+
body: ctx.request.body,
|
|
31
|
+
params: ctx.params,
|
|
32
|
+
headers: ctx.headers,
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
response: {
|
|
36
|
+
status: ctx.status,
|
|
37
|
+
body: ctx.body,
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
resourceName: ctx.state.resourceName,
|
|
41
|
+
endpoint: ctx.state.endpoint,
|
|
42
|
+
|
|
43
|
+
time: new Date() - startedOn,
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
if (error) {
|
|
47
|
+
requestLog.isSuccess = false;
|
|
48
|
+
|
|
49
|
+
requestLog.error = {
|
|
50
|
+
message: error.message,
|
|
51
|
+
stack: error.stack,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
await requestLogService.create(requestLog);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
await next();
|
|
61
|
+
await saveLog({ error: ctx.state.error });
|
|
62
|
+
} catch (err) {
|
|
63
|
+
await saveLog({ error: err });
|
|
64
|
+
|
|
65
|
+
throw err;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const defineRoutes = async (app) => {
|
|
70
|
+
app.use(logRequestToMongo);
|
|
71
|
+
app.use(attachCustomErrors);
|
|
72
|
+
app.use(routeErrorHandler);
|
|
73
|
+
|
|
74
|
+
app.use(tryToAttachUser);
|
|
75
|
+
|
|
76
|
+
const resources = await getResources();
|
|
77
|
+
|
|
78
|
+
_.each(resources, async ({ name: resourceName }) => {
|
|
79
|
+
const resourceRouter = new Router();
|
|
80
|
+
const globalRouter = new Router();
|
|
81
|
+
|
|
82
|
+
const endpoints = (await getResourceEndpoints(resourceName))
|
|
83
|
+
.map(({ file: endpointFile, name }) => {
|
|
84
|
+
const {
|
|
85
|
+
endpoint = { method: 'get', url: '/' },
|
|
86
|
+
requestSchema,
|
|
87
|
+
middlewares = [],
|
|
88
|
+
handler,
|
|
89
|
+
} = require(endpointFile);
|
|
90
|
+
|
|
91
|
+
endpoint.name = name;
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
endpoint,
|
|
95
|
+
requestSchema,
|
|
96
|
+
middlewares,
|
|
97
|
+
handler,
|
|
98
|
+
};
|
|
99
|
+
})
|
|
100
|
+
.sort((e) => {
|
|
101
|
+
e.endpoint = e.endpoint || { method: 'get', url: '/' };
|
|
102
|
+
|
|
103
|
+
const url = e.endpoint.url || e.endpoint.absoluteUrl;
|
|
104
|
+
|
|
105
|
+
if (url.includes("/:")) {
|
|
106
|
+
return 1;
|
|
107
|
+
}
|
|
108
|
+
return -1;
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
endpoints.forEach(({ endpoint, requestSchema, middlewares, handler }) => {
|
|
113
|
+
const additionalMiddlewares = [];
|
|
114
|
+
|
|
115
|
+
if (requestSchema) {
|
|
116
|
+
additionalMiddlewares.push(validate(requestSchema));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
let targetRouter;
|
|
120
|
+
let url = endpoint.absoluteUrl || endpoint.url;
|
|
121
|
+
|
|
122
|
+
if (url.startsWith("$HOST/")) {
|
|
123
|
+
url = url.replace("$HOST", "");
|
|
124
|
+
targetRouter = globalRouter;
|
|
125
|
+
} else if (endpoint.absoluteUrl) {
|
|
126
|
+
targetRouter = globalRouter;
|
|
127
|
+
} else {
|
|
128
|
+
targetRouter = resourceRouter;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
targetRouter[endpoint.method?.toLowerCase() || "get"](
|
|
132
|
+
url,
|
|
133
|
+
async (ctx, next) => {
|
|
134
|
+
ctx.state.resourceName = resourceName;
|
|
135
|
+
ctx.state.endpoint = endpoint;
|
|
136
|
+
|
|
137
|
+
await next();
|
|
138
|
+
},
|
|
139
|
+
...additionalMiddlewares,
|
|
140
|
+
...middlewares,
|
|
141
|
+
handler
|
|
142
|
+
);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
app.use(globalRouter.routes());
|
|
146
|
+
|
|
147
|
+
app.use(mount(`/${resourceName}`, resourceRouter.routes()));
|
|
148
|
+
});
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
module.exports = defineRoutes;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const _ = require("lodash");
|
|
2
|
+
|
|
3
|
+
const formatError = (customError) => {
|
|
4
|
+
const errors = {};
|
|
5
|
+
|
|
6
|
+
Object.keys(customError).forEach((key) => {
|
|
7
|
+
errors[key] = _.isArray(customError[key])
|
|
8
|
+
? customError[key]
|
|
9
|
+
: [customError[key]];
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
return errors;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const attachCustomErrors = async (ctx, next) => {
|
|
16
|
+
ctx.throwError = (message) => ctx.throw(500, { message });
|
|
17
|
+
ctx.assertError = (condition, message) =>
|
|
18
|
+
ctx.assert(condition, 500, { message });
|
|
19
|
+
|
|
20
|
+
ctx.throwClientError = (errors) =>
|
|
21
|
+
ctx.throw(400, { errors: formatError(errors) });
|
|
22
|
+
ctx.assertClientError = (condition, errors) =>
|
|
23
|
+
ctx.assert(condition, 400, { errors: formatError(errors) });
|
|
24
|
+
|
|
25
|
+
await next();
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
module.exports = attachCustomErrors;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const logger = require("logger");
|
|
2
|
+
|
|
3
|
+
const routeErrorHandler = async (ctx, next) => {
|
|
4
|
+
try {
|
|
5
|
+
await next();
|
|
6
|
+
} catch (error) {
|
|
7
|
+
const clientError = error.errors;
|
|
8
|
+
const serverError = { global: error.message };
|
|
9
|
+
|
|
10
|
+
const errors = clientError || serverError;
|
|
11
|
+
|
|
12
|
+
console.log("Route Error", errors);
|
|
13
|
+
|
|
14
|
+
logger.error(errors);
|
|
15
|
+
|
|
16
|
+
if (serverError && process.env.APP_ENV === "production") {
|
|
17
|
+
serverError.global = "Something went wrong";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
ctx.state.error = error;
|
|
21
|
+
|
|
22
|
+
ctx.status = error.status || 500;
|
|
23
|
+
ctx.body = { errors };
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
module.exports = routeErrorHandler;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const moment = require("moment");
|
|
2
|
+
const schedule = require("node-schedule");
|
|
3
|
+
|
|
4
|
+
const requireDir = require("require-dir");
|
|
5
|
+
|
|
6
|
+
requireDir("scheduler/handlers", {
|
|
7
|
+
mapValue: (handler, handlerName) => {
|
|
8
|
+
console.log(
|
|
9
|
+
`[scheduler] Registering handler ${handlerName} with cron ${handler.cron}`
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
schedule.scheduleJob(handler.cron, () => {
|
|
13
|
+
console.log(
|
|
14
|
+
`[scheduler] ${moment().format()} executing ${handlerName} with cron ${
|
|
15
|
+
handler.cron
|
|
16
|
+
}`
|
|
17
|
+
);
|
|
18
|
+
handler.handler();
|
|
19
|
+
});
|
|
20
|
+
},
|
|
21
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const crypto = require("crypto");
|
|
2
|
+
const bcrypt = require("bcryptjs");
|
|
3
|
+
const util = require("util");
|
|
4
|
+
|
|
5
|
+
const randomBytes = util.promisify(crypto.randomBytes, crypto);
|
|
6
|
+
const bcryptHash = util.promisify(bcrypt.hash, bcrypt);
|
|
7
|
+
const compare = util.promisify(bcrypt.compare, bcrypt);
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @desc Generates random string, useful for creating secure tokens
|
|
11
|
+
*
|
|
12
|
+
* @return {string} - random string
|
|
13
|
+
*/
|
|
14
|
+
exports.generateSecureToken = async (tokenLength = 48) => {
|
|
15
|
+
const buf = await randomBytes(tokenLength);
|
|
16
|
+
return buf.toString("hex");
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @desc Generate hash from any string. Could be used to generate a hash from password
|
|
21
|
+
*
|
|
22
|
+
* @param text {string} - a text to produce hash from
|
|
23
|
+
* @return {Promise} - a hash from input text
|
|
24
|
+
*/
|
|
25
|
+
exports.getHash = (text) => {
|
|
26
|
+
return bcryptHash(text, 10);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @desc Compares if text and hash are equal
|
|
31
|
+
*
|
|
32
|
+
* @param text {string} - a text to compare with hash
|
|
33
|
+
* @param hash {string} - a hash to compare with text
|
|
34
|
+
* @return {Promise} - are hash and text equal
|
|
35
|
+
*/
|
|
36
|
+
exports.compareTextWithHash = (text, hash) => {
|
|
37
|
+
return compare(text, hash);
|
|
38
|
+
};
|
|
File without changes
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
const { Server } = require("socket.io");
|
|
2
|
+
const { createClient } = require("redis");
|
|
3
|
+
const { createAdapter } = require("@socket.io/redis-adapter");
|
|
4
|
+
|
|
5
|
+
const config = require("config");
|
|
6
|
+
const logger = require("logger");
|
|
7
|
+
|
|
8
|
+
module.exports = (server) => {
|
|
9
|
+
const io = new Server(server);
|
|
10
|
+
|
|
11
|
+
const pubClient = createClient({ url: config.redis.url });
|
|
12
|
+
const subClient = pubClient.duplicate();
|
|
13
|
+
|
|
14
|
+
io.adapter(createAdapter(pubClient, subClient));
|
|
15
|
+
|
|
16
|
+
const getCookie = (cookieString, name) => {
|
|
17
|
+
const value = `; ${cookieString}`;
|
|
18
|
+
const parts = value.split(`; ${name}=`);
|
|
19
|
+
|
|
20
|
+
if (parts.length === 2) {
|
|
21
|
+
return parts.pop().split(";").shift();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return null;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// #TODO get user using accessToken
|
|
28
|
+
const getUserData = async (socket) => {
|
|
29
|
+
const accessToken = getCookie(
|
|
30
|
+
socket.handshake.headers.cookie,
|
|
31
|
+
"ship_access_token"
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
if (!accessToken) {
|
|
35
|
+
logger.info(
|
|
36
|
+
"Note: socket io anonymous auth. Add user authentication in socketIoService"
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
_id: "anonymous",
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
io.use(async (socket, next) => {
|
|
46
|
+
const userData = await getUserData(socket);
|
|
47
|
+
|
|
48
|
+
if (userData) {
|
|
49
|
+
// eslint-disable-next-line no-param-reassign
|
|
50
|
+
socket.handshake.data = {
|
|
51
|
+
userId: userData.userId,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
return next();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return next(new Error("token is invalid"));
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
function checkAccessToRoom(roomId, data) {
|
|
61
|
+
let result = false;
|
|
62
|
+
const [roomType, id] = roomId.split("-");
|
|
63
|
+
|
|
64
|
+
switch (roomType) {
|
|
65
|
+
case "user":
|
|
66
|
+
result = id === data.userId;
|
|
67
|
+
break;
|
|
68
|
+
default:
|
|
69
|
+
result = true;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
io.on("connection", (client) => {
|
|
76
|
+
client.on("subscribe", (roomId) => {
|
|
77
|
+
const { userId } = client.handshake.data;
|
|
78
|
+
const hasAccessToRoom = checkAccessToRoom(roomId, { userId });
|
|
79
|
+
|
|
80
|
+
if (hasAccessToRoom) {
|
|
81
|
+
client.join(roomId);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
client.on("unsubscribe", (roomId) => {
|
|
86
|
+
client.leave(roomId);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
console.log(`Socket.io server is started on app instance`);
|
|
91
|
+
};
|