@venturialstd/user 0.0.1 โ†’ 0.0.2

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/README.md CHANGED
@@ -1,1357 +1,1357 @@
1
- # @venturialstd/user
2
-
3
- User Management Module for Venturial - A NestJS module for managing users with comprehensive profile management, settings, and role-based access control.
4
-
5
- **๐ŸŽฏ Key Design Principle:** This module provides a minimal, extensible base User entity. Extend it with related entities for your specific needs (employment data, contact info, etc.) for optimal performance.
6
-
7
- ## ๐Ÿ“‹ Table of Contents
8
-
9
- - [Features](#features)
10
- - [Installation](#installation)
11
- - [Module Structure](#module-structure)
12
- - [Quick Start](#quick-start)
13
- - [ACL System](#acl-system)
14
- - [Extending User Statuses](#extending-user-statuses)
15
- - [Extending the Module](#extending-the-module)
16
- - [Performance Considerations](#performance-considerations)
17
- - [Recommended Approach: Related Entities](#recommended-approach-related-entities)
18
- - [Hybrid Approach](#hybrid-approach-optional-flexibility)
19
- - [Complete Extension Example](#complete-extension-example)
20
- - [Extension Methods Comparison](#extension-methods-comparison)
21
- - [Configuration](#configuration)
22
- - [Entities](#entities)
23
- - [API Reference](#api-reference)
24
- - [Enums & Constants](#enums--constants)
25
- - [Guards & Decorators](#guards--decorators)
26
- - [Test Server](#test-server)
27
- - [NPM Scripts](#npm-scripts)
28
- - [Usage Examples](#usage-examples)
29
- - [Performance Best Practices](#performance-best-practices)
30
- - [Migration Guide](#migration-guide)
31
-
32
- ---
33
-
34
- ## โœจ Features
35
-
36
- - ๐Ÿ‘ค **User Management**: Complete CRUD operations for users
37
- - ๐Ÿ“ง **Email Verification**: Email verification system
38
- - โš™๏ธ **User Settings**: Configurable user preferences
39
- - ๐ŸŽญ **Extensible Statuses**: Define custom user statuses for your application
40
- - ๐Ÿ” **ACL System**: Complete database-driven role and permission management
41
- - ๐Ÿ›ก๏ธ **Guards & Decorators**: Request-level access control
42
- - ๐Ÿ”„ **TypeORM Integration**: Full database support with migrations
43
- - ๐Ÿ“ฆ **Fully Typed**: Complete TypeScript support
44
- - ๐Ÿงช **Test Infrastructure**: Standalone test server on port 3003
45
- - โšก **High Performance**: Indexed columns and optimized queries
46
-
47
- ---
48
-
49
- ## ๐Ÿ“ฆ Installation
50
-
51
- ```bash
52
- npm install @venturialstd/user
53
- ```
54
-
55
- ### Peer Dependencies
56
-
57
- ```json
58
- {
59
- "@nestjs/common": "^11.0.11",
60
- "@nestjs/core": "^11.0.5",
61
- "@nestjs/typeorm": "^10.0.0",
62
- "@venturialstd/core": "^1.0.16",
63
- "class-transformer": "^0.5.1",
64
- "class-validator": "^0.14.1",
65
- "typeorm": "^0.3.20"
66
- }
67
- ```
68
-
69
- ---
70
-
71
- ## ๐Ÿ“ Module Structure
72
-
73
- ```
74
- src/user/
75
- โ”œโ”€โ”€ src/
76
- โ”‚ โ”œโ”€โ”€ constants/
77
- โ”‚ โ”‚ โ”œโ”€โ”€ user.constant.ts # USER_STATUS enum
78
- โ”‚ โ”‚ โ””โ”€โ”€ user.settings.constant.ts # Settings keys and config
79
- โ”‚ โ”œโ”€โ”€ decorators/
80
- โ”‚ โ”‚ โ”œโ”€โ”€ user.decorator.ts # @CurrentUserId(), @CurrentUser()
81
- โ”‚ โ”‚ โ””โ”€โ”€ acl.decorator.ts # @RequirePermissions(), @RequireAclRoles()
82
- โ”‚ โ”œโ”€โ”€ entities/
83
- โ”‚ โ”‚ โ”œโ”€โ”€ user.entity.ts # User entity
84
- โ”‚ โ”‚ โ”œโ”€โ”€ role.entity.ts # ACL Role entity
85
- โ”‚ โ”‚ โ”œโ”€โ”€ permission.entity.ts # ACL Permission entity
86
- โ”‚ โ”‚ โ”œโ”€โ”€ role-permission.entity.ts # ACL Role-Permission junction
87
- โ”‚ โ”‚ โ””โ”€โ”€ user-role.entity.ts # ACL User-Role junction
88
- โ”‚ โ”œโ”€โ”€ guards/
89
- โ”‚ โ”‚ โ”œโ”€โ”€ user.guard.ts # UserGuard
90
- โ”‚ โ”‚ โ””โ”€โ”€ acl.guard.ts # PermissionGuard, AclRoleGuard
91
- โ”‚ โ”œโ”€โ”€ services/
92
- โ”‚ โ”‚ โ”œโ”€โ”€ user.service.ts # UserService
93
- โ”‚ โ”‚ โ””โ”€โ”€ acl.service.ts # AclService
94
- โ”‚ โ”œโ”€โ”€ settings/
95
- โ”‚ โ”‚ โ””โ”€โ”€ user.settings.ts # UserSettings interface
96
- โ”‚ โ”œโ”€โ”€ index.ts # Public API exports
97
- โ”‚ โ””โ”€โ”€ user.module.ts # Main module
98
- โ”œโ”€โ”€ test/
99
- โ”‚ โ”œโ”€โ”€ controllers/
100
- โ”‚ โ”‚ โ””โ”€โ”€ user-test.controller.ts # Test controller
101
- โ”‚ โ”œโ”€โ”€ migrations/ # Database migrations
102
- โ”‚ โ”œโ”€โ”€ .env.example # Environment template
103
- โ”‚ โ”œโ”€โ”€ data-source.ts # TypeORM data source
104
- โ”‚ โ”œโ”€โ”€ main.ts # Test server bootstrap
105
- โ”‚ โ””โ”€โ”€ test-app.module.ts # Test module
106
- โ”œโ”€โ”€ package.json
107
- โ”œโ”€โ”€ tsconfig.json
108
- โ””โ”€โ”€ README.md
109
- ```
110
-
111
- ---
112
-
113
- ## ๐Ÿš€ Quick Start
114
-
115
- ### 1. Import the Module
116
-
117
- ```typescript
118
- import { Module } from '@nestjs/common';
119
- import { UserModule } from '@venturialstd/user';
120
-
121
- @Module({
122
- imports: [
123
- // Basic import (uses default statuses)
124
- UserModule.forRoot(),
125
-
126
- // Or with custom statuses
127
- UserModule.forRoot({
128
- allowedStatuses: ['ACTIVE', 'INACTIVE', 'SUSPENDED', 'BANNED'],
129
- additionalEntities: [UserEmployment, UserProfile],
130
- }),
131
- ],
132
- })
133
- export class AppModule {}
134
- ```
135
-
136
- ### 2. Use in Your Service
137
-
138
- ```typescript
139
- import { Injectable } from '@nestjs/common';
140
- import { UserService, AclService } from '@venturialstd/user';
141
-
142
- @Injectable()
143
- export class MyService {
144
- constructor(
145
- private readonly userService: UserService,
146
- private readonly aclService: AclService,
147
- ) {}
148
-
149
- async getUser(userId: string) {
150
- return this.userService.getUserById(userId);
151
- }
152
-
153
- async assignModeratorRole(userId: string) {
154
- const role = await this.aclService.getRoleByName('MODERATOR');
155
- return this.aclService.assignRoleToUser(userId, role.id);
156
- }
157
- }
158
- ```
159
-
160
- ### 3. Use Guards and Decorators
161
-
162
- ```typescript
163
- import { Controller, Get, UseGuards } from '@nestjs/common';
164
- import { CurrentUserId, UserGuard, RequirePermissions, PermissionGuard } from '@venturialstd/user';
165
-
166
- @Controller('profile')
167
- @UseGuards(UserGuard)
168
- export class ProfileController {
169
- @Get()
170
- async getProfile(@CurrentUserId() userId: string) {
171
- return { userId };
172
- }
173
-
174
- @Get('admin')
175
- @RequirePermissions('users.delete', 'content.manage')
176
- @UseGuards(UserGuard, PermissionGuard)
177
- async adminAction() {
178
- return { message: 'Admin action' };
179
- }
180
- }
181
- ```
182
-
183
- ---
184
-
185
- ## ๐Ÿ” ACL System
186
-
187
- The module includes a complete database-driven Access Control List (ACL) system for managing roles and permissions. See [ACL_SYSTEM.md](./ACL_SYSTEM.md) for comprehensive documentation.
188
-
189
- ### Quick Overview
190
-
191
- - **Roles**: Named collections of permissions (e.g., ADMIN, MODERATOR)
192
- - **Permissions**: Granular access rights in `resource.action` format (e.g., `users.delete`, `posts.create`)
193
- - **Multiple Roles**: Users can have multiple roles with different scopes
194
- - **Temporal Access**: Roles can expire automatically
195
- - **Conditional Permissions**: Optional conditions on role-permission assignments
196
-
197
- ### Example Usage
198
-
199
- ```typescript
200
- // Create roles and permissions
201
- const adminRole = await aclService.createRole('ADMIN', 'Administrator');
202
- const userPerm = await aclService.createPermission('users', 'delete', 'Delete users');
203
-
204
- // Assign permission to role
205
- await aclService.assignPermissionToRole(adminRole.id, userPerm.id);
206
-
207
- // Assign role to user
208
- await aclService.assignRoleToUser(userId, adminRole.id);
209
-
210
- // Check permissions
211
- const canDelete = await aclService.userHasPermission(userId, 'users.delete');
212
- ```
213
-
214
- ---
215
-
216
- ## ๐ŸŽญ Extending User Statuses
217
-
218
- The module provides a flexible system for defining custom user statuses.
219
-
220
- ## ๐ŸŽญ Extending User Statuses
221
-
222
- The module provides a flexible system for defining custom user statuses.
223
-
224
- ### Default Values
225
-
226
- By default, the module includes:
227
-
228
- **Statuses:**
229
- - `ACTIVE` - User is active and can use the system
230
- - `INACTIVE` - User is inactive
231
- - `SUSPENDED` - User has been suspended
232
- - `PENDING_VERIFICATION` - User email needs verification
233
-
234
- ### Defining Custom Statuses
235
-
236
- Create an enum for your application-specific statuses:
237
-
238
- ```typescript
239
- // Define custom statuses
240
- export enum AppUserStatus {
241
- ACTIVE = 'ACTIVE',
242
- INACTIVE = 'INACTIVE',
243
- SUSPENDED = 'SUSPENDED',
244
- PENDING_VERIFICATION = 'PENDING_VERIFICATION',
245
- ON_HOLD = 'ON_HOLD',
246
- BANNED = 'BANNED',
247
- DELETED = 'DELETED',
248
- }
249
-
250
- @Module({
251
- imports: [
252
- UserModule.forRoot({
253
- allowedStatuses: Object.values(AppUserStatus),
254
- }),
255
- ],
256
- })
257
- export class AppModule {}
258
- ```
259
-
260
- ### Managing Statuses
261
-
262
- ```typescript
263
- @Injectable()
264
- export class UserManagementService {
265
- constructor(private userService: UserService) {}
266
-
267
- // Set user status with validation
268
- async banUser(userId: string) {
269
- return this.userService.setStatus(userId, AppUserStatus.BANNED);
270
- }
271
-
272
- // Get users by status
273
- async getBannedUsers() {
274
- return this.userService.getUsersByStatus(AppUserStatus.BANNED);
275
- }
276
-
277
- // Get all allowed statuses for your application
278
- getAllStatuses() {
279
- return this.userService.getAllowedStatuses();
280
- }
281
- }
282
- ```
283
-
284
- ### Validation
285
-
286
- The module automatically validates statuses:
287
-
288
- ```typescript
289
- // โœ… Valid - status is in allowedStatuses
290
- await userService.setStatus(userId, AppUserStatus.BANNED);
291
-
292
- // โŒ Invalid - throws BadRequestException
293
- await userService.setStatus(userId, 'INVALID_STATUS');
294
- // Error: Invalid status "INVALID_STATUS". Allowed statuses: ACTIVE, INACTIVE, SUSPENDED, ...
295
- ```
296
-
297
- ---
298
-
299
- ## ๐Ÿ”ง Extending the Module
300
-
301
- ### Performance Considerations
302
-
303
- **โš ๏ธ Important:** The `metadata` and `settings` JSONB fields are available but **NOT recommended for filterable attributes** due to poor query performance.
304
-
305
- **Performance Comparison:**
306
- ```sql
307
- -- โŒ SLOW: JSONB query (full table scan)
308
- SELECT * FROM "user" WHERE metadata->>'department' = 'Engineering';
309
- -- 1M records: ~50 seconds
310
-
311
- -- โšก FAST: Indexed column query
312
- SELECT * FROM user_employment WHERE department = 'Engineering';
313
- -- 1M records: ~10 milliseconds
314
- ```
315
-
316
- **Result: 5,000x faster with proper indexes!**
317
-
318
- ### Recommended Approach: Related Entities
319
-
320
- **Always use related entities for any data you need to query or filter.**
321
-
322
- #### Step 1: Create Your Extended Entity
323
-
324
- ```typescript
325
- import { Entity, PrimaryGeneratedColumn, Column, OneToOne, JoinColumn, Index } from 'typeorm';
326
- import { User } from '@venturialstd/user';
327
-
328
- @Entity('user_employment')
329
- @Index(['department', 'isActive']) // Composite index for common queries
330
- export class UserEmployment {
331
- @PrimaryGeneratedColumn('uuid')
332
- id: string;
333
-
334
- @Column()
335
- @Index() // Fast lookups by userId
336
- userId: string;
337
-
338
- @OneToOne(() => User)
339
- @JoinColumn({ name: 'userId' })
340
- user: User;
341
-
342
- // โšก Indexed fields for fast filtering
343
- @Column({ unique: true })
344
- employeeId: string;
345
-
346
- @Column()
347
- @Index() // Fast department filtering
348
- department: string;
349
-
350
- @Column()
351
- jobTitle: string;
352
-
353
- @Column({ nullable: true })
354
- @Index() // Fast manager lookups
355
- managerId: string;
356
-
357
- @Column({ type: 'date' })
358
- hireDate: Date;
359
-
360
- @Column({ default: true })
361
- @Index() // Fast active status queries
362
- isActive: boolean;
363
-
364
- @Column({ type: 'timestamptz', default: () => 'CURRENT_TIMESTAMP' })
365
- createdAt: Date;
366
-
367
- @Column({ type: 'timestamptz', default: () => 'CURRENT_TIMESTAMP' })
368
- updatedAt: Date;
369
- }
370
- ```
371
-
372
- #### Step 2: Create Service with Efficient Queries
373
-
374
- ```typescript
375
- import { Injectable } from '@nestjs/common';
376
- import { InjectRepository } from '@nestjs/typeorm';
377
- import { Repository } from 'typeorm';
378
- import { UserService } from '@venturialstd/user';
379
- import { UserEmployment } from './entities/user-employment.entity';
380
-
381
- @Injectable()
382
- export class EmployeeService {
383
- constructor(
384
- @InjectRepository(UserEmployment)
385
- private employmentRepo: Repository<UserEmployment>,
386
- private userService: UserService,
387
- ) {}
388
-
389
- async createEmployee(data: {
390
- firstname: string;
391
- lastname: string;
392
- email: string;
393
- department: string;
394
- employeeId: string;
395
- jobTitle: string;
396
- }) {
397
- // Create base user
398
- const user = await this.userService.createUser(
399
- data.firstname,
400
- data.lastname,
401
- data.email,
402
- );
403
-
404
- // Create employment record
405
- const employment = this.employmentRepo.create({
406
- userId: user.id,
407
- employeeId: data.employeeId,
408
- department: data.department,
409
- jobTitle: data.jobTitle,
410
- hireDate: new Date(),
411
- isActive: true,
412
- });
413
-
414
- await this.employmentRepo.save(employment);
415
- return this.getEmployeeWithUser(user.id);
416
- }
417
-
418
- // โšก FAST: Uses department index
419
- async getEmployeesByDepartment(department: string) {
420
- return this.employmentRepo.find({
421
- where: { department, isActive: true },
422
- relations: ['user'],
423
- order: { createdAt: 'DESC' },
424
- });
425
- }
426
-
427
- // โšก FAST: Complex query with proper indexes
428
- async searchEmployees(searchTerm: string, department?: string) {
429
- const query = this.employmentRepo
430
- .createQueryBuilder('emp')
431
- .leftJoinAndSelect('emp.user', 'user')
432
- .where('emp.isActive = :active', { active: true });
433
-
434
- if (department) {
435
- query.andWhere('emp.department = :dept', { dept: department });
436
- }
437
-
438
- query.andWhere(
439
- '(user.firstname ILIKE :search OR user.lastname ILIKE :search OR emp.employeeId ILIKE :search)',
440
- { search: `%${searchTerm}%` },
441
- );
442
-
443
- return query.getMany();
444
- }
445
-
446
- async getEmployeeWithUser(userId: string) {
447
- return this.employmentRepo.findOne({
448
- where: { userId },
449
- relations: ['user'],
450
- });
451
- }
452
- }
453
- ```
454
-
455
- #### Step 3: Register with UserModule
456
-
457
- ```typescript
458
- import { Module } from '@nestjs/common';
459
- import { TypeOrmModule } from '@nestjs/typeorm';
460
- import { UserModule } from '@venturialstd/user';
461
- import { UserEmployment } from './entities/user-employment.entity';
462
- import { EmployeeService } from './services/employee.service';
463
-
464
- @Module({
465
- imports: [
466
- // Register your entities with UserModule
467
- UserModule.forRoot({
468
- additionalEntities: [UserEmployment],
469
- }),
470
- // Also register for your service
471
- TypeOrmModule.forFeature([UserEmployment]),
472
- ],
473
- providers: [EmployeeService],
474
- exports: [EmployeeService],
475
- })
476
- export class EmployeeModule {}
477
- ```
478
-
479
- #### Benefits:
480
- - โšก **100x-5000x faster queries** with proper indexes
481
- - โœ… Database constraints (unique, foreign keys)
482
- - โœ… Full TypeScript type safety
483
- - โœ… Complex queries (JOINs, aggregations)
484
- - โœ… Scales to millions of records
485
-
486
- ### Hybrid Approach (Optional Flexibility)
487
-
488
- If you need **some** flexibility for truly dynamic, non-queryable fields:
489
-
490
- ```typescript
491
- @Entity('user_employment')
492
- export class UserEmployment {
493
- // ... indexed columns for queryable fields ...
494
-
495
- @Column()
496
- @Index()
497
- department: string; // โšก Fast queries
498
-
499
- @Column({ unique: true })
500
- employeeId: string; // โšก Fast lookups
501
-
502
- // ๐Ÿ“ฆ FLEXIBLE: For truly dynamic, non-queryable data only
503
- @Column({ type: 'jsonb', nullable: true })
504
- additionalInfo: {
505
- shirtSize?: string;
506
- parkingSpot?: string;
507
- badgeNumber?: string;
508
- emergencyContact?: {
509
- name: string;
510
- phone: string;
511
- };
512
- [key: string]: any;
513
- };
514
- }
515
- ```
516
-
517
- ### Complete Extension Example
518
-
519
- See the `examples/` directory for complete working examples:
520
- - `user-employee-profile.entity.ts` - Entity with proper indexes
521
- - `user-employee.service.ts` - Service with efficient queries
522
- - `user-employee.module.ts` - Module configuration
523
-
524
- ### Extension Methods Comparison
525
-
526
- | Method | Query Performance | Flexibility | Type Safety | Best For |
527
- |--------|------------------|-------------|-------------|----------|
528
- | **Related Entities** โœ… | โšกโšกโšก Excellent | โญโญ Medium | โญโญโญ Strong | **Production (99% of cases)** |
529
- | **Hybrid (Entities + JSONB)** | โšกโšกโญ Good | โญโญโญ High | โญโญโญ Strong | Some flexibility needed |
530
- | **Pure JSONB** โŒ | โš ๏ธ Poor | โญโญโญ High | โญ Weak | **Avoid for queryable data** |
531
- | **EAV Pattern** โŒ | โš ๏ธ Very Poor | โญโญโญ High | โญ Weak | **Not recommended** |
532
-
533
- **Decision Tree:**
534
- - Need to filter/query this data? โ†’ **Use Related Entity** โœ…
535
- - Just storing UI preferences? โ†’ **Can use JSONB** ๐Ÿ“
536
- - Truly unknown runtime schema? โ†’ **Consider Hybrid approach**
537
-
538
- ---
539
-
540
- ## ๐Ÿ“Š Entities
541
-
542
- ### User Entity
543
-
544
- The main `User` entity represents a user in the system:
545
-
546
- ```typescript
547
- {
548
- id: string; // UUID primary key
549
- firstname: string; // User's first name
550
- lastname: string; // User's last name
551
- email: string; // Unique email address
552
- phone?: string; // Optional phone number
553
- avatar?: string; // Optional avatar URL
554
- timezone?: string; // User's timezone
555
- locale?: string; // User's locale (e.g., 'en-US')
556
- status?: string; // User status (extensible, e.g., 'ACTIVE', 'SUSPENDED')
557
- role?: string; // User role (extensible, e.g., 'ADMIN', 'USER', 'MODERATOR')
558
- isActive: boolean; // Account active flag
559
- isEmailVerified: boolean; // Email verification status
560
- settings?: Record<string, unknown>; // User settings (JSONB) - for UI preferences only
561
- metadata?: Record<string, unknown>; // Additional metadata (JSONB) - for non-queryable data only
562
- createdAt: Date; // Creation timestamp
563
- updatedAt: Date; // Last update timestamp
564
- }
565
- ```
566
-
567
- **โš ๏ธ Important Notes:**
568
-
569
- 1. **`role` and `status` are extensible** - Define your own values via `UserModule.forRoot()` configuration
570
- 2. **Indexed fields** - Both `role` and `status` have database indexes for fast queries
571
- 3. **JSONB fields (`settings`, `metadata`)** - Should only be used for data you'll never query or filter on
572
- 4. **For business data** - Use related entities instead of JSONB (see Extensibility section)
573
-
574
- ---
575
-
576
- ## ๐Ÿ”Œ API Reference
577
-
578
- ### UserService
579
-
580
- #### `createUser(firstname, lastname, email, phone?, avatar?, timezone?, locale?)`
581
- Creates a new user with validation.
582
-
583
- ```typescript
584
- const user = await userService.createUser(
585
- 'John',
586
- 'Doe',
587
- 'john@example.com',
588
- '+1234567890',
589
- 'https://avatar.url',
590
- 'America/New_York',
591
- 'en-US'
592
- );
593
- ```
594
-
595
- #### `getUserById(userId: string)`
596
- Gets a user by their ID.
597
-
598
- ```typescript
599
- const user = await userService.getUserById('user-uuid');
600
- ```
601
-
602
- #### `getUserByEmail(email: string)`
603
- Gets a user by their email address.
604
-
605
- ```typescript
606
- const user = await userService.getUserByEmail('john@example.com');
607
- ```
608
-
609
- #### `updateUserProfile(userId, updates)`
610
- Updates user profile fields.
611
-
612
- ```typescript
613
- const user = await userService.updateUserProfile('user-uuid', {
614
- firstname: 'Jane',
615
- timezone: 'Europe/London',
616
- });
617
- ```
618
-
619
- #### `updateUserSettings(userId, settings)`
620
- Updates user settings.
621
-
622
- ```typescript
623
- const user = await userService.updateUserSettings('user-uuid', {
624
- notificationsEnabled: true,
625
- theme: 'dark',
626
- });
627
- ```
628
-
629
- #### `updateUserMetadata(userId, metadata)`
630
- Updates user metadata.
631
-
632
- ```typescript
633
- const user = await userService.updateUserMetadata('user-uuid', {
634
- role: 'ADMIN',
635
- department: 'Engineering',
636
- });
637
- ```
638
-
639
- #### `setUserStatus(userId, isActive)`
640
- Activates or deactivates a user.
641
-
642
- ```typescript
643
- const user = await userService.setUserStatus('user-uuid', false);
644
- ```
645
-
646
- #### `verifyEmail(userId)`
647
- Marks a user's email as verified.
648
-
649
- ```typescript
650
- const user = await userService.verifyEmail('user-uuid');
651
- ```
652
-
653
- #### `getActiveUsers()`
654
- Gets all active users.
655
-
656
- ```typescript
657
- const users = await userService.getActiveUsers();
658
- ```
659
-
660
- #### `getVerifiedUsers()`
661
- Gets all users with verified emails.
662
-
663
- ```typescript
664
- const users = await userService.getVerifiedUsers();
665
- ```
666
-
667
- #### `setRole(userId: string, role: string)`
668
- Sets a user's role with validation.
669
-
670
- ```typescript
671
- const user = await userService.setRole('user-uuid', 'MODERATOR');
672
- // Validates against allowedRoles from UserModule configuration
673
- ```
674
-
675
- #### `setStatus(userId: string, status: string)`
676
- Sets a user's status with validation.
677
-
678
- ```typescript
679
- const user = await userService.setStatus('user-uuid', 'BANNED');
680
- // Validates against allowedStatuses from UserModule configuration
681
- ```
682
-
683
- #### `getUsersByRole(role: string)`
684
- Gets all users with a specific role.
685
-
686
- ```typescript
687
- const moderators = await userService.getUsersByRole('MODERATOR');
688
- ```
689
-
690
- #### `getUsersByStatus(status: string)`
691
- Gets all users with a specific status.
692
-
693
- ```typescript
694
- const bannedUsers = await userService.getUsersByStatus('BANNED');
695
- ```
696
-
697
- #### `getAllowedRoles()`
698
- Gets the list of allowed roles for your application.
699
-
700
- ```typescript
701
- const roles = userService.getAllowedRoles();
702
- // Returns: ['ADMIN', 'USER', 'MODERATOR', ...]
703
- ```
704
-
705
- #### `getAllowedStatuses()`
706
- Gets the list of allowed statuses for your application.
707
-
708
- ```typescript
709
- const statuses = userService.getAllowedStatuses();
710
- // Returns: ['ACTIVE', 'INACTIVE', 'SUSPENDED', ...]
711
- ```
712
-
713
- #### `isValidRole(role: string)`
714
- Checks if a role is valid for your application.
715
-
716
- ```typescript
717
- if (userService.isValidRole('MODERATOR')) {
718
- // Role is valid
719
- }
720
- ```
721
-
722
- #### `isValidStatus(status: string)`
723
- Checks if a status is valid for your application.
724
-
725
- ```typescript
726
- if (userService.isValidStatus('BANNED')) {
727
- // Status is valid
728
- }
729
- ```
730
-
731
- ---
732
-
733
- ## ๐Ÿ” Enums & Constants
734
-
735
- ### USER_STATUS
736
-
737
- ```typescript
738
- enum USER_STATUS {
739
- ACTIVE = 'ACTIVE',
740
- INACTIVE = 'INACTIVE',
741
- SUSPENDED = 'SUSPENDED',
742
- PENDING_VERIFICATION = 'PENDING_VERIFICATION',
743
- }
744
- ```
745
-
746
- ### USER_ROLE
747
-
748
- ```typescript
749
- enum USER_ROLE {
750
- ADMIN = 'ADMIN',
751
- USER = 'USER',
752
- }
753
- ```
754
-
755
- ### USER_SETTINGS_KEY
756
-
757
- ```typescript
758
- const USER_SETTINGS_KEY = 'USER_SETTINGS';
759
- ```
760
-
761
- ---
762
-
763
- ## ๐Ÿ›ก๏ธ Guards & Decorators
764
-
765
- ### Guards
766
-
767
- #### `UserGuard`
768
- Ensures the user is authenticated and active.
769
-
770
- ```typescript
771
- @UseGuards(UserGuard)
772
- @Get()
773
- async getProfile(@CurrentUserId() userId: string) {
774
- return this.userService.getUserById(userId);
775
- }
776
- ```
777
-
778
- #### `UserRoleGuard`
779
- Enforces role-based access control.
780
-
781
- ```typescript
782
- @RequireRoles(USER_ROLE.ADMIN)
783
- @UseGuards(UserGuard, UserRoleGuard)
784
- @Delete(':id')
785
- async deleteUser(@Param('id') id: string) {
786
- return this.userService.setUserStatus(id, false);
787
- }
788
- ```
789
-
790
- ### Decorators
791
-
792
- #### `@CurrentUserId()`
793
- Extracts the current user ID from the request.
794
-
795
- ```typescript
796
- @Get()
797
- async getData(@CurrentUserId() userId: string) {
798
- return this.service.getUserData(userId);
799
- }
800
- ```
801
-
802
- #### `@CurrentUser()`
803
- Extracts the full user object from the request.
804
-
805
- ```typescript
806
- @Get()
807
- async getData(@CurrentUser() user: User) {
808
- return { user };
809
- }
810
- ```
811
-
812
- #### `@RequireRoles(...roles)`
813
- Specifies required roles for a route.
814
-
815
- ```typescript
816
- @RequireRoles(USER_ROLE.ADMIN)
817
- @UseGuards(UserGuard, UserRoleGuard)
818
- @Delete(':id')
819
- async delete(@Param('id') id: string) {
820
- // Only admins can access
821
- }
822
- ```
823
-
824
- ---
825
-
826
- ## ๐Ÿงช Test Server
827
-
828
- The module includes a standalone test server for development and testing:
829
-
830
- ### Start Test Server
831
-
832
- ```bash
833
- cd src/user
834
- npm run test:dev
835
- ```
836
-
837
- The server starts on port **3003** (configurable via `.env`).
838
-
839
- ### Watch Mode
840
-
841
- ```bash
842
- npm run test:watch
843
- ```
844
-
845
- ### Environment Setup
846
-
847
- 1. Copy `.env.example` to `.env`:
848
- ```bash
849
- cp test/.env.example test/.env
850
- ```
851
-
852
- 2. Configure your database settings in `test/.env`
853
-
854
- ### Available Test Endpoints
855
-
856
- ```
857
- POST /users - Create user
858
- GET /users - Get all users
859
- GET /users/active - Get active users
860
- GET /users/verified - Get verified users
861
- GET /users/email/:email - Get user by email
862
- GET /users/:id - Get user by ID
863
- PUT /users/:id/profile - Update user profile
864
- PUT /users/:id/settings - Update user settings
865
- PUT /users/:id/metadata - Update user metadata
866
- PUT /users/:id/status - Update user status
867
- PUT /users/:id/verify-email - Verify user email
868
- DELETE /users/:id - Delete user
869
- ```
870
-
871
- ---
872
-
873
- ## ๐Ÿ“ NPM Scripts
874
-
875
- ```json
876
- {
877
- "build": "tsc -p tsconfig.json",
878
- "prepublishOnly": "npm run build",
879
- "release:patch": "npm run build && npm version patch --no-git-tag-version && npm publish",
880
- "test:dev": "ts-node -r tsconfig-paths/register test/main.ts",
881
- "test:watch": "nodemon --watch src --watch test --ext ts --exec npm run test:dev",
882
- "migration:generate": "ts-node node_modules/.bin/typeorm migration:generate -d test/data-source.ts test/migrations/$npm_config_name",
883
- "migration:run": "ts-node node_modules/.bin/typeorm migration:run -d test/data-source.ts",
884
- "migration:revert": "ts-node node_modules/.bin/typeorm migration:revert -d test/data-source.ts"
885
- }
886
- ```
887
-
888
- ---
889
-
890
- ## ๐Ÿ’ก Usage Examples
891
-
892
- ### Example 1: Create and Verify User
893
-
894
- ```typescript
895
- // Create a new user
896
- const user = await userService.createUser(
897
- 'Jane',
898
- 'Smith',
899
- 'jane@example.com',
900
- '+1987654321',
901
- null,
902
- 'America/Los_Angeles',
903
- 'en-US'
904
- );
905
-
906
- // Verify email
907
- await userService.verifyEmail(user.id);
908
- ```
909
-
910
- ### Example 2: Update User Profile
911
-
912
- ```typescript
913
- const updatedUser = await userService.updateUserProfile(userId, {
914
- firstname: 'Janet',
915
- timezone: 'Europe/Paris',
916
- locale: 'fr-FR',
917
- });
918
- ```
919
-
920
- ### Example 3: Manage User Settings
921
-
922
- ```typescript
923
- // Update settings
924
- await userService.updateUserSettings(userId, {
925
- notificationsEnabled: true,
926
- emailNotifications: false,
927
- theme: 'dark',
928
- });
929
-
930
- // Get user with settings
931
- const user = await userService.getUserById(userId);
932
- console.log(user.settings);
933
- ```
934
-
935
- ### Example 4: Role-Based Access
936
-
937
- ```typescript
938
- // Set user role in metadata
939
- await userService.updateUserMetadata(userId, {
940
- role: USER_ROLE.ADMIN,
941
- });
942
-
943
- // Controller with role guard
944
- @Controller('admin')
945
- export class AdminController {
946
- @RequireRoles(USER_ROLE.ADMIN)
947
- @UseGuards(UserGuard, UserRoleGuard)
948
- @Get('dashboard')
949
- async getDashboard(@CurrentUser() user: User) {
950
- return { admin: user };
951
- }
952
- }
953
- ```
954
-
955
- ### Example 5: Filter Users
956
-
957
- ```typescript
958
- // Get all active users
959
- const activeUsers = await userService.getActiveUsers();
960
-
961
- // Get verified users
962
- const verifiedUsers = await userService.getVerifiedUsers();
963
-
964
- // Find by email
965
- const user = await userService.getUserByEmail('john@example.com');
966
- ```
967
-
968
- ---
969
-
970
- ## ๐Ÿ”ง Database Migrations
971
-
972
- ### Generate a Migration
973
-
974
- ```bash
975
- npm run migration:generate --name=AddUserFields
976
- ```
977
-
978
- ### Run Migrations
979
-
980
- ```bash
981
- npm run migration:run
982
- ```
983
-
984
- ### Revert Migration
985
-
986
- ```bash
987
- npm run migration:revert
988
- ```
989
-
990
- ---
991
-
992
- ## ๏ฟฝ Best Practices
993
-
994
- ### 1. Always Use Indexes for Queryable Fields
995
-
996
- ```typescript
997
- // โœ… GOOD: Indexed columns
998
- @Entity('user_profile')
999
- export class UserProfile {
1000
- @Column() @Index() // Fast lookups
1001
- userId: string;
1002
-
1003
- @Column() @Index() // Fast filtering
1004
- country: string;
1005
-
1006
- @Column({ unique: true }) // Automatically indexed
1007
- taxId: string;
1008
- }
1009
-
1010
- // โŒ BAD: JSONB for queryable data
1011
- user.metadata = {
1012
- country: 'US', // Will be slow to query!
1013
- taxId: '123' // No unique constraint possible!
1014
- };
1015
- ```
1016
-
1017
- ### 2. Use JSONB Only for Display Data
1018
-
1019
- ```typescript
1020
- // โœ… GOOD: Non-queryable UI preferences
1021
- user.settings = {
1022
- theme: 'dark',
1023
- language: 'en',
1024
- sidebarCollapsed: true
1025
- };
1026
-
1027
- // โœ… GOOD: Audit/log data you never filter on
1028
- user.metadata = {
1029
- lastLoginIp: '192.168.1.1',
1030
- userAgent: 'Mozilla/5.0...',
1031
- registrationSource: 'mobile_app'
1032
- };
1033
-
1034
- // โŒ BAD: Business data you need to query
1035
- user.metadata = {
1036
- subscriptionTier: 'premium', // Use UserSubscription entity!
1037
- department: 'Engineering', // Use UserEmployment entity!
1038
- isActive: true // Use column on User entity!
1039
- };
1040
- ```
1041
-
1042
- ### 3. Create Composite Indexes for Common Queries
1043
-
1044
- ```typescript
1045
- @Entity('user_subscription')
1046
- @Index(['userId', 'isActive']) // For userId + status queries
1047
- @Index(['plan', 'expiresAt']) // For plan + expiration queries
1048
- export class UserSubscription {
1049
- @Column() userId: string;
1050
- @Column() plan: string;
1051
- @Column() isActive: boolean;
1052
- @Column() expiresAt: Date;
1053
- }
1054
-
1055
- // Now these queries are super fast:
1056
- await repo.find({
1057
- where: { userId: 'abc-123', isActive: true } // Uses composite index
1058
- });
1059
-
1060
- await repo.find({
1061
- where: {
1062
- plan: 'premium',
1063
- expiresAt: MoreThan(new Date())
1064
- } // Uses composite index
1065
- });
1066
- ```
1067
-
1068
- ### 4. Use TypeORM QueryBuilder for Complex Queries
1069
-
1070
- ```typescript
1071
- // โœ… Complex query with proper indexes
1072
- const result = await employmentRepo
1073
- .createQueryBuilder('emp')
1074
- .leftJoinAndSelect('emp.user', 'user')
1075
- .where('emp.department = :dept', { dept: 'Engineering' })
1076
- .andWhere('emp.isActive = :active', { active: true })
1077
- .andWhere('user.isEmailVerified = :verified', { verified: true })
1078
- .orderBy('emp.hireDate', 'DESC')
1079
- .limit(50)
1080
- .getMany();
1081
- ```
1082
-
1083
- ### 5. Validate Data at the Application Layer
1084
-
1085
- ```typescript
1086
- // Use DTOs with class-validator
1087
- export class CreateEmployeeDto {
1088
- @IsString()
1089
- @MinLength(2)
1090
- firstname: string;
1091
-
1092
- @IsEmail()
1093
- email: string;
1094
-
1095
- @IsEnum(['Engineering', 'Sales', 'Marketing'])
1096
- department: string;
1097
-
1098
- @IsPositive()
1099
- @Max(1000000)
1100
- salary: number;
1101
- }
1102
- ```
1103
-
1104
- ### 6. Handle Relations Efficiently
1105
-
1106
- ```typescript
1107
- // โœ… GOOD: Eager load when you know you need it
1108
- const employees = await repo.find({
1109
- where: { department: 'Engineering' },
1110
- relations: ['user'] // Load user in same query
1111
- });
1112
-
1113
- // โœ… GOOD: Load separately for optional data
1114
- const employee = await repo.findOne({ where: { userId } });
1115
- if (needUserDetails) {
1116
- employee.user = await userService.findOne(userId);
1117
- }
1118
-
1119
- // โŒ BAD: N+1 query problem
1120
- const employees = await repo.find({ where: { department: 'Engineering' } });
1121
- for (const emp of employees) {
1122
- emp.user = await userService.findOne(emp.userId); // Queries in loop!
1123
- }
1124
- ```
1125
-
1126
- ### 7. Use Transactions for Multi-Entity Operations
1127
-
1128
- ```typescript
1129
- async createEmployeeWithProfile(data: CreateEmployeeDto) {
1130
- return this.dataSource.transaction(async (manager) => {
1131
- // Create user
1132
- const user = await manager.save(User, {
1133
- firstname: data.firstname,
1134
- lastname: data.lastname,
1135
- email: data.email
1136
- });
1137
-
1138
- // Create employment record
1139
- const employment = await manager.save(UserEmployment, {
1140
- userId: user.id,
1141
- department: data.department,
1142
- salary: data.salary
1143
- });
1144
-
1145
- // If anything fails, both are rolled back
1146
- return { user, employment };
1147
- });
1148
- }
1149
- ```
1150
-
1151
- ### 8. Document Your Extension Entities
1152
-
1153
- ```typescript
1154
- /**
1155
- * UserEmployment Entity
1156
- *
1157
- * Stores employee-specific information for users who are employees.
1158
- * Related to User entity via userId.
1159
- *
1160
- * Indexes:
1161
- * - userId: For fast user lookups
1162
- * - department: For filtering by department
1163
- * - (department, isActive): For active employee queries by department
1164
- *
1165
- * @example
1166
- * const emp = await employmentRepo.findOne({
1167
- * where: { userId: 'abc-123' }
1168
- * });
1169
- */
1170
- @Entity('user_employment')
1171
- @Index(['department', 'isActive'])
1172
- export class UserEmployment {
1173
- // ...
1174
- }
1175
- ```
1176
-
1177
- ### 9. Plan for Data Growth
1178
-
1179
- ```typescript
1180
- // Consider pagination for large result sets
1181
- async getEmployeesByDepartment(
1182
- department: string,
1183
- page: number = 1,
1184
- limit: number = 50
1185
- ) {
1186
- return this.employmentRepo.find({
1187
- where: { department, isActive: true },
1188
- relations: ['user'],
1189
- skip: (page - 1) * limit,
1190
- take: limit,
1191
- order: { createdAt: 'DESC' }
1192
- });
1193
- }
1194
-
1195
- // Add count for pagination UI
1196
- async getEmployeeCount(department: string) {
1197
- return this.employmentRepo.count({
1198
- where: { department, isActive: true }
1199
- });
1200
- }
1201
- ```
1202
-
1203
- ### 10. Monitor Query Performance
1204
-
1205
- ```typescript
1206
- // Enable query logging in development
1207
- TypeOrmModule.forRoot({
1208
- // ...
1209
- logging: ['query', 'error', 'warn'],
1210
- maxQueryExecutionTime: 1000, // Warn if query takes > 1s
1211
- });
1212
-
1213
- // Log slow queries in your service
1214
- const startTime = Date.now();
1215
- const result = await this.repo.find({ ... });
1216
- const duration = Date.now() - startTime;
1217
-
1218
- if (duration > 100) {
1219
- this.logger.warn(`Slow query detected: ${duration}ms`);
1220
- }
1221
- ```
1222
-
1223
- ---
1224
-
1225
- ## ๐Ÿ”„ Migration from JSONB
1226
-
1227
- If you have existing data in JSONB that needs to be queryable:
1228
-
1229
- ### Step 1: Create New Entity
1230
-
1231
- ```typescript
1232
- @Entity('user_employment')
1233
- @Index(['userId'])
1234
- @Index(['department'])
1235
- export class UserEmployment {
1236
- @PrimaryGeneratedColumn('uuid')
1237
- id: string;
1238
-
1239
- @Column()
1240
- userId: string;
1241
-
1242
- @Column()
1243
- department: string;
1244
-
1245
- @Column({ type: 'decimal', precision: 10, scale: 2 })
1246
- salary: number;
1247
- }
1248
- ```
1249
-
1250
- ### Step 2: Create Migration
1251
-
1252
- ```bash
1253
- npm run migration:generate --name=MigrateEmploymentData
1254
- ```
1255
-
1256
- ### Step 3: Write Migration Logic
1257
-
1258
- ```typescript
1259
- export class MigrateEmploymentData1234567890 implements MigrationInterface {
1260
- public async up(queryRunner: QueryRunner): Promise<void> {
1261
- // 1. Create table with indexes
1262
- await queryRunner.query(`
1263
- CREATE TABLE user_employment (
1264
- id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
1265
- "userId" UUID NOT NULL,
1266
- department VARCHAR(100),
1267
- salary DECIMAL(10,2),
1268
- "createdAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP
1269
- );
1270
-
1271
- CREATE INDEX idx_employment_user ON user_employment("userId");
1272
- CREATE INDEX idx_employment_dept ON user_employment(department);
1273
- `);
1274
-
1275
- // 2. Migrate data
1276
- await queryRunner.query(`
1277
- INSERT INTO user_employment ("userId", department, salary)
1278
- SELECT
1279
- id,
1280
- metadata->>'department',
1281
- (metadata->>'salary')::DECIMAL
1282
- FROM "user"
1283
- WHERE metadata ? 'department';
1284
- `);
1285
-
1286
- // 3. Clean up old data
1287
- await queryRunner.query(`
1288
- UPDATE "user"
1289
- SET metadata = metadata - 'department' - 'salary'
1290
- WHERE metadata ? 'department';
1291
- `);
1292
- }
1293
-
1294
- public async down(queryRunner: QueryRunner): Promise<void> {
1295
- // Migrate back if needed
1296
- await queryRunner.query(`
1297
- UPDATE "user"
1298
- SET metadata = jsonb_set(
1299
- jsonb_set(
1300
- COALESCE(metadata, '{}'::jsonb),
1301
- '{department}',
1302
- to_jsonb(emp.department)
1303
- ),
1304
- '{salary}',
1305
- to_jsonb(emp.salary)
1306
- )
1307
- FROM user_employment emp
1308
- WHERE "user".id = emp."userId";
1309
- `);
1310
-
1311
- await queryRunner.query(`DROP TABLE IF EXISTS user_employment;`);
1312
- }
1313
- }
1314
- ```
1315
-
1316
- ### Step 4: Run Migration
1317
-
1318
- ```bash
1319
- npm run migration:run
1320
- ```
1321
-
1322
- ---
1323
-
1324
- ## ๐Ÿ“š Additional Resources
1325
-
1326
- ### Official Documentation
1327
- - [NestJS Documentation](https://docs.nestjs.com)
1328
- - [TypeORM Documentation](https://typeorm.io)
1329
- - [PostgreSQL Performance Tips](https://wiki.postgresql.org/wiki/Performance_Optimization)
1330
-
1331
- ### Related Modules
1332
- - `@venturialstd/core` - Core shared functionality
1333
- - `@venturialstd/organization` - Organization management module
1334
- - `@venturialstd/auth` - Authentication module
1335
-
1336
- ### Performance References
1337
- - [PostgreSQL Index Types](https://www.postgresql.org/docs/current/indexes-types.html)
1338
- - [JSONB Performance](https://www.postgresql.org/docs/current/datatype-json.html)
1339
- - [Query Optimization](https://www.postgresql.org/docs/current/performance-tips.html)
1340
-
1341
- ---
1342
-
1343
- ## ๏ฟฝ๐Ÿ“„ License
1344
-
1345
- This module is part of the Venturial Standard Library.
1346
-
1347
- ---
1348
-
1349
- ## ๐Ÿค Contributing
1350
-
1351
- For contribution guidelines, please refer to the main repository's CONTRIBUTING.md.
1352
-
1353
- ---
1354
-
1355
- ## ๐Ÿ“ž Support
1356
-
1357
- For issues and questions, please open an issue in the main repository.
1
+ # @venturialstd/user
2
+
3
+ User Management Module for Venturial - A NestJS module for managing users with comprehensive profile management, settings, and role-based access control.
4
+
5
+ **๐ŸŽฏ Key Design Principle:** This module provides a minimal, extensible base User entity. Extend it with related entities for your specific needs (employment data, contact info, etc.) for optimal performance.
6
+
7
+ ## ๐Ÿ“‹ Table of Contents
8
+
9
+ - [Features](#features)
10
+ - [Installation](#installation)
11
+ - [Module Structure](#module-structure)
12
+ - [Quick Start](#quick-start)
13
+ - [ACL System](#acl-system)
14
+ - [Extending User Statuses](#extending-user-statuses)
15
+ - [Extending the Module](#extending-the-module)
16
+ - [Performance Considerations](#performance-considerations)
17
+ - [Recommended Approach: Related Entities](#recommended-approach-related-entities)
18
+ - [Hybrid Approach](#hybrid-approach-optional-flexibility)
19
+ - [Complete Extension Example](#complete-extension-example)
20
+ - [Extension Methods Comparison](#extension-methods-comparison)
21
+ - [Configuration](#configuration)
22
+ - [Entities](#entities)
23
+ - [API Reference](#api-reference)
24
+ - [Enums & Constants](#enums--constants)
25
+ - [Guards & Decorators](#guards--decorators)
26
+ - [Test Server](#test-server)
27
+ - [NPM Scripts](#npm-scripts)
28
+ - [Usage Examples](#usage-examples)
29
+ - [Performance Best Practices](#performance-best-practices)
30
+ - [Migration Guide](#migration-guide)
31
+
32
+ ---
33
+
34
+ ## โœจ Features
35
+
36
+ - ๐Ÿ‘ค **User Management**: Complete CRUD operations for users
37
+ - ๐Ÿ“ง **Email Verification**: Email verification system
38
+ - โš™๏ธ **User Settings**: Configurable user preferences
39
+ - ๐ŸŽญ **Extensible Statuses**: Define custom user statuses for your application
40
+ - ๐Ÿ” **ACL System**: Complete database-driven role and permission management
41
+ - ๐Ÿ›ก๏ธ **Guards & Decorators**: Request-level access control
42
+ - ๐Ÿ”„ **TypeORM Integration**: Full database support with migrations
43
+ - ๐Ÿ“ฆ **Fully Typed**: Complete TypeScript support
44
+ - ๐Ÿงช **Test Infrastructure**: Standalone test server on port 3003
45
+ - โšก **High Performance**: Indexed columns and optimized queries
46
+
47
+ ---
48
+
49
+ ## ๐Ÿ“ฆ Installation
50
+
51
+ ```bash
52
+ npm install @venturialstd/user
53
+ ```
54
+
55
+ ### Peer Dependencies
56
+
57
+ ```json
58
+ {
59
+ "@nestjs/common": "^11.0.11",
60
+ "@nestjs/core": "^11.0.5",
61
+ "@nestjs/typeorm": "^10.0.0",
62
+ "@venturialstd/core": "^1.0.16",
63
+ "class-transformer": "^0.5.1",
64
+ "class-validator": "^0.14.1",
65
+ "typeorm": "^0.3.20"
66
+ }
67
+ ```
68
+
69
+ ---
70
+
71
+ ## ๐Ÿ“ Module Structure
72
+
73
+ ```
74
+ src/user/
75
+ โ”œโ”€โ”€ src/
76
+ โ”‚ โ”œโ”€โ”€ constants/
77
+ โ”‚ โ”‚ โ”œโ”€โ”€ user.constant.ts # USER_STATUS enum
78
+ โ”‚ โ”‚ โ””โ”€โ”€ user.settings.constant.ts # Settings keys and config
79
+ โ”‚ โ”œโ”€โ”€ decorators/
80
+ โ”‚ โ”‚ โ”œโ”€โ”€ user.decorator.ts # @CurrentUserId(), @CurrentUser()
81
+ โ”‚ โ”‚ โ””โ”€โ”€ acl.decorator.ts # @RequirePermissions(), @RequireAclRoles()
82
+ โ”‚ โ”œโ”€โ”€ entities/
83
+ โ”‚ โ”‚ โ”œโ”€โ”€ user.entity.ts # User entity
84
+ โ”‚ โ”‚ โ”œโ”€โ”€ role.entity.ts # ACL Role entity
85
+ โ”‚ โ”‚ โ”œโ”€โ”€ permission.entity.ts # ACL Permission entity
86
+ โ”‚ โ”‚ โ”œโ”€โ”€ role-permission.entity.ts # ACL Role-Permission junction
87
+ โ”‚ โ”‚ โ””โ”€โ”€ user-role.entity.ts # ACL User-Role junction
88
+ โ”‚ โ”œโ”€โ”€ guards/
89
+ โ”‚ โ”‚ โ”œโ”€โ”€ user.guard.ts # UserGuard
90
+ โ”‚ โ”‚ โ””โ”€โ”€ acl.guard.ts # PermissionGuard, AclRoleGuard
91
+ โ”‚ โ”œโ”€โ”€ services/
92
+ โ”‚ โ”‚ โ”œโ”€โ”€ user.service.ts # UserService
93
+ โ”‚ โ”‚ โ””โ”€โ”€ acl.service.ts # AclService
94
+ โ”‚ โ”œโ”€โ”€ settings/
95
+ โ”‚ โ”‚ โ””โ”€โ”€ user.settings.ts # UserSettings interface
96
+ โ”‚ โ”œโ”€โ”€ index.ts # Public API exports
97
+ โ”‚ โ””โ”€โ”€ user.module.ts # Main module
98
+ โ”œโ”€โ”€ test/
99
+ โ”‚ โ”œโ”€โ”€ controllers/
100
+ โ”‚ โ”‚ โ””โ”€โ”€ user-test.controller.ts # Test controller
101
+ โ”‚ โ”œโ”€โ”€ migrations/ # Database migrations
102
+ โ”‚ โ”œโ”€โ”€ .env.example # Environment template
103
+ โ”‚ โ”œโ”€โ”€ data-source.ts # TypeORM data source
104
+ โ”‚ โ”œโ”€โ”€ main.ts # Test server bootstrap
105
+ โ”‚ โ””โ”€โ”€ test-app.module.ts # Test module
106
+ โ”œโ”€โ”€ package.json
107
+ โ”œโ”€โ”€ tsconfig.json
108
+ โ””โ”€โ”€ README.md
109
+ ```
110
+
111
+ ---
112
+
113
+ ## ๐Ÿš€ Quick Start
114
+
115
+ ### 1. Import the Module
116
+
117
+ ```typescript
118
+ import { Module } from '@nestjs/common';
119
+ import { UserModule } from '@venturialstd/user';
120
+
121
+ @Module({
122
+ imports: [
123
+ // Basic import (uses default statuses)
124
+ UserModule.forRoot(),
125
+
126
+ // Or with custom statuses
127
+ UserModule.forRoot({
128
+ allowedStatuses: ['ACTIVE', 'INACTIVE', 'SUSPENDED', 'BANNED'],
129
+ additionalEntities: [UserEmployment, UserProfile],
130
+ }),
131
+ ],
132
+ })
133
+ export class AppModule {}
134
+ ```
135
+
136
+ ### 2. Use in Your Service
137
+
138
+ ```typescript
139
+ import { Injectable } from '@nestjs/common';
140
+ import { UserService, AclService } from '@venturialstd/user';
141
+
142
+ @Injectable()
143
+ export class MyService {
144
+ constructor(
145
+ private readonly userService: UserService,
146
+ private readonly aclService: AclService,
147
+ ) {}
148
+
149
+ async getUser(userId: string) {
150
+ return this.userService.getUserById(userId);
151
+ }
152
+
153
+ async assignModeratorRole(userId: string) {
154
+ const role = await this.aclService.getRoleByName('MODERATOR');
155
+ return this.aclService.assignRoleToUser(userId, role.id);
156
+ }
157
+ }
158
+ ```
159
+
160
+ ### 3. Use Guards and Decorators
161
+
162
+ ```typescript
163
+ import { Controller, Get, UseGuards } from '@nestjs/common';
164
+ import { CurrentUserId, UserGuard, RequirePermissions, PermissionGuard } from '@venturialstd/user';
165
+
166
+ @Controller('profile')
167
+ @UseGuards(UserGuard)
168
+ export class ProfileController {
169
+ @Get()
170
+ async getProfile(@CurrentUserId() userId: string) {
171
+ return { userId };
172
+ }
173
+
174
+ @Get('admin')
175
+ @RequirePermissions('users.delete', 'content.manage')
176
+ @UseGuards(UserGuard, PermissionGuard)
177
+ async adminAction() {
178
+ return { message: 'Admin action' };
179
+ }
180
+ }
181
+ ```
182
+
183
+ ---
184
+
185
+ ## ๐Ÿ” ACL System
186
+
187
+ The module includes a complete database-driven Access Control List (ACL) system for managing roles and permissions. See [ACL_SYSTEM.md](./ACL_SYSTEM.md) for comprehensive documentation.
188
+
189
+ ### Quick Overview
190
+
191
+ - **Roles**: Named collections of permissions (e.g., ADMIN, MODERATOR)
192
+ - **Permissions**: Granular access rights in `resource.action` format (e.g., `users.delete`, `posts.create`)
193
+ - **Multiple Roles**: Users can have multiple roles with different scopes
194
+ - **Temporal Access**: Roles can expire automatically
195
+ - **Conditional Permissions**: Optional conditions on role-permission assignments
196
+
197
+ ### Example Usage
198
+
199
+ ```typescript
200
+ // Create roles and permissions
201
+ const adminRole = await aclService.createRole('ADMIN', 'Administrator');
202
+ const userPerm = await aclService.createPermission('users', 'delete', 'Delete users');
203
+
204
+ // Assign permission to role
205
+ await aclService.assignPermissionToRole(adminRole.id, userPerm.id);
206
+
207
+ // Assign role to user
208
+ await aclService.assignRoleToUser(userId, adminRole.id);
209
+
210
+ // Check permissions
211
+ const canDelete = await aclService.userHasPermission(userId, 'users.delete');
212
+ ```
213
+
214
+ ---
215
+
216
+ ## ๐ŸŽญ Extending User Statuses
217
+
218
+ The module provides a flexible system for defining custom user statuses.
219
+
220
+ ## ๐ŸŽญ Extending User Statuses
221
+
222
+ The module provides a flexible system for defining custom user statuses.
223
+
224
+ ### Default Values
225
+
226
+ By default, the module includes:
227
+
228
+ **Statuses:**
229
+ - `ACTIVE` - User is active and can use the system
230
+ - `INACTIVE` - User is inactive
231
+ - `SUSPENDED` - User has been suspended
232
+ - `PENDING_VERIFICATION` - User email needs verification
233
+
234
+ ### Defining Custom Statuses
235
+
236
+ Create an enum for your application-specific statuses:
237
+
238
+ ```typescript
239
+ // Define custom statuses
240
+ export enum AppUserStatus {
241
+ ACTIVE = 'ACTIVE',
242
+ INACTIVE = 'INACTIVE',
243
+ SUSPENDED = 'SUSPENDED',
244
+ PENDING_VERIFICATION = 'PENDING_VERIFICATION',
245
+ ON_HOLD = 'ON_HOLD',
246
+ BANNED = 'BANNED',
247
+ DELETED = 'DELETED',
248
+ }
249
+
250
+ @Module({
251
+ imports: [
252
+ UserModule.forRoot({
253
+ allowedStatuses: Object.values(AppUserStatus),
254
+ }),
255
+ ],
256
+ })
257
+ export class AppModule {}
258
+ ```
259
+
260
+ ### Managing Statuses
261
+
262
+ ```typescript
263
+ @Injectable()
264
+ export class UserManagementService {
265
+ constructor(private userService: UserService) {}
266
+
267
+ // Set user status with validation
268
+ async banUser(userId: string) {
269
+ return this.userService.setStatus(userId, AppUserStatus.BANNED);
270
+ }
271
+
272
+ // Get users by status
273
+ async getBannedUsers() {
274
+ return this.userService.getUsersByStatus(AppUserStatus.BANNED);
275
+ }
276
+
277
+ // Get all allowed statuses for your application
278
+ getAllStatuses() {
279
+ return this.userService.getAllowedStatuses();
280
+ }
281
+ }
282
+ ```
283
+
284
+ ### Validation
285
+
286
+ The module automatically validates statuses:
287
+
288
+ ```typescript
289
+ // โœ… Valid - status is in allowedStatuses
290
+ await userService.setStatus(userId, AppUserStatus.BANNED);
291
+
292
+ // โŒ Invalid - throws BadRequestException
293
+ await userService.setStatus(userId, 'INVALID_STATUS');
294
+ // Error: Invalid status "INVALID_STATUS". Allowed statuses: ACTIVE, INACTIVE, SUSPENDED, ...
295
+ ```
296
+
297
+ ---
298
+
299
+ ## ๐Ÿ”ง Extending the Module
300
+
301
+ ### Performance Considerations
302
+
303
+ **โš ๏ธ Important:** The `metadata` and `settings` JSONB fields are available but **NOT recommended for filterable attributes** due to poor query performance.
304
+
305
+ **Performance Comparison:**
306
+ ```sql
307
+ -- โŒ SLOW: JSONB query (full table scan)
308
+ SELECT * FROM "user" WHERE metadata->>'department' = 'Engineering';
309
+ -- 1M records: ~50 seconds
310
+
311
+ -- โšก FAST: Indexed column query
312
+ SELECT * FROM user_employment WHERE department = 'Engineering';
313
+ -- 1M records: ~10 milliseconds
314
+ ```
315
+
316
+ **Result: 5,000x faster with proper indexes!**
317
+
318
+ ### Recommended Approach: Related Entities
319
+
320
+ **Always use related entities for any data you need to query or filter.**
321
+
322
+ #### Step 1: Create Your Extended Entity
323
+
324
+ ```typescript
325
+ import { Entity, PrimaryGeneratedColumn, Column, OneToOne, JoinColumn, Index } from 'typeorm';
326
+ import { User } from '@venturialstd/user';
327
+
328
+ @Entity('user_employment')
329
+ @Index(['department', 'isActive']) // Composite index for common queries
330
+ export class UserEmployment {
331
+ @PrimaryGeneratedColumn('uuid')
332
+ id: string;
333
+
334
+ @Column()
335
+ @Index() // Fast lookups by userId
336
+ userId: string;
337
+
338
+ @OneToOne(() => User)
339
+ @JoinColumn({ name: 'userId' })
340
+ user: User;
341
+
342
+ // โšก Indexed fields for fast filtering
343
+ @Column({ unique: true })
344
+ employeeId: string;
345
+
346
+ @Column()
347
+ @Index() // Fast department filtering
348
+ department: string;
349
+
350
+ @Column()
351
+ jobTitle: string;
352
+
353
+ @Column({ nullable: true })
354
+ @Index() // Fast manager lookups
355
+ managerId: string;
356
+
357
+ @Column({ type: 'date' })
358
+ hireDate: Date;
359
+
360
+ @Column({ default: true })
361
+ @Index() // Fast active status queries
362
+ isActive: boolean;
363
+
364
+ @Column({ type: 'timestamptz', default: () => 'CURRENT_TIMESTAMP' })
365
+ createdAt: Date;
366
+
367
+ @Column({ type: 'timestamptz', default: () => 'CURRENT_TIMESTAMP' })
368
+ updatedAt: Date;
369
+ }
370
+ ```
371
+
372
+ #### Step 2: Create Service with Efficient Queries
373
+
374
+ ```typescript
375
+ import { Injectable } from '@nestjs/common';
376
+ import { InjectRepository } from '@nestjs/typeorm';
377
+ import { Repository } from 'typeorm';
378
+ import { UserService } from '@venturialstd/user';
379
+ import { UserEmployment } from './entities/user-employment.entity';
380
+
381
+ @Injectable()
382
+ export class EmployeeService {
383
+ constructor(
384
+ @InjectRepository(UserEmployment)
385
+ private employmentRepo: Repository<UserEmployment>,
386
+ private userService: UserService,
387
+ ) {}
388
+
389
+ async createEmployee(data: {
390
+ firstname: string;
391
+ lastname: string;
392
+ email: string;
393
+ department: string;
394
+ employeeId: string;
395
+ jobTitle: string;
396
+ }) {
397
+ // Create base user
398
+ const user = await this.userService.createUser(
399
+ data.firstname,
400
+ data.lastname,
401
+ data.email,
402
+ );
403
+
404
+ // Create employment record
405
+ const employment = this.employmentRepo.create({
406
+ userId: user.id,
407
+ employeeId: data.employeeId,
408
+ department: data.department,
409
+ jobTitle: data.jobTitle,
410
+ hireDate: new Date(),
411
+ isActive: true,
412
+ });
413
+
414
+ await this.employmentRepo.save(employment);
415
+ return this.getEmployeeWithUser(user.id);
416
+ }
417
+
418
+ // โšก FAST: Uses department index
419
+ async getEmployeesByDepartment(department: string) {
420
+ return this.employmentRepo.find({
421
+ where: { department, isActive: true },
422
+ relations: ['user'],
423
+ order: { createdAt: 'DESC' },
424
+ });
425
+ }
426
+
427
+ // โšก FAST: Complex query with proper indexes
428
+ async searchEmployees(searchTerm: string, department?: string) {
429
+ const query = this.employmentRepo
430
+ .createQueryBuilder('emp')
431
+ .leftJoinAndSelect('emp.user', 'user')
432
+ .where('emp.isActive = :active', { active: true });
433
+
434
+ if (department) {
435
+ query.andWhere('emp.department = :dept', { dept: department });
436
+ }
437
+
438
+ query.andWhere(
439
+ '(user.firstname ILIKE :search OR user.lastname ILIKE :search OR emp.employeeId ILIKE :search)',
440
+ { search: `%${searchTerm}%` },
441
+ );
442
+
443
+ return query.getMany();
444
+ }
445
+
446
+ async getEmployeeWithUser(userId: string) {
447
+ return this.employmentRepo.findOne({
448
+ where: { userId },
449
+ relations: ['user'],
450
+ });
451
+ }
452
+ }
453
+ ```
454
+
455
+ #### Step 3: Register with UserModule
456
+
457
+ ```typescript
458
+ import { Module } from '@nestjs/common';
459
+ import { TypeOrmModule } from '@nestjs/typeorm';
460
+ import { UserModule } from '@venturialstd/user';
461
+ import { UserEmployment } from './entities/user-employment.entity';
462
+ import { EmployeeService } from './services/employee.service';
463
+
464
+ @Module({
465
+ imports: [
466
+ // Register your entities with UserModule
467
+ UserModule.forRoot({
468
+ additionalEntities: [UserEmployment],
469
+ }),
470
+ // Also register for your service
471
+ TypeOrmModule.forFeature([UserEmployment]),
472
+ ],
473
+ providers: [EmployeeService],
474
+ exports: [EmployeeService],
475
+ })
476
+ export class EmployeeModule {}
477
+ ```
478
+
479
+ #### Benefits:
480
+ - โšก **100x-5000x faster queries** with proper indexes
481
+ - โœ… Database constraints (unique, foreign keys)
482
+ - โœ… Full TypeScript type safety
483
+ - โœ… Complex queries (JOINs, aggregations)
484
+ - โœ… Scales to millions of records
485
+
486
+ ### Hybrid Approach (Optional Flexibility)
487
+
488
+ If you need **some** flexibility for truly dynamic, non-queryable fields:
489
+
490
+ ```typescript
491
+ @Entity('user_employment')
492
+ export class UserEmployment {
493
+ // ... indexed columns for queryable fields ...
494
+
495
+ @Column()
496
+ @Index()
497
+ department: string; // โšก Fast queries
498
+
499
+ @Column({ unique: true })
500
+ employeeId: string; // โšก Fast lookups
501
+
502
+ // ๐Ÿ“ฆ FLEXIBLE: For truly dynamic, non-queryable data only
503
+ @Column({ type: 'jsonb', nullable: true })
504
+ additionalInfo: {
505
+ shirtSize?: string;
506
+ parkingSpot?: string;
507
+ badgeNumber?: string;
508
+ emergencyContact?: {
509
+ name: string;
510
+ phone: string;
511
+ };
512
+ [key: string]: any;
513
+ };
514
+ }
515
+ ```
516
+
517
+ ### Complete Extension Example
518
+
519
+ See the `examples/` directory for complete working examples:
520
+ - `user-employee-profile.entity.ts` - Entity with proper indexes
521
+ - `user-employee.service.ts` - Service with efficient queries
522
+ - `user-employee.module.ts` - Module configuration
523
+
524
+ ### Extension Methods Comparison
525
+
526
+ | Method | Query Performance | Flexibility | Type Safety | Best For |
527
+ |--------|------------------|-------------|-------------|----------|
528
+ | **Related Entities** โœ… | โšกโšกโšก Excellent | โญโญ Medium | โญโญโญ Strong | **Production (99% of cases)** |
529
+ | **Hybrid (Entities + JSONB)** | โšกโšกโญ Good | โญโญโญ High | โญโญโญ Strong | Some flexibility needed |
530
+ | **Pure JSONB** โŒ | โš ๏ธ Poor | โญโญโญ High | โญ Weak | **Avoid for queryable data** |
531
+ | **EAV Pattern** โŒ | โš ๏ธ Very Poor | โญโญโญ High | โญ Weak | **Not recommended** |
532
+
533
+ **Decision Tree:**
534
+ - Need to filter/query this data? โ†’ **Use Related Entity** โœ…
535
+ - Just storing UI preferences? โ†’ **Can use JSONB** ๐Ÿ“
536
+ - Truly unknown runtime schema? โ†’ **Consider Hybrid approach**
537
+
538
+ ---
539
+
540
+ ## ๐Ÿ“Š Entities
541
+
542
+ ### User Entity
543
+
544
+ The main `User` entity represents a user in the system:
545
+
546
+ ```typescript
547
+ {
548
+ id: string; // UUID primary key
549
+ firstname: string; // User's first name
550
+ lastname: string; // User's last name
551
+ email: string; // Unique email address
552
+ phone?: string; // Optional phone number
553
+ avatar?: string; // Optional avatar URL
554
+ timezone?: string; // User's timezone
555
+ locale?: string; // User's locale (e.g., 'en-US')
556
+ status?: string; // User status (extensible, e.g., 'ACTIVE', 'SUSPENDED')
557
+ role?: string; // User role (extensible, e.g., 'ADMIN', 'USER', 'MODERATOR')
558
+ isActive: boolean; // Account active flag
559
+ isEmailVerified: boolean; // Email verification status
560
+ settings?: Record<string, unknown>; // User settings (JSONB) - for UI preferences only
561
+ metadata?: Record<string, unknown>; // Additional metadata (JSONB) - for non-queryable data only
562
+ createdAt: Date; // Creation timestamp
563
+ updatedAt: Date; // Last update timestamp
564
+ }
565
+ ```
566
+
567
+ **โš ๏ธ Important Notes:**
568
+
569
+ 1. **`role` and `status` are extensible** - Define your own values via `UserModule.forRoot()` configuration
570
+ 2. **Indexed fields** - Both `role` and `status` have database indexes for fast queries
571
+ 3. **JSONB fields (`settings`, `metadata`)** - Should only be used for data you'll never query or filter on
572
+ 4. **For business data** - Use related entities instead of JSONB (see Extensibility section)
573
+
574
+ ---
575
+
576
+ ## ๐Ÿ”Œ API Reference
577
+
578
+ ### UserService
579
+
580
+ #### `createUser(firstname, lastname, email, phone?, avatar?, timezone?, locale?)`
581
+ Creates a new user with validation.
582
+
583
+ ```typescript
584
+ const user = await userService.createUser(
585
+ 'John',
586
+ 'Doe',
587
+ 'john@example.com',
588
+ '+1234567890',
589
+ 'https://avatar.url',
590
+ 'America/New_York',
591
+ 'en-US'
592
+ );
593
+ ```
594
+
595
+ #### `getUserById(userId: string)`
596
+ Gets a user by their ID.
597
+
598
+ ```typescript
599
+ const user = await userService.getUserById('user-uuid');
600
+ ```
601
+
602
+ #### `getUserByEmail(email: string)`
603
+ Gets a user by their email address.
604
+
605
+ ```typescript
606
+ const user = await userService.getUserByEmail('john@example.com');
607
+ ```
608
+
609
+ #### `updateUserProfile(userId, updates)`
610
+ Updates user profile fields.
611
+
612
+ ```typescript
613
+ const user = await userService.updateUserProfile('user-uuid', {
614
+ firstname: 'Jane',
615
+ timezone: 'Europe/London',
616
+ });
617
+ ```
618
+
619
+ #### `updateUserSettings(userId, settings)`
620
+ Updates user settings.
621
+
622
+ ```typescript
623
+ const user = await userService.updateUserSettings('user-uuid', {
624
+ notificationsEnabled: true,
625
+ theme: 'dark',
626
+ });
627
+ ```
628
+
629
+ #### `updateUserMetadata(userId, metadata)`
630
+ Updates user metadata.
631
+
632
+ ```typescript
633
+ const user = await userService.updateUserMetadata('user-uuid', {
634
+ role: 'ADMIN',
635
+ department: 'Engineering',
636
+ });
637
+ ```
638
+
639
+ #### `setUserStatus(userId, isActive)`
640
+ Activates or deactivates a user.
641
+
642
+ ```typescript
643
+ const user = await userService.setUserStatus('user-uuid', false);
644
+ ```
645
+
646
+ #### `verifyEmail(userId)`
647
+ Marks a user's email as verified.
648
+
649
+ ```typescript
650
+ const user = await userService.verifyEmail('user-uuid');
651
+ ```
652
+
653
+ #### `getActiveUsers()`
654
+ Gets all active users.
655
+
656
+ ```typescript
657
+ const users = await userService.getActiveUsers();
658
+ ```
659
+
660
+ #### `getVerifiedUsers()`
661
+ Gets all users with verified emails.
662
+
663
+ ```typescript
664
+ const users = await userService.getVerifiedUsers();
665
+ ```
666
+
667
+ #### `setRole(userId: string, role: string)`
668
+ Sets a user's role with validation.
669
+
670
+ ```typescript
671
+ const user = await userService.setRole('user-uuid', 'MODERATOR');
672
+ // Validates against allowedRoles from UserModule configuration
673
+ ```
674
+
675
+ #### `setStatus(userId: string, status: string)`
676
+ Sets a user's status with validation.
677
+
678
+ ```typescript
679
+ const user = await userService.setStatus('user-uuid', 'BANNED');
680
+ // Validates against allowedStatuses from UserModule configuration
681
+ ```
682
+
683
+ #### `getUsersByRole(role: string)`
684
+ Gets all users with a specific role.
685
+
686
+ ```typescript
687
+ const moderators = await userService.getUsersByRole('MODERATOR');
688
+ ```
689
+
690
+ #### `getUsersByStatus(status: string)`
691
+ Gets all users with a specific status.
692
+
693
+ ```typescript
694
+ const bannedUsers = await userService.getUsersByStatus('BANNED');
695
+ ```
696
+
697
+ #### `getAllowedRoles()`
698
+ Gets the list of allowed roles for your application.
699
+
700
+ ```typescript
701
+ const roles = userService.getAllowedRoles();
702
+ // Returns: ['ADMIN', 'USER', 'MODERATOR', ...]
703
+ ```
704
+
705
+ #### `getAllowedStatuses()`
706
+ Gets the list of allowed statuses for your application.
707
+
708
+ ```typescript
709
+ const statuses = userService.getAllowedStatuses();
710
+ // Returns: ['ACTIVE', 'INACTIVE', 'SUSPENDED', ...]
711
+ ```
712
+
713
+ #### `isValidRole(role: string)`
714
+ Checks if a role is valid for your application.
715
+
716
+ ```typescript
717
+ if (userService.isValidRole('MODERATOR')) {
718
+ // Role is valid
719
+ }
720
+ ```
721
+
722
+ #### `isValidStatus(status: string)`
723
+ Checks if a status is valid for your application.
724
+
725
+ ```typescript
726
+ if (userService.isValidStatus('BANNED')) {
727
+ // Status is valid
728
+ }
729
+ ```
730
+
731
+ ---
732
+
733
+ ## ๐Ÿ” Enums & Constants
734
+
735
+ ### USER_STATUS
736
+
737
+ ```typescript
738
+ enum USER_STATUS {
739
+ ACTIVE = 'ACTIVE',
740
+ INACTIVE = 'INACTIVE',
741
+ SUSPENDED = 'SUSPENDED',
742
+ PENDING_VERIFICATION = 'PENDING_VERIFICATION',
743
+ }
744
+ ```
745
+
746
+ ### USER_ROLE
747
+
748
+ ```typescript
749
+ enum USER_ROLE {
750
+ ADMIN = 'ADMIN',
751
+ USER = 'USER',
752
+ }
753
+ ```
754
+
755
+ ### USER_SETTINGS_KEY
756
+
757
+ ```typescript
758
+ const USER_SETTINGS_KEY = 'USER_SETTINGS';
759
+ ```
760
+
761
+ ---
762
+
763
+ ## ๐Ÿ›ก๏ธ Guards & Decorators
764
+
765
+ ### Guards
766
+
767
+ #### `UserGuard`
768
+ Ensures the user is authenticated and active.
769
+
770
+ ```typescript
771
+ @UseGuards(UserGuard)
772
+ @Get()
773
+ async getProfile(@CurrentUserId() userId: string) {
774
+ return this.userService.getUserById(userId);
775
+ }
776
+ ```
777
+
778
+ #### `UserRoleGuard`
779
+ Enforces role-based access control.
780
+
781
+ ```typescript
782
+ @RequireRoles(USER_ROLE.ADMIN)
783
+ @UseGuards(UserGuard, UserRoleGuard)
784
+ @Delete(':id')
785
+ async deleteUser(@Param('id') id: string) {
786
+ return this.userService.setUserStatus(id, false);
787
+ }
788
+ ```
789
+
790
+ ### Decorators
791
+
792
+ #### `@CurrentUserId()`
793
+ Extracts the current user ID from the request.
794
+
795
+ ```typescript
796
+ @Get()
797
+ async getData(@CurrentUserId() userId: string) {
798
+ return this.service.getUserData(userId);
799
+ }
800
+ ```
801
+
802
+ #### `@CurrentUser()`
803
+ Extracts the full user object from the request.
804
+
805
+ ```typescript
806
+ @Get()
807
+ async getData(@CurrentUser() user: User) {
808
+ return { user };
809
+ }
810
+ ```
811
+
812
+ #### `@RequireRoles(...roles)`
813
+ Specifies required roles for a route.
814
+
815
+ ```typescript
816
+ @RequireRoles(USER_ROLE.ADMIN)
817
+ @UseGuards(UserGuard, UserRoleGuard)
818
+ @Delete(':id')
819
+ async delete(@Param('id') id: string) {
820
+ // Only admins can access
821
+ }
822
+ ```
823
+
824
+ ---
825
+
826
+ ## ๐Ÿงช Test Server
827
+
828
+ The module includes a standalone test server for development and testing:
829
+
830
+ ### Start Test Server
831
+
832
+ ```bash
833
+ cd src/user
834
+ npm run test:dev
835
+ ```
836
+
837
+ The server starts on port **3003** (configurable via `.env`).
838
+
839
+ ### Watch Mode
840
+
841
+ ```bash
842
+ npm run test:watch
843
+ ```
844
+
845
+ ### Environment Setup
846
+
847
+ 1. Copy `.env.example` to `.env`:
848
+ ```bash
849
+ cp test/.env.example test/.env
850
+ ```
851
+
852
+ 2. Configure your database settings in `test/.env`
853
+
854
+ ### Available Test Endpoints
855
+
856
+ ```
857
+ POST /users - Create user
858
+ GET /users - Get all users
859
+ GET /users/active - Get active users
860
+ GET /users/verified - Get verified users
861
+ GET /users/email/:email - Get user by email
862
+ GET /users/:id - Get user by ID
863
+ PUT /users/:id/profile - Update user profile
864
+ PUT /users/:id/settings - Update user settings
865
+ PUT /users/:id/metadata - Update user metadata
866
+ PUT /users/:id/status - Update user status
867
+ PUT /users/:id/verify-email - Verify user email
868
+ DELETE /users/:id - Delete user
869
+ ```
870
+
871
+ ---
872
+
873
+ ## ๐Ÿ“ NPM Scripts
874
+
875
+ ```json
876
+ {
877
+ "build": "tsc -p tsconfig.json",
878
+ "prepublishOnly": "npm run build",
879
+ "release:patch": "npm run build && npm version patch --no-git-tag-version && npm publish",
880
+ "test:dev": "ts-node -r tsconfig-paths/register test/main.ts",
881
+ "test:watch": "nodemon --watch src --watch test --ext ts --exec npm run test:dev",
882
+ "migration:generate": "ts-node node_modules/.bin/typeorm migration:generate -d test/data-source.ts test/migrations/$npm_config_name",
883
+ "migration:run": "ts-node node_modules/.bin/typeorm migration:run -d test/data-source.ts",
884
+ "migration:revert": "ts-node node_modules/.bin/typeorm migration:revert -d test/data-source.ts"
885
+ }
886
+ ```
887
+
888
+ ---
889
+
890
+ ## ๐Ÿ’ก Usage Examples
891
+
892
+ ### Example 1: Create and Verify User
893
+
894
+ ```typescript
895
+ // Create a new user
896
+ const user = await userService.createUser(
897
+ 'Jane',
898
+ 'Smith',
899
+ 'jane@example.com',
900
+ '+1987654321',
901
+ null,
902
+ 'America/Los_Angeles',
903
+ 'en-US'
904
+ );
905
+
906
+ // Verify email
907
+ await userService.verifyEmail(user.id);
908
+ ```
909
+
910
+ ### Example 2: Update User Profile
911
+
912
+ ```typescript
913
+ const updatedUser = await userService.updateUserProfile(userId, {
914
+ firstname: 'Janet',
915
+ timezone: 'Europe/Paris',
916
+ locale: 'fr-FR',
917
+ });
918
+ ```
919
+
920
+ ### Example 3: Manage User Settings
921
+
922
+ ```typescript
923
+ // Update settings
924
+ await userService.updateUserSettings(userId, {
925
+ notificationsEnabled: true,
926
+ emailNotifications: false,
927
+ theme: 'dark',
928
+ });
929
+
930
+ // Get user with settings
931
+ const user = await userService.getUserById(userId);
932
+ console.log(user.settings);
933
+ ```
934
+
935
+ ### Example 4: Role-Based Access
936
+
937
+ ```typescript
938
+ // Set user role in metadata
939
+ await userService.updateUserMetadata(userId, {
940
+ role: USER_ROLE.ADMIN,
941
+ });
942
+
943
+ // Controller with role guard
944
+ @Controller('admin')
945
+ export class AdminController {
946
+ @RequireRoles(USER_ROLE.ADMIN)
947
+ @UseGuards(UserGuard, UserRoleGuard)
948
+ @Get('dashboard')
949
+ async getDashboard(@CurrentUser() user: User) {
950
+ return { admin: user };
951
+ }
952
+ }
953
+ ```
954
+
955
+ ### Example 5: Filter Users
956
+
957
+ ```typescript
958
+ // Get all active users
959
+ const activeUsers = await userService.getActiveUsers();
960
+
961
+ // Get verified users
962
+ const verifiedUsers = await userService.getVerifiedUsers();
963
+
964
+ // Find by email
965
+ const user = await userService.getUserByEmail('john@example.com');
966
+ ```
967
+
968
+ ---
969
+
970
+ ## ๐Ÿ”ง Database Migrations
971
+
972
+ ### Generate a Migration
973
+
974
+ ```bash
975
+ npm run migration:generate --name=AddUserFields
976
+ ```
977
+
978
+ ### Run Migrations
979
+
980
+ ```bash
981
+ npm run migration:run
982
+ ```
983
+
984
+ ### Revert Migration
985
+
986
+ ```bash
987
+ npm run migration:revert
988
+ ```
989
+
990
+ ---
991
+
992
+ ## ๏ฟฝ Best Practices
993
+
994
+ ### 1. Always Use Indexes for Queryable Fields
995
+
996
+ ```typescript
997
+ // โœ… GOOD: Indexed columns
998
+ @Entity('user_profile')
999
+ export class UserProfile {
1000
+ @Column() @Index() // Fast lookups
1001
+ userId: string;
1002
+
1003
+ @Column() @Index() // Fast filtering
1004
+ country: string;
1005
+
1006
+ @Column({ unique: true }) // Automatically indexed
1007
+ taxId: string;
1008
+ }
1009
+
1010
+ // โŒ BAD: JSONB for queryable data
1011
+ user.metadata = {
1012
+ country: 'US', // Will be slow to query!
1013
+ taxId: '123' // No unique constraint possible!
1014
+ };
1015
+ ```
1016
+
1017
+ ### 2. Use JSONB Only for Display Data
1018
+
1019
+ ```typescript
1020
+ // โœ… GOOD: Non-queryable UI preferences
1021
+ user.settings = {
1022
+ theme: 'dark',
1023
+ language: 'en',
1024
+ sidebarCollapsed: true
1025
+ };
1026
+
1027
+ // โœ… GOOD: Audit/log data you never filter on
1028
+ user.metadata = {
1029
+ lastLoginIp: '192.168.1.1',
1030
+ userAgent: 'Mozilla/5.0...',
1031
+ registrationSource: 'mobile_app'
1032
+ };
1033
+
1034
+ // โŒ BAD: Business data you need to query
1035
+ user.metadata = {
1036
+ subscriptionTier: 'premium', // Use UserSubscription entity!
1037
+ department: 'Engineering', // Use UserEmployment entity!
1038
+ isActive: true // Use column on User entity!
1039
+ };
1040
+ ```
1041
+
1042
+ ### 3. Create Composite Indexes for Common Queries
1043
+
1044
+ ```typescript
1045
+ @Entity('user_subscription')
1046
+ @Index(['userId', 'isActive']) // For userId + status queries
1047
+ @Index(['plan', 'expiresAt']) // For plan + expiration queries
1048
+ export class UserSubscription {
1049
+ @Column() userId: string;
1050
+ @Column() plan: string;
1051
+ @Column() isActive: boolean;
1052
+ @Column() expiresAt: Date;
1053
+ }
1054
+
1055
+ // Now these queries are super fast:
1056
+ await repo.find({
1057
+ where: { userId: 'abc-123', isActive: true } // Uses composite index
1058
+ });
1059
+
1060
+ await repo.find({
1061
+ where: {
1062
+ plan: 'premium',
1063
+ expiresAt: MoreThan(new Date())
1064
+ } // Uses composite index
1065
+ });
1066
+ ```
1067
+
1068
+ ### 4. Use TypeORM QueryBuilder for Complex Queries
1069
+
1070
+ ```typescript
1071
+ // โœ… Complex query with proper indexes
1072
+ const result = await employmentRepo
1073
+ .createQueryBuilder('emp')
1074
+ .leftJoinAndSelect('emp.user', 'user')
1075
+ .where('emp.department = :dept', { dept: 'Engineering' })
1076
+ .andWhere('emp.isActive = :active', { active: true })
1077
+ .andWhere('user.isEmailVerified = :verified', { verified: true })
1078
+ .orderBy('emp.hireDate', 'DESC')
1079
+ .limit(50)
1080
+ .getMany();
1081
+ ```
1082
+
1083
+ ### 5. Validate Data at the Application Layer
1084
+
1085
+ ```typescript
1086
+ // Use DTOs with class-validator
1087
+ export class CreateEmployeeDto {
1088
+ @IsString()
1089
+ @MinLength(2)
1090
+ firstname: string;
1091
+
1092
+ @IsEmail()
1093
+ email: string;
1094
+
1095
+ @IsEnum(['Engineering', 'Sales', 'Marketing'])
1096
+ department: string;
1097
+
1098
+ @IsPositive()
1099
+ @Max(1000000)
1100
+ salary: number;
1101
+ }
1102
+ ```
1103
+
1104
+ ### 6. Handle Relations Efficiently
1105
+
1106
+ ```typescript
1107
+ // โœ… GOOD: Eager load when you know you need it
1108
+ const employees = await repo.find({
1109
+ where: { department: 'Engineering' },
1110
+ relations: ['user'] // Load user in same query
1111
+ });
1112
+
1113
+ // โœ… GOOD: Load separately for optional data
1114
+ const employee = await repo.findOne({ where: { userId } });
1115
+ if (needUserDetails) {
1116
+ employee.user = await userService.findOne(userId);
1117
+ }
1118
+
1119
+ // โŒ BAD: N+1 query problem
1120
+ const employees = await repo.find({ where: { department: 'Engineering' } });
1121
+ for (const emp of employees) {
1122
+ emp.user = await userService.findOne(emp.userId); // Queries in loop!
1123
+ }
1124
+ ```
1125
+
1126
+ ### 7. Use Transactions for Multi-Entity Operations
1127
+
1128
+ ```typescript
1129
+ async createEmployeeWithProfile(data: CreateEmployeeDto) {
1130
+ return this.dataSource.transaction(async (manager) => {
1131
+ // Create user
1132
+ const user = await manager.save(User, {
1133
+ firstname: data.firstname,
1134
+ lastname: data.lastname,
1135
+ email: data.email
1136
+ });
1137
+
1138
+ // Create employment record
1139
+ const employment = await manager.save(UserEmployment, {
1140
+ userId: user.id,
1141
+ department: data.department,
1142
+ salary: data.salary
1143
+ });
1144
+
1145
+ // If anything fails, both are rolled back
1146
+ return { user, employment };
1147
+ });
1148
+ }
1149
+ ```
1150
+
1151
+ ### 8. Document Your Extension Entities
1152
+
1153
+ ```typescript
1154
+ /**
1155
+ * UserEmployment Entity
1156
+ *
1157
+ * Stores employee-specific information for users who are employees.
1158
+ * Related to User entity via userId.
1159
+ *
1160
+ * Indexes:
1161
+ * - userId: For fast user lookups
1162
+ * - department: For filtering by department
1163
+ * - (department, isActive): For active employee queries by department
1164
+ *
1165
+ * @example
1166
+ * const emp = await employmentRepo.findOne({
1167
+ * where: { userId: 'abc-123' }
1168
+ * });
1169
+ */
1170
+ @Entity('user_employment')
1171
+ @Index(['department', 'isActive'])
1172
+ export class UserEmployment {
1173
+ // ...
1174
+ }
1175
+ ```
1176
+
1177
+ ### 9. Plan for Data Growth
1178
+
1179
+ ```typescript
1180
+ // Consider pagination for large result sets
1181
+ async getEmployeesByDepartment(
1182
+ department: string,
1183
+ page: number = 1,
1184
+ limit: number = 50
1185
+ ) {
1186
+ return this.employmentRepo.find({
1187
+ where: { department, isActive: true },
1188
+ relations: ['user'],
1189
+ skip: (page - 1) * limit,
1190
+ take: limit,
1191
+ order: { createdAt: 'DESC' }
1192
+ });
1193
+ }
1194
+
1195
+ // Add count for pagination UI
1196
+ async getEmployeeCount(department: string) {
1197
+ return this.employmentRepo.count({
1198
+ where: { department, isActive: true }
1199
+ });
1200
+ }
1201
+ ```
1202
+
1203
+ ### 10. Monitor Query Performance
1204
+
1205
+ ```typescript
1206
+ // Enable query logging in development
1207
+ TypeOrmModule.forRoot({
1208
+ // ...
1209
+ logging: ['query', 'error', 'warn'],
1210
+ maxQueryExecutionTime: 1000, // Warn if query takes > 1s
1211
+ });
1212
+
1213
+ // Log slow queries in your service
1214
+ const startTime = Date.now();
1215
+ const result = await this.repo.find({ ... });
1216
+ const duration = Date.now() - startTime;
1217
+
1218
+ if (duration > 100) {
1219
+ this.logger.warn(`Slow query detected: ${duration}ms`);
1220
+ }
1221
+ ```
1222
+
1223
+ ---
1224
+
1225
+ ## ๐Ÿ”„ Migration from JSONB
1226
+
1227
+ If you have existing data in JSONB that needs to be queryable:
1228
+
1229
+ ### Step 1: Create New Entity
1230
+
1231
+ ```typescript
1232
+ @Entity('user_employment')
1233
+ @Index(['userId'])
1234
+ @Index(['department'])
1235
+ export class UserEmployment {
1236
+ @PrimaryGeneratedColumn('uuid')
1237
+ id: string;
1238
+
1239
+ @Column()
1240
+ userId: string;
1241
+
1242
+ @Column()
1243
+ department: string;
1244
+
1245
+ @Column({ type: 'decimal', precision: 10, scale: 2 })
1246
+ salary: number;
1247
+ }
1248
+ ```
1249
+
1250
+ ### Step 2: Create Migration
1251
+
1252
+ ```bash
1253
+ npm run migration:generate --name=MigrateEmploymentData
1254
+ ```
1255
+
1256
+ ### Step 3: Write Migration Logic
1257
+
1258
+ ```typescript
1259
+ export class MigrateEmploymentData1234567890 implements MigrationInterface {
1260
+ public async up(queryRunner: QueryRunner): Promise<void> {
1261
+ // 1. Create table with indexes
1262
+ await queryRunner.query(`
1263
+ CREATE TABLE user_employment (
1264
+ id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
1265
+ "userId" UUID NOT NULL,
1266
+ department VARCHAR(100),
1267
+ salary DECIMAL(10,2),
1268
+ "createdAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP
1269
+ );
1270
+
1271
+ CREATE INDEX idx_employment_user ON user_employment("userId");
1272
+ CREATE INDEX idx_employment_dept ON user_employment(department);
1273
+ `);
1274
+
1275
+ // 2. Migrate data
1276
+ await queryRunner.query(`
1277
+ INSERT INTO user_employment ("userId", department, salary)
1278
+ SELECT
1279
+ id,
1280
+ metadata->>'department',
1281
+ (metadata->>'salary')::DECIMAL
1282
+ FROM "user"
1283
+ WHERE metadata ? 'department';
1284
+ `);
1285
+
1286
+ // 3. Clean up old data
1287
+ await queryRunner.query(`
1288
+ UPDATE "user"
1289
+ SET metadata = metadata - 'department' - 'salary'
1290
+ WHERE metadata ? 'department';
1291
+ `);
1292
+ }
1293
+
1294
+ public async down(queryRunner: QueryRunner): Promise<void> {
1295
+ // Migrate back if needed
1296
+ await queryRunner.query(`
1297
+ UPDATE "user"
1298
+ SET metadata = jsonb_set(
1299
+ jsonb_set(
1300
+ COALESCE(metadata, '{}'::jsonb),
1301
+ '{department}',
1302
+ to_jsonb(emp.department)
1303
+ ),
1304
+ '{salary}',
1305
+ to_jsonb(emp.salary)
1306
+ )
1307
+ FROM user_employment emp
1308
+ WHERE "user".id = emp."userId";
1309
+ `);
1310
+
1311
+ await queryRunner.query(`DROP TABLE IF EXISTS user_employment;`);
1312
+ }
1313
+ }
1314
+ ```
1315
+
1316
+ ### Step 4: Run Migration
1317
+
1318
+ ```bash
1319
+ npm run migration:run
1320
+ ```
1321
+
1322
+ ---
1323
+
1324
+ ## ๐Ÿ“š Additional Resources
1325
+
1326
+ ### Official Documentation
1327
+ - [NestJS Documentation](https://docs.nestjs.com)
1328
+ - [TypeORM Documentation](https://typeorm.io)
1329
+ - [PostgreSQL Performance Tips](https://wiki.postgresql.org/wiki/Performance_Optimization)
1330
+
1331
+ ### Related Modules
1332
+ - `@venturialstd/core` - Core shared functionality
1333
+ - `@venturialstd/organization` - Organization management module
1334
+ - `@venturialstd/auth` - Authentication module
1335
+
1336
+ ### Performance References
1337
+ - [PostgreSQL Index Types](https://www.postgresql.org/docs/current/indexes-types.html)
1338
+ - [JSONB Performance](https://www.postgresql.org/docs/current/datatype-json.html)
1339
+ - [Query Optimization](https://www.postgresql.org/docs/current/performance-tips.html)
1340
+
1341
+ ---
1342
+
1343
+ ## ๏ฟฝ๐Ÿ“„ License
1344
+
1345
+ This module is part of the Venturial Standard Library.
1346
+
1347
+ ---
1348
+
1349
+ ## ๐Ÿค Contributing
1350
+
1351
+ For contribution guidelines, please refer to the main repository's CONTRIBUTING.md.
1352
+
1353
+ ---
1354
+
1355
+ ## ๐Ÿ“ž Support
1356
+
1357
+ For issues and questions, please open an issue in the main repository.