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.
Files changed (217) hide show
  1. package/.claude/settings.local.json +30 -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/LICENSE +21 -0
  9. package/README.md +1102 -1
  10. package/dist/cli/index.cjs +2026 -2168
  11. package/dist/cli/index.cjs.map +1 -1
  12. package/dist/cli/index.js +2026 -2168
  13. package/dist/cli/index.js.map +1 -1
  14. package/dist/index.cjs +595 -616
  15. package/dist/index.cjs.map +1 -1
  16. package/dist/index.d.cts +114 -52
  17. package/dist/index.d.ts +114 -52
  18. package/dist/index.js +595 -616
  19. package/dist/index.js.map +1 -1
  20. package/docs/CLI-001_MULTI_DB_PLAN.md +546 -0
  21. package/docs/DATABASE_MULTI_ORM.md +399 -0
  22. package/docs/PHASE1_BREAKDOWN.md +346 -0
  23. package/docs/PROGRESS.md +550 -0
  24. package/docs/modules/ANALYTICS.md +226 -0
  25. package/docs/modules/API-VERSIONING.md +252 -0
  26. package/docs/modules/AUDIT.md +192 -0
  27. package/docs/modules/AUTH.md +431 -0
  28. package/docs/modules/CACHE.md +346 -0
  29. package/docs/modules/EMAIL.md +254 -0
  30. package/docs/modules/FEATURE-FLAG.md +291 -0
  31. package/docs/modules/I18N.md +294 -0
  32. package/docs/modules/MEDIA-PROCESSING.md +281 -0
  33. package/docs/modules/MFA.md +266 -0
  34. package/docs/modules/NOTIFICATION.md +311 -0
  35. package/docs/modules/OAUTH.md +237 -0
  36. package/docs/modules/PAYMENT.md +804 -0
  37. package/docs/modules/QUEUE.md +540 -0
  38. package/docs/modules/RATE-LIMIT.md +339 -0
  39. package/docs/modules/SEARCH.md +288 -0
  40. package/docs/modules/SECURITY.md +327 -0
  41. package/docs/modules/SESSION.md +382 -0
  42. package/docs/modules/SWAGGER.md +305 -0
  43. package/docs/modules/UPLOAD.md +296 -0
  44. package/docs/modules/USER.md +505 -0
  45. package/docs/modules/VALIDATION.md +294 -0
  46. package/docs/modules/WEBHOOK.md +270 -0
  47. package/docs/modules/WEBSOCKET.md +691 -0
  48. package/package.json +53 -38
  49. package/prisma/schema.prisma +395 -1
  50. package/src/cli/commands/add-module.ts +520 -87
  51. package/src/cli/commands/db.ts +3 -4
  52. package/src/cli/commands/docs.ts +256 -6
  53. package/src/cli/commands/generate.ts +12 -19
  54. package/src/cli/commands/init.ts +384 -214
  55. package/src/cli/index.ts +0 -4
  56. package/src/cli/templates/repository.ts +6 -1
  57. package/src/cli/templates/routes.ts +6 -21
  58. package/src/cli/utils/docs-generator.ts +6 -7
  59. package/src/cli/utils/env-manager.ts +717 -0
  60. package/src/cli/utils/field-parser.ts +16 -7
  61. package/src/cli/utils/interactive-prompt.ts +223 -0
  62. package/src/cli/utils/template-manager.ts +346 -0
  63. package/src/config/database.config.ts +183 -0
  64. package/src/config/env.ts +0 -10
  65. package/src/config/index.ts +0 -14
  66. package/src/core/server.ts +1 -1
  67. package/src/database/adapters/mongoose.adapter.ts +132 -0
  68. package/src/database/adapters/prisma.adapter.ts +118 -0
  69. package/src/database/connection.ts +190 -0
  70. package/src/database/interfaces/database.interface.ts +85 -0
  71. package/src/database/interfaces/index.ts +7 -0
  72. package/src/database/interfaces/repository.interface.ts +129 -0
  73. package/src/database/models/mongoose/index.ts +7 -0
  74. package/src/database/models/mongoose/payment.schema.ts +347 -0
  75. package/src/database/models/mongoose/user.schema.ts +154 -0
  76. package/src/database/prisma.ts +1 -4
  77. package/src/database/redis.ts +101 -0
  78. package/src/database/repositories/mongoose/index.ts +7 -0
  79. package/src/database/repositories/mongoose/payment.repository.ts +380 -0
  80. package/src/database/repositories/mongoose/user.repository.ts +255 -0
  81. package/src/database/seed.ts +6 -1
  82. package/src/index.ts +9 -20
  83. package/src/middleware/security.ts +2 -6
  84. package/src/modules/analytics/analytics.routes.ts +80 -0
  85. package/src/modules/analytics/analytics.service.ts +364 -0
  86. package/src/modules/analytics/index.ts +18 -0
  87. package/src/modules/analytics/types.ts +180 -0
  88. package/src/modules/api-versioning/index.ts +15 -0
  89. package/src/modules/api-versioning/types.ts +86 -0
  90. package/src/modules/api-versioning/versioning.middleware.ts +120 -0
  91. package/src/modules/api-versioning/versioning.routes.ts +54 -0
  92. package/src/modules/api-versioning/versioning.service.ts +189 -0
  93. package/src/modules/audit/audit.repository.ts +206 -0
  94. package/src/modules/audit/audit.service.ts +27 -59
  95. package/src/modules/auth/auth.controller.ts +2 -2
  96. package/src/modules/auth/auth.middleware.ts +3 -9
  97. package/src/modules/auth/auth.routes.ts +10 -107
  98. package/src/modules/auth/auth.service.ts +126 -23
  99. package/src/modules/auth/index.ts +3 -4
  100. package/src/modules/cache/cache.service.ts +367 -0
  101. package/src/modules/cache/index.ts +10 -0
  102. package/src/modules/cache/types.ts +44 -0
  103. package/src/modules/email/email.service.ts +3 -10
  104. package/src/modules/email/templates.ts +2 -8
  105. package/src/modules/feature-flag/feature-flag.repository.ts +303 -0
  106. package/src/modules/feature-flag/feature-flag.routes.ts +247 -0
  107. package/src/modules/feature-flag/feature-flag.service.ts +566 -0
  108. package/src/modules/feature-flag/index.ts +20 -0
  109. package/src/modules/feature-flag/types.ts +192 -0
  110. package/src/modules/i18n/i18n.middleware.ts +186 -0
  111. package/src/modules/i18n/i18n.routes.ts +191 -0
  112. package/src/modules/i18n/i18n.service.ts +456 -0
  113. package/src/modules/i18n/index.ts +18 -0
  114. package/src/modules/i18n/types.ts +118 -0
  115. package/src/modules/media-processing/index.ts +17 -0
  116. package/src/modules/media-processing/media-processing.routes.ts +111 -0
  117. package/src/modules/media-processing/media-processing.service.ts +245 -0
  118. package/src/modules/media-processing/types.ts +156 -0
  119. package/src/modules/mfa/index.ts +20 -0
  120. package/src/modules/mfa/mfa.repository.ts +206 -0
  121. package/src/modules/mfa/mfa.routes.ts +595 -0
  122. package/src/modules/mfa/mfa.service.ts +572 -0
  123. package/src/modules/mfa/totp.ts +150 -0
  124. package/src/modules/mfa/types.ts +57 -0
  125. package/src/modules/notification/index.ts +20 -0
  126. package/src/modules/notification/notification.repository.ts +356 -0
  127. package/src/modules/notification/notification.service.ts +483 -0
  128. package/src/modules/notification/types.ts +119 -0
  129. package/src/modules/oauth/index.ts +20 -0
  130. package/src/modules/oauth/oauth.repository.ts +219 -0
  131. package/src/modules/oauth/oauth.routes.ts +446 -0
  132. package/src/modules/oauth/oauth.service.ts +293 -0
  133. package/src/modules/oauth/providers/apple.provider.ts +250 -0
  134. package/src/modules/oauth/providers/facebook.provider.ts +181 -0
  135. package/src/modules/oauth/providers/github.provider.ts +248 -0
  136. package/src/modules/oauth/providers/google.provider.ts +189 -0
  137. package/src/modules/oauth/providers/twitter.provider.ts +214 -0
  138. package/src/modules/oauth/types.ts +94 -0
  139. package/src/modules/payment/index.ts +19 -0
  140. package/src/modules/payment/payment.repository.ts +733 -0
  141. package/src/modules/payment/payment.routes.ts +390 -0
  142. package/src/modules/payment/payment.service.ts +354 -0
  143. package/src/modules/payment/providers/mobile-money.provider.ts +274 -0
  144. package/src/modules/payment/providers/paypal.provider.ts +190 -0
  145. package/src/modules/payment/providers/stripe.provider.ts +215 -0
  146. package/src/modules/payment/types.ts +140 -0
  147. package/src/modules/queue/cron.ts +438 -0
  148. package/src/modules/queue/index.ts +87 -0
  149. package/src/modules/queue/queue.routes.ts +600 -0
  150. package/src/modules/queue/queue.service.ts +842 -0
  151. package/src/modules/queue/types.ts +222 -0
  152. package/src/modules/queue/workers.ts +366 -0
  153. package/src/modules/rate-limit/index.ts +59 -0
  154. package/src/modules/rate-limit/rate-limit.middleware.ts +134 -0
  155. package/src/modules/rate-limit/rate-limit.routes.ts +269 -0
  156. package/src/modules/rate-limit/rate-limit.service.ts +348 -0
  157. package/src/modules/rate-limit/stores/memory.store.ts +165 -0
  158. package/src/modules/rate-limit/stores/redis.store.ts +322 -0
  159. package/src/modules/rate-limit/types.ts +153 -0
  160. package/src/modules/search/adapters/elasticsearch.adapter.ts +326 -0
  161. package/src/modules/search/adapters/meilisearch.adapter.ts +261 -0
  162. package/src/modules/search/adapters/memory.adapter.ts +278 -0
  163. package/src/modules/search/index.ts +21 -0
  164. package/src/modules/search/search.service.ts +234 -0
  165. package/src/modules/search/types.ts +214 -0
  166. package/src/modules/security/index.ts +40 -0
  167. package/src/modules/security/sanitize.ts +223 -0
  168. package/src/modules/security/security-audit.service.ts +388 -0
  169. package/src/modules/security/security.middleware.ts +398 -0
  170. package/src/modules/session/index.ts +3 -0
  171. package/src/modules/session/session.repository.ts +159 -0
  172. package/src/modules/session/session.service.ts +340 -0
  173. package/src/modules/session/types.ts +38 -0
  174. package/src/modules/swagger/index.ts +7 -1
  175. package/src/modules/swagger/schema-builder.ts +16 -4
  176. package/src/modules/swagger/swagger.service.ts +9 -10
  177. package/src/modules/swagger/types.ts +0 -2
  178. package/src/modules/upload/index.ts +14 -0
  179. package/src/modules/upload/types.ts +83 -0
  180. package/src/modules/upload/upload.repository.ts +199 -0
  181. package/src/modules/upload/upload.routes.ts +311 -0
  182. package/src/modules/upload/upload.service.ts +448 -0
  183. package/src/modules/user/index.ts +3 -3
  184. package/src/modules/user/user.controller.ts +15 -9
  185. package/src/modules/user/user.repository.ts +237 -113
  186. package/src/modules/user/user.routes.ts +39 -164
  187. package/src/modules/user/user.service.ts +4 -3
  188. package/src/modules/validation/validator.ts +12 -17
  189. package/src/modules/webhook/index.ts +91 -0
  190. package/src/modules/webhook/retry.ts +196 -0
  191. package/src/modules/webhook/signature.ts +135 -0
  192. package/src/modules/webhook/types.ts +181 -0
  193. package/src/modules/webhook/webhook.repository.ts +358 -0
  194. package/src/modules/webhook/webhook.routes.ts +442 -0
  195. package/src/modules/webhook/webhook.service.ts +457 -0
  196. package/src/modules/websocket/features.ts +504 -0
  197. package/src/modules/websocket/index.ts +106 -0
  198. package/src/modules/websocket/middlewares.ts +298 -0
  199. package/src/modules/websocket/types.ts +181 -0
  200. package/src/modules/websocket/websocket.service.ts +692 -0
  201. package/src/utils/errors.ts +7 -0
  202. package/src/utils/pagination.ts +4 -1
  203. package/tests/helpers/db-check.ts +79 -0
  204. package/tests/integration/auth-redis.test.ts +94 -0
  205. package/tests/integration/cache-redis.test.ts +387 -0
  206. package/tests/integration/mongoose-repositories.test.ts +410 -0
  207. package/tests/integration/payment-prisma.test.ts +637 -0
  208. package/tests/integration/queue-bullmq.test.ts +417 -0
  209. package/tests/integration/user-prisma.test.ts +441 -0
  210. package/tests/integration/websocket-socketio.test.ts +552 -0
  211. package/tests/setup.ts +11 -9
  212. package/vitest.config.ts +3 -8
  213. package/npm-cache/_cacache/content-v2/sha512/1c/d0/03440d500a0487621aad1d6402978340698976602046db8e24fa03c01ee6c022c69b0582f969042d9442ee876ac35c038e960dd427d1e622fa24b8eb7dba +0 -0
  214. package/npm-cache/_cacache/content-v2/sha512/42/55/28b493ca491833e5aab0e9c3108d29ab3f36c248ca88f45d4630674fce9130959e56ae308797ac2b6328fa7f09a610b9550ed09cb971d039876d293fc69d +0 -0
  215. package/npm-cache/_cacache/content-v2/sha512/e0/12/f360dc9315ee5f17844a0c8c233ee6bf7c30837c4a02ea0d56c61c7f7ab21c0e958e50ed2c57c59f983c762b93056778c9009b2398ffc26def0183999b13 +0 -0
  216. package/npm-cache/_cacache/content-v2/sha512/ed/b0/fae1161902898f4c913c67d7f6cdf6be0665aec3b389b9c4f4f0a101ca1da59badf1b59c4e0030f5223023b8d63cfe501c46a32c20c895d4fb3f11ca2232 +0 -0
  217. package/npm-cache/_cacache/index-v5/58/94/c2cba79e0f16b4c10e95a87e32255741149e8222cc314a476aab67c39cc0 +0 -5
