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.
- package/.claude/settings.local.json +30 -0
- package/.github/CODEOWNERS +18 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +46 -0
- package/.github/dependabot.yml +59 -0
- package/.github/workflows/ci.yml +188 -0
- package/.github/workflows/release.yml +195 -0
- package/AUDIT.md +602 -0
- package/LICENSE +21 -0
- package/README.md +1102 -1
- package/dist/cli/index.cjs +2026 -2168
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +2026 -2168
- package/dist/cli/index.js.map +1 -1
- package/dist/index.cjs +595 -616
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +114 -52
- package/dist/index.d.ts +114 -52
- package/dist/index.js +595 -616
- package/dist/index.js.map +1 -1
- package/docs/CLI-001_MULTI_DB_PLAN.md +546 -0
- package/docs/DATABASE_MULTI_ORM.md +399 -0
- package/docs/PHASE1_BREAKDOWN.md +346 -0
- package/docs/PROGRESS.md +550 -0
- package/docs/modules/ANALYTICS.md +226 -0
- package/docs/modules/API-VERSIONING.md +252 -0
- package/docs/modules/AUDIT.md +192 -0
- package/docs/modules/AUTH.md +431 -0
- package/docs/modules/CACHE.md +346 -0
- package/docs/modules/EMAIL.md +254 -0
- package/docs/modules/FEATURE-FLAG.md +291 -0
- package/docs/modules/I18N.md +294 -0
- package/docs/modules/MEDIA-PROCESSING.md +281 -0
- package/docs/modules/MFA.md +266 -0
- package/docs/modules/NOTIFICATION.md +311 -0
- package/docs/modules/OAUTH.md +237 -0
- package/docs/modules/PAYMENT.md +804 -0
- package/docs/modules/QUEUE.md +540 -0
- package/docs/modules/RATE-LIMIT.md +339 -0
- package/docs/modules/SEARCH.md +288 -0
- package/docs/modules/SECURITY.md +327 -0
- package/docs/modules/SESSION.md +382 -0
- package/docs/modules/SWAGGER.md +305 -0
- package/docs/modules/UPLOAD.md +296 -0
- package/docs/modules/USER.md +505 -0
- package/docs/modules/VALIDATION.md +294 -0
- package/docs/modules/WEBHOOK.md +270 -0
- package/docs/modules/WEBSOCKET.md +691 -0
- package/package.json +53 -38
- package/prisma/schema.prisma +395 -1
- package/src/cli/commands/add-module.ts +520 -87
- package/src/cli/commands/db.ts +3 -4
- package/src/cli/commands/docs.ts +256 -6
- package/src/cli/commands/generate.ts +12 -19
- package/src/cli/commands/init.ts +384 -214
- package/src/cli/index.ts +0 -4
- package/src/cli/templates/repository.ts +6 -1
- package/src/cli/templates/routes.ts +6 -21
- package/src/cli/utils/docs-generator.ts +6 -7
- package/src/cli/utils/env-manager.ts +717 -0
- package/src/cli/utils/field-parser.ts +16 -7
- package/src/cli/utils/interactive-prompt.ts +223 -0
- package/src/cli/utils/template-manager.ts +346 -0
- package/src/config/database.config.ts +183 -0
- package/src/config/env.ts +0 -10
- package/src/config/index.ts +0 -14
- package/src/core/server.ts +1 -1
- package/src/database/adapters/mongoose.adapter.ts +132 -0
- package/src/database/adapters/prisma.adapter.ts +118 -0
- package/src/database/connection.ts +190 -0
- package/src/database/interfaces/database.interface.ts +85 -0
- package/src/database/interfaces/index.ts +7 -0
- package/src/database/interfaces/repository.interface.ts +129 -0
- package/src/database/models/mongoose/index.ts +7 -0
- package/src/database/models/mongoose/payment.schema.ts +347 -0
- package/src/database/models/mongoose/user.schema.ts +154 -0
- package/src/database/prisma.ts +1 -4
- package/src/database/redis.ts +101 -0
- package/src/database/repositories/mongoose/index.ts +7 -0
- package/src/database/repositories/mongoose/payment.repository.ts +380 -0
- package/src/database/repositories/mongoose/user.repository.ts +255 -0
- package/src/database/seed.ts +6 -1
- package/src/index.ts +9 -20
- package/src/middleware/security.ts +2 -6
- package/src/modules/analytics/analytics.routes.ts +80 -0
- package/src/modules/analytics/analytics.service.ts +364 -0
- package/src/modules/analytics/index.ts +18 -0
- package/src/modules/analytics/types.ts +180 -0
- package/src/modules/api-versioning/index.ts +15 -0
- package/src/modules/api-versioning/types.ts +86 -0
- package/src/modules/api-versioning/versioning.middleware.ts +120 -0
- package/src/modules/api-versioning/versioning.routes.ts +54 -0
- package/src/modules/api-versioning/versioning.service.ts +189 -0
- package/src/modules/audit/audit.repository.ts +206 -0
- package/src/modules/audit/audit.service.ts +27 -59
- package/src/modules/auth/auth.controller.ts +2 -2
- package/src/modules/auth/auth.middleware.ts +3 -9
- package/src/modules/auth/auth.routes.ts +10 -107
- package/src/modules/auth/auth.service.ts +126 -23
- package/src/modules/auth/index.ts +3 -4
- package/src/modules/cache/cache.service.ts +367 -0
- package/src/modules/cache/index.ts +10 -0
- package/src/modules/cache/types.ts +44 -0
- package/src/modules/email/email.service.ts +3 -10
- package/src/modules/email/templates.ts +2 -8
- package/src/modules/feature-flag/feature-flag.repository.ts +303 -0
- package/src/modules/feature-flag/feature-flag.routes.ts +247 -0
- package/src/modules/feature-flag/feature-flag.service.ts +566 -0
- package/src/modules/feature-flag/index.ts +20 -0
- package/src/modules/feature-flag/types.ts +192 -0
- package/src/modules/i18n/i18n.middleware.ts +186 -0
- package/src/modules/i18n/i18n.routes.ts +191 -0
- package/src/modules/i18n/i18n.service.ts +456 -0
- package/src/modules/i18n/index.ts +18 -0
- package/src/modules/i18n/types.ts +118 -0
- package/src/modules/media-processing/index.ts +17 -0
- package/src/modules/media-processing/media-processing.routes.ts +111 -0
- package/src/modules/media-processing/media-processing.service.ts +245 -0
- package/src/modules/media-processing/types.ts +156 -0
- package/src/modules/mfa/index.ts +20 -0
- package/src/modules/mfa/mfa.repository.ts +206 -0
- package/src/modules/mfa/mfa.routes.ts +595 -0
- package/src/modules/mfa/mfa.service.ts +572 -0
- package/src/modules/mfa/totp.ts +150 -0
- package/src/modules/mfa/types.ts +57 -0
- package/src/modules/notification/index.ts +20 -0
- package/src/modules/notification/notification.repository.ts +356 -0
- package/src/modules/notification/notification.service.ts +483 -0
- package/src/modules/notification/types.ts +119 -0
- package/src/modules/oauth/index.ts +20 -0
- package/src/modules/oauth/oauth.repository.ts +219 -0
- package/src/modules/oauth/oauth.routes.ts +446 -0
- package/src/modules/oauth/oauth.service.ts +293 -0
- package/src/modules/oauth/providers/apple.provider.ts +250 -0
- package/src/modules/oauth/providers/facebook.provider.ts +181 -0
- package/src/modules/oauth/providers/github.provider.ts +248 -0
- package/src/modules/oauth/providers/google.provider.ts +189 -0
- package/src/modules/oauth/providers/twitter.provider.ts +214 -0
- package/src/modules/oauth/types.ts +94 -0
- package/src/modules/payment/index.ts +19 -0
- package/src/modules/payment/payment.repository.ts +733 -0
- package/src/modules/payment/payment.routes.ts +390 -0
- package/src/modules/payment/payment.service.ts +354 -0
- package/src/modules/payment/providers/mobile-money.provider.ts +274 -0
- package/src/modules/payment/providers/paypal.provider.ts +190 -0
- package/src/modules/payment/providers/stripe.provider.ts +215 -0
- package/src/modules/payment/types.ts +140 -0
- package/src/modules/queue/cron.ts +438 -0
- package/src/modules/queue/index.ts +87 -0
- package/src/modules/queue/queue.routes.ts +600 -0
- package/src/modules/queue/queue.service.ts +842 -0
- package/src/modules/queue/types.ts +222 -0
- package/src/modules/queue/workers.ts +366 -0
- package/src/modules/rate-limit/index.ts +59 -0
- package/src/modules/rate-limit/rate-limit.middleware.ts +134 -0
- package/src/modules/rate-limit/rate-limit.routes.ts +269 -0
- package/src/modules/rate-limit/rate-limit.service.ts +348 -0
- package/src/modules/rate-limit/stores/memory.store.ts +165 -0
- package/src/modules/rate-limit/stores/redis.store.ts +322 -0
- package/src/modules/rate-limit/types.ts +153 -0
- package/src/modules/search/adapters/elasticsearch.adapter.ts +326 -0
- package/src/modules/search/adapters/meilisearch.adapter.ts +261 -0
- package/src/modules/search/adapters/memory.adapter.ts +278 -0
- package/src/modules/search/index.ts +21 -0
- package/src/modules/search/search.service.ts +234 -0
- package/src/modules/search/types.ts +214 -0
- package/src/modules/security/index.ts +40 -0
- package/src/modules/security/sanitize.ts +223 -0
- package/src/modules/security/security-audit.service.ts +388 -0
- package/src/modules/security/security.middleware.ts +398 -0
- package/src/modules/session/index.ts +3 -0
- package/src/modules/session/session.repository.ts +159 -0
- package/src/modules/session/session.service.ts +340 -0
- package/src/modules/session/types.ts +38 -0
- package/src/modules/swagger/index.ts +7 -1
- package/src/modules/swagger/schema-builder.ts +16 -4
- package/src/modules/swagger/swagger.service.ts +9 -10
- package/src/modules/swagger/types.ts +0 -2
- package/src/modules/upload/index.ts +14 -0
- package/src/modules/upload/types.ts +83 -0
- package/src/modules/upload/upload.repository.ts +199 -0
- package/src/modules/upload/upload.routes.ts +311 -0
- package/src/modules/upload/upload.service.ts +448 -0
- package/src/modules/user/index.ts +3 -3
- package/src/modules/user/user.controller.ts +15 -9
- package/src/modules/user/user.repository.ts +237 -113
- package/src/modules/user/user.routes.ts +39 -164
- package/src/modules/user/user.service.ts +4 -3
- package/src/modules/validation/validator.ts +12 -17
- package/src/modules/webhook/index.ts +91 -0
- package/src/modules/webhook/retry.ts +196 -0
- package/src/modules/webhook/signature.ts +135 -0
- package/src/modules/webhook/types.ts +181 -0
- package/src/modules/webhook/webhook.repository.ts +358 -0
- package/src/modules/webhook/webhook.routes.ts +442 -0
- package/src/modules/webhook/webhook.service.ts +457 -0
- package/src/modules/websocket/features.ts +504 -0
- package/src/modules/websocket/index.ts +106 -0
- package/src/modules/websocket/middlewares.ts +298 -0
- package/src/modules/websocket/types.ts +181 -0
- package/src/modules/websocket/websocket.service.ts +692 -0
- package/src/utils/errors.ts +7 -0
- package/src/utils/pagination.ts +4 -1
- package/tests/helpers/db-check.ts +79 -0
- package/tests/integration/auth-redis.test.ts +94 -0
- package/tests/integration/cache-redis.test.ts +387 -0
- package/tests/integration/mongoose-repositories.test.ts +410 -0
- package/tests/integration/payment-prisma.test.ts +637 -0
- package/tests/integration/queue-bullmq.test.ts +417 -0
- package/tests/integration/user-prisma.test.ts +441 -0
- package/tests/integration/websocket-socketio.test.ts +552 -0
- package/tests/setup.ts +11 -9
- package/vitest.config.ts +3 -8
- package/npm-cache/_cacache/content-v2/sha512/1c/d0/03440d500a0487621aad1d6402978340698976602046db8e24fa03c01ee6c022c69b0582f969042d9442ee876ac35c038e960dd427d1e622fa24b8eb7dba +0 -0
- package/npm-cache/_cacache/content-v2/sha512/42/55/28b493ca491833e5aab0e9c3108d29ab3f36c248ca88f45d4630674fce9130959e56ae308797ac2b6328fa7f09a610b9550ed09cb971d039876d293fc69d +0 -0
- package/npm-cache/_cacache/content-v2/sha512/e0/12/f360dc9315ee5f17844a0c8c233ee6bf7c30837c4a02ea0d56c61c7f7ab21c0e958e50ed2c57c59f983c762b93056778c9009b2398ffc26def0183999b13 +0 -0
- package/npm-cache/_cacache/content-v2/sha512/ed/b0/fae1161902898f4c913c67d7f6cdf6be0665aec3b389b9c4f4f0a101ca1da59badf1b59c4e0030f5223023b8d63cfe501c46a32c20c895d4fb3f11ca2232 +0 -0
- 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
|
+
}
|
package/src/database/seed.ts
CHANGED
|
@@ -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
|
-
{
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
+
}
|