@paralect/hive 0.1.49 → 0.1.50-alpha.2
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/.hive/.babelrc +3 -0
- package/.hive/.cursor/commands/add-endpoint.md +262 -0
- package/.hive/.cursor/commands/add-handler.md +137 -0
- package/.hive/.cursor/commands/add-middleware.md +95 -0
- package/.hive/.cursor/commands/add-resource.md +71 -0
- package/.hive/.cursor/commands/add-scheduler.md +138 -0
- package/.hive/.cursor/commands/add-service.md +188 -0
- package/.hive/.cursor/skills/hive-auth/SKILL.md +134 -0
- package/.hive/.cursor/skills/hive-database/SKILL.md +103 -0
- package/.hive/.cursor/skills/hive-endpoint/SKILL.md +103 -0
- package/.hive/.cursor/skills/hive-handler/SKILL.md +88 -0
- package/.hive/.cursor/skills/hive-mapping/SKILL.md +85 -0
- package/.hive/.cursor/skills/hive-middleware/SKILL.md +104 -0
- package/.hive/.cursor/skills/hive-overview/SKILL.md +50 -0
- package/.hive/.cursor/skills/hive-scheduler/SKILL.md +94 -0
- package/.hive/.cursor/skills/hive-schema/SKILL.md +73 -0
- package/.hive/.cursor/skills/hive-service/SKILL.md +90 -0
- package/.hive/.dockerignore +1 -0
- package/.hive/Dockerfile +22 -0
- package/.hive/Dockerfile.dev +33 -0
- package/.hive/Dockerfile.prod +29 -0
- package/.hive/README.md +11 -0
- package/.hive/bin/deploy.sh +5 -0
- package/.hive/bin/start.sh +2 -0
- package/.hive/bootstrap-hive.js +118 -0
- package/.hive/deploy/api/Chart.yaml +6 -0
- package/.hive/deploy/api/staging.yaml +3 -0
- package/.hive/deploy/api/templates/deployment.yaml +44 -0
- package/.hive/deploy/api/templates/ingress.yaml +26 -0
- package/.hive/deploy/api/templates/service.yaml +14 -0
- package/.hive/deploy/script/Dockerfile +39 -0
- package/.hive/deploy/script/package-lock.json +1499 -0
- package/.hive/deploy/script/package.json +12 -0
- package/.hive/deploy/script/src/config.js +48 -0
- package/.hive/deploy/script/src/index.js +108 -0
- package/.hive/deploy/script/src/util.js +19 -0
- package/.hive/initial-data.json +176 -0
- package/.hive/package-lock.json +10242 -0
- package/.hive/package.json +98 -0
- package/.hive/ship_logo.png +0 -0
- package/.hive/src/app-config/app.js +3 -0
- package/.hive/src/app-config/assertEnv.js +15 -0
- package/.hive/src/app-config/index.js +62 -0
- package/.hive/src/app.js +69 -0
- package/.hive/src/assets/emails/components/header.mjml +13 -0
- package/.hive/src/assets/emails/dist/.gitkeep +0 -0
- package/.hive/src/assets/emails/signup-welcome.mjml +34 -0
- package/.hive/src/assets/emails/styles/index.mjml +77 -0
- package/.hive/src/autoMap/addHandlers.js +142 -0
- package/.hive/src/autoMap/getDependentFields.js +37 -0
- package/.hive/src/autoMap/mapSchema.js +99 -0
- package/.hive/src/autoMap/schemaMappings.js +13 -0
- package/.hive/src/autoMap/schemaMappings.json +3 -0
- package/.hive/src/bullMqBus.js +21 -0
- package/.hive/src/bullMqWrapper.js +23 -0
- package/.hive/src/db.js +52 -0
- package/.hive/src/emails/MyEmailComponent.jsx +14 -0
- package/.hive/src/emails/compiled/MyEmailComponent.js +18 -0
- package/.hive/src/emails/compiled/compiled/MyEmailComponent.js +18 -0
- package/.hive/src/helpers/db/ifUpdated.js +22 -0
- package/.hive/src/helpers/getMiddlewares.js +38 -0
- package/.hive/src/helpers/getResourceEndpoints.js +28 -0
- package/.hive/src/helpers/getResources.js +32 -0
- package/.hive/src/helpers/getSchemas.js +50 -0
- package/.hive/src/helpers/importHandlers.js +29 -0
- package/.hive/src/helpers/isZodArray.js +13 -0
- package/.hive/src/helpers/prettierFormat.js +8 -0
- package/.hive/src/helpers/schema/db.schema.js +9 -0
- package/.hive/src/helpers/schema/pagination.schema.js +14 -0
- package/.hive/src/ioEmitter.js +9 -0
- package/.hive/src/jsconfig.json +5 -0
- package/.hive/src/lib/node-mongo/.github/workflows/npm-publish.yml +32 -0
- package/.hive/src/lib/node-mongo/API.md +654 -0
- package/.hive/src/lib/node-mongo/CHANGELOG.md +98 -0
- package/.hive/src/lib/node-mongo/README.md +97 -0
- package/.hive/src/lib/node-mongo/package-lock.json +3682 -0
- package/.hive/src/lib/node-mongo/package.json +74 -0
- package/.hive/src/lib/node-mongo/src/index.js +64 -0
- package/.hive/src/lib/node-mongo/src/mongo-query-service.js +78 -0
- package/.hive/src/lib/node-mongo/src/mongo-service-error.js +15 -0
- package/.hive/src/lib/node-mongo/src/mongo-service.js +303 -0
- package/.hive/src/logger.js +43 -0
- package/.hive/src/middlewares/allowNoAuth.js +9 -0
- package/.hive/src/middlewares/attachUser.js +41 -0
- package/.hive/src/middlewares/global/extractUserTokens.js +15 -0
- package/.hive/src/middlewares/global/tryToAttachUser.js +33 -0
- package/.hive/src/middlewares/isAuthorized.js +18 -0
- package/.hive/src/middlewares/shouldExist.js +37 -0
- package/.hive/src/middlewares/shouldNotExist.js +19 -0
- package/.hive/src/middlewares/uploadFile.js +5 -0
- package/.hive/src/middlewares/validate.js +32 -0
- package/.hive/src/migrations/migration.js +8 -0
- package/.hive/src/migrations/migration.service.js +73 -0
- package/.hive/src/migrations/migrations/1.js +22 -0
- package/.hive/src/migrations/migrations-log/migration-log.schema.js +13 -0
- package/.hive/src/migrations/migrations-log/migration-log.service.js +50 -0
- package/.hive/src/migrations/migrations.schema.js +6 -0
- package/.hive/src/migrations/migrator.js +75 -0
- package/.hive/src/migrator.js +4 -0
- package/.hive/src/resources/_dev/endpoints/triggerSchedulerHandler.js +32 -0
- package/.hive/src/resources/health/endpoints/get.js +19 -0
- package/.hive/src/resources/schemaMappings/schemaMappings.schema.js +6 -0
- package/.hive/src/resources/tokens/methods/generateSecureToken.js +9 -0
- package/.hive/src/resources/tokens/methods/setToken.js +8 -0
- package/.hive/src/resources/tokens/methods/storeToken.js +35 -0
- package/.hive/src/resources/tokens/tokens.schema.js +11 -0
- package/.hive/src/resources/users/endpoints/getCurrentUser.js +14 -0
- package/.hive/src/resources/users/endpoints/getUserProfile.js +19 -0
- package/.hive/src/resources/users/handlers/test.js +1 -0
- package/.hive/src/resources/users/methods/ensureUserCreated.js +68 -0
- package/.hive/src/resources/users/users.schema.js +16 -0
- package/.hive/src/routes/index.js +172 -0
- package/.hive/src/routes/middlewares/attachCustomErrors.js +28 -0
- package/.hive/src/routes/middlewares/routeErrorHandler.js +27 -0
- package/.hive/src/scheduler/handlers/sendDailyReport.example.js +7 -0
- package/.hive/src/scheduler.js +32 -0
- package/.hive/src/security.util.js +38 -0
- package/.hive/src/services/emailService.js +15 -0
- package/.hive/src/services/globalTest.js +0 -0
- package/.hive/src/services/setCookie.js +21 -0
- package/.hive/src/socketIo.js +99 -0
- package/.hive/tsconfig.json +31 -0
- package/cli/helpers/docker.js +59 -0
- package/cli/helpers/envCheck.js +127 -0
- package/cli/helpers/findPort.js +32 -0
- package/cli/hive.js +91 -17
- package/package.json +1 -1
- package/starter/loader.mjs +40 -0
- package/starter/package-lock.json +3512 -262
- package/starter/package.json +7 -2
- package/starter/register.mjs +6 -0
- package/starter/src/app-config/index.js +3 -0
- package/starter/src/app.js +10 -14
- package/starter/src/autoMap/addHandlers.js +3 -3
- package/starter/src/autoMap/getDependentFields.js +1 -1
- package/starter/src/autoMap/mapSchema.js +2 -2
- package/starter/src/bullMqBus.js +1 -1
- package/starter/src/bullMqWrapper.js +1 -1
- package/starter/src/db.js +12 -11
- package/starter/src/helpers/esm.js +56 -0
- package/starter/src/helpers/getMiddlewares.js +3 -0
- package/starter/src/helpers/getResourceEndpoints.js +3 -0
- package/starter/src/helpers/getResources.js +3 -0
- package/starter/src/helpers/getSchemas.js +3 -0
- package/starter/src/helpers/importHandlers.js +11 -20
- package/starter/src/ioEmitter.js +1 -1
- package/starter/src/logger.js +1 -1
- package/starter/src/middlewares/attachUser.js +2 -2
- package/starter/src/middlewares/global/tryToAttachUser.js +1 -1
- package/starter/src/middlewares/shouldExist.js +1 -1
- package/starter/src/middlewares/shouldNotExist.js +1 -1
- package/starter/src/migrations/migration.service.js +4 -1
- package/starter/src/migrations/migrations-log/migration-log.schema.js +1 -1
- package/starter/src/migrations/migrations-log/migration-log.service.js +1 -1
- package/starter/src/migrations/migrations.schema.js +1 -1
- package/starter/src/migrations/migrator.js +1 -1
- package/starter/src/migrator.js +2 -3
- package/starter/src/resources/schemaMappings/schemaMappings.schema.js +1 -1
- package/starter/src/resources/tokens/methods/setToken.js +1 -1
- package/starter/src/resources/tokens/methods/storeToken.js +2 -2
- package/starter/src/resources/tokens/tokens.schema.js +1 -1
- package/starter/src/resources/users/endpoints/getCurrentUser.js +1 -1
- package/starter/src/resources/users/endpoints/getUserProfile.js +1 -1
- package/starter/src/resources/users/methods/ensureUserCreated.js +1 -1
- package/starter/src/resources/users/users.schema.js +1 -1
- package/starter/src/routes/index.js +8 -8
- package/starter/src/routes/middlewares/routeErrorHandler.js +1 -1
- package/starter/src/scheduler.js +10 -7
- package/starter/src/services/emailService.js +1 -1
- package/starter/src/services/setCookie.js +2 -2
- package/starter/src/socketIo.js +3 -3
- package/test-app/.cursor/commands/add-endpoint.md +262 -0
- package/test-app/.cursor/commands/add-handler.md +137 -0
- package/test-app/.cursor/commands/add-middleware.md +95 -0
- package/test-app/.cursor/commands/add-resource.md +71 -0
- package/test-app/.cursor/commands/add-scheduler.md +138 -0
- package/test-app/.cursor/commands/add-service.md +188 -0
- package/test-app/.cursor/skills/hive-auth/SKILL.md +134 -0
- package/test-app/.cursor/skills/hive-database/SKILL.md +103 -0
- package/test-app/.cursor/skills/hive-endpoint/SKILL.md +103 -0
- package/test-app/.cursor/skills/hive-handler/SKILL.md +88 -0
- package/test-app/.cursor/skills/hive-mapping/SKILL.md +85 -0
- package/test-app/.cursor/skills/hive-middleware/SKILL.md +104 -0
- package/test-app/.cursor/skills/hive-overview/SKILL.md +50 -0
- package/test-app/.cursor/skills/hive-scheduler/SKILL.md +94 -0
- package/test-app/.cursor/skills/hive-schema/SKILL.md +73 -0
- package/test-app/.cursor/skills/hive-service/SKILL.md +90 -0
- package/test-app/package-lock.json +462 -0
- package/test-app/package.json +21 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import _ from "lodash";
|
|
2
|
+
import getResources from "helpers/getResources";
|
|
3
|
+
import getMiddlewares from "helpers/getMiddlewares";
|
|
4
|
+
import getResourceEndpoints from "helpers/getResourceEndpoints";
|
|
5
|
+
import mount from "koa-mount";
|
|
6
|
+
import Router from "@koa/router";
|
|
7
|
+
import validate from "middlewares/validate";
|
|
8
|
+
import db from "db";
|
|
9
|
+
import tryToAttachUser from "middlewares/global/tryToAttachUser";
|
|
10
|
+
import attachCustomErrors from "./middlewares/attachCustomErrors";
|
|
11
|
+
import routeErrorHandler from "./middlewares/routeErrorHandler";
|
|
12
|
+
import isAuthorized from "middlewares/isAuthorized";
|
|
13
|
+
import config from 'app-config';
|
|
14
|
+
|
|
15
|
+
const requestLogService = db.createService("_request_logs");
|
|
16
|
+
|
|
17
|
+
const logRequestToMongo = async (ctx, next) => {
|
|
18
|
+
const startedOn = new Date();
|
|
19
|
+
|
|
20
|
+
const saveLog = async ({ error = null } = {}) => {
|
|
21
|
+
if (ctx.state.isSkipLog) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (ctx.state.resourceName && ctx.state.endpoint) {
|
|
26
|
+
const requestLog = {
|
|
27
|
+
isSuccess: true,
|
|
28
|
+
request: {
|
|
29
|
+
url: ctx.originalUrl,
|
|
30
|
+
method: ctx.request.method,
|
|
31
|
+
query: ctx.query,
|
|
32
|
+
body: ctx.request.body,
|
|
33
|
+
params: ctx.params,
|
|
34
|
+
headers: ctx.headers,
|
|
35
|
+
},
|
|
36
|
+
response: {
|
|
37
|
+
status: ctx.status,
|
|
38
|
+
body: ctx.body,
|
|
39
|
+
},
|
|
40
|
+
resourceName: ctx.state.resourceName,
|
|
41
|
+
endpoint: ctx.state.endpoint,
|
|
42
|
+
time: new Date() - startedOn,
|
|
43
|
+
};
|
|
44
|
+
if (error) {
|
|
45
|
+
requestLog.isSuccess = false;
|
|
46
|
+
requestLog.error = {
|
|
47
|
+
message: error.message,
|
|
48
|
+
stack: error.stack,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
await requestLogService.create(requestLog);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
await next();
|
|
57
|
+
|
|
58
|
+
await saveLog({ error: ctx.state.error });
|
|
59
|
+
|
|
60
|
+
} catch (err) {
|
|
61
|
+
await saveLog({ error: err });
|
|
62
|
+
throw err;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export default async (app) => {
|
|
67
|
+
app.use(logRequestToMongo);
|
|
68
|
+
app.use(attachCustomErrors);
|
|
69
|
+
app.use(routeErrorHandler);
|
|
70
|
+
app.use(tryToAttachUser);
|
|
71
|
+
|
|
72
|
+
const [resources, allMiddlewares] = await Promise.all([getResources(), getMiddlewares()]);
|
|
73
|
+
|
|
74
|
+
await Promise.all(_.map(resources, async ({ name: resourceName }) => {
|
|
75
|
+
const resourceRouter = new Router();
|
|
76
|
+
const globalRouter = new Router();
|
|
77
|
+
const endpoints = await Promise.all((await getResourceEndpoints(resourceName))
|
|
78
|
+
.map(async ({ file: endpointFile, name }) => {
|
|
79
|
+
let endpointDef = (await import(endpointFile));
|
|
80
|
+
|
|
81
|
+
if (!endpointDef.endpoint) {
|
|
82
|
+
console.log('missing endpoint for', name);
|
|
83
|
+
}
|
|
84
|
+
endpointDef.endpoint.name = name;
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
endpoint: endpointDef.endpoint,
|
|
88
|
+
requestSchema: endpointDef.requestSchema,
|
|
89
|
+
middlewares: endpointDef.middlewares,
|
|
90
|
+
handler: endpointDef.handler,
|
|
91
|
+
};
|
|
92
|
+
})
|
|
93
|
+
.sort((e) => {
|
|
94
|
+
e.endpoint = e.endpoint || { method: "get", url: "/" };
|
|
95
|
+
const url = e.endpoint.url || e.endpoint.absoluteUrl;
|
|
96
|
+
if (url.includes("/:")) {
|
|
97
|
+
return 1;
|
|
98
|
+
}
|
|
99
|
+
return -1;
|
|
100
|
+
}));
|
|
101
|
+
|
|
102
|
+
endpoints.forEach(({ endpoint, requestSchema, middlewares = [], handler }) => {
|
|
103
|
+
let targetRouter;
|
|
104
|
+
console.log('[routes] Register endpoint', resourceName, endpoint?.method || 'GET', endpoint?.url);
|
|
105
|
+
|
|
106
|
+
let url = endpoint.absoluteUrl || endpoint.url;
|
|
107
|
+
|
|
108
|
+
if (url.startsWith("$HOST/")) {
|
|
109
|
+
url = url.replace("$HOST", "");
|
|
110
|
+
targetRouter = globalRouter;
|
|
111
|
+
} else if (endpoint.absoluteUrl) {
|
|
112
|
+
targetRouter = globalRouter;
|
|
113
|
+
} else {
|
|
114
|
+
targetRouter = resourceRouter;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const globalMiddleware = allMiddlewares.find(m => m.name === 'global');
|
|
118
|
+
|
|
119
|
+
if (globalMiddleware) {
|
|
120
|
+
globalMiddleware.runOrder = 0;
|
|
121
|
+
middlewares.unshift(globalMiddleware.fn);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
middlewares = middlewares.map(middleware => {
|
|
125
|
+
if (_.isString(middleware)) {
|
|
126
|
+
if (!allMiddlewares.find(m => m.name === middleware)) {
|
|
127
|
+
throw new Error(`Middleware ${middleware} not found`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
middleware = allMiddlewares.find(m => m.name === middleware).fn;
|
|
131
|
+
} else if (middleware?.name && middleware?.args) {
|
|
132
|
+
if (!allMiddlewares.find(m => m.name === middleware.name)) {
|
|
133
|
+
throw new Error(`Middleware ${middleware.name} not found`);
|
|
134
|
+
};
|
|
135
|
+
middleware = allMiddlewares.find(m => m.name === middleware.name).fn(...middleware.args);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return middleware;
|
|
139
|
+
}).map(middleware => {
|
|
140
|
+
middleware.runOrder = _.isNumber(middleware.runOrder) ? middleware.runOrder : 1;
|
|
141
|
+
return middleware;
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
if (config._hive.isRequireAuthAllEndpoints) {
|
|
146
|
+
isAuthorized.runOrder = 0;
|
|
147
|
+
middlewares.unshift(isAuthorized);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
targetRouter[endpoint.method?.toLowerCase() || "get"](
|
|
151
|
+
url,
|
|
152
|
+
async (ctx, next) => {
|
|
153
|
+
ctx.state.resourceName = resourceName;
|
|
154
|
+
ctx.state.endpoint = endpoint;
|
|
155
|
+
await next();
|
|
156
|
+
},
|
|
157
|
+
validate(requestSchema),
|
|
158
|
+
..._.sortBy(middlewares, m => m.runOrder),
|
|
159
|
+
async ctx => {
|
|
160
|
+
const result = await handler(ctx);
|
|
161
|
+
|
|
162
|
+
if (!ctx.body) {
|
|
163
|
+
ctx.body = result || { isOk: true };
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
app.use(globalRouter.routes());
|
|
170
|
+
app.use(mount(`/${resourceName}`, resourceRouter.routes()));
|
|
171
|
+
}));
|
|
172
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import _ from '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
|
+
export default attachCustomErrors;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import logger from 'logger';
|
|
2
|
+
|
|
3
|
+
const routeErrorHandler = async (ctx, next) => {
|
|
4
|
+
try {
|
|
5
|
+
await next();
|
|
6
|
+
} catch (error) {
|
|
7
|
+
console.log("Route Error", error, error.stack);
|
|
8
|
+
|
|
9
|
+
const clientError = error.errors;
|
|
10
|
+
const serverError = { global: error.message };
|
|
11
|
+
|
|
12
|
+
const errors = clientError || serverError;
|
|
13
|
+
|
|
14
|
+
logger.error(errors);
|
|
15
|
+
|
|
16
|
+
if (serverError && (error.status === 500 || !error.status) && 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
|
+
export default routeErrorHandler;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import moment from 'moment';
|
|
4
|
+
import schedule from 'node-schedule';
|
|
5
|
+
import requireDir from 'require-dir';
|
|
6
|
+
|
|
7
|
+
export default () => {
|
|
8
|
+
const paths = [path.resolve(__dirname, './scheduler/handlers')];
|
|
9
|
+
if (process.env.HIVE_SRC) {
|
|
10
|
+
paths.push(path.resolve(process.env.HIVE_SRC, './scheduler/handlers'))
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
paths.forEach((pathName) => {
|
|
14
|
+
if (fs.existsSync(pathName)) {
|
|
15
|
+
requireDir(pathName, {
|
|
16
|
+
mapValue: (handler, handlerName) => {
|
|
17
|
+
console.log(
|
|
18
|
+
`[scheduler] Registering handler ${handlerName} with cron ${handler.cron}`
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
schedule.scheduleJob(handler.cron, () => {
|
|
22
|
+
console.log(
|
|
23
|
+
`[scheduler] ${moment().format()} executing ${handlerName} with cron ${handler.cron
|
|
24
|
+
}`
|
|
25
|
+
);
|
|
26
|
+
handler.handler();
|
|
27
|
+
});
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import crypto from 'crypto';
|
|
2
|
+
import bcrypt from 'bcryptjs';
|
|
3
|
+
import util from '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
|
+
export const 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
|
+
export const 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
|
+
export const compareTextWithHash = (text, hash) => {
|
|
37
|
+
return compare(text, hash);
|
|
38
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import config from 'app-config';
|
|
2
|
+
import nodemailer from 'nodemailer';
|
|
3
|
+
|
|
4
|
+
config.assert('smtp');
|
|
5
|
+
|
|
6
|
+
export default {
|
|
7
|
+
sendEmail: async ({ to, subject, html }) => {
|
|
8
|
+
const transporter = nodemailer.createTransport(config.smtp)
|
|
9
|
+
|
|
10
|
+
return await transporter.sendMail({
|
|
11
|
+
from: config.smtp.fromEmail,
|
|
12
|
+
to, subject, html
|
|
13
|
+
})
|
|
14
|
+
}
|
|
15
|
+
};
|
|
File without changes
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import psl from 'psl';
|
|
2
|
+
import url from 'url';
|
|
3
|
+
import { env } from 'app-config';
|
|
4
|
+
import { webUri } from 'app-config';
|
|
5
|
+
|
|
6
|
+
let cookiesDomain;
|
|
7
|
+
|
|
8
|
+
if (webUri) {
|
|
9
|
+
const parsedUrl = url.parse(webUri);
|
|
10
|
+
const parsed = psl.parse(parsedUrl.hostname);
|
|
11
|
+
cookiesDomain = parsed.domain;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export default (ctx, { name, value }, options = {}) => {
|
|
15
|
+
ctx.cookies.set(name, value, {
|
|
16
|
+
domain: '',
|
|
17
|
+
expires: new Date(Date.now() + 10 * 365 * 24 * 60 * 60 * 1000),
|
|
18
|
+
httpOnly: false,
|
|
19
|
+
...options,
|
|
20
|
+
});
|
|
21
|
+
};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import db from 'db';
|
|
2
|
+
import { Server } from 'socket.io';
|
|
3
|
+
import { createClient } from 'redis';
|
|
4
|
+
import { createAdapter } from '@socket.io/redis-adapter';
|
|
5
|
+
import config from 'app-config';
|
|
6
|
+
import logger from 'logger';
|
|
7
|
+
|
|
8
|
+
export default (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
|
+
"access_token"
|
|
32
|
+
) || '';
|
|
33
|
+
|
|
34
|
+
console.log('socket: cookie access token', accessToken[0], accessToken[1], accessToken[2]);
|
|
35
|
+
|
|
36
|
+
let tokenDoc = null;
|
|
37
|
+
|
|
38
|
+
if (!accessToken) {
|
|
39
|
+
logger.info(
|
|
40
|
+
"Note: socket io anonymous auth. Add user authentication in socketIoService"
|
|
41
|
+
);
|
|
42
|
+
} else {
|
|
43
|
+
tokenDoc = await db.services.tokens.findOne({ token: accessToken });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
_id: tokenDoc?.user?._id || "anonymous",
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
io.use(async (socket, next) => {
|
|
52
|
+
const userData = await getUserData(socket);
|
|
53
|
+
|
|
54
|
+
if (userData) {
|
|
55
|
+
// eslint-disable-next-line no-param-reassign
|
|
56
|
+
socket.handshake.data = {
|
|
57
|
+
userId: userData.userId,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
return next();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return next(new Error("token is invalid"));
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
function checkAccessToRoom(roomId, data) {
|
|
67
|
+
let result = false;
|
|
68
|
+
const [roomType, id] = roomId.split("-");
|
|
69
|
+
|
|
70
|
+
switch (roomType) {
|
|
71
|
+
case "user":
|
|
72
|
+
result = id === data.userId;
|
|
73
|
+
break;
|
|
74
|
+
default:
|
|
75
|
+
result = true;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
io.on("connection", (client) => {
|
|
82
|
+
client.on("subscribe", (roomId) => {
|
|
83
|
+
const { userId } = client.handshake.data;
|
|
84
|
+
// const hasAccessToRoom = checkAccessToRoom(roomId, { userId });
|
|
85
|
+
|
|
86
|
+
const hasAccessToRoom = true;
|
|
87
|
+
|
|
88
|
+
if (hasAccessToRoom) {
|
|
89
|
+
client.join(roomId);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
client.on("unsubscribe", (roomId) => {
|
|
94
|
+
client.leave(roomId);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
console.log(`Socket.io server is started on app instance`);
|
|
99
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
|
|
4
|
+
// Treat files as modules even if it doesn't use import/export
|
|
5
|
+
"moduleDetection": "force",
|
|
6
|
+
|
|
7
|
+
// Ignore module structure
|
|
8
|
+
"module": "Preserve",
|
|
9
|
+
|
|
10
|
+
// Allow JSON modules to be imported
|
|
11
|
+
"resolveJsonModule": true,
|
|
12
|
+
|
|
13
|
+
// Allow JS files to be imported from TS and vice versa
|
|
14
|
+
"allowJs": true,
|
|
15
|
+
|
|
16
|
+
// Use correct ESM import behavior
|
|
17
|
+
"esModuleInterop": true,
|
|
18
|
+
|
|
19
|
+
// Disallow features that require cross-file awareness
|
|
20
|
+
"isolatedModules": true,
|
|
21
|
+
|
|
22
|
+
"baseUrl": "src",
|
|
23
|
+
"paths": {
|
|
24
|
+
"*": ["*"]
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
"include": [
|
|
28
|
+
"src/**/*",
|
|
29
|
+
".hive/**/*"
|
|
30
|
+
]
|
|
31
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
const { execSync } = require('child_process');
|
|
2
|
+
|
|
3
|
+
const isDockerInstalled = () => {
|
|
4
|
+
try {
|
|
5
|
+
execSync('docker --version', { stdio: 'ignore' });
|
|
6
|
+
return true;
|
|
7
|
+
} catch {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const getContainerStatus = (name) => {
|
|
13
|
+
try {
|
|
14
|
+
const result = execSync(`docker inspect --format='{{.State.Status}}' ${name}`, {
|
|
15
|
+
encoding: 'utf8',
|
|
16
|
+
stdio: ['pipe', 'pipe', 'ignore']
|
|
17
|
+
});
|
|
18
|
+
return result.trim();
|
|
19
|
+
} catch {
|
|
20
|
+
return null; // Container doesn't exist
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const startContainer = async (name, image, hostPort, containerPort, extraArgs = '') => {
|
|
25
|
+
const status = getContainerStatus(name);
|
|
26
|
+
|
|
27
|
+
if (status === 'running') {
|
|
28
|
+
console.log(` ${name} already running`);
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (status === 'exited' || status === 'created') {
|
|
33
|
+
console.log(` Starting ${name}...`);
|
|
34
|
+
execSync(`docker start ${name}`, { stdio: 'inherit' });
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Container doesn't exist, create it
|
|
39
|
+
console.log(` Creating ${name}...`);
|
|
40
|
+
const cmd = `docker run -d --name ${name} -p ${hostPort}:${containerPort} ${extraArgs} ${image}`;
|
|
41
|
+
execSync(cmd, { stdio: 'inherit' });
|
|
42
|
+
return true;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const startMongo = async () => {
|
|
46
|
+
return startContainer('hive-mongo', 'mongo:7', 27027, 27017, '-e MONGO_INITDB_DATABASE=hive');
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const startRedis = async () => {
|
|
50
|
+
return startContainer('hive-redis', 'redis:7-alpine', 6389, 6379);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
module.exports = {
|
|
54
|
+
isDockerInstalled,
|
|
55
|
+
getContainerStatus,
|
|
56
|
+
startContainer,
|
|
57
|
+
startMongo,
|
|
58
|
+
startRedis,
|
|
59
|
+
};
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const inquirer = require('inquirer');
|
|
4
|
+
|
|
5
|
+
const hashCode = (str) => {
|
|
6
|
+
let hash = 0;
|
|
7
|
+
for (let i = 0; i < str.length; i++) {
|
|
8
|
+
const char = str.charCodeAt(i);
|
|
9
|
+
hash = ((hash << 5) - hash) + char;
|
|
10
|
+
hash = hash & hash;
|
|
11
|
+
}
|
|
12
|
+
return Math.abs(hash);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const readEnvFile = (envPath) => {
|
|
16
|
+
if (!fs.existsSync(envPath)) {
|
|
17
|
+
return {};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const content = fs.readFileSync(envPath, 'utf8');
|
|
21
|
+
const env = {};
|
|
22
|
+
|
|
23
|
+
content.split('\n').forEach(line => {
|
|
24
|
+
const trimmed = line.trim();
|
|
25
|
+
if (trimmed && !trimmed.startsWith('#')) {
|
|
26
|
+
const [key, ...valueParts] = trimmed.split('=');
|
|
27
|
+
if (key) {
|
|
28
|
+
env[key.trim()] = valueParts.join('=').trim();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
return env;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const writeEnvFile = (envPath, env) => {
|
|
37
|
+
const content = Object.entries(env)
|
|
38
|
+
.map(([key, value]) => `${key}=${value}`)
|
|
39
|
+
.join('\n') + '\n';
|
|
40
|
+
|
|
41
|
+
fs.writeFileSync(envPath, content);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const updateEnvFile = (envPath, updates) => {
|
|
45
|
+
const env = readEnvFile(envPath);
|
|
46
|
+
Object.assign(env, updates);
|
|
47
|
+
writeEnvFile(envPath, env);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const ensureEnvConfig = async (hiveSrc) => {
|
|
51
|
+
const appConfigDir = path.join(hiveSrc, 'app-config');
|
|
52
|
+
if (!fs.existsSync(appConfigDir)) {
|
|
53
|
+
fs.mkdirSync(appConfigDir, { recursive: true });
|
|
54
|
+
}
|
|
55
|
+
const envPath = path.join(appConfigDir, '.env');
|
|
56
|
+
const env = readEnvFile(envPath);
|
|
57
|
+
const projectName = path.basename(path.dirname(hiveSrc));
|
|
58
|
+
const updates = {};
|
|
59
|
+
|
|
60
|
+
// Check MONGODB_URI
|
|
61
|
+
if (!env.MONGODB_URI) {
|
|
62
|
+
const { mongoChoice } = await inquirer.prompt([{
|
|
63
|
+
type: 'list',
|
|
64
|
+
name: 'mongoChoice',
|
|
65
|
+
message: 'MONGODB_URI not set. How would you like to configure MongoDB?',
|
|
66
|
+
choices: [
|
|
67
|
+
{ name: 'Use local Docker (localhost:27027)', value: 'local' },
|
|
68
|
+
{ name: 'Enter custom URI', value: 'custom' },
|
|
69
|
+
]
|
|
70
|
+
}]);
|
|
71
|
+
|
|
72
|
+
if (mongoChoice === 'local') {
|
|
73
|
+
updates.MONGODB_URI = `mongodb://localhost:27027/${projectName}`;
|
|
74
|
+
} else {
|
|
75
|
+
const { mongoUri } = await inquirer.prompt([{
|
|
76
|
+
type: 'input',
|
|
77
|
+
name: 'mongoUri',
|
|
78
|
+
message: 'Enter MongoDB URI:',
|
|
79
|
+
default: `mongodb://localhost:27027/${projectName}`
|
|
80
|
+
}]);
|
|
81
|
+
updates.MONGODB_URI = mongoUri;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Check REDIS_URI
|
|
86
|
+
if (!env.REDIS_URI) {
|
|
87
|
+
const { redisChoice } = await inquirer.prompt([{
|
|
88
|
+
type: 'list',
|
|
89
|
+
name: 'redisChoice',
|
|
90
|
+
message: 'REDIS_URI not set. How would you like to configure Redis?',
|
|
91
|
+
choices: [
|
|
92
|
+
{ name: 'Use local Docker (localhost:6389)', value: 'local' },
|
|
93
|
+
{ name: 'Enter custom URI', value: 'custom' },
|
|
94
|
+
{ name: 'Skip (Redis optional)', value: 'skip' },
|
|
95
|
+
]
|
|
96
|
+
}]);
|
|
97
|
+
|
|
98
|
+
if (redisChoice === 'local') {
|
|
99
|
+
const redisDb = hashCode(projectName) % 16;
|
|
100
|
+
updates.REDIS_URI = `redis://localhost:6389/${redisDb}`;
|
|
101
|
+
} else if (redisChoice === 'custom') {
|
|
102
|
+
const { redisUri } = await inquirer.prompt([{
|
|
103
|
+
type: 'input',
|
|
104
|
+
name: 'redisUri',
|
|
105
|
+
message: 'Enter Redis URI:',
|
|
106
|
+
default: `redis://localhost:6389/0`
|
|
107
|
+
}]);
|
|
108
|
+
updates.REDIS_URI = redisUri;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Write updates if any
|
|
113
|
+
if (Object.keys(updates).length > 0) {
|
|
114
|
+
updateEnvFile(envPath, updates);
|
|
115
|
+
console.log(`\n Updated app-config/.env with: ${Object.keys(updates).join(', ')}\n`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return { ...env, ...updates };
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
module.exports = {
|
|
122
|
+
readEnvFile,
|
|
123
|
+
writeEnvFile,
|
|
124
|
+
updateEnvFile,
|
|
125
|
+
ensureEnvConfig,
|
|
126
|
+
hashCode,
|
|
127
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const net = require('net');
|
|
2
|
+
|
|
3
|
+
const isPortAvailable = (port) => {
|
|
4
|
+
return new Promise((resolve) => {
|
|
5
|
+
const server = net.createServer();
|
|
6
|
+
|
|
7
|
+
server.once('error', () => {
|
|
8
|
+
resolve(false);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
server.once('listening', () => {
|
|
12
|
+
server.close();
|
|
13
|
+
resolve(true);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
server.listen(port, '127.0.0.1');
|
|
17
|
+
});
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const findAvailablePort = async (startPort = 3001, maxAttempts = 100) => {
|
|
21
|
+
for (let port = startPort; port < startPort + maxAttempts; port++) {
|
|
22
|
+
if (await isPortAvailable(port)) {
|
|
23
|
+
return port;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
throw new Error(`No available port found between ${startPort} and ${startPort + maxAttempts - 1}`);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
module.exports = {
|
|
30
|
+
isPortAvailable,
|
|
31
|
+
findAvailablePort,
|
|
32
|
+
};
|