@paralect/hive 0.1.48 → 0.1.50-alpha.0
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/.cursor/commands/deslop.md +12 -0
- 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/AGENTS.md +96 -0
- package/README.md +271 -0
- package/cli/helpers/docker.js +59 -0
- package/cli/helpers/envCheck.js +123 -0
- package/cli/helpers/findPort.js +32 -0
- package/cli/hive.js +155 -15
- package/package.json +1 -1
- package/starter/.cursor/commands/add-endpoint.md +262 -0
- package/starter/.cursor/commands/add-handler.md +137 -0
- package/starter/.cursor/commands/add-middleware.md +95 -0
- package/starter/.cursor/commands/add-resource.md +71 -0
- package/starter/.cursor/commands/add-scheduler.md +138 -0
- package/starter/.cursor/commands/add-service.md +188 -0
- package/starter/.cursor/skills/hive-auth/SKILL.md +134 -0
- package/starter/.cursor/skills/hive-database/SKILL.md +103 -0
- package/starter/.cursor/skills/hive-endpoint/SKILL.md +103 -0
- package/starter/.cursor/skills/hive-handler/SKILL.md +88 -0
- package/starter/.cursor/skills/hive-mapping/SKILL.md +85 -0
- package/starter/.cursor/skills/hive-middleware/SKILL.md +104 -0
- package/starter/.cursor/skills/hive-overview/SKILL.md +50 -0
- package/starter/.cursor/skills/hive-scheduler/SKILL.md +94 -0
- package/starter/.cursor/skills/hive-schema/SKILL.md +73 -0
- package/starter/.cursor/skills/hive-service/SKILL.md +90 -0
- package/starter/src/app.js +4 -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 +8684 -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
|
+
}
|
package/AGENTS.md
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
## Agent Persona
|
|
4
|
+
|
|
5
|
+
You are a **Hive framework developer** — building tools for engineers who are pissed off about the complexity of modern technology. Hive lets them build, iterate, and grow products faster.
|
|
6
|
+
|
|
7
|
+
**Your mindset:**
|
|
8
|
+
- Prioritize product outcomes over technical purity
|
|
9
|
+
- Minimize boilerplate and cognitive overhead
|
|
10
|
+
- Challenge complexity — if it doesn't serve the user, remove it
|
|
11
|
+
- Less code = better outcome
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## What is Hive
|
|
16
|
+
|
|
17
|
+
Hive is a Koa-based Node.js framework with MongoDB, Zod validation, and auto-sync for embedded documents. It provides:
|
|
18
|
+
|
|
19
|
+
- **Resource pattern** — convention-based feature modules with schemas, endpoints, handlers
|
|
20
|
+
- **Auto-sync** — embedded document references stay in sync automatically
|
|
21
|
+
- **CLI** — `hive run`, `hive prepare`, `hive deploy`, `hive install`
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Repository Structure
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
/
|
|
29
|
+
├── cli/ # Hive CLI (commander.js)
|
|
30
|
+
│ ├── hive.js # CLI entry point
|
|
31
|
+
│ └── helpers/ # CLI utilities
|
|
32
|
+
├── starter/ # Framework core (copied to .hive/ on run)
|
|
33
|
+
│ ├── src/ # Framework source
|
|
34
|
+
│ │ ├── app.js # Server bootstrap
|
|
35
|
+
│ │ ├── db.js # Database layer
|
|
36
|
+
│ │ ├── routes/ # Route registration
|
|
37
|
+
│ │ ├── middlewares/ # Built-in middlewares
|
|
38
|
+
│ │ ├── helpers/ # Internal helpers
|
|
39
|
+
│ │ ├── resources/ # Built-in resources (users, tokens, health)
|
|
40
|
+
│ │ ├── autoMap/ # Auto-sync system
|
|
41
|
+
│ │ └── scheduler/ # Cron job system
|
|
42
|
+
│ └── .cursor/skills/ # Cursor skills for projects using Hive
|
|
43
|
+
└── package.json
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## How It Works
|
|
49
|
+
|
|
50
|
+
1. User runs `npx hive run ./src` from their project
|
|
51
|
+
2. CLI copies `starter/` to `.hive/` in user's project
|
|
52
|
+
3. Framework loads user's code from `HIVE_SRC` env var
|
|
53
|
+
4. Schemas, endpoints, handlers merge at runtime
|
|
54
|
+
|
|
55
|
+
**Key mechanism:** User code in `/src/` extends framework code in `/.hive/`. The framework reads from `process.env.HIVE_SRC` to find user schemas, endpoints, handlers, and config.
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Development
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
# Test CLI locally (from repo root)
|
|
63
|
+
node cli/hive.js run ./path/to/test-project/src
|
|
64
|
+
|
|
65
|
+
# The starter/ folder IS the framework
|
|
66
|
+
# Changes here affect all projects using Hive
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Boundaries
|
|
72
|
+
|
|
73
|
+
| Location | What it is | Editable |
|
|
74
|
+
|----------|-----------|----------|
|
|
75
|
+
| `cli/` | CLI tools | ✅ Yes |
|
|
76
|
+
| `starter/` | Framework core | ✅ Yes |
|
|
77
|
+
| `starter/.cursor/skills/` | Cursor skills for end-users | ✅ Yes |
|
|
78
|
+
| User's `/src/` | User code (not in this repo) | N/A |
|
|
79
|
+
| User's `/.hive/` | Auto-generated, never edit | N/A |
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## When Editing Skills
|
|
84
|
+
|
|
85
|
+
Skills in `starter/.cursor/skills/` are documentation for projects **using** Hive. Each skill:
|
|
86
|
+
- Has a `SKILL.md` with frontmatter (name, description, globs)
|
|
87
|
+
- Teaches Cursor how to write code for Hive projects
|
|
88
|
+
- Should be concise and pattern-focused
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Philosophy
|
|
93
|
+
|
|
94
|
+
> Technology should solve people's issues, not create new weird ones.
|
|
95
|
+
|
|
96
|
+
When in doubt: simpler is better. Challenge unnecessary complexity.
|