@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,95 @@
|
|
|
1
|
+
# Add Middleware
|
|
2
|
+
|
|
3
|
+
Creates a new middleware for endpoints.
|
|
4
|
+
|
|
5
|
+
## Command Format
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
add-middleware {name}
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
**Examples:**
|
|
12
|
+
- `add-middleware isAdmin`
|
|
13
|
+
- `add-middleware canEditProject`
|
|
14
|
+
- `add-middleware rateLimit`
|
|
15
|
+
|
|
16
|
+
## Location
|
|
17
|
+
|
|
18
|
+
`src/middlewares/{name}.js`
|
|
19
|
+
|
|
20
|
+
## Template (simple)
|
|
21
|
+
|
|
22
|
+
```javascript
|
|
23
|
+
export default async (ctx, next) => {
|
|
24
|
+
// Middleware logic here
|
|
25
|
+
|
|
26
|
+
return next();
|
|
27
|
+
};
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Template (with parameters)
|
|
31
|
+
|
|
32
|
+
```javascript
|
|
33
|
+
export default (param) => async (ctx, next) => {
|
|
34
|
+
// Use param here
|
|
35
|
+
|
|
36
|
+
return next();
|
|
37
|
+
};
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Examples
|
|
41
|
+
|
|
42
|
+
### Access control
|
|
43
|
+
|
|
44
|
+
```javascript
|
|
45
|
+
export default async (ctx, next) => {
|
|
46
|
+
if (!ctx.state.user?.isAdmin) {
|
|
47
|
+
ctx.throw(403, 'Admin access required');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return next();
|
|
51
|
+
};
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### With parameters
|
|
55
|
+
|
|
56
|
+
```javascript
|
|
57
|
+
import db from 'db';
|
|
58
|
+
|
|
59
|
+
export default (resource, getId = (ctx) => ctx.params.id) => async (ctx, next) => {
|
|
60
|
+
const id = getId(ctx);
|
|
61
|
+
const doc = await db.services[resource].findOne({ _id: id });
|
|
62
|
+
|
|
63
|
+
if (!doc) {
|
|
64
|
+
ctx.throw(404, `${resource} not found`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
ctx.state[resource] = doc;
|
|
68
|
+
return next();
|
|
69
|
+
};
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### With runOrder (lower runs first)
|
|
73
|
+
|
|
74
|
+
```javascript
|
|
75
|
+
const middleware = async (ctx, next) => {
|
|
76
|
+
ctx.state.isSkipAuth = true;
|
|
77
|
+
return next();
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
middleware.runOrder = -1;
|
|
81
|
+
|
|
82
|
+
export default middleware;
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Usage in endpoints
|
|
86
|
+
|
|
87
|
+
```javascript
|
|
88
|
+
// Import and use directly
|
|
89
|
+
import isAdmin from 'middlewares/isAdmin';
|
|
90
|
+
export const middlewares = [isAdmin];
|
|
91
|
+
|
|
92
|
+
// With parameters
|
|
93
|
+
import canEdit from 'middlewares/canEdit';
|
|
94
|
+
export const middlewares = [canEdit('projects', (ctx) => ctx.params.projectId)];
|
|
95
|
+
```
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Add Resource
|
|
2
|
+
|
|
3
|
+
Creates a complete resource with schema and folder structure.
|
|
4
|
+
|
|
5
|
+
## Steps
|
|
6
|
+
|
|
7
|
+
1. Create folder: `src/resources/{name}/`
|
|
8
|
+
2. Create schema file: `src/resources/{name}/{name}.schema.js`
|
|
9
|
+
3. Create folders: `endpoints/`, `handlers/`, `methods/`
|
|
10
|
+
4. Create first endpoint: `src/resources/{name}/endpoints/list.js`
|
|
11
|
+
|
|
12
|
+
## Schema Template
|
|
13
|
+
|
|
14
|
+
**File:** `src/resources/{name}/{name}.schema.js`
|
|
15
|
+
|
|
16
|
+
```javascript
|
|
17
|
+
import { z } from 'zod';
|
|
18
|
+
import dbSchema from 'helpers/schema/db.schema.js';
|
|
19
|
+
|
|
20
|
+
const schema = dbSchema.extend({
|
|
21
|
+
// Add fields here
|
|
22
|
+
title: z.coerce.string().nullable().optional(),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export default schema;
|
|
26
|
+
|
|
27
|
+
export const secureFields = [];
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## First Endpoint Template
|
|
31
|
+
|
|
32
|
+
**File:** `src/resources/{name}/endpoints/list.js`
|
|
33
|
+
|
|
34
|
+
```javascript
|
|
35
|
+
import { z } from 'zod';
|
|
36
|
+
import db from 'db';
|
|
37
|
+
|
|
38
|
+
const {name}Service = db.services.{name};
|
|
39
|
+
|
|
40
|
+
export const middlewares = [];
|
|
41
|
+
|
|
42
|
+
export const requestSchema = z.object({
|
|
43
|
+
page: z.coerce.number().default(1),
|
|
44
|
+
perPage: z.coerce.number().default(20),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
export const handler = async (ctx) => {
|
|
48
|
+
const { page, perPage } = ctx.validatedData;
|
|
49
|
+
|
|
50
|
+
return {name}Service.find(
|
|
51
|
+
{},
|
|
52
|
+
{ page, perPage, sort: '-createdOn' }
|
|
53
|
+
);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export const endpoint = {
|
|
57
|
+
url: '/',
|
|
58
|
+
method: 'get',
|
|
59
|
+
};
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Result
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
src/resources/{name}/
|
|
66
|
+
├── {name}.schema.js
|
|
67
|
+
├── endpoints/
|
|
68
|
+
│ └── list.js
|
|
69
|
+
├── handlers/
|
|
70
|
+
└── methods/
|
|
71
|
+
```
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# Add Scheduler
|
|
2
|
+
|
|
3
|
+
Creates a new scheduled background job.
|
|
4
|
+
|
|
5
|
+
## Command Format
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
add-scheduler {name} [{schedule}]
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
**Examples:**
|
|
12
|
+
- `add-scheduler syncExternalData every 5 minutes`
|
|
13
|
+
- `add-scheduler sendDailyReport daily at 9am`
|
|
14
|
+
- `add-scheduler markOverdueInvoices every day at midnight`
|
|
15
|
+
- `add-scheduler weeklyCleanup every monday at 3am`
|
|
16
|
+
|
|
17
|
+
## Schedule to Cron
|
|
18
|
+
|
|
19
|
+
Translate natural language to cron:
|
|
20
|
+
|
|
21
|
+
| User says | Cron |
|
|
22
|
+
|-----------|------|
|
|
23
|
+
| every minute | `* * * * *` |
|
|
24
|
+
| every 5 minutes | `*/5 * * * *` |
|
|
25
|
+
| every hour | `0 * * * *` |
|
|
26
|
+
| every 6 hours | `0 */6 * * *` |
|
|
27
|
+
| daily at midnight | `0 0 * * *` |
|
|
28
|
+
| daily at 9am | `0 9 * * *` |
|
|
29
|
+
| every monday at 9am | `0 9 * * 1` |
|
|
30
|
+
| first of month | `0 0 1 * *` |
|
|
31
|
+
|
|
32
|
+
## Location
|
|
33
|
+
|
|
34
|
+
`src/scheduler/handlers/{name}.js`
|
|
35
|
+
|
|
36
|
+
## Template
|
|
37
|
+
|
|
38
|
+
```javascript
|
|
39
|
+
import db from 'db';
|
|
40
|
+
|
|
41
|
+
export const handler = async () => {
|
|
42
|
+
// Job logic here
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const cron = '0 * * * *'; // Every hour
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Examples
|
|
49
|
+
|
|
50
|
+
### Sync external data
|
|
51
|
+
|
|
52
|
+
```javascript
|
|
53
|
+
import db from 'db';
|
|
54
|
+
import moment from 'moment';
|
|
55
|
+
import externalApi from 'services/externalApi';
|
|
56
|
+
|
|
57
|
+
const itemsService = db.services.items;
|
|
58
|
+
|
|
59
|
+
export const handler = async () => {
|
|
60
|
+
const items = await externalApi.list({
|
|
61
|
+
updatedSince: moment().subtract(5, 'minutes').toDate(),
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
for (const item of items) {
|
|
65
|
+
await itemsService.updateOne(
|
|
66
|
+
{ externalId: item.id },
|
|
67
|
+
(doc) => ({ ...doc, ...item })
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export const cron = '*/5 * * * *';
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Send daily report
|
|
76
|
+
|
|
77
|
+
```javascript
|
|
78
|
+
import db from 'db';
|
|
79
|
+
import slack from 'services/slack';
|
|
80
|
+
|
|
81
|
+
const tasksService = db.services.tasks;
|
|
82
|
+
|
|
83
|
+
export const handler = async () => {
|
|
84
|
+
const { count: completed } = await tasksService.find({ status: 'completed' });
|
|
85
|
+
const { count: pending } = await tasksService.find({ status: 'pending' });
|
|
86
|
+
|
|
87
|
+
await slack.sendChannelMessage({
|
|
88
|
+
channel: '#reports',
|
|
89
|
+
text: `Daily report: ${completed} completed, ${pending} pending`,
|
|
90
|
+
});
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export const cron = '0 9 * * *';
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Mark overdue invoices
|
|
97
|
+
|
|
98
|
+
```javascript
|
|
99
|
+
import db from 'db';
|
|
100
|
+
|
|
101
|
+
const invoicesService = db.services.invoices;
|
|
102
|
+
|
|
103
|
+
export const handler = async () => {
|
|
104
|
+
await invoicesService.updateMany(
|
|
105
|
+
{
|
|
106
|
+
isPaid: { $ne: true },
|
|
107
|
+
isOverdue: { $ne: true },
|
|
108
|
+
dueOn: { $lt: new Date() },
|
|
109
|
+
},
|
|
110
|
+
(doc) => ({ ...doc, isOverdue: true })
|
|
111
|
+
);
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
export const cron = '0 0 * * *';
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Cleanup old records
|
|
118
|
+
|
|
119
|
+
```javascript
|
|
120
|
+
import db from 'db';
|
|
121
|
+
import moment from 'moment';
|
|
122
|
+
|
|
123
|
+
const logsService = db.services.logs;
|
|
124
|
+
|
|
125
|
+
export const handler = async () => {
|
|
126
|
+
await logsService.remove({
|
|
127
|
+
createdOn: { $lt: moment().subtract(30, 'days').toDate() },
|
|
128
|
+
});
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
export const cron = '0 3 * * *';
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Tips
|
|
135
|
+
|
|
136
|
+
- Keep jobs idempotent (safe to re-run)
|
|
137
|
+
- Log progress for debugging
|
|
138
|
+
- Use `atomic.update` for bulk operations
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# Add Service
|
|
2
|
+
|
|
3
|
+
Creates a new service for external APIs or utilities.
|
|
4
|
+
|
|
5
|
+
## Command Format
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
add-service {name} [{methods}]
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
**Examples:**
|
|
12
|
+
- `add-service slack sendChannelMessage, replyToThread`
|
|
13
|
+
- `add-service stripe createCustomer, createPaymentIntent, listInvoices`
|
|
14
|
+
- `add-service openai chat, generateImage`
|
|
15
|
+
- `add-service sendgrid sendEmail, sendTemplateEmail`
|
|
16
|
+
|
|
17
|
+
## Location
|
|
18
|
+
|
|
19
|
+
`src/services/{name}.js`
|
|
20
|
+
|
|
21
|
+
## Known Services & Libraries
|
|
22
|
+
|
|
23
|
+
When creating a service, use these go-to libraries:
|
|
24
|
+
|
|
25
|
+
| Service | Library | Install |
|
|
26
|
+
|---------|---------|---------|
|
|
27
|
+
| Slack | `@slack/web-api` | `npm i @slack/web-api` |
|
|
28
|
+
| Stripe | `stripe` | `npm i stripe` |
|
|
29
|
+
| OpenAI | `openai` | `npm i openai` |
|
|
30
|
+
| SendGrid | `@sendgrid/mail` | `npm i @sendgrid/mail` |
|
|
31
|
+
| Twilio | `twilio` | `npm i twilio` |
|
|
32
|
+
| AWS S3 | `@aws-sdk/client-s3` | `npm i @aws-sdk/client-s3` |
|
|
33
|
+
| Resend | `resend` | `npm i resend` |
|
|
34
|
+
| Postmark | `postmark` | `npm i postmark` |
|
|
35
|
+
|
|
36
|
+
## Template
|
|
37
|
+
|
|
38
|
+
```javascript
|
|
39
|
+
import config from 'app-config';
|
|
40
|
+
|
|
41
|
+
// Initialize client here
|
|
42
|
+
|
|
43
|
+
export default {
|
|
44
|
+
client,
|
|
45
|
+
|
|
46
|
+
// Methods here
|
|
47
|
+
};
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Full Examples
|
|
51
|
+
|
|
52
|
+
### `add-service slack sendChannelMessage, replyToThread`
|
|
53
|
+
|
|
54
|
+
```javascript
|
|
55
|
+
import config from 'app-config';
|
|
56
|
+
import { WebClient } from '@slack/web-api';
|
|
57
|
+
|
|
58
|
+
const client = new WebClient(config.slack.botToken);
|
|
59
|
+
|
|
60
|
+
export default {
|
|
61
|
+
client,
|
|
62
|
+
|
|
63
|
+
sendChannelMessage: async ({ channel, text, blocks }) => {
|
|
64
|
+
return client.chat.postMessage({ channel, text, blocks });
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
replyToThread: async ({ channel, threadTs, text }) => {
|
|
68
|
+
return client.chat.postMessage({ channel, text, thread_ts: threadTs });
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### `add-service stripe createCustomer, createPaymentIntent, listInvoices`
|
|
74
|
+
|
|
75
|
+
```javascript
|
|
76
|
+
import config from 'app-config';
|
|
77
|
+
import Stripe from 'stripe';
|
|
78
|
+
|
|
79
|
+
const client = new Stripe(config.stripe.secretKey);
|
|
80
|
+
|
|
81
|
+
export default {
|
|
82
|
+
client,
|
|
83
|
+
|
|
84
|
+
createCustomer: async ({ email, name, metadata }) => {
|
|
85
|
+
return client.customers.create({ email, name, metadata });
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
createPaymentIntent: async ({ amount, currency, customerId }) => {
|
|
89
|
+
return client.paymentIntents.create({
|
|
90
|
+
amount,
|
|
91
|
+
currency,
|
|
92
|
+
customer: customerId,
|
|
93
|
+
});
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
listInvoices: async ({ customerId, limit = 10 }) => {
|
|
97
|
+
return client.invoices.list({ customer: customerId, limit });
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### `add-service openai chat, generateImage`
|
|
103
|
+
|
|
104
|
+
```javascript
|
|
105
|
+
import config from 'app-config';
|
|
106
|
+
import OpenAI from 'openai';
|
|
107
|
+
|
|
108
|
+
const client = new OpenAI({ apiKey: config.openai.apiKey });
|
|
109
|
+
|
|
110
|
+
export default {
|
|
111
|
+
client,
|
|
112
|
+
|
|
113
|
+
chat: async ({ messages, model = 'gpt-4' }) => {
|
|
114
|
+
const response = await client.chat.completions.create({ model, messages });
|
|
115
|
+
return response.choices[0].message;
|
|
116
|
+
},
|
|
117
|
+
|
|
118
|
+
generateImage: async ({ prompt, size = '1024x1024' }) => {
|
|
119
|
+
const response = await client.images.generate({ prompt, size, n: 1 });
|
|
120
|
+
return response.data[0].url;
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### `add-service sendgrid sendEmail, sendTemplateEmail`
|
|
126
|
+
|
|
127
|
+
```javascript
|
|
128
|
+
import config from 'app-config';
|
|
129
|
+
import sgMail from '@sendgrid/mail';
|
|
130
|
+
|
|
131
|
+
sgMail.setApiKey(config.sendgrid.apiKey);
|
|
132
|
+
|
|
133
|
+
export default {
|
|
134
|
+
sendEmail: async ({ to, from, subject, html }) => {
|
|
135
|
+
return sgMail.send({ to, from, subject, html });
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
sendTemplateEmail: async ({ to, from, templateId, dynamicTemplateData }) => {
|
|
139
|
+
return sgMail.send({ to, from, templateId, dynamicTemplateData });
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### `add-service s3 upload, getSignedUrl, delete`
|
|
145
|
+
|
|
146
|
+
```javascript
|
|
147
|
+
import config from 'app-config';
|
|
148
|
+
import { S3Client, PutObjectCommand, GetObjectCommand, DeleteObjectCommand } from '@aws-sdk/client-s3';
|
|
149
|
+
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
|
150
|
+
|
|
151
|
+
const client = new S3Client({
|
|
152
|
+
region: config.aws.region,
|
|
153
|
+
credentials: {
|
|
154
|
+
accessKeyId: config.aws.accessKeyId,
|
|
155
|
+
secretAccessKey: config.aws.secretAccessKey,
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
export default {
|
|
160
|
+
client,
|
|
161
|
+
|
|
162
|
+
upload: async ({ bucket, key, body, contentType }) => {
|
|
163
|
+
const command = new PutObjectCommand({ Bucket: bucket, Key: key, Body: body, ContentType: contentType });
|
|
164
|
+
return client.send(command);
|
|
165
|
+
},
|
|
166
|
+
|
|
167
|
+
getSignedUrl: async ({ bucket, key, expiresIn = 3600 }) => {
|
|
168
|
+
const command = new GetObjectCommand({ Bucket: bucket, Key: key });
|
|
169
|
+
return getSignedUrl(client, command, { expiresIn });
|
|
170
|
+
},
|
|
171
|
+
|
|
172
|
+
delete: async ({ bucket, key }) => {
|
|
173
|
+
const command = new DeleteObjectCommand({ Bucket: bucket, Key: key });
|
|
174
|
+
return client.send(command);
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Usage
|
|
180
|
+
|
|
181
|
+
```javascript
|
|
182
|
+
import slack from 'services/slack';
|
|
183
|
+
import stripe from 'services/stripe';
|
|
184
|
+
|
|
185
|
+
await slack.sendChannelMessage({ channel: '#general', text: 'Hello' });
|
|
186
|
+
|
|
187
|
+
const customer = await stripe.createCustomer({ email: 'user@example.com' });
|
|
188
|
+
```
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: hive-auth
|
|
3
|
+
description: How authentication works in Hive framework
|
|
4
|
+
globs:
|
|
5
|
+
- src/resources/auth/**/*.js
|
|
6
|
+
alwaysApply: false
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Authentication
|
|
10
|
+
|
|
11
|
+
Hive provides auth infrastructure but **no built-in auth endpoints**. You implement login/signup yourself.
|
|
12
|
+
|
|
13
|
+
## Built-in (in .hive/)
|
|
14
|
+
|
|
15
|
+
- `tokens` collection - stores access tokens
|
|
16
|
+
- `attachUser` middleware - loads user from token
|
|
17
|
+
- `isAuthorized` middleware - requires auth
|
|
18
|
+
- `allowNoAuth` middleware - skips auth
|
|
19
|
+
|
|
20
|
+
## How It Works
|
|
21
|
+
|
|
22
|
+
1. Token extracted from cookie `access_token` or `Authorization: Bearer` header
|
|
23
|
+
2. Token looked up in `tokens` collection
|
|
24
|
+
3. User loaded from `users` collection
|
|
25
|
+
4. User attached to `ctx.state.user`
|
|
26
|
+
|
|
27
|
+
## Token Schema (built-in)
|
|
28
|
+
|
|
29
|
+
```javascript
|
|
30
|
+
{
|
|
31
|
+
_id: string,
|
|
32
|
+
user: { _id: string },
|
|
33
|
+
token: string,
|
|
34
|
+
metadata: object (optional),
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Implement Auth Endpoints
|
|
39
|
+
|
|
40
|
+
**1. Create token helper:**
|
|
41
|
+
```javascript
|
|
42
|
+
// src/resources/auth/methods/createToken.js
|
|
43
|
+
import db from 'db';
|
|
44
|
+
import crypto from 'crypto';
|
|
45
|
+
|
|
46
|
+
const tokenService = db.services.tokens;
|
|
47
|
+
|
|
48
|
+
export default async (ctx, { userId, metadata }) => {
|
|
49
|
+
const token = crypto.randomBytes(32).toString('hex');
|
|
50
|
+
|
|
51
|
+
await tokenService.create({
|
|
52
|
+
token,
|
|
53
|
+
user: { _id: userId },
|
|
54
|
+
...(metadata && { metadata }),
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
ctx.cookies.set('access_token', token, {
|
|
58
|
+
httpOnly: false,
|
|
59
|
+
expires: new Date(Date.now() + 10 * 365 * 24 * 60 * 60 * 1000),
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
return { token };
|
|
63
|
+
};
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**2. Login endpoint:**
|
|
67
|
+
```javascript
|
|
68
|
+
// src/resources/auth/endpoints/login.js
|
|
69
|
+
import { z } from 'zod';
|
|
70
|
+
import db from 'db';
|
|
71
|
+
import bcrypt from 'bcrypt';
|
|
72
|
+
import createToken from '../methods/createToken';
|
|
73
|
+
|
|
74
|
+
export const handler = async (ctx) => {
|
|
75
|
+
const { email, password } = ctx.validatedData;
|
|
76
|
+
|
|
77
|
+
const user = await db.services.users.findOne(
|
|
78
|
+
{ email },
|
|
79
|
+
{ isIncludeSecureFields: true }
|
|
80
|
+
);
|
|
81
|
+
ctx.assert(user, 401, 'Invalid credentials');
|
|
82
|
+
|
|
83
|
+
const valid = await bcrypt.compare(password, user.password);
|
|
84
|
+
ctx.assert(valid, 401, 'Invalid credentials');
|
|
85
|
+
|
|
86
|
+
const { token } = await createToken(ctx, { userId: user._id });
|
|
87
|
+
return { user, token };
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export const middlewares = ['allowNoAuth'];
|
|
91
|
+
export const endpoint = { url: '/login', method: 'post' };
|
|
92
|
+
export const requestSchema = z.object({
|
|
93
|
+
email: z.string().email(),
|
|
94
|
+
password: z.string(),
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**3. Logout endpoint:**
|
|
99
|
+
```javascript
|
|
100
|
+
// src/resources/auth/endpoints/logout.js
|
|
101
|
+
import { z } from 'zod';
|
|
102
|
+
import db from 'db';
|
|
103
|
+
|
|
104
|
+
export const handler = async (ctx) => {
|
|
105
|
+
await db.services.tokens.remove({ token: ctx.state.accessToken });
|
|
106
|
+
ctx.cookies.set('access_token', null);
|
|
107
|
+
return { success: true };
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export const endpoint = { url: '/logout', method: 'post' };
|
|
111
|
+
export const requestSchema = z.object({});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Users Schema (add password)
|
|
115
|
+
|
|
116
|
+
```javascript
|
|
117
|
+
// src/resources/users/users.schema.js
|
|
118
|
+
password: z.coerce.string().nullable().optional(),
|
|
119
|
+
|
|
120
|
+
// Hide from responses
|
|
121
|
+
export const secureFields = ['password'];
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Client Usage
|
|
125
|
+
|
|
126
|
+
**Browser (cookies):**
|
|
127
|
+
```javascript
|
|
128
|
+
await fetch('/auth/login', { method: 'POST', credentials: 'include', body });
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
**API (header):**
|
|
132
|
+
```javascript
|
|
133
|
+
await fetch('/tasks', { headers: { Authorization: `Bearer ${token}` } });
|
|
134
|
+
```
|