servcraft 0.1.0 → 0.1.3
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/.claude/settings.local.json +30 -0
- package/.github/CODEOWNERS +18 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +46 -0
- package/.github/dependabot.yml +59 -0
- package/.github/workflows/ci.yml +188 -0
- package/.github/workflows/release.yml +195 -0
- package/AUDIT.md +602 -0
- package/LICENSE +21 -0
- package/README.md +1102 -1
- package/dist/cli/index.cjs +2026 -2168
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +2026 -2168
- package/dist/cli/index.js.map +1 -1
- package/dist/index.cjs +595 -616
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +114 -52
- package/dist/index.d.ts +114 -52
- package/dist/index.js +595 -616
- package/dist/index.js.map +1 -1
- package/docs/CLI-001_MULTI_DB_PLAN.md +546 -0
- package/docs/DATABASE_MULTI_ORM.md +399 -0
- package/docs/PHASE1_BREAKDOWN.md +346 -0
- package/docs/PROGRESS.md +550 -0
- package/docs/modules/ANALYTICS.md +226 -0
- package/docs/modules/API-VERSIONING.md +252 -0
- package/docs/modules/AUDIT.md +192 -0
- package/docs/modules/AUTH.md +431 -0
- package/docs/modules/CACHE.md +346 -0
- package/docs/modules/EMAIL.md +254 -0
- package/docs/modules/FEATURE-FLAG.md +291 -0
- package/docs/modules/I18N.md +294 -0
- package/docs/modules/MEDIA-PROCESSING.md +281 -0
- package/docs/modules/MFA.md +266 -0
- package/docs/modules/NOTIFICATION.md +311 -0
- package/docs/modules/OAUTH.md +237 -0
- package/docs/modules/PAYMENT.md +804 -0
- package/docs/modules/QUEUE.md +540 -0
- package/docs/modules/RATE-LIMIT.md +339 -0
- package/docs/modules/SEARCH.md +288 -0
- package/docs/modules/SECURITY.md +327 -0
- package/docs/modules/SESSION.md +382 -0
- package/docs/modules/SWAGGER.md +305 -0
- package/docs/modules/UPLOAD.md +296 -0
- package/docs/modules/USER.md +505 -0
- package/docs/modules/VALIDATION.md +294 -0
- package/docs/modules/WEBHOOK.md +270 -0
- package/docs/modules/WEBSOCKET.md +691 -0
- package/package.json +53 -38
- package/prisma/schema.prisma +395 -1
- package/src/cli/commands/add-module.ts +520 -87
- package/src/cli/commands/db.ts +3 -4
- package/src/cli/commands/docs.ts +256 -6
- package/src/cli/commands/generate.ts +12 -19
- package/src/cli/commands/init.ts +384 -214
- package/src/cli/index.ts +0 -4
- package/src/cli/templates/repository.ts +6 -1
- package/src/cli/templates/routes.ts +6 -21
- package/src/cli/utils/docs-generator.ts +6 -7
- package/src/cli/utils/env-manager.ts +717 -0
- package/src/cli/utils/field-parser.ts +16 -7
- package/src/cli/utils/interactive-prompt.ts +223 -0
- package/src/cli/utils/template-manager.ts +346 -0
- package/src/config/database.config.ts +183 -0
- package/src/config/env.ts +0 -10
- package/src/config/index.ts +0 -14
- package/src/core/server.ts +1 -1
- package/src/database/adapters/mongoose.adapter.ts +132 -0
- package/src/database/adapters/prisma.adapter.ts +118 -0
- package/src/database/connection.ts +190 -0
- package/src/database/interfaces/database.interface.ts +85 -0
- package/src/database/interfaces/index.ts +7 -0
- package/src/database/interfaces/repository.interface.ts +129 -0
- package/src/database/models/mongoose/index.ts +7 -0
- package/src/database/models/mongoose/payment.schema.ts +347 -0
- package/src/database/models/mongoose/user.schema.ts +154 -0
- package/src/database/prisma.ts +1 -4
- package/src/database/redis.ts +101 -0
- package/src/database/repositories/mongoose/index.ts +7 -0
- package/src/database/repositories/mongoose/payment.repository.ts +380 -0
- package/src/database/repositories/mongoose/user.repository.ts +255 -0
- package/src/database/seed.ts +6 -1
- package/src/index.ts +9 -20
- package/src/middleware/security.ts +2 -6
- package/src/modules/analytics/analytics.routes.ts +80 -0
- package/src/modules/analytics/analytics.service.ts +364 -0
- package/src/modules/analytics/index.ts +18 -0
- package/src/modules/analytics/types.ts +180 -0
- package/src/modules/api-versioning/index.ts +15 -0
- package/src/modules/api-versioning/types.ts +86 -0
- package/src/modules/api-versioning/versioning.middleware.ts +120 -0
- package/src/modules/api-versioning/versioning.routes.ts +54 -0
- package/src/modules/api-versioning/versioning.service.ts +189 -0
- package/src/modules/audit/audit.repository.ts +206 -0
- package/src/modules/audit/audit.service.ts +27 -59
- package/src/modules/auth/auth.controller.ts +2 -2
- package/src/modules/auth/auth.middleware.ts +3 -9
- package/src/modules/auth/auth.routes.ts +10 -107
- package/src/modules/auth/auth.service.ts +126 -23
- package/src/modules/auth/index.ts +3 -4
- package/src/modules/cache/cache.service.ts +367 -0
- package/src/modules/cache/index.ts +10 -0
- package/src/modules/cache/types.ts +44 -0
- package/src/modules/email/email.service.ts +3 -10
- package/src/modules/email/templates.ts +2 -8
- package/src/modules/feature-flag/feature-flag.repository.ts +303 -0
- package/src/modules/feature-flag/feature-flag.routes.ts +247 -0
- package/src/modules/feature-flag/feature-flag.service.ts +566 -0
- package/src/modules/feature-flag/index.ts +20 -0
- package/src/modules/feature-flag/types.ts +192 -0
- package/src/modules/i18n/i18n.middleware.ts +186 -0
- package/src/modules/i18n/i18n.routes.ts +191 -0
- package/src/modules/i18n/i18n.service.ts +456 -0
- package/src/modules/i18n/index.ts +18 -0
- package/src/modules/i18n/types.ts +118 -0
- package/src/modules/media-processing/index.ts +17 -0
- package/src/modules/media-processing/media-processing.routes.ts +111 -0
- package/src/modules/media-processing/media-processing.service.ts +245 -0
- package/src/modules/media-processing/types.ts +156 -0
- package/src/modules/mfa/index.ts +20 -0
- package/src/modules/mfa/mfa.repository.ts +206 -0
- package/src/modules/mfa/mfa.routes.ts +595 -0
- package/src/modules/mfa/mfa.service.ts +572 -0
- package/src/modules/mfa/totp.ts +150 -0
- package/src/modules/mfa/types.ts +57 -0
- package/src/modules/notification/index.ts +20 -0
- package/src/modules/notification/notification.repository.ts +356 -0
- package/src/modules/notification/notification.service.ts +483 -0
- package/src/modules/notification/types.ts +119 -0
- package/src/modules/oauth/index.ts +20 -0
- package/src/modules/oauth/oauth.repository.ts +219 -0
- package/src/modules/oauth/oauth.routes.ts +446 -0
- package/src/modules/oauth/oauth.service.ts +293 -0
- package/src/modules/oauth/providers/apple.provider.ts +250 -0
- package/src/modules/oauth/providers/facebook.provider.ts +181 -0
- package/src/modules/oauth/providers/github.provider.ts +248 -0
- package/src/modules/oauth/providers/google.provider.ts +189 -0
- package/src/modules/oauth/providers/twitter.provider.ts +214 -0
- package/src/modules/oauth/types.ts +94 -0
- package/src/modules/payment/index.ts +19 -0
- package/src/modules/payment/payment.repository.ts +733 -0
- package/src/modules/payment/payment.routes.ts +390 -0
- package/src/modules/payment/payment.service.ts +354 -0
- package/src/modules/payment/providers/mobile-money.provider.ts +274 -0
- package/src/modules/payment/providers/paypal.provider.ts +190 -0
- package/src/modules/payment/providers/stripe.provider.ts +215 -0
- package/src/modules/payment/types.ts +140 -0
- package/src/modules/queue/cron.ts +438 -0
- package/src/modules/queue/index.ts +87 -0
- package/src/modules/queue/queue.routes.ts +600 -0
- package/src/modules/queue/queue.service.ts +842 -0
- package/src/modules/queue/types.ts +222 -0
- package/src/modules/queue/workers.ts +366 -0
- package/src/modules/rate-limit/index.ts +59 -0
- package/src/modules/rate-limit/rate-limit.middleware.ts +134 -0
- package/src/modules/rate-limit/rate-limit.routes.ts +269 -0
- package/src/modules/rate-limit/rate-limit.service.ts +348 -0
- package/src/modules/rate-limit/stores/memory.store.ts +165 -0
- package/src/modules/rate-limit/stores/redis.store.ts +322 -0
- package/src/modules/rate-limit/types.ts +153 -0
- package/src/modules/search/adapters/elasticsearch.adapter.ts +326 -0
- package/src/modules/search/adapters/meilisearch.adapter.ts +261 -0
- package/src/modules/search/adapters/memory.adapter.ts +278 -0
- package/src/modules/search/index.ts +21 -0
- package/src/modules/search/search.service.ts +234 -0
- package/src/modules/search/types.ts +214 -0
- package/src/modules/security/index.ts +40 -0
- package/src/modules/security/sanitize.ts +223 -0
- package/src/modules/security/security-audit.service.ts +388 -0
- package/src/modules/security/security.middleware.ts +398 -0
- package/src/modules/session/index.ts +3 -0
- package/src/modules/session/session.repository.ts +159 -0
- package/src/modules/session/session.service.ts +340 -0
- package/src/modules/session/types.ts +38 -0
- package/src/modules/swagger/index.ts +7 -1
- package/src/modules/swagger/schema-builder.ts +16 -4
- package/src/modules/swagger/swagger.service.ts +9 -10
- package/src/modules/swagger/types.ts +0 -2
- package/src/modules/upload/index.ts +14 -0
- package/src/modules/upload/types.ts +83 -0
- package/src/modules/upload/upload.repository.ts +199 -0
- package/src/modules/upload/upload.routes.ts +311 -0
- package/src/modules/upload/upload.service.ts +448 -0
- package/src/modules/user/index.ts +3 -3
- package/src/modules/user/user.controller.ts +15 -9
- package/src/modules/user/user.repository.ts +237 -113
- package/src/modules/user/user.routes.ts +39 -164
- package/src/modules/user/user.service.ts +4 -3
- package/src/modules/validation/validator.ts +12 -17
- package/src/modules/webhook/index.ts +91 -0
- package/src/modules/webhook/retry.ts +196 -0
- package/src/modules/webhook/signature.ts +135 -0
- package/src/modules/webhook/types.ts +181 -0
- package/src/modules/webhook/webhook.repository.ts +358 -0
- package/src/modules/webhook/webhook.routes.ts +442 -0
- package/src/modules/webhook/webhook.service.ts +457 -0
- package/src/modules/websocket/features.ts +504 -0
- package/src/modules/websocket/index.ts +106 -0
- package/src/modules/websocket/middlewares.ts +298 -0
- package/src/modules/websocket/types.ts +181 -0
- package/src/modules/websocket/websocket.service.ts +692 -0
- package/src/utils/errors.ts +7 -0
- package/src/utils/pagination.ts +4 -1
- package/tests/helpers/db-check.ts +79 -0
- package/tests/integration/auth-redis.test.ts +94 -0
- package/tests/integration/cache-redis.test.ts +387 -0
- package/tests/integration/mongoose-repositories.test.ts +410 -0
- package/tests/integration/payment-prisma.test.ts +637 -0
- package/tests/integration/queue-bullmq.test.ts +417 -0
- package/tests/integration/user-prisma.test.ts +441 -0
- package/tests/integration/websocket-socketio.test.ts +552 -0
- package/tests/setup.ts +11 -9
- package/vitest.config.ts +3 -8
- package/npm-cache/_cacache/content-v2/sha512/1c/d0/03440d500a0487621aad1d6402978340698976602046db8e24fa03c01ee6c022c69b0582f969042d9442ee876ac35c038e960dd427d1e622fa24b8eb7dba +0 -0
- package/npm-cache/_cacache/content-v2/sha512/42/55/28b493ca491833e5aab0e9c3108d29ab3f36c248ca88f45d4630674fce9130959e56ae308797ac2b6328fa7f09a610b9550ed09cb971d039876d293fc69d +0 -0
- package/npm-cache/_cacache/content-v2/sha512/e0/12/f360dc9315ee5f17844a0c8c233ee6bf7c30837c4a02ea0d56c61c7f7ab21c0e958e50ed2c57c59f983c762b93056778c9009b2398ffc26def0183999b13 +0 -0
- package/npm-cache/_cacache/content-v2/sha512/ed/b0/fae1161902898f4c913c67d7f6cdf6be0665aec3b389b9c4f4f0a101ca1da59badf1b59c4e0030f5223023b8d63cfe501c46a32c20c895d4fb3f11ca2232 +0 -0
- package/npm-cache/_cacache/index-v5/58/94/c2cba79e0f16b4c10e95a87e32255741149e8222cc314a476aab67c39cc0 +0 -5
|
@@ -0,0 +1,600 @@
|
|
|
1
|
+
import type { Response } from 'express';
|
|
2
|
+
import { Router, type Request } from 'express';
|
|
3
|
+
import type { QueueService } from './queue.service.js';
|
|
4
|
+
import type { CronJobManager } from './cron.js';
|
|
5
|
+
import type { JobStatus, JobOptions } from './types.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Helper to get required param or send error
|
|
9
|
+
*/
|
|
10
|
+
function getRequiredParam(req: Request, res: Response, paramName: string): string | null {
|
|
11
|
+
const value = req.params[paramName];
|
|
12
|
+
if (!value) {
|
|
13
|
+
res.status(400).json({ error: 'Bad Request', message: `${paramName} parameter is required` });
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
return value;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Create queue management routes
|
|
21
|
+
* These routes should be protected with authentication/authorization
|
|
22
|
+
*/
|
|
23
|
+
export function createQueueRoutes(queueService: QueueService, cronManager: CronJobManager): Router {
|
|
24
|
+
const router = Router();
|
|
25
|
+
|
|
26
|
+
// Queue Management
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* GET /queues
|
|
30
|
+
* List all queues
|
|
31
|
+
*/
|
|
32
|
+
router.get('/queues', async (_req: Request, res: Response) => {
|
|
33
|
+
try {
|
|
34
|
+
const queues = await queueService.listQueues();
|
|
35
|
+
|
|
36
|
+
res.json({
|
|
37
|
+
success: true,
|
|
38
|
+
data: queues,
|
|
39
|
+
count: queues.length,
|
|
40
|
+
});
|
|
41
|
+
} catch (error) {
|
|
42
|
+
console.error('[QueueRoutes] Error listing queues:', error);
|
|
43
|
+
res.status(500).json({
|
|
44
|
+
error: 'Internal Server Error',
|
|
45
|
+
message: 'Failed to list queues',
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* GET /queues/:name/stats
|
|
52
|
+
* Get queue statistics
|
|
53
|
+
*/
|
|
54
|
+
router.get('/queues/:name/stats', async (req: Request, res: Response) => {
|
|
55
|
+
const name = getRequiredParam(req, res, 'name');
|
|
56
|
+
if (!name) return;
|
|
57
|
+
try {
|
|
58
|
+
const stats = await queueService.getStats(name);
|
|
59
|
+
|
|
60
|
+
res.json({
|
|
61
|
+
success: true,
|
|
62
|
+
data: stats,
|
|
63
|
+
});
|
|
64
|
+
} catch (error) {
|
|
65
|
+
if (error instanceof Error && error.message.includes('not found')) {
|
|
66
|
+
res.status(404).json({
|
|
67
|
+
error: 'Not Found',
|
|
68
|
+
message: 'Queue not found',
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
console.error('[QueueRoutes] Error getting queue stats:', error);
|
|
73
|
+
res.status(500).json({
|
|
74
|
+
error: 'Internal Server Error',
|
|
75
|
+
message: 'Failed to get queue statistics',
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* GET /queues/:name/metrics
|
|
82
|
+
* Get queue metrics
|
|
83
|
+
*/
|
|
84
|
+
router.get('/queues/:name/metrics', async (req: Request, res: Response) => {
|
|
85
|
+
try {
|
|
86
|
+
const name = getRequiredParam(req, res, 'name');
|
|
87
|
+
if (!name) return;
|
|
88
|
+
const metrics = await queueService.getMetrics(name);
|
|
89
|
+
|
|
90
|
+
res.json({
|
|
91
|
+
success: true,
|
|
92
|
+
data: metrics,
|
|
93
|
+
});
|
|
94
|
+
} catch (error) {
|
|
95
|
+
if (error instanceof Error && error.message.includes('not found')) {
|
|
96
|
+
res.status(404).json({
|
|
97
|
+
error: 'Not Found',
|
|
98
|
+
message: 'Queue not found or metrics disabled',
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
console.error('[QueueRoutes] Error getting queue metrics:', error);
|
|
103
|
+
res.status(500).json({
|
|
104
|
+
error: 'Internal Server Error',
|
|
105
|
+
message: 'Failed to get queue metrics',
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* POST /queues/:name/pause
|
|
112
|
+
* Pause a queue
|
|
113
|
+
*/
|
|
114
|
+
router.post('/queues/:name/pause', async (req: Request, res: Response) => {
|
|
115
|
+
try {
|
|
116
|
+
const name = getRequiredParam(req, res, 'name');
|
|
117
|
+
if (!name) return;
|
|
118
|
+
await queueService.pauseQueue(name);
|
|
119
|
+
|
|
120
|
+
res.json({
|
|
121
|
+
success: true,
|
|
122
|
+
message: `Queue ${name} paused`,
|
|
123
|
+
});
|
|
124
|
+
} catch (error) {
|
|
125
|
+
if (error instanceof Error && error.message.includes('not found')) {
|
|
126
|
+
res.status(404).json({
|
|
127
|
+
error: 'Not Found',
|
|
128
|
+
message: 'Queue not found',
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
console.error('[QueueRoutes] Error pausing queue:', error);
|
|
133
|
+
res.status(500).json({
|
|
134
|
+
error: 'Internal Server Error',
|
|
135
|
+
message: 'Failed to pause queue',
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* POST /queues/:name/resume
|
|
142
|
+
* Resume a paused queue
|
|
143
|
+
*/
|
|
144
|
+
router.post('/queues/:name/resume', async (req: Request, res: Response) => {
|
|
145
|
+
try {
|
|
146
|
+
const name = getRequiredParam(req, res, 'name');
|
|
147
|
+
if (!name) return;
|
|
148
|
+
await queueService.resumeQueue(name);
|
|
149
|
+
|
|
150
|
+
res.json({
|
|
151
|
+
success: true,
|
|
152
|
+
message: `Queue ${name} resumed`,
|
|
153
|
+
});
|
|
154
|
+
} catch (error) {
|
|
155
|
+
if (error instanceof Error && error.message.includes('not found')) {
|
|
156
|
+
res.status(404).json({
|
|
157
|
+
error: 'Not Found',
|
|
158
|
+
message: 'Queue not found',
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
console.error('[QueueRoutes] Error resuming queue:', error);
|
|
163
|
+
res.status(500).json({
|
|
164
|
+
error: 'Internal Server Error',
|
|
165
|
+
message: 'Failed to resume queue',
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// Job Management
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* POST /queues/:name/jobs
|
|
174
|
+
* Add a job to queue
|
|
175
|
+
*/
|
|
176
|
+
router.post('/queues/:name/jobs', async (req: Request, res: Response) => {
|
|
177
|
+
try {
|
|
178
|
+
const name = getRequiredParam(req, res, 'name');
|
|
179
|
+
if (!name) return;
|
|
180
|
+
const { jobName, data, options } = req.body;
|
|
181
|
+
|
|
182
|
+
if (!jobName || !data) {
|
|
183
|
+
res.status(400).json({
|
|
184
|
+
error: 'Bad Request',
|
|
185
|
+
message: 'jobName and data are required',
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const job = await queueService.addJob(name, jobName, data, options as JobOptions);
|
|
190
|
+
|
|
191
|
+
res.status(201).json({
|
|
192
|
+
success: true,
|
|
193
|
+
data: job,
|
|
194
|
+
});
|
|
195
|
+
} catch (error) {
|
|
196
|
+
console.error('[QueueRoutes] Error adding job:', error);
|
|
197
|
+
res.status(500).json({
|
|
198
|
+
error: 'Internal Server Error',
|
|
199
|
+
message: error instanceof Error ? error.message : 'Failed to add job',
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* POST /queues/:name/jobs/bulk
|
|
206
|
+
* Add multiple jobs to queue
|
|
207
|
+
*/
|
|
208
|
+
router.post('/queues/:name/jobs/bulk', async (req: Request, res: Response) => {
|
|
209
|
+
try {
|
|
210
|
+
const name = getRequiredParam(req, res, 'name');
|
|
211
|
+
if (!name) return;
|
|
212
|
+
const { jobs } = req.body;
|
|
213
|
+
|
|
214
|
+
if (!jobs || !Array.isArray(jobs)) {
|
|
215
|
+
res.status(400).json({
|
|
216
|
+
error: 'Bad Request',
|
|
217
|
+
message: 'jobs array is required',
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const addedJobs = await queueService.addBulkJobs(name, { jobs });
|
|
222
|
+
|
|
223
|
+
res.status(201).json({
|
|
224
|
+
success: true,
|
|
225
|
+
data: addedJobs,
|
|
226
|
+
count: addedJobs.length,
|
|
227
|
+
});
|
|
228
|
+
} catch (error) {
|
|
229
|
+
console.error('[QueueRoutes] Error adding bulk jobs:', error);
|
|
230
|
+
res.status(500).json({
|
|
231
|
+
error: 'Internal Server Error',
|
|
232
|
+
message: 'Failed to add bulk jobs',
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* GET /queues/:name/jobs
|
|
239
|
+
* List jobs in queue with filters
|
|
240
|
+
*/
|
|
241
|
+
router.get('/queues/:name/jobs', async (req: Request, res: Response) => {
|
|
242
|
+
try {
|
|
243
|
+
const name = getRequiredParam(req, res, 'name');
|
|
244
|
+
if (!name) return;
|
|
245
|
+
const { status, jobName, limit, offset, startDate, endDate } = req.query;
|
|
246
|
+
|
|
247
|
+
const jobs = await queueService.listJobs(name, {
|
|
248
|
+
status: status as JobStatus | JobStatus[],
|
|
249
|
+
name: jobName as string,
|
|
250
|
+
limit: limit ? parseInt(limit as string, 10) : undefined,
|
|
251
|
+
offset: offset ? parseInt(offset as string, 10) : undefined,
|
|
252
|
+
startDate: startDate ? new Date(startDate as string) : undefined,
|
|
253
|
+
endDate: endDate ? new Date(endDate as string) : undefined,
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
res.json({
|
|
257
|
+
success: true,
|
|
258
|
+
data: jobs,
|
|
259
|
+
count: jobs.length,
|
|
260
|
+
});
|
|
261
|
+
} catch (error) {
|
|
262
|
+
if (error instanceof Error && error.message.includes('not found')) {
|
|
263
|
+
res.status(404).json({
|
|
264
|
+
error: 'Not Found',
|
|
265
|
+
message: 'Queue not found',
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
console.error('[QueueRoutes] Error listing jobs:', error);
|
|
270
|
+
res.status(500).json({
|
|
271
|
+
error: 'Internal Server Error',
|
|
272
|
+
message: 'Failed to list jobs',
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* GET /queues/:name/jobs/:id
|
|
279
|
+
* Get a specific job
|
|
280
|
+
*/
|
|
281
|
+
router.get('/queues/:name/jobs/:id', async (req: Request, res: Response) => {
|
|
282
|
+
const name = getRequiredParam(req, res, 'name');
|
|
283
|
+
const id = getRequiredParam(req, res, 'id');
|
|
284
|
+
if (!name || !id) return;
|
|
285
|
+
try {
|
|
286
|
+
const job = await queueService.getJob(name, id);
|
|
287
|
+
|
|
288
|
+
res.json({
|
|
289
|
+
success: true,
|
|
290
|
+
data: job,
|
|
291
|
+
});
|
|
292
|
+
} catch (error) {
|
|
293
|
+
if (error instanceof Error && error.message.includes('not found')) {
|
|
294
|
+
res.status(404).json({
|
|
295
|
+
error: 'Not Found',
|
|
296
|
+
message: 'Job or queue not found',
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
console.error('[QueueRoutes] Error getting job:', error);
|
|
301
|
+
res.status(500).json({
|
|
302
|
+
error: 'Internal Server Error',
|
|
303
|
+
message: 'Failed to get job',
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* DELETE /queues/:name/jobs/:id
|
|
310
|
+
* Remove a job
|
|
311
|
+
*/
|
|
312
|
+
router.delete('/queues/:name/jobs/:id', async (req: Request, res: Response) => {
|
|
313
|
+
const name = getRequiredParam(req, res, 'name');
|
|
314
|
+
const id = getRequiredParam(req, res, 'id');
|
|
315
|
+
if (!name || !id) return;
|
|
316
|
+
try {
|
|
317
|
+
await queueService.removeJob(name, id);
|
|
318
|
+
|
|
319
|
+
res.json({
|
|
320
|
+
success: true,
|
|
321
|
+
message: 'Job removed',
|
|
322
|
+
});
|
|
323
|
+
} catch (error) {
|
|
324
|
+
if (error instanceof Error && error.message.includes('not found')) {
|
|
325
|
+
res.status(404).json({
|
|
326
|
+
error: 'Not Found',
|
|
327
|
+
message: 'Job or queue not found',
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (error instanceof Error && error.message.includes('Cannot remove')) {
|
|
332
|
+
res.status(400).json({
|
|
333
|
+
error: 'Bad Request',
|
|
334
|
+
message: error.message,
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
console.error('[QueueRoutes] Error removing job:', error);
|
|
339
|
+
res.status(500).json({
|
|
340
|
+
error: 'Internal Server Error',
|
|
341
|
+
message: 'Failed to remove job',
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* POST /queues/:name/jobs/:id/retry
|
|
348
|
+
* Retry a failed job
|
|
349
|
+
*/
|
|
350
|
+
router.post('/queues/:name/jobs/:id/retry', async (req: Request, res: Response) => {
|
|
351
|
+
const name = getRequiredParam(req, res, 'name');
|
|
352
|
+
const id = getRequiredParam(req, res, 'id');
|
|
353
|
+
if (!name || !id) return;
|
|
354
|
+
try {
|
|
355
|
+
await queueService.retryJob(name, id);
|
|
356
|
+
|
|
357
|
+
res.json({
|
|
358
|
+
success: true,
|
|
359
|
+
message: 'Job retry initiated',
|
|
360
|
+
});
|
|
361
|
+
} catch (error) {
|
|
362
|
+
if (error instanceof Error && error.message.includes('not found')) {
|
|
363
|
+
res.status(404).json({
|
|
364
|
+
error: 'Not Found',
|
|
365
|
+
message: 'Job or queue not found',
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (error instanceof Error && error.message.includes('Can only retry')) {
|
|
370
|
+
res.status(400).json({
|
|
371
|
+
error: 'Bad Request',
|
|
372
|
+
message: error.message,
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
console.error('[QueueRoutes] Error retrying job:', error);
|
|
377
|
+
res.status(500).json({
|
|
378
|
+
error: 'Internal Server Error',
|
|
379
|
+
message: 'Failed to retry job',
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* POST /queues/:name/clean
|
|
386
|
+
* Clean completed/failed jobs
|
|
387
|
+
*/
|
|
388
|
+
router.post('/queues/:name/clean', async (req: Request, res: Response) => {
|
|
389
|
+
try {
|
|
390
|
+
const name = getRequiredParam(req, res, 'name');
|
|
391
|
+
if (!name) return;
|
|
392
|
+
const { status = 'completed', olderThanMs = 86400000 } = req.body;
|
|
393
|
+
|
|
394
|
+
const cleaned = await queueService.cleanJobs(name, status as JobStatus, olderThanMs);
|
|
395
|
+
|
|
396
|
+
res.json({
|
|
397
|
+
success: true,
|
|
398
|
+
message: `Cleaned ${cleaned} jobs`,
|
|
399
|
+
cleaned,
|
|
400
|
+
});
|
|
401
|
+
} catch (error) {
|
|
402
|
+
if (error instanceof Error && error.message.includes('not found')) {
|
|
403
|
+
res.status(404).json({
|
|
404
|
+
error: 'Not Found',
|
|
405
|
+
message: 'Queue not found',
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
console.error('[QueueRoutes] Error cleaning jobs:', error);
|
|
410
|
+
res.status(500).json({
|
|
411
|
+
error: 'Internal Server Error',
|
|
412
|
+
message: 'Failed to clean jobs',
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
// Cron Jobs
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* POST /cron
|
|
421
|
+
* Create a cron job
|
|
422
|
+
*/
|
|
423
|
+
router.post('/cron', async (req: Request, res: Response) => {
|
|
424
|
+
try {
|
|
425
|
+
const { name, cron, queueName, jobName, data, options } = req.body;
|
|
426
|
+
|
|
427
|
+
if (!name || !cron || !queueName || !jobName) {
|
|
428
|
+
res.status(400).json({
|
|
429
|
+
error: 'Bad Request',
|
|
430
|
+
message: 'name, cron, queueName, and jobName are required',
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
const cronJob = await cronManager.createCronJob(
|
|
435
|
+
name,
|
|
436
|
+
cron,
|
|
437
|
+
queueName,
|
|
438
|
+
jobName,
|
|
439
|
+
data,
|
|
440
|
+
options
|
|
441
|
+
);
|
|
442
|
+
|
|
443
|
+
res.status(201).json({
|
|
444
|
+
success: true,
|
|
445
|
+
data: cronJob,
|
|
446
|
+
});
|
|
447
|
+
} catch (error) {
|
|
448
|
+
console.error('[QueueRoutes] Error creating cron job:', error);
|
|
449
|
+
res.status(500).json({
|
|
450
|
+
error: 'Internal Server Error',
|
|
451
|
+
message: error instanceof Error ? error.message : 'Failed to create cron job',
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* GET /cron
|
|
458
|
+
* List all cron jobs
|
|
459
|
+
*/
|
|
460
|
+
router.get('/cron', async (_req: Request, res: Response) => {
|
|
461
|
+
try {
|
|
462
|
+
const cronJobs = await cronManager.listCronJobs();
|
|
463
|
+
|
|
464
|
+
res.json({
|
|
465
|
+
success: true,
|
|
466
|
+
data: cronJobs,
|
|
467
|
+
count: cronJobs.length,
|
|
468
|
+
});
|
|
469
|
+
} catch (error) {
|
|
470
|
+
console.error('[QueueRoutes] Error listing cron jobs:', error);
|
|
471
|
+
res.status(500).json({
|
|
472
|
+
error: 'Internal Server Error',
|
|
473
|
+
message: 'Failed to list cron jobs',
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* GET /cron/:id
|
|
480
|
+
* Get a specific cron job
|
|
481
|
+
*/
|
|
482
|
+
router.get('/cron/:id', async (req: Request, res: Response) => {
|
|
483
|
+
const id = getRequiredParam(req, res, 'id');
|
|
484
|
+
if (!id) return;
|
|
485
|
+
try {
|
|
486
|
+
const cronJob = await cronManager.getCronJob(id);
|
|
487
|
+
|
|
488
|
+
res.json({
|
|
489
|
+
success: true,
|
|
490
|
+
data: cronJob,
|
|
491
|
+
});
|
|
492
|
+
} catch (error) {
|
|
493
|
+
if (error instanceof Error && error.message.includes('not found')) {
|
|
494
|
+
res.status(404).json({
|
|
495
|
+
error: 'Not Found',
|
|
496
|
+
message: 'Cron job not found',
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
console.error('[QueueRoutes] Error getting cron job:', error);
|
|
501
|
+
res.status(500).json({
|
|
502
|
+
error: 'Internal Server Error',
|
|
503
|
+
message: 'Failed to get cron job',
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* PATCH /cron/:id
|
|
510
|
+
* Update a cron job
|
|
511
|
+
*/
|
|
512
|
+
router.patch('/cron/:id', async (req: Request, res: Response) => {
|
|
513
|
+
const id = getRequiredParam(req, res, 'id');
|
|
514
|
+
if (!id) return;
|
|
515
|
+
try {
|
|
516
|
+
const updates = req.body;
|
|
517
|
+
const cronJob = await cronManager.updateCronJob(id, updates);
|
|
518
|
+
|
|
519
|
+
res.json({
|
|
520
|
+
success: true,
|
|
521
|
+
data: cronJob,
|
|
522
|
+
});
|
|
523
|
+
} catch (error) {
|
|
524
|
+
if (error instanceof Error && error.message.includes('not found')) {
|
|
525
|
+
res.status(404).json({
|
|
526
|
+
error: 'Not Found',
|
|
527
|
+
message: 'Cron job not found',
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
console.error('[QueueRoutes] Error updating cron job:', error);
|
|
532
|
+
res.status(500).json({
|
|
533
|
+
error: 'Internal Server Error',
|
|
534
|
+
message: error instanceof Error ? error.message : 'Failed to update cron job',
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* DELETE /cron/:id
|
|
541
|
+
* Delete a cron job
|
|
542
|
+
*/
|
|
543
|
+
router.delete('/cron/:id', async (req: Request, res: Response) => {
|
|
544
|
+
const id = getRequiredParam(req, res, 'id');
|
|
545
|
+
if (!id) return;
|
|
546
|
+
try {
|
|
547
|
+
await cronManager.deleteCronJob(id);
|
|
548
|
+
|
|
549
|
+
res.json({
|
|
550
|
+
success: true,
|
|
551
|
+
message: 'Cron job deleted',
|
|
552
|
+
});
|
|
553
|
+
} catch (error) {
|
|
554
|
+
if (error instanceof Error && error.message.includes('not found')) {
|
|
555
|
+
res.status(404).json({
|
|
556
|
+
error: 'Not Found',
|
|
557
|
+
message: 'Cron job not found',
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
console.error('[QueueRoutes] Error deleting cron job:', error);
|
|
562
|
+
res.status(500).json({
|
|
563
|
+
error: 'Internal Server Error',
|
|
564
|
+
message: 'Failed to delete cron job',
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* POST /cron/:id/trigger
|
|
571
|
+
* Manually trigger a cron job
|
|
572
|
+
*/
|
|
573
|
+
router.post('/cron/:id/trigger', async (req: Request, res: Response) => {
|
|
574
|
+
const id = getRequiredParam(req, res, 'id');
|
|
575
|
+
if (!id) return;
|
|
576
|
+
try {
|
|
577
|
+
await cronManager.triggerCronJob(id);
|
|
578
|
+
|
|
579
|
+
res.json({
|
|
580
|
+
success: true,
|
|
581
|
+
message: 'Cron job triggered',
|
|
582
|
+
});
|
|
583
|
+
} catch (error) {
|
|
584
|
+
if (error instanceof Error && error.message.includes('not found')) {
|
|
585
|
+
res.status(404).json({
|
|
586
|
+
error: 'Not Found',
|
|
587
|
+
message: 'Cron job not found',
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
console.error('[QueueRoutes] Error triggering cron job:', error);
|
|
592
|
+
res.status(500).json({
|
|
593
|
+
error: 'Internal Server Error',
|
|
594
|
+
message: 'Failed to trigger cron job',
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
return router;
|
|
600
|
+
}
|