@@ -0,0 +1,442 @@
1
+ import type { Response } from 'express';
2
+ import { Router, type Request } from 'express';
3
+ import type { WebhookService } from './webhook.service.js';
4
+ import type { WebhookEventType } from './types.js';
5
+
6
+ /**
7
+ * Create webhook management routes
8
+ * These routes should be protected with authentication/authorization
9
+ */
10
+ export function createWebhookRoutes(service: WebhookService): Router {
11
+ const router = Router();
12
+
13
+ // Endpoint Management
14
+
15
+ /**
16
+ * POST /webhooks/endpoints
17
+ * Create a new webhook endpoint
18
+ */
19
+ router.post('/endpoints', async (req: Request, res: Response): Promise<void> => {
20
+ try {
21
+ const { url, events, description, headers, metadata } = req.body;
22
+
23
+ if (!url || !events || !Array.isArray(events)) {
24
+ res.status(400).json({
25
+ error: 'Bad Request',
26
+ message: 'url and events array are required',
27
+ });
28
+ return;
29
+ }
30
+
31
+ const endpoint = await service.createEndpoint({
32
+ url,
33
+ events,
34
+ description,
35
+ headers,
36
+ metadata,
37
+ });
38
+
39
+ res.status(201).json({
40
+ success: true,
41
+ data: endpoint,
42
+ });
43
+ } catch (error) {
44
+ console.error('[WebhookRoutes] Error creating endpoint:', error);
45
+ res.status(500).json({
46
+ error: 'Internal Server Error',
47
+ message: error instanceof Error ? error.message : 'Failed to create webhook endpoint',
48
+ });
49
+ }
50
+ });
51
+
52
+ /**
53
+ * GET /webhooks/endpoints
54
+ * List all webhook endpoints
55
+ */
56
+ router.get('/endpoints', async (_req: Request, res: Response) => {
57
+ try {
58
+ const endpoints = await service.listEndpoints();
59
+
60
+ res.json({
61
+ success: true,
62
+ data: endpoints,
63
+ count: endpoints.length,
64
+ });
65
+ } catch (error) {
66
+ console.error('[WebhookRoutes] Error listing endpoints:', error);
67
+ res.status(500).json({
68
+ error: 'Internal Server Error',
69
+ message: 'Failed to list webhook endpoints',
70
+ });
71
+ }
72
+ });
73
+
74
+ /**
75
+ * GET /webhooks/endpoints/:id
76
+ * Get a specific webhook endpoint
77
+ */
78
+ router.get('/endpoints/:id', async (req: Request, res: Response): Promise<void> => {
79
+ try {
80
+ const { id } = req.params;
81
+ if (!id) {
82
+ res.status(400).json({ error: 'Bad Request', message: 'id is required' });
83
+ return;
84
+ }
85
+ const endpoint = await service.getEndpoint(id);
86
+
87
+ res.json({
88
+ success: true,
89
+ data: endpoint,
90
+ });
91
+ } catch (error) {
92
+ if (error instanceof Error && error.message.includes('not found')) {
93
+ res.status(404).json({
94
+ error: 'Not Found',
95
+ message: 'Webhook endpoint not found',
96
+ });
97
+ return;
98
+ }
99
+
100
+ console.error('[WebhookRoutes] Error getting endpoint:', error);
101
+ res.status(500).json({
102
+ error: 'Internal Server Error',
103
+ message: 'Failed to get webhook endpoint',
104
+ });
105
+ }
106
+ });
107
+
108
+ /**
109
+ * PATCH /webhooks/endpoints/:id
110
+ * Update a webhook endpoint
111
+ */
112
+ router.patch('/endpoints/:id', async (req: Request, res: Response): Promise<void> => {
113
+ try {
114
+ const { id } = req.params;
115
+ if (!id) {
116
+ res.status(400).json({ error: 'Bad Request', message: 'id is required' });
117
+ return;
118
+ }
119
+ const { url, events, enabled, description, headers, metadata } = req.body;
120
+
121
+ const endpoint = await service.updateEndpoint(id, {
122
+ url,
123
+ events,
124
+ enabled,
125
+ description,
126
+ headers,
127
+ metadata,
128
+ });
129
+
130
+ res.json({
131
+ success: true,
132
+ data: endpoint,
133
+ });
134
+ } catch (error) {
135
+ if (error instanceof Error && error.message.includes('not found')) {
136
+ res.status(404).json({
137
+ error: 'Not Found',
138
+ message: 'Webhook endpoint not found',
139
+ });
140
+ return;
141
+ }
142
+
143
+ console.error('[WebhookRoutes] Error updating endpoint:', error);
144
+ res.status(500).json({
145
+ error: 'Internal Server Error',
146
+ message: error instanceof Error ? error.message : 'Failed to update webhook endpoint',
147
+ });
148
+ }
149
+ });
150
+
151
+ /**
152
+ * DELETE /webhooks/endpoints/:id
153
+ * Delete a webhook endpoint
154
+ */
155
+ router.delete('/endpoints/:id', async (req: Request, res: Response): Promise<void> => {
156
+ try {
157
+ const { id } = req.params;
158
+ if (!id) {
159
+ res.status(400).json({ error: 'Bad Request', message: 'id is required' });
160
+ return;
161
+ }
162
+ await service.deleteEndpoint(id);
163
+
164
+ res.json({
165
+ success: true,
166
+ message: 'Webhook endpoint deleted',
167
+ });
168
+ } catch (error) {
169
+ if (error instanceof Error && error.message.includes('not found')) {
170
+ res.status(404).json({
171
+ error: 'Not Found',
172
+ message: 'Webhook endpoint not found',
173
+ });
174
+ return;
175
+ }
176
+
177
+ console.error('[WebhookRoutes] Error deleting endpoint:', error);
178
+ res.status(500).json({
179
+ error: 'Internal Server Error',
180
+ message: 'Failed to delete webhook endpoint',
181
+ });
182
+ }
183
+ });
184
+
185
+ /**
186
+ * POST /webhooks/endpoints/:id/rotate-secret
187
+ * Rotate webhook endpoint secret
188
+ */
189
+ router.post(
190
+ '/endpoints/:id/rotate-secret',
191
+ async (req: Request, res: Response): Promise<void> => {
192
+ try {
193
+ const { id } = req.params;
194
+ if (!id) {
195
+ res.status(400).json({ error: 'Bad Request', message: 'id is required' });
196
+ return;
197
+ }
198
+ const endpoint = await service.rotateSecret(id);
199
+
200
+ res.json({
201
+ success: true,
202
+ data: endpoint,
203
+ message: 'Secret rotated successfully',
204
+ });
205
+ } catch (error) {
206
+ if (error instanceof Error && error.message.includes('not found')) {
207
+ res.status(404).json({
208
+ error: 'Not Found',
209
+ message: 'Webhook endpoint not found',
210
+ });
211
+ return;
212
+ }
213
+
214
+ console.error('[WebhookRoutes] Error rotating secret:', error);
215
+ res.status(500).json({
216
+ error: 'Internal Server Error',
217
+ message: 'Failed to rotate webhook secret',
218
+ });
219
+ }
220
+ }
221
+ );
222
+
223
+ // Event Publishing
224
+
225
+ /**
226
+ * POST /webhooks/events
227
+ * Publish a webhook event
228
+ */
229
+ router.post('/events', async (req: Request, res: Response): Promise<void> => {
230
+ try {
231
+ const { type, payload, endpoints } = req.body;
232
+
233
+ if (!type || !payload) {
234
+ res.status(400).json({
235
+ error: 'Bad Request',
236
+ message: 'type and payload are required',
237
+ });
238
+ return;
239
+ }
240
+
241
+ const event = await service.publishEvent(type as WebhookEventType, payload, endpoints);
242
+
243
+ res.status(201).json({
244
+ success: true,
245
+ data: event,
246
+ message: 'Event published successfully',
247
+ });
248
+ } catch (error) {
249
+ console.error('[WebhookRoutes] Error publishing event:', error);
250
+ res.status(500).json({
251
+ error: 'Internal Server Error',
252
+ message: 'Failed to publish webhook event',
253
+ });
254
+ }
255
+ });
256
+
257
+ // Delivery Management
258
+
259
+ /**
260
+ * GET /webhooks/deliveries
261
+ * List webhook deliveries with optional filters
262
+ */
263
+ router.get('/deliveries', async (req: Request, res: Response) => {
264
+ try {
265
+ const { endpointId, eventType, status, startDate, endDate, limit, offset } = req.query;
266
+
267
+ const deliveries = await service.listDeliveries({
268
+ endpointId: endpointId as string,
269
+ eventType: eventType as WebhookEventType,
270
+ status: status as 'pending' | 'success' | 'failed' | 'retrying',
271
+ startDate: startDate ? new Date(startDate as string) : undefined,
272
+ endDate: endDate ? new Date(endDate as string) : undefined,
273
+ limit: limit ? parseInt(limit as string, 10) : undefined,
274
+ offset: offset ? parseInt(offset as string, 10) : undefined,
275
+ });
276
+
277
+ res.json({
278
+ success: true,
279
+ data: deliveries,
280
+ count: deliveries.length,
281
+ });
282
+ } catch (error) {
283
+ console.error('[WebhookRoutes] Error listing deliveries:', error);
284
+ res.status(500).json({
285
+ error: 'Internal Server Error',
286
+ message: 'Failed to list webhook deliveries',
287
+ });
288
+ }
289
+ });
290
+
291
+ /**
292
+ * GET /webhooks/deliveries/:id
293
+ * Get a specific webhook delivery
294
+ */
295
+ router.get('/deliveries/:id', async (req: Request, res: Response): Promise<void> => {
296
+ try {
297
+ const { id } = req.params;
298
+ if (!id) {
299
+ res.status(400).json({ error: 'Bad Request', message: 'id is required' });
300
+ return;
301
+ }
302
+ const delivery = await service.getDelivery(id);
303
+
304
+ res.json({
305
+ success: true,
306
+ data: delivery,
307
+ });
308
+ } catch (error) {
309
+ if (error instanceof Error && error.message.includes('not found')) {
310
+ res.status(404).json({
311
+ error: 'Not Found',
312
+ message: 'Webhook delivery not found',
313
+ });
314
+ return;
315
+ }
316
+
317
+ console.error('[WebhookRoutes] Error getting delivery:', error);
318
+ res.status(500).json({
319
+ error: 'Internal Server Error',
320
+ message: 'Failed to get webhook delivery',
321
+ });
322
+ }
323
+ });
324
+
325
+ /**
326
+ * GET /webhooks/deliveries/:id/attempts
327
+ * Get delivery attempts for a specific delivery
328
+ */
329
+ router.get('/deliveries/:id/attempts', async (req: Request, res: Response): Promise<void> => {
330
+ try {
331
+ const { id } = req.params;
332
+ if (!id) {
333
+ res.status(400).json({ error: 'Bad Request', message: 'id is required' });
334
+ return;
335
+ }
336
+ const attempts = await service.getDeliveryAttempts(id);
337
+
338
+ res.json({
339
+ success: true,
340
+ data: attempts,
341
+ count: attempts.length,
342
+ });
343
+ } catch (error) {
344
+ console.error('[WebhookRoutes] Error getting delivery attempts:', error);
345
+ res.status(500).json({
346
+ error: 'Internal Server Error',
347
+ message: 'Failed to get delivery attempts',
348
+ });
349
+ }
350
+ });
351
+
352
+ /**
353
+ * POST /webhooks/deliveries/:id/retry
354
+ * Manually retry a failed delivery
355
+ */
356
+ router.post('/deliveries/:id/retry', async (req: Request, res: Response): Promise<void> => {
357
+ try {
358
+ const { id } = req.params;
359
+ if (!id) {
360
+ res.status(400).json({ error: 'Bad Request', message: 'id is required' });
361
+ return;
362
+ }
363
+ await service.retryDelivery(id);
364
+
365
+ res.json({
366
+ success: true,
367
+ message: 'Delivery retry initiated',
368
+ });
369
+ } catch (error) {
370
+ if (error instanceof Error && error.message.includes('not found')) {
371
+ res.status(404).json({
372
+ error: 'Not Found',
373
+ message: 'Webhook delivery not found',
374
+ });
375
+ return;
376
+ }
377
+
378
+ if (error instanceof Error && error.message.includes('Cannot retry')) {
379
+ res.status(400).json({
380
+ error: 'Bad Request',
381
+ message: error.message,
382
+ });
383
+ return;
384
+ }
385
+
386
+ console.error('[WebhookRoutes] Error retrying delivery:', error);
387
+ res.status(500).json({
388
+ error: 'Internal Server Error',
389
+ message: 'Failed to retry webhook delivery',
390
+ });
391
+ }
392
+ });
393
+
394
+ // Statistics
395
+
396
+ /**
397
+ * GET /webhooks/stats
398
+ * Get webhook statistics
399
+ */
400
+ router.get('/stats', async (req: Request, res: Response) => {
401
+ try {
402
+ const { endpointId } = req.query;
403
+ const stats = await service.getStats(endpointId as string);
404
+
405
+ res.json({
406
+ success: true,
407
+ data: stats,
408
+ });
409
+ } catch (error) {
410
+ console.error('[WebhookRoutes] Error getting stats:', error);
411
+ res.status(500).json({
412
+ error: 'Internal Server Error',
413
+ message: 'Failed to get webhook statistics',
414
+ });
415
+ }
416
+ });
417
+
418
+ /**
419
+ * POST /webhooks/cleanup
420
+ * Cleanup old webhook data
421
+ */
422
+ router.post('/cleanup', async (req: Request, res: Response) => {
423
+ try {
424
+ const { olderThanDays = 30 } = req.body;
425
+ const cleaned = await service.cleanup(olderThanDays);
426
+
427
+ res.json({
428
+ success: true,
429
+ message: `Cleaned up ${cleaned} old records`,
430
+ cleaned,
431
+ });
432
+ } catch (error) {
433
+ console.error('[WebhookRoutes] Error cleaning up:', error);
434
+ res.status(500).json({
435
+ error: 'Internal Server Error',
436
+ message: 'Failed to cleanup webhook data',
437
+ });
438
+ }
439
+ });
440
+
441
+ return router;
442
+ }