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,255 @@
1
+ /**
2
+ * Mongoose User Repository
3
+ * Implements IRepository for User entity using Mongoose
4
+ */
5
+
6
+ import { UserModel, type IUserDocument } from '../../models/mongoose/user.schema.js';
7
+ import type { IRepository, PaginationOptions, PaginatedResult } from '../../interfaces/index.js';
8
+
9
+ // User type matching application format (lowercase enums)
10
+ export interface User {
11
+ id: string;
12
+ email: string;
13
+ password: string;
14
+ name: string;
15
+ role: 'user' | 'moderator' | 'admin' | 'super_admin';
16
+ status: 'active' | 'inactive' | 'suspended' | 'pending';
17
+ emailVerified: boolean;
18
+ emailVerifiedAt?: Date | null;
19
+ lastLoginAt?: Date | null;
20
+ metadata?: Record<string, unknown> | null;
21
+ createdAt: Date;
22
+ updatedAt: Date;
23
+ }
24
+
25
+ /**
26
+ * Mongoose User Repository
27
+ */
28
+ export class MongooseUserRepository implements IRepository<User> {
29
+ /**
30
+ * Convert Mongoose document to User
31
+ */
32
+ private toUser(doc: IUserDocument): User {
33
+ return {
34
+ id: doc._id.toString(),
35
+ email: doc.email,
36
+ password: doc.password,
37
+ name: doc.name,
38
+ role: doc.role,
39
+ status: doc.status,
40
+ emailVerified: doc.emailVerified,
41
+ emailVerifiedAt: doc.emailVerifiedAt || null,
42
+ lastLoginAt: doc.lastLoginAt || null,
43
+ metadata: doc.metadata || null,
44
+ createdAt: doc.createdAt,
45
+ updatedAt: doc.updatedAt,
46
+ };
47
+ }
48
+
49
+ /**
50
+ * Find user by ID
51
+ */
52
+ async findById(id: string): Promise<User | null> {
53
+ try {
54
+ const user = await UserModel.findById(id).select('+password');
55
+ return user ? this.toUser(user) : null;
56
+ } catch {
57
+ return null;
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Find one user by filter
63
+ */
64
+ async findOne(filter: Record<string, unknown>): Promise<User | null> {
65
+ try {
66
+ const user = await UserModel.findOne(filter).select('+password');
67
+ return user ? this.toUser(user) : null;
68
+ } catch {
69
+ return null;
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Find many users with pagination
75
+ */
76
+ async findMany(
77
+ filter: Record<string, unknown> = {},
78
+ options: PaginationOptions = {}
79
+ ): Promise<PaginatedResult<User>> {
80
+ const page = options.page || 1;
81
+ const limit = options.limit || 10;
82
+ const skip = (page - 1) * limit;
83
+
84
+ // Build sort
85
+ const sortBy = options.sortBy || 'createdAt';
86
+ const sortOrder = options.sortOrder === 'asc' ? 1 : -1;
87
+ const sort: Record<string, 1 | -1> = { [sortBy]: sortOrder };
88
+
89
+ // Execute queries
90
+ const [users, total] = await Promise.all([
91
+ UserModel.find(filter).sort(sort).skip(skip).limit(limit).exec(),
92
+ UserModel.countDocuments(filter),
93
+ ]);
94
+
95
+ const totalPages = Math.ceil(total / limit);
96
+
97
+ return {
98
+ data: users.map((user) => this.toUser(user)),
99
+ pagination: {
100
+ page,
101
+ limit,
102
+ total,
103
+ totalPages,
104
+ hasNext: page < totalPages,
105
+ hasPrev: page > 1,
106
+ },
107
+ };
108
+ }
109
+
110
+ /**
111
+ * Create new user
112
+ */
113
+ async create(data: Partial<User>): Promise<User> {
114
+ const user = await UserModel.create({
115
+ email: data.email,
116
+ password: data.password,
117
+ name: data.name,
118
+ role: data.role || 'user',
119
+ status: data.status || 'active',
120
+ emailVerified: data.emailVerified || false,
121
+ emailVerifiedAt: data.emailVerifiedAt,
122
+ lastLoginAt: data.lastLoginAt,
123
+ metadata: data.metadata,
124
+ });
125
+
126
+ // Fetch with password included
127
+ const created = await UserModel.findById(user._id).select('+password');
128
+ if (!created) {
129
+ throw new Error('Failed to create user');
130
+ }
131
+
132
+ return this.toUser(created);
133
+ }
134
+
135
+ /**
136
+ * Update user by ID
137
+ */
138
+ async update(id: string, data: Partial<User>): Promise<User | null> {
139
+ try {
140
+ // Don't allow updating password directly (use separate method)
141
+ const updateData = { ...data };
142
+ delete updateData.password;
143
+
144
+ const user = await UserModel.findByIdAndUpdate(
145
+ id,
146
+ { $set: updateData },
147
+ { new: true, runValidators: true }
148
+ ).select('+password');
149
+
150
+ return user ? this.toUser(user) : null;
151
+ } catch {
152
+ return null;
153
+ }
154
+ }
155
+
156
+ /**
157
+ * Delete user by ID
158
+ */
159
+ async delete(id: string): Promise<boolean> {
160
+ try {
161
+ const result = await UserModel.findByIdAndDelete(id);
162
+ return result !== null;
163
+ } catch {
164
+ return false;
165
+ }
166
+ }
167
+
168
+ /**
169
+ * Count users
170
+ */
171
+ async count(filter: Record<string, unknown> = {}): Promise<number> {
172
+ return UserModel.countDocuments(filter);
173
+ }
174
+
175
+ /**
176
+ * Check if user exists
177
+ */
178
+ async exists(id: string): Promise<boolean> {
179
+ const count = await UserModel.countDocuments({ _id: id });
180
+ return count > 0;
181
+ }
182
+
183
+ /**
184
+ * Find user by email
185
+ */
186
+ async findByEmail(email: string): Promise<User | null> {
187
+ return this.findOne({ email: email.toLowerCase() });
188
+ }
189
+
190
+ /**
191
+ * Update password
192
+ */
193
+ async updatePassword(id: string, newPassword: string): Promise<boolean> {
194
+ try {
195
+ const user = await UserModel.findById(id);
196
+ if (!user) {
197
+ return false;
198
+ }
199
+
200
+ user.password = newPassword;
201
+ await user.save(); // Will trigger pre-save hook to hash password
202
+
203
+ return true;
204
+ } catch {
205
+ return false;
206
+ }
207
+ }
208
+
209
+ /**
210
+ * Verify user password
211
+ */
212
+ async verifyPassword(id: string, password: string): Promise<boolean> {
213
+ try {
214
+ const user = await UserModel.findById(id).select('+password');
215
+ if (!user) {
216
+ return false;
217
+ }
218
+
219
+ return user.comparePassword(password);
220
+ } catch {
221
+ return false;
222
+ }
223
+ }
224
+
225
+ /**
226
+ * Update last login
227
+ */
228
+ async updateLastLogin(id: string): Promise<boolean> {
229
+ try {
230
+ const result = await UserModel.findByIdAndUpdate(id, {
231
+ $set: { lastLoginAt: new Date() },
232
+ });
233
+ return result !== null;
234
+ } catch {
235
+ return false;
236
+ }
237
+ }
238
+
239
+ /**
240
+ * Verify email
241
+ */
242
+ async verifyEmail(id: string): Promise<boolean> {
243
+ try {
244
+ const result = await UserModel.findByIdAndUpdate(id, {
245
+ $set: {
246
+ emailVerified: true,
247
+ emailVerifiedAt: new Date(),
248
+ },
249
+ });
250
+ return result !== null;
251
+ } catch {
252
+ return false;
253
+ }
254
+ }
255
+ }
@@ -26,7 +26,12 @@ async function main(): Promise<void> {
26
26
  // Create default settings
27
27
  const defaultSettings = [
28
28
  { key: 'app.name', value: 'Servcraft', type: 'string', group: 'general' },
29
- { key: 'app.description', value: 'A modular Node.js backend framework', type: 'string', group: 'general' },
29
+ {
30
+ key: 'app.description',
31
+ value: 'A modular Node.js backend framework',
32
+ type: 'string',
33
+ group: 'general',
34
+ },
30
35
  { key: 'auth.registration_enabled', value: true, type: 'boolean', group: 'auth' },
31
36
  { key: 'auth.email_verification_required', value: false, type: 'boolean', group: 'auth' },
32
37
  { key: 'email.enabled', value: false, type: 'boolean', group: 'email' },
package/src/index.ts CHANGED
@@ -1,10 +1,8 @@
1
- import { Server, createServer } from './core/server.js';
1
+ import { createServer } from './core/server.js';
2
2
  import { logger } from './core/logger.js';
3
3
  import { config } from './config/index.js';
4
4
  import { registerSecurity, registerErrorHandler } from './middleware/index.js';
5
5
  import { registerAuthModule } from './modules/auth/index.js';
6
- import { registerUserModule } from './modules/user/index.js';
7
- import { registerSwagger } from './modules/swagger/index.js';
8
6
 
9
7
  async function bootstrap(): Promise<void> {
10
8
  // Create server instance
@@ -21,28 +19,19 @@ async function bootstrap(): Promise<void> {
21
19
  // Register security middleware
22
20
  await registerSecurity(app);
23
21
 
24
- // Register Swagger documentation (auto-updates as routes are added)
25
- await registerSwagger(app, {
26
- enabled: config.swagger.enabled,
27
- route: config.swagger.route,
28
- title: config.swagger.title,
29
- description: config.swagger.description,
30
- version: config.swagger.version,
31
- });
32
-
33
22
  // Register auth module
34
- const authService = await registerAuthModule(app);
35
-
36
- // Register user module (depends on auth for guard middleware)
37
- await registerUserModule(app, authService);
23
+ await registerAuthModule(app);
38
24
 
39
25
  // Start server
40
26
  await server.start();
41
27
 
42
- logger.info({
43
- env: config.env.NODE_ENV,
44
- port: config.server.port,
45
- }, 'Servcraft server started');
28
+ logger.info(
29
+ {
30
+ env: config.env.NODE_ENV,
31
+ port: config.server.port,
32
+ },
33
+ 'Servcraft server started'
34
+ );
46
35
  }
47
36
 
48
37
  bootstrap().catch((err) => {
@@ -65,9 +65,7 @@ export async function registerSecurity(
65
65
  keyGenerator: (request) => {
66
66
  // Use X-Forwarded-For if behind proxy, otherwise use IP
67
67
  return (
68
- request.headers['x-forwarded-for']?.toString().split(',')[0] ||
69
- request.ip ||
70
- 'unknown'
68
+ request.headers['x-forwarded-for']?.toString().split(',')[0] || request.ip || 'unknown'
71
69
  );
72
70
  },
73
71
  });
@@ -94,9 +92,7 @@ export async function registerBruteForceProtection(
94
92
  timeWindow,
95
93
  keyGenerator: (request) => {
96
94
  const ip =
97
- request.headers['x-forwarded-for']?.toString().split(',')[0] ||
98
- request.ip ||
99
- 'unknown';
95
+ request.headers['x-forwarded-for']?.toString().split(',')[0] || request.ip || 'unknown';
100
96
  return `brute:${routePrefix}:${ip}`;
101
97
  },
102
98
  errorResponseBuilder: () => ({
@@ -0,0 +1,80 @@
1
+ import { Router } from 'express';
2
+ import type { Request, Response } from 'express';
3
+ import type { AnalyticsService } from './analytics.service.js';
4
+ import type { AnalyticsEvent, MetricQuery } from './types.js';
5
+
6
+ /**
7
+ * Create analytics routes
8
+ */
9
+ export function createAnalyticsRoutes(analyticsService: AnalyticsService): Router {
10
+ const router = Router();
11
+
12
+ /**
13
+ * Track event
14
+ * POST /events
15
+ */
16
+ router.post('/events', (req: Request, res: Response) => {
17
+ const event = req.body as Omit<AnalyticsEvent, 'timestamp'>;
18
+ analyticsService.trackEvent(event);
19
+ res.json({ success: true });
20
+ });
21
+
22
+ /**
23
+ * Get events
24
+ * GET /events?limit=100
25
+ */
26
+ router.get('/events', (req: Request, res: Response) => {
27
+ const limit = req.query.limit ? parseInt(req.query.limit as string, 10) : 100;
28
+ const events = analyticsService.getEvents(limit);
29
+ res.json({ events, count: events.length });
30
+ });
31
+
32
+ /**
33
+ * Get counters
34
+ * GET /metrics/counters
35
+ */
36
+ router.get('/metrics/counters', (_req: Request, res: Response) => {
37
+ const counters = analyticsService.getCounters();
38
+ res.json({ counters, count: counters.length });
39
+ });
40
+
41
+ /**
42
+ * Get gauges
43
+ * GET /metrics/gauges
44
+ */
45
+ router.get('/metrics/gauges', (_req: Request, res: Response) => {
46
+ const gauges = analyticsService.getGauges();
47
+ res.json({ gauges, count: gauges.length });
48
+ });
49
+
50
+ /**
51
+ * Query metrics
52
+ * POST /metrics/query
53
+ */
54
+ router.post('/metrics/query', async (req: Request, res: Response) => {
55
+ const query = req.body as MetricQuery;
56
+ const result = await analyticsService.queryMetrics(query);
57
+ res.json(result);
58
+ });
59
+
60
+ /**
61
+ * Prometheus metrics endpoint
62
+ * GET /metrics
63
+ */
64
+ router.get('/metrics', (_req: Request, res: Response) => {
65
+ const metrics = analyticsService.getPrometheusMetrics();
66
+ res.set('Content-Type', 'text/plain');
67
+ res.send(metrics);
68
+ });
69
+
70
+ /**
71
+ * Clear all metrics
72
+ * POST /clear
73
+ */
74
+ router.post('/clear', (_req: Request, res: Response) => {
75
+ analyticsService.clear();
76
+ res.json({ success: true, message: 'All metrics cleared' });
77
+ });
78
+
79
+ return router;
80
+ }