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.
- package/.claude/settings.local.json +29 -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/README.md +1070 -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,505 @@
|
|
|
1
|
+
# User Module Documentation
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The User module manages user data persistence using Prisma ORM with PostgreSQL/MySQL/SQLite support. It provides a complete CRUD interface with pagination, filtering, and search capabilities.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- ✅ Prisma ORM integration (PostgreSQL, MySQL, SQLite)
|
|
10
|
+
- ✅ Full CRUD operations
|
|
11
|
+
- ✅ Pagination and filtering
|
|
12
|
+
- ✅ Case-insensitive email search
|
|
13
|
+
- ✅ Role-based access control (RBAC)
|
|
14
|
+
- ✅ User status management
|
|
15
|
+
- ✅ Metadata storage (JSON field)
|
|
16
|
+
- ✅ Enum mapping (Prisma ↔ Application types)
|
|
17
|
+
|
|
18
|
+
## Architecture
|
|
19
|
+
|
|
20
|
+
### Components
|
|
21
|
+
|
|
22
|
+
1. **UserRepository** (`user.repository.ts`)
|
|
23
|
+
- Data access layer using Prisma
|
|
24
|
+
- **Migrated from Map<> to Prisma** ✅ (Completed 2025-12-19)
|
|
25
|
+
- Handles type conversions between Prisma and application types
|
|
26
|
+
|
|
27
|
+
2. **UserService** (`user.service.ts`)
|
|
28
|
+
- Business logic layer
|
|
29
|
+
- Delegates to repository for data operations
|
|
30
|
+
|
|
31
|
+
3. **UserController** (`user.controller.ts`)
|
|
32
|
+
- HTTP request handlers
|
|
33
|
+
- Input validation
|
|
34
|
+
- Response formatting
|
|
35
|
+
|
|
36
|
+
## Database Schema
|
|
37
|
+
|
|
38
|
+
The User model in Prisma:
|
|
39
|
+
|
|
40
|
+
```prisma
|
|
41
|
+
model User {
|
|
42
|
+
id String @id @default(uuid())
|
|
43
|
+
email String @unique
|
|
44
|
+
password String
|
|
45
|
+
name String?
|
|
46
|
+
role UserRole @default(USER)
|
|
47
|
+
status UserStatus @default(ACTIVE)
|
|
48
|
+
emailVerified Boolean @default(false)
|
|
49
|
+
lastLoginAt DateTime?
|
|
50
|
+
metadata Json?
|
|
51
|
+
|
|
52
|
+
createdAt DateTime @default(now())
|
|
53
|
+
updatedAt DateTime @updatedAt
|
|
54
|
+
|
|
55
|
+
// Relations
|
|
56
|
+
refreshTokens RefreshToken[]
|
|
57
|
+
sessions Session[]
|
|
58
|
+
auditLogs AuditLog[]
|
|
59
|
+
|
|
60
|
+
@@index([email])
|
|
61
|
+
@@index([status])
|
|
62
|
+
@@index([role])
|
|
63
|
+
@@map("users")
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
enum UserRole {
|
|
67
|
+
USER
|
|
68
|
+
MODERATOR
|
|
69
|
+
ADMIN
|
|
70
|
+
SUPER_ADMIN
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
enum UserStatus {
|
|
74
|
+
ACTIVE
|
|
75
|
+
INACTIVE
|
|
76
|
+
SUSPENDED
|
|
77
|
+
BANNED
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Repository API
|
|
82
|
+
|
|
83
|
+
### Create Operations
|
|
84
|
+
|
|
85
|
+
#### `create(data: CreateUserData): Promise<User>`
|
|
86
|
+
|
|
87
|
+
Create a new user.
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
const user = await userRepository.create({
|
|
91
|
+
email: 'user@example.com',
|
|
92
|
+
password: await authService.hashPassword('password123'),
|
|
93
|
+
name: 'John Doe',
|
|
94
|
+
role: 'user', // optional, defaults to 'user'
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**Features:**
|
|
99
|
+
- Email is automatically lowercased
|
|
100
|
+
- Default role: 'user'
|
|
101
|
+
- Default status: 'active'
|
|
102
|
+
- Default emailVerified: false
|
|
103
|
+
- Throws error on duplicate email
|
|
104
|
+
|
|
105
|
+
### Read Operations
|
|
106
|
+
|
|
107
|
+
#### `findById(id: string): Promise<User | null>`
|
|
108
|
+
|
|
109
|
+
Find user by ID.
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
const user = await userRepository.findById('user-uuid');
|
|
113
|
+
if (user) {
|
|
114
|
+
console.log(user.email);
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
#### `findByEmail(email: string): Promise<User | null>`
|
|
119
|
+
|
|
120
|
+
Find user by email (case-insensitive).
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
const user = await userRepository.findByEmail('USER@EXAMPLE.COM');
|
|
124
|
+
// Returns user with email 'user@example.com'
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
#### `findMany(params: PaginationParams, filters?: UserFilters): Promise<PaginatedResult<User>>`
|
|
128
|
+
|
|
129
|
+
Find multiple users with pagination and filtering.
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
const result = await userRepository.findMany(
|
|
133
|
+
{ page: 1, limit: 10, sortBy: 'createdAt', sortOrder: 'desc' },
|
|
134
|
+
{ role: 'admin', status: 'active', search: 'john' }
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
console.log(result.data); // Array of users
|
|
138
|
+
console.log(result.meta); // Pagination metadata
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
**Pagination params:**
|
|
142
|
+
- `page`: Page number (1-indexed)
|
|
143
|
+
- `limit`: Items per page
|
|
144
|
+
- `sortBy`: Field to sort by (optional)
|
|
145
|
+
- `sortOrder`: 'asc' or 'desc' (optional)
|
|
146
|
+
|
|
147
|
+
**Filter options:**
|
|
148
|
+
- `role`: Filter by role
|
|
149
|
+
- `status`: Filter by status
|
|
150
|
+
- `emailVerified`: Filter by verification status
|
|
151
|
+
- `search`: Search in email or name (case-insensitive)
|
|
152
|
+
|
|
153
|
+
### Update Operations
|
|
154
|
+
|
|
155
|
+
#### `update(id: string, data: UpdateUserData): Promise<User | null>`
|
|
156
|
+
|
|
157
|
+
Update user data.
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
const updated = await userRepository.update('user-id', {
|
|
161
|
+
name: 'Jane Doe',
|
|
162
|
+
role: 'admin',
|
|
163
|
+
emailVerified: true,
|
|
164
|
+
metadata: { preferences: { theme: 'dark' } },
|
|
165
|
+
});
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
**Updatable fields:**
|
|
169
|
+
- `email`
|
|
170
|
+
- `name`
|
|
171
|
+
- `role`
|
|
172
|
+
- `status`
|
|
173
|
+
- `emailVerified`
|
|
174
|
+
- `metadata`
|
|
175
|
+
|
|
176
|
+
#### `updatePassword(id: string, password: string): Promise<User | null>`
|
|
177
|
+
|
|
178
|
+
Update user password.
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
const newHash = await authService.hashPassword('newPassword123');
|
|
182
|
+
await userRepository.updatePassword('user-id', newHash);
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
#### `updateLastLogin(id: string): Promise<User | null>`
|
|
186
|
+
|
|
187
|
+
Update last login timestamp.
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
await userRepository.updateLastLogin('user-id');
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Delete Operations
|
|
194
|
+
|
|
195
|
+
#### `delete(id: string): Promise<boolean>`
|
|
196
|
+
|
|
197
|
+
Delete user by ID.
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
const deleted = await userRepository.delete('user-id');
|
|
201
|
+
if (deleted) {
|
|
202
|
+
console.log('User deleted successfully');
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
**Note:** Cascades to related records (sessions, refresh tokens, audit logs).
|
|
207
|
+
|
|
208
|
+
### Count Operations
|
|
209
|
+
|
|
210
|
+
#### `count(filters?: UserFilters): Promise<number>`
|
|
211
|
+
|
|
212
|
+
Count users with optional filters.
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
const totalUsers = await userRepository.count();
|
|
216
|
+
const activeAdmins = await userRepository.count({ role: 'admin', status: 'active' });
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### Testing Utilities
|
|
220
|
+
|
|
221
|
+
#### `clear(): Promise<void>`
|
|
222
|
+
|
|
223
|
+
**⚠️ WARNING:** Deletes all users from the database. Use only in tests.
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
// In test setup
|
|
227
|
+
beforeEach(async () => {
|
|
228
|
+
await userRepository.clear();
|
|
229
|
+
});
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## User Roles
|
|
233
|
+
|
|
234
|
+
| Role | Description | Permissions |
|
|
235
|
+
|------|-------------|-------------|
|
|
236
|
+
| `user` | Regular user | Profile read/update |
|
|
237
|
+
| `moderator` | Content moderator | User read, content CRUD |
|
|
238
|
+
| `admin` | Administrator | User CRUD, settings read |
|
|
239
|
+
| `super_admin` | Super administrator | All permissions |
|
|
240
|
+
|
|
241
|
+
## User Statuses
|
|
242
|
+
|
|
243
|
+
| Status | Description |
|
|
244
|
+
|--------|-------------|
|
|
245
|
+
| `active` | Normal active user |
|
|
246
|
+
| `inactive` | Inactive account (can be reactivated) |
|
|
247
|
+
| `suspended` | Temporarily suspended |
|
|
248
|
+
| `banned` | Permanently banned |
|
|
249
|
+
|
|
250
|
+
## Type Mapping
|
|
251
|
+
|
|
252
|
+
The repository handles enum conversions between Prisma (UPPERCASE) and application types (lowercase):
|
|
253
|
+
|
|
254
|
+
| Application Type | Prisma Enum |
|
|
255
|
+
|------------------|-------------|
|
|
256
|
+
| `user` | `UserRole.USER` |
|
|
257
|
+
| `admin` | `UserRole.ADMIN` |
|
|
258
|
+
| `moderator` | `UserRole.MODERATOR` |
|
|
259
|
+
| `super_admin` | `UserRole.SUPER_ADMIN` |
|
|
260
|
+
| `active` | `UserStatus.ACTIVE` |
|
|
261
|
+
| `inactive` | `UserStatus.INACTIVE` |
|
|
262
|
+
| `suspended` | `UserStatus.SUSPENDED` |
|
|
263
|
+
| `banned` | `UserStatus.BANNED` |
|
|
264
|
+
|
|
265
|
+
This is handled automatically by private mapping methods.
|
|
266
|
+
|
|
267
|
+
## Usage Examples
|
|
268
|
+
|
|
269
|
+
### Complete User Lifecycle
|
|
270
|
+
|
|
271
|
+
```typescript
|
|
272
|
+
import { UserRepository } from './modules/user/user.repository.js';
|
|
273
|
+
import { AuthService } from './modules/auth/auth.service.js';
|
|
274
|
+
|
|
275
|
+
const userRepo = new UserRepository();
|
|
276
|
+
const authService = new AuthService(app);
|
|
277
|
+
|
|
278
|
+
// 1. Create user
|
|
279
|
+
const user = await userRepo.create({
|
|
280
|
+
email: 'newuser@example.com',
|
|
281
|
+
password: await authService.hashPassword('SecurePass123!'),
|
|
282
|
+
name: 'New User',
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
// 2. Find user
|
|
286
|
+
const found = await userRepo.findByEmail('newuser@example.com');
|
|
287
|
+
|
|
288
|
+
// 3. Update user
|
|
289
|
+
await userRepo.update(user.id, {
|
|
290
|
+
emailVerified: true,
|
|
291
|
+
metadata: { onboarded: true },
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
// 4. Track login
|
|
295
|
+
await userRepo.updateLastLogin(user.id);
|
|
296
|
+
|
|
297
|
+
// 5. Promote to admin
|
|
298
|
+
await userRepo.update(user.id, { role: 'admin' });
|
|
299
|
+
|
|
300
|
+
// 6. Suspend user
|
|
301
|
+
await userRepo.update(user.id, { status: 'suspended' });
|
|
302
|
+
|
|
303
|
+
// 7. Delete user
|
|
304
|
+
await userRepo.delete(user.id);
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### Admin Dashboard - User List
|
|
308
|
+
|
|
309
|
+
```typescript
|
|
310
|
+
// Get paginated users with filters
|
|
311
|
+
const result = await userRepo.findMany(
|
|
312
|
+
{
|
|
313
|
+
page: 1,
|
|
314
|
+
limit: 20,
|
|
315
|
+
sortBy: 'createdAt',
|
|
316
|
+
sortOrder: 'desc'
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
status: 'active',
|
|
320
|
+
// Optional: filter by role
|
|
321
|
+
// role: 'user',
|
|
322
|
+
}
|
|
323
|
+
);
|
|
324
|
+
|
|
325
|
+
// Display results
|
|
326
|
+
result.data.forEach(user => {
|
|
327
|
+
console.log(`${user.name} (${user.email}) - ${user.role}`);
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
console.log(`Page ${result.meta.page} of ${result.meta.totalPages}`);
|
|
331
|
+
console.log(`Total users: ${result.meta.total}`);
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### Search Users
|
|
335
|
+
|
|
336
|
+
```typescript
|
|
337
|
+
// Search by name or email
|
|
338
|
+
const searchResult = await userRepo.findMany(
|
|
339
|
+
{ page: 1, limit: 10 },
|
|
340
|
+
{ search: 'john' }
|
|
341
|
+
);
|
|
342
|
+
// Returns users with 'john' in name or email
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
## Migration from In-Memory
|
|
346
|
+
|
|
347
|
+
**Previous implementation** (v0.1.0):
|
|
348
|
+
```typescript
|
|
349
|
+
// ❌ OLD: In-memory storage
|
|
350
|
+
const users = new Map<string, User>();
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
**Current implementation** (v0.2.0):
|
|
354
|
+
```typescript
|
|
355
|
+
// ✅ NEW: Prisma ORM
|
|
356
|
+
await prisma.user.create({ data: { ... } });
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
### Migration Steps
|
|
360
|
+
|
|
361
|
+
1. **Setup database** (if not already done):
|
|
362
|
+
```bash
|
|
363
|
+
# PostgreSQL (recommended)
|
|
364
|
+
docker run -d -p 5432:5432 -e POSTGRES_PASSWORD=postgres postgres:16-alpine
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
2. **Configure environment**:
|
|
368
|
+
```bash
|
|
369
|
+
echo "DATABASE_URL=postgresql://postgres:postgres@localhost:5432/servcraft" >> .env
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
3. **Run migrations**:
|
|
373
|
+
```bash
|
|
374
|
+
npm run db:migrate
|
|
375
|
+
npm run db:seed
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
4. **Update code** (already done):
|
|
379
|
+
- UserRepository now uses Prisma
|
|
380
|
+
- All Map<> operations replaced with Prisma queries
|
|
381
|
+
|
|
382
|
+
5. **Test**:
|
|
383
|
+
```bash
|
|
384
|
+
npm test tests/integration/user-prisma.test.ts
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
## Performance Considerations
|
|
388
|
+
|
|
389
|
+
### Indexes
|
|
390
|
+
|
|
391
|
+
The User table has indexes on:
|
|
392
|
+
- `email` (unique + indexed for fast lookups)
|
|
393
|
+
- `status` (for filtering)
|
|
394
|
+
- `role` (for filtering)
|
|
395
|
+
|
|
396
|
+
### Query Optimization
|
|
397
|
+
|
|
398
|
+
- Use `findByEmail()` for single lookups (indexed)
|
|
399
|
+
- Use `findMany()` with filters instead of loading all users
|
|
400
|
+
- Leverage pagination to avoid loading large datasets
|
|
401
|
+
- The `search` filter uses case-insensitive matching (may be slower on large datasets)
|
|
402
|
+
|
|
403
|
+
### N+1 Query Prevention
|
|
404
|
+
|
|
405
|
+
```typescript
|
|
406
|
+
// ❌ BAD: N+1 queries
|
|
407
|
+
const users = await userRepo.findMany({ page: 1, limit: 100 });
|
|
408
|
+
for (const user of users.data) {
|
|
409
|
+
const sessions = await prisma.session.findMany({ where: { userId: user.id } });
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// ✅ GOOD: Use Prisma includes (future enhancement)
|
|
413
|
+
// Currently, the repository doesn't expose relations
|
|
414
|
+
// Use separate queries or extend the repository
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
## Production Checklist
|
|
418
|
+
|
|
419
|
+
- [x] Prisma client generated (`npm run db:generate`)
|
|
420
|
+
- [x] Migrations applied (`npm run db:migrate`)
|
|
421
|
+
- [ ] Database connection pooling configured
|
|
422
|
+
- [ ] Backup strategy in place
|
|
423
|
+
- [ ] Monitor slow queries
|
|
424
|
+
- [ ] Set up read replicas (for high traffic)
|
|
425
|
+
- [ ] Regular vacuum/analyze (PostgreSQL)
|
|
426
|
+
|
|
427
|
+
## Troubleshooting
|
|
428
|
+
|
|
429
|
+
### "No users found after restart"
|
|
430
|
+
|
|
431
|
+
**Problem**: Users created in previous session are gone.
|
|
432
|
+
|
|
433
|
+
**Solution**: With Prisma, this is no longer an issue. Data persists in the database.
|
|
434
|
+
|
|
435
|
+
### "Prisma Client not generated"
|
|
436
|
+
|
|
437
|
+
**Problem**: Import errors with `@prisma/client`.
|
|
438
|
+
|
|
439
|
+
**Solution**:
|
|
440
|
+
```bash
|
|
441
|
+
npm run db:generate
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
### "Database connection failed"
|
|
445
|
+
|
|
446
|
+
**Problem**: Can't connect to database.
|
|
447
|
+
|
|
448
|
+
**Solution**:
|
|
449
|
+
1. Check if database is running: `pg_isready` (PostgreSQL)
|
|
450
|
+
2. Verify `DATABASE_URL` in `.env`
|
|
451
|
+
3. Check network connectivity
|
|
452
|
+
4. Verify credentials
|
|
453
|
+
|
|
454
|
+
### "Migration failed"
|
|
455
|
+
|
|
456
|
+
**Problem**: `npm run db:migrate` fails.
|
|
457
|
+
|
|
458
|
+
**Solution**:
|
|
459
|
+
```bash
|
|
460
|
+
# Reset database (dev only!)
|
|
461
|
+
npm run db:push
|
|
462
|
+
|
|
463
|
+
# Or manually fix migration
|
|
464
|
+
npx prisma migrate resolve --rolled-back <migration-name>
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
## Testing
|
|
468
|
+
|
|
469
|
+
Run user repository tests:
|
|
470
|
+
```bash
|
|
471
|
+
# All user tests
|
|
472
|
+
npm test tests/integration/user-prisma.test.ts
|
|
473
|
+
|
|
474
|
+
# With coverage
|
|
475
|
+
npm run test:coverage -- tests/integration/user-prisma.test.ts
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
**Test coverage:** 100% (all CRUD operations, filtering, pagination, enum mapping)
|
|
479
|
+
|
|
480
|
+
## Related Modules
|
|
481
|
+
|
|
482
|
+
- **Auth Module**: User authentication and JWT tokens
|
|
483
|
+
- **Audit Module**: Track user actions
|
|
484
|
+
- **Session Module**: User sessions (via RefreshToken, Session models)
|
|
485
|
+
|
|
486
|
+
## API Reference
|
|
487
|
+
|
|
488
|
+
See `src/modules/user/types.ts` for complete type definitions.
|
|
489
|
+
|
|
490
|
+
## Changelog
|
|
491
|
+
|
|
492
|
+
### v0.2.0 (2025-12-19)
|
|
493
|
+
|
|
494
|
+
**USER-001 Completed:**
|
|
495
|
+
- ✅ Migrated from `Map<string, User>` to Prisma ORM
|
|
496
|
+
- ✅ Added full test coverage (33 integration tests)
|
|
497
|
+
- ✅ Implemented enum mapping (Prisma ↔ Application)
|
|
498
|
+
- ✅ Preserved API compatibility (no breaking changes to public interface)
|
|
499
|
+
- ✅ Added this documentation
|
|
500
|
+
|
|
501
|
+
### v0.1.0 (Initial)
|
|
502
|
+
|
|
503
|
+
- In-memory storage with Map<>
|
|
504
|
+
- Basic CRUD operations
|
|
505
|
+
- No persistence across restarts
|