servcraft 0.1.0 → 0.1.1

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.
Files changed (216) hide show
  1. package/.claude/settings.local.json +29 -0
  2. package/.github/CODEOWNERS +18 -0
  3. package/.github/PULL_REQUEST_TEMPLATE.md +46 -0
  4. package/.github/dependabot.yml +59 -0
  5. package/.github/workflows/ci.yml +188 -0
  6. package/.github/workflows/release.yml +195 -0
  7. package/AUDIT.md +602 -0
  8. package/README.md +1070 -1
  9. package/dist/cli/index.cjs +2026 -2168
  10. package/dist/cli/index.cjs.map +1 -1
  11. package/dist/cli/index.js +2026 -2168
  12. package/dist/cli/index.js.map +1 -1
  13. package/dist/index.cjs +595 -616
  14. package/dist/index.cjs.map +1 -1
  15. package/dist/index.d.cts +114 -52
  16. package/dist/index.d.ts +114 -52
  17. package/dist/index.js +595 -616
  18. package/dist/index.js.map +1 -1
  19. package/docs/CLI-001_MULTI_DB_PLAN.md +546 -0
  20. package/docs/DATABASE_MULTI_ORM.md +399 -0
  21. package/docs/PHASE1_BREAKDOWN.md +346 -0
  22. package/docs/PROGRESS.md +550 -0
  23. package/docs/modules/ANALYTICS.md +226 -0
  24. package/docs/modules/API-VERSIONING.md +252 -0
  25. package/docs/modules/AUDIT.md +192 -0
  26. package/docs/modules/AUTH.md +431 -0
  27. package/docs/modules/CACHE.md +346 -0
  28. package/docs/modules/EMAIL.md +254 -0
  29. package/docs/modules/FEATURE-FLAG.md +291 -0
  30. package/docs/modules/I18N.md +294 -0
  31. package/docs/modules/MEDIA-PROCESSING.md +281 -0
  32. package/docs/modules/MFA.md +266 -0
  33. package/docs/modules/NOTIFICATION.md +311 -0
  34. package/docs/modules/OAUTH.md +237 -0
  35. package/docs/modules/PAYMENT.md +804 -0
  36. package/docs/modules/QUEUE.md +540 -0
  37. package/docs/modules/RATE-LIMIT.md +339 -0
  38. package/docs/modules/SEARCH.md +288 -0
  39. package/docs/modules/SECURITY.md +327 -0
  40. package/docs/modules/SESSION.md +382 -0
  41. package/docs/modules/SWAGGER.md +305 -0
  42. package/docs/modules/UPLOAD.md +296 -0
  43. package/docs/modules/USER.md +505 -0
  44. package/docs/modules/VALIDATION.md +294 -0
  45. package/docs/modules/WEBHOOK.md +270 -0
  46. package/docs/modules/WEBSOCKET.md +691 -0
  47. package/package.json +53 -38
  48. package/prisma/schema.prisma +395 -1
  49. package/src/cli/commands/add-module.ts +520 -87
  50. package/src/cli/commands/db.ts +3 -4
  51. package/src/cli/commands/docs.ts +256 -6
  52. package/src/cli/commands/generate.ts +12 -19
  53. package/src/cli/commands/init.ts +384 -214
  54. package/src/cli/index.ts +0 -4
  55. package/src/cli/templates/repository.ts +6 -1
  56. package/src/cli/templates/routes.ts +6 -21
  57. package/src/cli/utils/docs-generator.ts +6 -7
  58. package/src/cli/utils/env-manager.ts +717 -0
  59. package/src/cli/utils/field-parser.ts +16 -7
  60. package/src/cli/utils/interactive-prompt.ts +223 -0
  61. package/src/cli/utils/template-manager.ts +346 -0
  62. package/src/config/database.config.ts +183 -0
  63. package/src/config/env.ts +0 -10
  64. package/src/config/index.ts +0 -14
  65. package/src/core/server.ts +1 -1
  66. package/src/database/adapters/mongoose.adapter.ts +132 -0
  67. package/src/database/adapters/prisma.adapter.ts +118 -0
  68. package/src/database/connection.ts +190 -0
  69. package/src/database/interfaces/database.interface.ts +85 -0
  70. package/src/database/interfaces/index.ts +7 -0
  71. package/src/database/interfaces/repository.interface.ts +129 -0
  72. package/src/database/models/mongoose/index.ts +7 -0
  73. package/src/database/models/mongoose/payment.schema.ts +347 -0
  74. package/src/database/models/mongoose/user.schema.ts +154 -0
  75. package/src/database/prisma.ts +1 -4
  76. package/src/database/redis.ts +101 -0
  77. package/src/database/repositories/mongoose/index.ts +7 -0
  78. package/src/database/repositories/mongoose/payment.repository.ts +380 -0
  79. package/src/database/repositories/mongoose/user.repository.ts +255 -0
  80. package/src/database/seed.ts +6 -1
  81. package/src/index.ts +9 -20
  82. package/src/middleware/security.ts +2 -6
  83. package/src/modules/analytics/analytics.routes.ts +80 -0
  84. package/src/modules/analytics/analytics.service.ts +364 -0
  85. package/src/modules/analytics/index.ts +18 -0
  86. package/src/modules/analytics/types.ts +180 -0
  87. package/src/modules/api-versioning/index.ts +15 -0
  88. package/src/modules/api-versioning/types.ts +86 -0
  89. package/src/modules/api-versioning/versioning.middleware.ts +120 -0
  90. package/src/modules/api-versioning/versioning.routes.ts +54 -0
  91. package/src/modules/api-versioning/versioning.service.ts +189 -0
  92. package/src/modules/audit/audit.repository.ts +206 -0
  93. package/src/modules/audit/audit.service.ts +27 -59
  94. package/src/modules/auth/auth.controller.ts +2 -2
  95. package/src/modules/auth/auth.middleware.ts +3 -9
  96. package/src/modules/auth/auth.routes.ts +10 -107
  97. package/src/modules/auth/auth.service.ts +126 -23
  98. package/src/modules/auth/index.ts +3 -4
  99. package/src/modules/cache/cache.service.ts +367 -0
  100. package/src/modules/cache/index.ts +10 -0
  101. package/src/modules/cache/types.ts +44 -0
  102. package/src/modules/email/email.service.ts +3 -10
  103. package/src/modules/email/templates.ts +2 -8
  104. package/src/modules/feature-flag/feature-flag.repository.ts +303 -0
  105. package/src/modules/feature-flag/feature-flag.routes.ts +247 -0
  106. package/src/modules/feature-flag/feature-flag.service.ts +566 -0
  107. package/src/modules/feature-flag/index.ts +20 -0
  108. package/src/modules/feature-flag/types.ts +192 -0
  109. package/src/modules/i18n/i18n.middleware.ts +186 -0
  110. package/src/modules/i18n/i18n.routes.ts +191 -0
  111. package/src/modules/i18n/i18n.service.ts +456 -0
  112. package/src/modules/i18n/index.ts +18 -0
  113. package/src/modules/i18n/types.ts +118 -0
  114. package/src/modules/media-processing/index.ts +17 -0
  115. package/src/modules/media-processing/media-processing.routes.ts +111 -0
  116. package/src/modules/media-processing/media-processing.service.ts +245 -0
  117. package/src/modules/media-processing/types.ts +156 -0
  118. package/src/modules/mfa/index.ts +20 -0
  119. package/src/modules/mfa/mfa.repository.ts +206 -0
  120. package/src/modules/mfa/mfa.routes.ts +595 -0
  121. package/src/modules/mfa/mfa.service.ts +572 -0
  122. package/src/modules/mfa/totp.ts +150 -0
  123. package/src/modules/mfa/types.ts +57 -0
  124. package/src/modules/notification/index.ts +20 -0
  125. package/src/modules/notification/notification.repository.ts +356 -0
  126. package/src/modules/notification/notification.service.ts +483 -0
  127. package/src/modules/notification/types.ts +119 -0
  128. package/src/modules/oauth/index.ts +20 -0
  129. package/src/modules/oauth/oauth.repository.ts +219 -0
  130. package/src/modules/oauth/oauth.routes.ts +446 -0
  131. package/src/modules/oauth/oauth.service.ts +293 -0
  132. package/src/modules/oauth/providers/apple.provider.ts +250 -0
  133. package/src/modules/oauth/providers/facebook.provider.ts +181 -0
  134. package/src/modules/oauth/providers/github.provider.ts +248 -0
  135. package/src/modules/oauth/providers/google.provider.ts +189 -0
  136. package/src/modules/oauth/providers/twitter.provider.ts +214 -0
  137. package/src/modules/oauth/types.ts +94 -0
  138. package/src/modules/payment/index.ts +19 -0
  139. package/src/modules/payment/payment.repository.ts +733 -0
  140. package/src/modules/payment/payment.routes.ts +390 -0
  141. package/src/modules/payment/payment.service.ts +354 -0
  142. package/src/modules/payment/providers/mobile-money.provider.ts +274 -0
  143. package/src/modules/payment/providers/paypal.provider.ts +190 -0
  144. package/src/modules/payment/providers/stripe.provider.ts +215 -0
  145. package/src/modules/payment/types.ts +140 -0
  146. package/src/modules/queue/cron.ts +438 -0
  147. package/src/modules/queue/index.ts +87 -0
  148. package/src/modules/queue/queue.routes.ts +600 -0
  149. package/src/modules/queue/queue.service.ts +842 -0
  150. package/src/modules/queue/types.ts +222 -0
  151. package/src/modules/queue/workers.ts +366 -0
  152. package/src/modules/rate-limit/index.ts +59 -0
  153. package/src/modules/rate-limit/rate-limit.middleware.ts +134 -0
  154. package/src/modules/rate-limit/rate-limit.routes.ts +269 -0
  155. package/src/modules/rate-limit/rate-limit.service.ts +348 -0
  156. package/src/modules/rate-limit/stores/memory.store.ts +165 -0
  157. package/src/modules/rate-limit/stores/redis.store.ts +322 -0
  158. package/src/modules/rate-limit/types.ts +153 -0
  159. package/src/modules/search/adapters/elasticsearch.adapter.ts +326 -0
  160. package/src/modules/search/adapters/meilisearch.adapter.ts +261 -0
  161. package/src/modules/search/adapters/memory.adapter.ts +278 -0
  162. package/src/modules/search/index.ts +21 -0
  163. package/src/modules/search/search.service.ts +234 -0
  164. package/src/modules/search/types.ts +214 -0
  165. package/src/modules/security/index.ts +40 -0
  166. package/src/modules/security/sanitize.ts +223 -0
  167. package/src/modules/security/security-audit.service.ts +388 -0
  168. package/src/modules/security/security.middleware.ts +398 -0
  169. package/src/modules/session/index.ts +3 -0
  170. package/src/modules/session/session.repository.ts +159 -0
  171. package/src/modules/session/session.service.ts +340 -0
  172. package/src/modules/session/types.ts +38 -0
  173. package/src/modules/swagger/index.ts +7 -1
  174. package/src/modules/swagger/schema-builder.ts +16 -4
  175. package/src/modules/swagger/swagger.service.ts +9 -10
  176. package/src/modules/swagger/types.ts +0 -2
  177. package/src/modules/upload/index.ts +14 -0
  178. package/src/modules/upload/types.ts +83 -0
  179. package/src/modules/upload/upload.repository.ts +199 -0
  180. package/src/modules/upload/upload.routes.ts +311 -0
  181. package/src/modules/upload/upload.service.ts +448 -0
  182. package/src/modules/user/index.ts +3 -3
  183. package/src/modules/user/user.controller.ts +15 -9
  184. package/src/modules/user/user.repository.ts +237 -113
  185. package/src/modules/user/user.routes.ts +39 -164
  186. package/src/modules/user/user.service.ts +4 -3
  187. package/src/modules/validation/validator.ts +12 -17
  188. package/src/modules/webhook/index.ts +91 -0
  189. package/src/modules/webhook/retry.ts +196 -0
  190. package/src/modules/webhook/signature.ts +135 -0
  191. package/src/modules/webhook/types.ts +181 -0
  192. package/src/modules/webhook/webhook.repository.ts +358 -0
  193. package/src/modules/webhook/webhook.routes.ts +442 -0
  194. package/src/modules/webhook/webhook.service.ts +457 -0
  195. package/src/modules/websocket/features.ts +504 -0
  196. package/src/modules/websocket/index.ts +106 -0
  197. package/src/modules/websocket/middlewares.ts +298 -0
  198. package/src/modules/websocket/types.ts +181 -0
  199. package/src/modules/websocket/websocket.service.ts +692 -0
  200. package/src/utils/errors.ts +7 -0
  201. package/src/utils/pagination.ts +4 -1
  202. package/tests/helpers/db-check.ts +79 -0
  203. package/tests/integration/auth-redis.test.ts +94 -0
  204. package/tests/integration/cache-redis.test.ts +387 -0
  205. package/tests/integration/mongoose-repositories.test.ts +410 -0
  206. package/tests/integration/payment-prisma.test.ts +637 -0
  207. package/tests/integration/queue-bullmq.test.ts +417 -0
  208. package/tests/integration/user-prisma.test.ts +441 -0
  209. package/tests/integration/websocket-socketio.test.ts +552 -0
  210. package/tests/setup.ts +11 -9
  211. package/vitest.config.ts +3 -8
  212. package/npm-cache/_cacache/content-v2/sha512/1c/d0/03440d500a0487621aad1d6402978340698976602046db8e24fa03c01ee6c022c69b0582f969042d9442ee876ac35c038e960dd427d1e622fa24b8eb7dba +0 -0
  213. package/npm-cache/_cacache/content-v2/sha512/42/55/28b493ca491833e5aab0e9c3108d29ab3f36c248ca88f45d4630674fce9130959e56ae308797ac2b6328fa7f09a610b9550ed09cb971d039876d293fc69d +0 -0
  214. package/npm-cache/_cacache/content-v2/sha512/e0/12/f360dc9315ee5f17844a0c8c233ee6bf7c30837c4a02ea0d56c61c7f7ab21c0e958e50ed2c57c59f983c762b93056778c9009b2398ffc26def0183999b13 +0 -0
  215. package/npm-cache/_cacache/content-v2/sha512/ed/b0/fae1161902898f4c913c67d7f6cdf6be0665aec3b389b9c4f4f0a101ca1da59badf1b59c4e0030f5223023b8d63cfe501c46a32c20c895d4fb3f11ca2232 +0 -0
  216. 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
+ }