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,85 @@
1
+ /**
2
+ * Common Database Adapter Interface
3
+ * All ORMs must implement this interface to ensure consistency
4
+ */
5
+ export interface IDatabaseAdapter {
6
+ /**
7
+ * Connect to the database
8
+ * @throws Error if connection fails
9
+ */
10
+ connect(): Promise<void>;
11
+
12
+ /**
13
+ * Disconnect from the database
14
+ * Gracefully closes all connections
15
+ */
16
+ disconnect(): Promise<void>;
17
+
18
+ /**
19
+ * Check if database connection is healthy
20
+ * @returns true if connected and responsive
21
+ */
22
+ healthCheck(): Promise<boolean>;
23
+
24
+ /**
25
+ * Get the ORM type
26
+ */
27
+ getType(): 'prisma' | 'mongoose' | 'sequelize' | 'typeorm';
28
+
29
+ /**
30
+ * Get the database type
31
+ */
32
+ getDatabaseType(): 'postgresql' | 'mysql' | 'sqlite' | 'mongodb' | 'mariadb';
33
+
34
+ /**
35
+ * Get raw client instance (use with caution)
36
+ * Type depends on ORM implementation
37
+ */
38
+ getRawClient(): unknown;
39
+ }
40
+
41
+ /**
42
+ * Database configuration
43
+ */
44
+ export interface DatabaseConfig {
45
+ /** ORM type */
46
+ orm: 'prisma' | 'mongoose' | 'sequelize' | 'typeorm';
47
+
48
+ /** Database type */
49
+ database: 'postgresql' | 'mysql' | 'sqlite' | 'mongodb' | 'mariadb';
50
+
51
+ /** Connection URL (preferred method) */
52
+ url?: string;
53
+
54
+ /** Host (alternative to url) */
55
+ host?: string;
56
+
57
+ /** Port (alternative to url) */
58
+ port?: number;
59
+
60
+ /** Username (alternative to url) */
61
+ username?: string;
62
+
63
+ /** Password (alternative to url) */
64
+ password?: string;
65
+
66
+ /** Database name (alternative to url) */
67
+ databaseName?: string;
68
+
69
+ /** SSL/TLS options */
70
+ ssl?: boolean | Record<string, unknown>;
71
+
72
+ /** Connection pool options */
73
+ pool?: {
74
+ min?: number;
75
+ max?: number;
76
+ idle?: number;
77
+ acquire?: number;
78
+ };
79
+
80
+ /** Logging */
81
+ logging?: boolean | ((query: string) => void);
82
+
83
+ /** Timezone */
84
+ timezone?: string;
85
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Database Interfaces
3
+ * Export all common interfaces for multi-ORM support
4
+ */
5
+
6
+ export * from './database.interface.js';
7
+ export * from './repository.interface.js';
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Common Repository Interface
3
+ * All repositories must implement this pattern regardless of ORM
4
+ */
5
+
6
+ export interface PaginationOptions {
7
+ page?: number;
8
+ limit?: number;
9
+ sortBy?: string;
10
+ sortOrder?: 'asc' | 'desc';
11
+ }
12
+
13
+ export interface PaginatedResult<T> {
14
+ data: T[];
15
+ pagination: {
16
+ page: number;
17
+ limit: number;
18
+ total: number;
19
+ totalPages: number;
20
+ hasNext: boolean;
21
+ hasPrev: boolean;
22
+ };
23
+ }
24
+
25
+ /**
26
+ * Base repository interface that all entity repositories must implement
27
+ */
28
+ export interface IRepository<T> {
29
+ /**
30
+ * Find entity by ID
31
+ * @param id - Entity ID
32
+ * @returns Entity or null if not found
33
+ */
34
+ findById(id: string): Promise<T | null>;
35
+
36
+ /**
37
+ * Find many entities with optional filtering and pagination
38
+ * @param filter - Filter criteria (ORM-specific)
39
+ * @param options - Pagination options
40
+ * @returns Paginated results
41
+ */
42
+ findMany(
43
+ filter?: Record<string, unknown>,
44
+ options?: PaginationOptions
45
+ ): Promise<PaginatedResult<T>>;
46
+
47
+ /**
48
+ * Find one entity matching filter
49
+ * @param filter - Filter criteria
50
+ * @returns First matching entity or null
51
+ */
52
+ findOne(filter: Record<string, unknown>): Promise<T | null>;
53
+
54
+ /**
55
+ * Create new entity
56
+ * @param data - Entity data
57
+ * @returns Created entity
58
+ */
59
+ create(data: Partial<T>): Promise<T>;
60
+
61
+ /**
62
+ * Update entity by ID
63
+ * @param id - Entity ID
64
+ * @param data - Update data
65
+ * @returns Updated entity or null if not found
66
+ */
67
+ update(id: string, data: Partial<T>): Promise<T | null>;
68
+
69
+ /**
70
+ * Delete entity by ID
71
+ * @param id - Entity ID
72
+ * @returns true if deleted, false if not found
73
+ */
74
+ delete(id: string): Promise<boolean>;
75
+
76
+ /**
77
+ * Count entities matching filter
78
+ * @param filter - Filter criteria
79
+ * @returns Number of matching entities
80
+ */
81
+ count(filter?: Record<string, unknown>): Promise<number>;
82
+
83
+ /**
84
+ * Check if entity exists
85
+ * @param id - Entity ID
86
+ * @returns true if exists
87
+ */
88
+ exists(id: string): Promise<boolean>;
89
+ }
90
+
91
+ /**
92
+ * Transaction interface for atomic operations
93
+ */
94
+ export interface ITransaction {
95
+ /**
96
+ * Commit the transaction
97
+ */
98
+ commit(): Promise<void>;
99
+
100
+ /**
101
+ * Rollback the transaction
102
+ */
103
+ rollback(): Promise<void>;
104
+
105
+ /**
106
+ * Execute operation within transaction
107
+ * @param operation - Async operation to execute
108
+ * @returns Operation result
109
+ */
110
+ execute<R>(operation: () => Promise<R>): Promise<R>;
111
+ }
112
+
113
+ /**
114
+ * Transaction manager interface
115
+ */
116
+ export interface ITransactionManager {
117
+ /**
118
+ * Begin a new transaction
119
+ * @returns Transaction instance
120
+ */
121
+ begin(): Promise<ITransaction>;
122
+
123
+ /**
124
+ * Execute operations in a transaction (auto commit/rollback)
125
+ * @param callback - Operations to execute
126
+ * @returns Callback result
127
+ */
128
+ transaction<R>(callback: (tx: ITransaction) => Promise<R>): Promise<R>;
129
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Mongoose Models Export
3
+ * Central export for all Mongoose schemas
4
+ */
5
+
6
+ export * from './user.schema.js';
7
+ export * from './payment.schema.js';
@@ -0,0 +1,347 @@
1
+ /**
2
+ * Mongoose Payment Schemas
3
+ * MongoDB schemas for Payment, Subscription, Plan, and Webhook entities
4
+ */
5
+
6
+ import { Schema, model, type Document } from 'mongoose';
7
+
8
+ // ==========================================
9
+ // PAYMENT SCHEMA
10
+ // ==========================================
11
+
12
+ export interface IPaymentDocument extends Document {
13
+ userId: string;
14
+ provider: 'stripe' | 'paypal' | 'mobile_money' | 'manual';
15
+ method: 'card' | 'bank_transfer' | 'mobile_money' | 'paypal' | 'other';
16
+ status: 'pending' | 'processing' | 'completed' | 'failed' | 'refunded' | 'cancelled';
17
+ amount: number;
18
+ currency: string;
19
+ description?: string;
20
+ providerPaymentId?: string;
21
+ providerCustomerId?: string;
22
+ metadata?: Record<string, unknown>;
23
+ failureReason?: string;
24
+ refundedAt?: Date;
25
+ completedAt?: Date;
26
+ createdAt: Date;
27
+ updatedAt: Date;
28
+ }
29
+
30
+ const paymentSchema = new Schema<IPaymentDocument>(
31
+ {
32
+ userId: {
33
+ type: String,
34
+ required: [true, 'User ID is required'],
35
+ index: true,
36
+ },
37
+ provider: {
38
+ type: String,
39
+ enum: ['stripe', 'paypal', 'mobile_money', 'manual'],
40
+ required: [true, 'Provider is required'],
41
+ default: 'manual',
42
+ },
43
+ method: {
44
+ type: String,
45
+ enum: ['card', 'bank_transfer', 'mobile_money', 'paypal', 'other'],
46
+ required: [true, 'Payment method is required'],
47
+ default: 'card',
48
+ },
49
+ status: {
50
+ type: String,
51
+ enum: ['pending', 'processing', 'completed', 'failed', 'refunded', 'cancelled'],
52
+ required: [true, 'Status is required'],
53
+ default: 'pending',
54
+ index: true,
55
+ },
56
+ amount: {
57
+ type: Number,
58
+ required: [true, 'Amount is required'],
59
+ min: [0, 'Amount must be positive'],
60
+ },
61
+ currency: {
62
+ type: String,
63
+ required: [true, 'Currency is required'],
64
+ default: 'USD',
65
+ uppercase: true,
66
+ length: 3,
67
+ },
68
+ description: {
69
+ type: String,
70
+ maxlength: [500, 'Description cannot exceed 500 characters'],
71
+ },
72
+ providerPaymentId: {
73
+ type: String,
74
+ index: true,
75
+ sparse: true,
76
+ },
77
+ providerCustomerId: {
78
+ type: String,
79
+ index: true,
80
+ sparse: true,
81
+ },
82
+ metadata: {
83
+ type: Schema.Types.Mixed,
84
+ default: null,
85
+ },
86
+ failureReason: {
87
+ type: String,
88
+ maxlength: [500, 'Failure reason cannot exceed 500 characters'],
89
+ },
90
+ refundedAt: {
91
+ type: Date,
92
+ default: null,
93
+ },
94
+ completedAt: {
95
+ type: Date,
96
+ default: null,
97
+ },
98
+ },
99
+ {
100
+ timestamps: true,
101
+ collection: 'payments',
102
+ toJSON: {
103
+ virtuals: true,
104
+ transform: (_doc, ret): Record<string, unknown> => {
105
+ const { _id, ...rest } = ret;
106
+ return { id: _id.toString(), ...rest };
107
+ },
108
+ },
109
+ }
110
+ );
111
+
112
+ // Indexes
113
+ paymentSchema.index({ userId: 1, createdAt: -1 });
114
+ paymentSchema.index({ provider: 1, providerPaymentId: 1 });
115
+ paymentSchema.index({ status: 1, createdAt: -1 });
116
+
117
+ export const PaymentModel = model<IPaymentDocument>('Payment', paymentSchema);
118
+
119
+ // ==========================================
120
+ // SUBSCRIPTION SCHEMA
121
+ // ==========================================
122
+
123
+ export interface ISubscriptionDocument extends Document {
124
+ userId: string;
125
+ planId: string;
126
+ provider: 'stripe' | 'paypal' | 'mobile_money' | 'manual';
127
+ status: 'active' | 'cancelled' | 'expired' | 'suspended' | 'trialing';
128
+ currentPeriodStart: Date;
129
+ currentPeriodEnd: Date;
130
+ cancelledAt?: Date;
131
+ providerSubscriptionId?: string;
132
+ metadata?: Record<string, unknown>;
133
+ createdAt: Date;
134
+ updatedAt: Date;
135
+ }
136
+
137
+ const subscriptionSchema = new Schema<ISubscriptionDocument>(
138
+ {
139
+ userId: {
140
+ type: String,
141
+ required: [true, 'User ID is required'],
142
+ index: true,
143
+ },
144
+ planId: {
145
+ type: String,
146
+ required: [true, 'Plan ID is required'],
147
+ index: true,
148
+ },
149
+ provider: {
150
+ type: String,
151
+ enum: ['stripe', 'paypal', 'mobile_money', 'manual'],
152
+ required: [true, 'Provider is required'],
153
+ default: 'manual',
154
+ },
155
+ status: {
156
+ type: String,
157
+ enum: ['active', 'cancelled', 'expired', 'suspended', 'trialing'],
158
+ required: [true, 'Status is required'],
159
+ default: 'active',
160
+ index: true,
161
+ },
162
+ currentPeriodStart: {
163
+ type: Date,
164
+ required: [true, 'Current period start is required'],
165
+ },
166
+ currentPeriodEnd: {
167
+ type: Date,
168
+ required: [true, 'Current period end is required'],
169
+ },
170
+ cancelledAt: {
171
+ type: Date,
172
+ default: null,
173
+ },
174
+ providerSubscriptionId: {
175
+ type: String,
176
+ index: true,
177
+ sparse: true,
178
+ },
179
+ metadata: {
180
+ type: Schema.Types.Mixed,
181
+ default: null,
182
+ },
183
+ },
184
+ {
185
+ timestamps: true,
186
+ collection: 'subscriptions',
187
+ toJSON: {
188
+ virtuals: true,
189
+ transform: (_doc, ret): Record<string, unknown> => {
190
+ const { _id, ...rest } = ret;
191
+ return { id: _id.toString(), ...rest };
192
+ },
193
+ },
194
+ }
195
+ );
196
+
197
+ // Indexes
198
+ subscriptionSchema.index({ userId: 1, status: 1 });
199
+ subscriptionSchema.index({ currentPeriodEnd: 1, status: 1 });
200
+
201
+ export const SubscriptionModel = model<ISubscriptionDocument>('Subscription', subscriptionSchema);
202
+
203
+ // ==========================================
204
+ // PLAN SCHEMA
205
+ // ==========================================
206
+
207
+ export interface IPlanDocument extends Document {
208
+ name: string;
209
+ amount: number;
210
+ currency: string;
211
+ interval: 'day' | 'week' | 'month' | 'year';
212
+ intervalCount: number;
213
+ trialPeriodDays?: number;
214
+ active: boolean;
215
+ metadata?: Record<string, unknown>;
216
+ createdAt: Date;
217
+ updatedAt: Date;
218
+ }
219
+
220
+ const planSchema = new Schema<IPlanDocument>(
221
+ {
222
+ name: {
223
+ type: String,
224
+ required: [true, 'Plan name is required'],
225
+ trim: true,
226
+ maxlength: [100, 'Plan name cannot exceed 100 characters'],
227
+ },
228
+ amount: {
229
+ type: Number,
230
+ required: [true, 'Amount is required'],
231
+ min: [0, 'Amount must be positive'],
232
+ },
233
+ currency: {
234
+ type: String,
235
+ required: [true, 'Currency is required'],
236
+ default: 'USD',
237
+ uppercase: true,
238
+ length: 3,
239
+ },
240
+ interval: {
241
+ type: String,
242
+ enum: ['day', 'week', 'month', 'year'],
243
+ required: [true, 'Interval is required'],
244
+ default: 'month',
245
+ },
246
+ intervalCount: {
247
+ type: Number,
248
+ required: [true, 'Interval count is required'],
249
+ default: 1,
250
+ min: [1, 'Interval count must be at least 1'],
251
+ },
252
+ trialPeriodDays: {
253
+ type: Number,
254
+ min: [0, 'Trial period must be positive'],
255
+ default: null,
256
+ },
257
+ active: {
258
+ type: Boolean,
259
+ default: true,
260
+ index: true,
261
+ },
262
+ metadata: {
263
+ type: Schema.Types.Mixed,
264
+ default: null,
265
+ },
266
+ },
267
+ {
268
+ timestamps: true,
269
+ collection: 'plans',
270
+ toJSON: {
271
+ virtuals: true,
272
+ transform: (_doc, ret): Record<string, unknown> => {
273
+ const { _id, ...rest } = ret;
274
+ return { id: _id.toString(), ...rest };
275
+ },
276
+ },
277
+ }
278
+ );
279
+
280
+ // Indexes
281
+ planSchema.index({ active: 1, createdAt: -1 });
282
+
283
+ export const PlanModel = model<IPlanDocument>('Plan', planSchema);
284
+
285
+ // ==========================================
286
+ // WEBHOOK SCHEMA
287
+ // ==========================================
288
+
289
+ export interface IWebhookDocument extends Document {
290
+ provider: 'stripe' | 'paypal' | 'mobile_money' | 'manual';
291
+ type: string;
292
+ data: Record<string, unknown>;
293
+ processed: boolean;
294
+ processedAt?: Date;
295
+ error?: string;
296
+ createdAt: Date;
297
+ updatedAt: Date;
298
+ }
299
+
300
+ const webhookSchema = new Schema<IWebhookDocument>(
301
+ {
302
+ provider: {
303
+ type: String,
304
+ enum: ['stripe', 'paypal', 'mobile_money', 'manual'],
305
+ required: [true, 'Provider is required'],
306
+ },
307
+ type: {
308
+ type: String,
309
+ required: [true, 'Webhook type is required'],
310
+ index: true,
311
+ },
312
+ data: {
313
+ type: Schema.Types.Mixed,
314
+ required: [true, 'Webhook data is required'],
315
+ },
316
+ processed: {
317
+ type: Boolean,
318
+ default: false,
319
+ index: true,
320
+ },
321
+ processedAt: {
322
+ type: Date,
323
+ default: null,
324
+ },
325
+ error: {
326
+ type: String,
327
+ maxlength: [1000, 'Error message cannot exceed 1000 characters'],
328
+ },
329
+ },
330
+ {
331
+ timestamps: true,
332
+ collection: 'payment_webhooks',
333
+ toJSON: {
334
+ virtuals: true,
335
+ transform: (_doc, ret): Record<string, unknown> => {
336
+ const { _id, ...rest } = ret;
337
+ return { id: _id.toString(), ...rest };
338
+ },
339
+ },
340
+ }
341
+ );
342
+
343
+ // Indexes
344
+ webhookSchema.index({ provider: 1, processed: 1 });
345
+ webhookSchema.index({ createdAt: -1 });
346
+
347
+ export const WebhookModel = model<IWebhookDocument>('PaymentWebhook', webhookSchema);