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,311 @@
1
+ # Notification Module
2
+
3
+ Multi-channel notification system with templates and persistent storage.
4
+
5
+ ## Features
6
+
7
+ - **Multi-Channel** - Email, SMS, Push, Webhook, In-App
8
+ - **Multiple Providers** - SendGrid, Resend, Twilio, Firebase, etc.
9
+ - **Templates** - Reusable notification templates with variables
10
+ - **Persistence** - All notifications stored in database
11
+ - **In-App Notifications** - Read/unread tracking
12
+
13
+ ## Supported Providers
14
+
15
+ | Channel | Providers |
16
+ |---------|-----------|
17
+ | Email | SendGrid, Resend, Mailgun, AWS SES, SMTP |
18
+ | SMS | Twilio, Nexmo (Vonage), Africa's Talking |
19
+ | Push | Firebase Cloud Messaging, OneSignal |
20
+ | Webhook | Any HTTP endpoint |
21
+ | In-App | Database storage |
22
+
23
+ ## Usage
24
+
25
+ ### Configuration
26
+
27
+ ```typescript
28
+ import { createNotificationService } from 'servcraft/modules/notification';
29
+
30
+ const notificationService = createNotificationService({
31
+ email: {
32
+ provider: 'sendgrid',
33
+ from: 'noreply@myapp.com',
34
+ sendgrid: {
35
+ apiKey: process.env.SENDGRID_API_KEY!,
36
+ },
37
+ },
38
+ sms: {
39
+ provider: 'twilio',
40
+ from: '+1234567890',
41
+ twilio: {
42
+ accountSid: process.env.TWILIO_ACCOUNT_SID!,
43
+ authToken: process.env.TWILIO_AUTH_TOKEN!,
44
+ },
45
+ },
46
+ push: {
47
+ provider: 'firebase',
48
+ firebase: {
49
+ projectId: process.env.FIREBASE_PROJECT_ID!,
50
+ privateKey: process.env.FIREBASE_PRIVATE_KEY!,
51
+ clientEmail: process.env.FIREBASE_CLIENT_EMAIL!,
52
+ },
53
+ },
54
+ });
55
+ ```
56
+
57
+ ### Sending Notifications
58
+
59
+ ```typescript
60
+ // Send single notification
61
+ const notification = await notificationService.send(
62
+ 'user-123',
63
+ 'email',
64
+ 'Welcome to MyApp',
65
+ 'Thank you for signing up!',
66
+ { email: 'user@example.com' }
67
+ );
68
+
69
+ // Send to multiple channels
70
+ const notifications = await notificationService.sendToUser(
71
+ 'user-123',
72
+ ['email', 'push', 'in_app'],
73
+ 'New Message',
74
+ 'You have a new message from John',
75
+ {
76
+ email: 'user@example.com',
77
+ tokens: ['fcm-token-1', 'fcm-token-2'],
78
+ }
79
+ );
80
+ ```
81
+
82
+ ### Email
83
+
84
+ ```typescript
85
+ // Simple email
86
+ await notificationService.sendEmail({
87
+ to: 'user@example.com',
88
+ subject: 'Welcome!',
89
+ text: 'Welcome to our platform.',
90
+ html: '<h1>Welcome!</h1><p>Welcome to our platform.</p>',
91
+ });
92
+
93
+ // With template
94
+ await notificationService.sendEmail({
95
+ to: 'user@example.com',
96
+ subject: 'Order Confirmation',
97
+ template: 'order-confirmation',
98
+ templateData: {
99
+ orderId: 'ORD-123',
100
+ total: '$99.99',
101
+ items: '3 items',
102
+ },
103
+ });
104
+
105
+ // Multiple recipients
106
+ await notificationService.sendEmail({
107
+ to: ['user1@example.com', 'user2@example.com'],
108
+ subject: 'Team Update',
109
+ text: 'Here is the weekly update...',
110
+ });
111
+ ```
112
+
113
+ ### SMS
114
+
115
+ ```typescript
116
+ await notificationService.sendSMS({
117
+ to: '+1234567890',
118
+ body: 'Your verification code is: 123456',
119
+ });
120
+
121
+ // Multiple recipients
122
+ await notificationService.sendSMS({
123
+ to: ['+1234567890', '+0987654321'],
124
+ body: 'Flash sale starts now!',
125
+ });
126
+ ```
127
+
128
+ ### Push Notifications
129
+
130
+ ```typescript
131
+ await notificationService.sendPush({
132
+ tokens: ['fcm-token-1', 'fcm-token-2'],
133
+ title: 'New Message',
134
+ body: 'You have a new message',
135
+ data: {
136
+ type: 'message',
137
+ messageId: 'msg-123',
138
+ },
139
+ badge: 5,
140
+ });
141
+ ```
142
+
143
+ ### Webhooks
144
+
145
+ ```typescript
146
+ await notificationService.sendWebhook({
147
+ url: 'https://example.com/webhook',
148
+ method: 'POST',
149
+ headers: {
150
+ 'X-Custom-Header': 'value',
151
+ },
152
+ body: {
153
+ event: 'user.created',
154
+ data: { userId: 'user-123' },
155
+ },
156
+ });
157
+ ```
158
+
159
+ ### In-App Notifications
160
+
161
+ ```typescript
162
+ // Send in-app notification
163
+ await notificationService.send(
164
+ 'user-123',
165
+ 'in_app',
166
+ 'New Feature Available',
167
+ 'Check out our new dashboard!'
168
+ );
169
+
170
+ // Get user's notifications
171
+ const notifications = await notificationService.getUserNotifications('user-123', {
172
+ limit: 20,
173
+ offset: 0,
174
+ });
175
+
176
+ // Get unread count
177
+ const unreadCount = await notificationService.getUnreadCount('user-123');
178
+
179
+ // Mark as read
180
+ await notificationService.markAsRead(notificationId);
181
+
182
+ // Mark all as read
183
+ await notificationService.markAllAsRead('user-123');
184
+ ```
185
+
186
+ ### Templates
187
+
188
+ ```typescript
189
+ // Register template
190
+ await notificationService.registerTemplate({
191
+ name: 'welcome-email',
192
+ channel: 'email',
193
+ subject: 'Welcome to {{appName}}!',
194
+ body: `
195
+ <h1>Welcome, {{userName}}!</h1>
196
+ <p>Thank you for joining {{appName}}.</p>
197
+ <p>Get started by visiting your <a href="{{dashboardUrl}}">dashboard</a>.</p>
198
+ `,
199
+ });
200
+
201
+ // Get template
202
+ const template = await notificationService.getTemplate('welcome-email');
203
+
204
+ // List all templates
205
+ const templates = await notificationService.getAllTemplates();
206
+
207
+ // Use template
208
+ await notificationService.sendEmail({
209
+ to: 'user@example.com',
210
+ subject: 'Welcome!',
211
+ template: 'welcome-email',
212
+ templateData: {
213
+ appName: 'MyApp',
214
+ userName: 'John',
215
+ dashboardUrl: 'https://myapp.com/dashboard',
216
+ },
217
+ });
218
+ ```
219
+
220
+ ### Cleanup
221
+
222
+ ```typescript
223
+ // Delete notifications older than 30 days
224
+ const deleted = await notificationService.cleanupOldNotifications(30);
225
+ ```
226
+
227
+ ## Configuration Types
228
+
229
+ ```typescript
230
+ interface NotificationConfig {
231
+ email?: {
232
+ provider: 'sendgrid' | 'resend' | 'mailgun' | 'ses' | 'smtp';
233
+ from: string;
234
+ sendgrid?: { apiKey: string };
235
+ resend?: { apiKey: string };
236
+ mailgun?: { apiKey: string; domain: string };
237
+ ses?: { region: string; accessKeyId: string; secretAccessKey: string };
238
+ smtp?: { host: string; port: number; user: string; pass: string };
239
+ };
240
+ sms?: {
241
+ provider: 'twilio' | 'nexmo' | 'africas_talking';
242
+ from: string;
243
+ twilio?: { accountSid: string; authToken: string };
244
+ nexmo?: { apiKey: string; apiSecret: string };
245
+ africasTalking?: { apiKey: string; username: string };
246
+ };
247
+ push?: {
248
+ provider: 'firebase' | 'onesignal';
249
+ firebase?: { projectId: string; privateKey: string; clientEmail: string };
250
+ onesignal?: { appId: string; apiKey: string };
251
+ };
252
+ webhook?: {
253
+ defaultHeaders?: Record<string, string>;
254
+ timeout?: number;
255
+ };
256
+ }
257
+ ```
258
+
259
+ ## Notification Status
260
+
261
+ ```typescript
262
+ type NotificationStatus = 'pending' | 'sent' | 'failed' | 'read';
263
+
264
+ interface Notification {
265
+ id: string;
266
+ userId: string;
267
+ channel: NotificationChannel;
268
+ status: NotificationStatus;
269
+ title: string;
270
+ body: string;
271
+ data?: Record<string, unknown>;
272
+ sentAt?: Date;
273
+ readAt?: Date;
274
+ createdAt: Date;
275
+ }
276
+ ```
277
+
278
+ ## Database Schema
279
+
280
+ ```sql
281
+ CREATE TABLE notifications (
282
+ id UUID PRIMARY KEY,
283
+ user_id TEXT NOT NULL,
284
+ channel TEXT NOT NULL,
285
+ status TEXT NOT NULL DEFAULT 'pending',
286
+ title TEXT NOT NULL,
287
+ body TEXT NOT NULL,
288
+ data JSONB,
289
+ sent_at TIMESTAMP,
290
+ read_at TIMESTAMP,
291
+ created_at TIMESTAMP DEFAULT NOW()
292
+ );
293
+
294
+ CREATE TABLE notification_templates (
295
+ id UUID PRIMARY KEY,
296
+ name TEXT UNIQUE NOT NULL,
297
+ channel TEXT NOT NULL,
298
+ subject TEXT,
299
+ body TEXT NOT NULL,
300
+ created_at TIMESTAMP DEFAULT NOW()
301
+ );
302
+ ```
303
+
304
+ ## Best Practices
305
+
306
+ 1. **Use Templates** - Create templates for consistent messaging
307
+ 2. **Handle Failures** - Check notification status and implement retries
308
+ 3. **Rate Limiting** - Implement rate limits to avoid spam
309
+ 4. **User Preferences** - Let users choose notification channels
310
+ 5. **Cleanup** - Regularly clean up old notifications
311
+ 6. **Logging** - Log all notification attempts for debugging
@@ -0,0 +1,237 @@
1
+ # OAuth Module
2
+
3
+ Social authentication with multiple providers (Google, Facebook, GitHub).
4
+
5
+ ## Features
6
+
7
+ - **Multiple Providers** - Google, Facebook, GitHub out of the box
8
+ - **PKCE Support** - Secure authorization code flow
9
+ - **Account Linking** - Link multiple OAuth accounts to one user
10
+ - **Token Management** - Automatic token refresh
11
+ - **State Protection** - CSRF protection with Redis-stored states
12
+
13
+ ## Architecture
14
+
15
+ ```
16
+ ┌─────────────────────────────────────────────────────────────┐
17
+ │ OAuth Service │
18
+ ├─────────────────────────────────────────────────────────────┤
19
+ │ Google Provider │ Facebook Provider │ GitHub Provider │
20
+ └────────┬──────────┴─────────┬───────────┴────────┬──────────┘
21
+ │ │ │
22
+ ▼ ▼ ▼
23
+ ┌─────────────────────────────────────────────────────────────┐
24
+ │ Repository │
25
+ ├─────────────────────────────────────────────────────────────┤
26
+ │ Redis (States) │ Prisma (Linked Accounts) │
27
+ └───────────────────────────┴─────────────────────────────────┘
28
+ ```
29
+
30
+ ## Storage
31
+
32
+ | Data | Storage | TTL |
33
+ |------|---------|-----|
34
+ | OAuth States | Redis | 10 minutes |
35
+ | Linked Accounts | PostgreSQL | Permanent |
36
+ | Access Tokens | PostgreSQL | Per provider |
37
+ | Refresh Tokens | PostgreSQL | Permanent |
38
+
39
+ ## Usage
40
+
41
+ ### Configuration
42
+
43
+ ```typescript
44
+ import { createOAuthService } from 'servcraft/modules/oauth';
45
+
46
+ const oauthService = createOAuthService({
47
+ callbackBaseUrl: 'https://myapp.com/auth',
48
+ google: {
49
+ clientId: process.env.GOOGLE_CLIENT_ID!,
50
+ clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
51
+ scopes: ['openid', 'email', 'profile'],
52
+ },
53
+ facebook: {
54
+ clientId: process.env.FACEBOOK_APP_ID!,
55
+ clientSecret: process.env.FACEBOOK_APP_SECRET!,
56
+ scopes: ['email', 'public_profile'],
57
+ },
58
+ github: {
59
+ clientId: process.env.GITHUB_CLIENT_ID!,
60
+ clientSecret: process.env.GITHUB_CLIENT_SECRET!,
61
+ scopes: ['user:email', 'read:user'],
62
+ },
63
+ });
64
+ ```
65
+
66
+ ### OAuth Flow
67
+
68
+ ```typescript
69
+ // 1. Generate authorization URL
70
+ const { url, state } = await oauthService.getAuthorizationUrl('google');
71
+ // Redirect user to url
72
+
73
+ // 2. Handle callback (after user authorizes)
74
+ const oauthUser = await oauthService.handleCallback('google', code, state);
75
+ // oauthUser: { provider, providerAccountId, email, name, picture, accessToken }
76
+
77
+ // 3. Find or create user
78
+ let userId = await oauthService.findUserByOAuth('google', oauthUser.providerAccountId);
79
+
80
+ if (!userId) {
81
+ // Create new user
82
+ const user = await userService.create({ email: oauthUser.email, name: oauthUser.name });
83
+ userId = user.id;
84
+ }
85
+
86
+ // 4. Link OAuth account to user
87
+ await oauthService.linkAccount(userId, oauthUser);
88
+
89
+ // 5. Generate session/JWT for user
90
+ const tokens = await authService.generateTokens(userId);
91
+ ```
92
+
93
+ ### Account Management
94
+
95
+ ```typescript
96
+ // Get user's linked accounts
97
+ const accounts = await oauthService.getUserLinkedAccounts(userId);
98
+ // [{ provider: 'google', email: '...', picture: '...' }, ...]
99
+
100
+ // Check if provider is linked
101
+ const googleAccount = accounts.find(a => a.provider === 'google');
102
+
103
+ // Unlink account
104
+ await oauthService.unlinkAccount(userId, 'facebook');
105
+
106
+ // Refresh tokens (for API access)
107
+ const refreshed = await oauthService.refreshTokens(linkedAccountId);
108
+ ```
109
+
110
+ ### Provider Utilities
111
+
112
+ ```typescript
113
+ // Get supported providers
114
+ const providers = oauthService.getSupportedProviders();
115
+ // ['google', 'facebook', 'github']
116
+
117
+ // Check if provider is enabled
118
+ if (oauthService.isProviderEnabled('google')) {
119
+ // Show Google login button
120
+ }
121
+ ```
122
+
123
+ ## Configuration Types
124
+
125
+ ```typescript
126
+ interface OAuthConfig {
127
+ callbackBaseUrl: string;
128
+ google?: {
129
+ clientId: string;
130
+ clientSecret: string;
131
+ scopes?: string[];
132
+ };
133
+ facebook?: {
134
+ clientId: string;
135
+ clientSecret: string;
136
+ scopes?: string[];
137
+ };
138
+ github?: {
139
+ clientId: string;
140
+ clientSecret: string;
141
+ scopes?: string[];
142
+ };
143
+ }
144
+
145
+ interface OAuthUser {
146
+ provider: OAuthProvider;
147
+ providerAccountId: string;
148
+ email?: string;
149
+ name?: string;
150
+ picture?: string;
151
+ accessToken?: string;
152
+ refreshToken?: string;
153
+ expiresAt?: number;
154
+ }
155
+
156
+ interface LinkedAccount {
157
+ id: string;
158
+ userId: string;
159
+ provider: OAuthProvider;
160
+ providerAccountId: string;
161
+ email?: string;
162
+ name?: string;
163
+ picture?: string;
164
+ accessToken?: string;
165
+ refreshToken?: string;
166
+ expiresAt?: Date;
167
+ createdAt: Date;
168
+ updatedAt: Date;
169
+ }
170
+ ```
171
+
172
+ ## Fastify Routes Example
173
+
174
+ ```typescript
175
+ // GET /auth/google
176
+ fastify.get('/auth/google', async (request, reply) => {
177
+ const { url, state } = await oauthService.getAuthorizationUrl('google');
178
+ reply.redirect(url);
179
+ });
180
+
181
+ // GET /auth/google/callback
182
+ fastify.get('/auth/google/callback', async (request, reply) => {
183
+ const { code, state } = request.query as { code: string; state: string };
184
+
185
+ try {
186
+ const oauthUser = await oauthService.handleCallback('google', code, state);
187
+
188
+ // Find or create user
189
+ let userId = await oauthService.findUserByOAuth('google', oauthUser.providerAccountId);
190
+
191
+ if (!userId) {
192
+ const user = await userService.create({
193
+ email: oauthUser.email!,
194
+ name: oauthUser.name,
195
+ emailVerified: true, // OAuth emails are verified
196
+ });
197
+ userId = user.id;
198
+ }
199
+
200
+ await oauthService.linkAccount(userId, oauthUser);
201
+
202
+ const tokens = await authService.generateTokens(userId);
203
+ reply.redirect(`/dashboard?token=${tokens.accessToken}`);
204
+ } catch (error) {
205
+ reply.redirect('/login?error=oauth_failed');
206
+ }
207
+ });
208
+ ```
209
+
210
+ ## Redis Key Structure
211
+
212
+ | Key Pattern | Purpose | TTL |
213
+ |-------------|---------|-----|
214
+ | `oauth:state:{state}` | CSRF state with PKCE verifier | 10 min |
215
+
216
+ ## Security Considerations
217
+
218
+ 1. **State Validation** - Always validate state parameter to prevent CSRF
219
+ 2. **PKCE** - Use PKCE for public clients (mobile, SPA)
220
+ 3. **Token Storage** - Store tokens encrypted in database
221
+ 4. **Scope Minimization** - Request only necessary scopes
222
+ 5. **Token Refresh** - Implement automatic token refresh for long-lived access
223
+
224
+ ## Error Handling
225
+
226
+ ```typescript
227
+ try {
228
+ await oauthService.handleCallback('google', code, state);
229
+ } catch (error) {
230
+ if (error.message === 'Invalid or expired OAuth state') {
231
+ // User took too long or CSRF attack
232
+ }
233
+ if (error.message === 'This account is already linked to another user') {
234
+ // OAuth account already linked
235
+ }
236
+ }
237
+ ```