@paralect/hive 0.1.49 → 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/.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 +123 -0
- package/cli/helpers/findPort.js +32 -0
- package/cli/hive.js +84 -12
- package/package.json +1 -1
- 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,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
|
+
```
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: hive-database
|
|
3
|
+
description: Database operations in Hive framework
|
|
4
|
+
globs:
|
|
5
|
+
- src/**/*.js
|
|
6
|
+
alwaysApply: false
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Database Operations
|
|
10
|
+
|
|
11
|
+
## Access Service
|
|
12
|
+
|
|
13
|
+
```javascript
|
|
14
|
+
import db from 'db';
|
|
15
|
+
const taskService = db.services.tasks; // From schema name
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Find Operations
|
|
19
|
+
|
|
20
|
+
```javascript
|
|
21
|
+
// Find many
|
|
22
|
+
const { results, pagesCount, count } = await service.find(
|
|
23
|
+
{ status: 'active' },
|
|
24
|
+
{ page: 1, perPage: 20, sort: '-createdOn', fields: ['_id', 'title'] }
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
// Find one
|
|
28
|
+
const doc = await service.findOne({ _id: id });
|
|
29
|
+
|
|
30
|
+
// Check exists
|
|
31
|
+
const exists = await service.exists({ _id: id });
|
|
32
|
+
|
|
33
|
+
// Count
|
|
34
|
+
const total = await service.count({ status: 'active' });
|
|
35
|
+
|
|
36
|
+
// Distinct values
|
|
37
|
+
const statuses = await service.distinct('status');
|
|
38
|
+
|
|
39
|
+
// Aggregate
|
|
40
|
+
const { results } = await service.aggregate([
|
|
41
|
+
{ $match: { status: 'done' } },
|
|
42
|
+
{ $group: { _id: '$project._id', count: { $sum: 1 } } },
|
|
43
|
+
]);
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Write Operations
|
|
47
|
+
|
|
48
|
+
```javascript
|
|
49
|
+
// Create
|
|
50
|
+
const doc = await service.create({ title: 'New', user: ctx.state.user });
|
|
51
|
+
|
|
52
|
+
// Update one (must use function)
|
|
53
|
+
const updated = await service.updateOne(
|
|
54
|
+
{ _id: id },
|
|
55
|
+
(doc) => ({ ...doc, title: 'Updated' })
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
// Update many
|
|
59
|
+
await service.updateMany(
|
|
60
|
+
{ status: 'pending' },
|
|
61
|
+
(doc) => ({ ...doc, status: 'active' })
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
// Remove
|
|
65
|
+
await service.remove({ _id: id });
|
|
66
|
+
|
|
67
|
+
// Generate ID
|
|
68
|
+
const newId = service.generateId();
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Atomic Operations (No Events)
|
|
72
|
+
|
|
73
|
+
```javascript
|
|
74
|
+
// Silent bulk update
|
|
75
|
+
await service.atomic.update(
|
|
76
|
+
{ 'project._id': projectId },
|
|
77
|
+
{ $set: { 'project.name': newName } },
|
|
78
|
+
{ multi: true }
|
|
79
|
+
);
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Query Patterns
|
|
83
|
+
|
|
84
|
+
```javascript
|
|
85
|
+
import { when } from 'services/utils';
|
|
86
|
+
|
|
87
|
+
const { results } = await service.find({
|
|
88
|
+
// Conditional fields
|
|
89
|
+
...when(status, { status }),
|
|
90
|
+
...when(userId, { 'user._id': userId }),
|
|
91
|
+
|
|
92
|
+
// Complex conditions
|
|
93
|
+
status: { $in: ['active', 'pending'] },
|
|
94
|
+
createdOn: { $gte: startDate, $lt: endDate },
|
|
95
|
+
$or: [{ 'user._id': id }, { isPublic: true }],
|
|
96
|
+
});
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Rules
|
|
100
|
+
|
|
101
|
+
- `updateOne` requires a function, not an object
|
|
102
|
+
- Use `atomic.update` for bulk/silent updates
|
|
103
|
+
- Use `when()` helper for conditional query building
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: hive-endpoint
|
|
3
|
+
description: How to create API endpoints in Hive framework
|
|
4
|
+
globs:
|
|
5
|
+
- src/resources/**/endpoints/*.js
|
|
6
|
+
alwaysApply: false
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Creating Endpoints
|
|
10
|
+
|
|
11
|
+
Location: `/src/resources/{name}/endpoints/{action}.js`
|
|
12
|
+
|
|
13
|
+
## Template (4 Required Exports)
|
|
14
|
+
|
|
15
|
+
```javascript
|
|
16
|
+
import { z } from 'zod';
|
|
17
|
+
import db from 'db';
|
|
18
|
+
|
|
19
|
+
const myService = db.services.myResource;
|
|
20
|
+
|
|
21
|
+
export const handler = async (ctx) => {
|
|
22
|
+
const { param } = ctx.validatedData;
|
|
23
|
+
// Logic here
|
|
24
|
+
return { result: 'data' };
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const middlewares = [];
|
|
28
|
+
|
|
29
|
+
export const endpoint = {
|
|
30
|
+
url: '/',
|
|
31
|
+
method: 'get',
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const requestSchema = z.object({
|
|
35
|
+
param: z.coerce.string().nullable().optional(),
|
|
36
|
+
});
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Handler Context (ctx)
|
|
40
|
+
|
|
41
|
+
```javascript
|
|
42
|
+
ctx.validatedData // Validated input (body + query + params)
|
|
43
|
+
ctx.state.user // Authenticated user
|
|
44
|
+
ctx.params // URL params
|
|
45
|
+
ctx.throw(404, 'msg') // Throw error
|
|
46
|
+
ctx.assert(cond, 400) // Assert or throw
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Common Patterns
|
|
50
|
+
|
|
51
|
+
**List:**
|
|
52
|
+
```javascript
|
|
53
|
+
export const handler = async (ctx) => {
|
|
54
|
+
const { page, perPage } = ctx.validatedData;
|
|
55
|
+
const { results, count } = await service.find(
|
|
56
|
+
{ 'user._id': ctx.state.user._id },
|
|
57
|
+
{ page, perPage, sort: '-createdOn' }
|
|
58
|
+
);
|
|
59
|
+
return { results, count };
|
|
60
|
+
};
|
|
61
|
+
export const endpoint = { url: '/', method: 'get' };
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**Create:**
|
|
65
|
+
```javascript
|
|
66
|
+
export const handler = async (ctx) => {
|
|
67
|
+
const doc = await service.create({
|
|
68
|
+
...ctx.validatedData,
|
|
69
|
+
user: ctx.state.user,
|
|
70
|
+
});
|
|
71
|
+
return doc;
|
|
72
|
+
};
|
|
73
|
+
export const endpoint = { url: '/', method: 'post' };
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**Update:**
|
|
77
|
+
```javascript
|
|
78
|
+
export const handler = async (ctx) => {
|
|
79
|
+
const { id, title } = ctx.validatedData;
|
|
80
|
+
return await service.updateOne({ _id: id }, (doc) => ({ ...doc, title }));
|
|
81
|
+
};
|
|
82
|
+
export const endpoint = { url: '/:id', method: 'put' };
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Middlewares
|
|
86
|
+
|
|
87
|
+
```javascript
|
|
88
|
+
// By name
|
|
89
|
+
export const middlewares = ['allowNoAuth'];
|
|
90
|
+
|
|
91
|
+
// With args
|
|
92
|
+
export const middlewares = [{ name: 'shouldExist', args: ['projects'] }];
|
|
93
|
+
|
|
94
|
+
// Direct function
|
|
95
|
+
import canEditProject from 'middlewares/canEditProject';
|
|
96
|
+
export const middlewares = [canEditProject((ctx) => ctx.params.projectId)];
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Public Endpoint (No Auth)
|
|
100
|
+
|
|
101
|
+
```javascript
|
|
102
|
+
export const middlewares = ['allowNoAuth'];
|
|
103
|
+
```
|