@flusys/nestjs-auth 0.1.0-beta.3 → 1.1.0-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/README.md +1 -11
  2. package/cjs/config/auth.constants.js +14 -4
  3. package/cjs/controllers/authentication.controller.js +173 -36
  4. package/cjs/controllers/branch.controller.js +3 -4
  5. package/cjs/controllers/company.controller.js +3 -4
  6. package/cjs/controllers/user-permission.controller.js +6 -18
  7. package/cjs/controllers/user.controller.js +168 -22
  8. package/cjs/dtos/authentication.dto.js +106 -3
  9. package/cjs/entities/index.js +5 -1
  10. package/cjs/entities/pending-registration.entity.js +125 -0
  11. package/cjs/entities/user-token.entity.js +129 -0
  12. package/cjs/interfaces/auth-email-provider.interface.js +45 -0
  13. package/cjs/interfaces/authentication.interface.js +0 -3
  14. package/cjs/interfaces/index.js +2 -0
  15. package/cjs/interfaces/user-enricher.interface.js +4 -0
  16. package/cjs/modules/auth.module.js +8 -2
  17. package/cjs/services/auth-config.service.js +22 -44
  18. package/cjs/services/auth-datasource.provider.js +17 -33
  19. package/cjs/services/auth-email.service.js +452 -0
  20. package/cjs/services/authentication.service.js +229 -115
  21. package/cjs/services/branch.service.js +10 -44
  22. package/cjs/services/company.service.js +7 -36
  23. package/cjs/services/index.js +1 -0
  24. package/cjs/services/user-permission.service.js +9 -27
  25. package/cjs/services/user.service.js +177 -99
  26. package/cjs/strategies/jwt.strategy.js +14 -1
  27. package/config/auth.constants.d.ts +3 -0
  28. package/controllers/authentication.controller.d.ts +13 -9
  29. package/controllers/user.controller.d.ts +27 -0
  30. package/dtos/authentication.dto.d.ts +21 -3
  31. package/dtos/user.dto.d.ts +1 -0
  32. package/entities/index.d.ts +7 -3
  33. package/entities/pending-registration.entity.d.ts +17 -0
  34. package/entities/user-token.entity.d.ts +15 -0
  35. package/fesm/config/auth.constants.js +18 -8
  36. package/fesm/controllers/authentication.controller.js +174 -37
  37. package/fesm/controllers/branch.controller.js +5 -6
  38. package/fesm/controllers/company.controller.js +5 -6
  39. package/fesm/controllers/user-permission.controller.js +6 -18
  40. package/fesm/controllers/user.controller.js +168 -22
  41. package/fesm/dtos/authentication.dto.js +109 -100
  42. package/fesm/entities/index.js +7 -1
  43. package/fesm/entities/pending-registration.entity.js +115 -0
  44. package/fesm/entities/user-token.entity.js +113 -0
  45. package/fesm/interfaces/auth-email-provider.interface.js +38 -0
  46. package/fesm/interfaces/authentication.interface.js +1 -7
  47. package/fesm/interfaces/index.js +2 -0
  48. package/fesm/interfaces/user-enricher.interface.js +26 -0
  49. package/fesm/modules/auth.module.js +9 -3
  50. package/fesm/services/auth-config.service.js +22 -44
  51. package/fesm/services/auth-datasource.provider.js +17 -33
  52. package/fesm/services/auth-email.service.js +401 -0
  53. package/fesm/services/authentication.service.js +227 -113
  54. package/fesm/services/branch.service.js +11 -45
  55. package/fesm/services/company.service.js +8 -37
  56. package/fesm/services/index.js +1 -0
  57. package/fesm/services/user-permission.service.js +9 -27
  58. package/fesm/services/user.service.js +178 -100
  59. package/fesm/strategies/jwt.strategy.js +14 -1
  60. package/interfaces/auth-email-provider.interface.d.ts +6 -0
  61. package/interfaces/auth-module-options.interface.d.ts +4 -1
  62. package/interfaces/authentication.interface.d.ts +14 -0
  63. package/interfaces/index.d.ts +2 -0
  64. package/interfaces/user-enricher.interface.d.ts +67 -0
  65. package/interfaces/user.interface.d.ts +1 -0
  66. package/package.json +3 -3
  67. package/services/auth-config.service.d.ts +1 -0
  68. package/services/auth-email.service.d.ts +50 -0
  69. package/services/authentication.service.d.ts +21 -5
  70. package/services/branch.service.d.ts +2 -5
  71. package/services/company.service.d.ts +1 -4
  72. package/services/index.d.ts +1 -0
  73. package/services/user.service.d.ts +17 -6
@@ -42,41 +42,15 @@ function _ts_param(paramIndex, decorator) {
42
42
  };
43
43
  }
44
44
  let BranchService = class BranchService extends _classes.RequestScopedApiService {
45
- /**
46
- * Resolve entity class for this service
47
- * @returns CompanyBranch entity class
48
- */ resolveEntity() {
45
+ resolveEntity() {
49
46
  return _companybranchentity.CompanyBranch;
50
47
  }
51
- /**
52
- * Get DataSource provider for this service
53
- * @returns AuthDataSourceProvider instance
54
- */ getDataSourceProvider() {
48
+ getDataSourceProvider() {
55
49
  return this.dataSourceProvider;
56
50
  }
57
- // ==================== Entity Conversion ====================
58
- async convertSingleDtoToEntity(dto, _user) {
59
- // Repository initialization is handled automatically by RequestScopedApiService
60
- let branch = new _companybranchentity.CompanyBranch();
61
- if ('id' in dto && dto.id && typeof dto.id === 'string') {
62
- const dbData = await this.repository.findOne({
63
- where: {
64
- id: dto.id
65
- }
66
- });
67
- if (!dbData) {
68
- throw new _common.NotFoundException('Branch not found');
69
- }
70
- branch = dbData;
71
- }
72
- return {
73
- ...branch,
74
- ...dto
75
- };
76
- }
51
+ // Query Customization
77
52
  async getSelectQuery(query, _user, select) {
78
- // Repository initialization is handled automatically by RequestScopedApiService
79
- if (!select || !select.length) {
53
+ if (!select?.length) {
80
54
  select = [
81
55
  'id',
82
56
  'name',
@@ -85,14 +59,13 @@ let BranchService = class BranchService extends _classes.RequestScopedApiService
85
59
  'createdAt'
86
60
  ];
87
61
  }
88
- const selectFields = select.map((field)=>`${this.entityName}.${field}`);
89
- query.select(selectFields);
62
+ query.select(select.map((f)=>`${this.entityName}.${f}`));
90
63
  return {
91
64
  query,
92
65
  isRaw: false
93
66
  };
94
67
  }
95
- async getGlobalSearchQuery(query, search, _user) {
68
+ async getGlobalSearchQuery(query, search) {
96
69
  query.andWhere('(company_branch.name LIKE :search OR company_branch.slug LIKE :search OR company_branch.email LIKE :search)', {
97
70
  search: `%${search}%`
98
71
  });
@@ -101,24 +74,17 @@ let BranchService = class BranchService extends _classes.RequestScopedApiService
101
74
  isRaw: false
102
75
  };
103
76
  }
104
- /**
105
- * Override: Filter branches by user's company
106
- * Ensures users can only see branches belonging to their company
107
- */ async getExtraManipulateQuery(query, _filterAndPaginationDto, user) {
108
- const result = await super.getExtraManipulateQuery(query, _filterAndPaginationDto, user);
109
- // Only apply company filtering if company feature is enabled and user has company context
110
- const isCompanyFeatureEnabled = this.authConfig.isCompanyFeatureEnabled();
111
- if (isCompanyFeatureEnabled && user?.companyId) {
77
+ async getExtraManipulateQuery(query, filterDto, user) {
78
+ const result = await super.getExtraManipulateQuery(query, filterDto, user);
79
+ if (this.authConfig.isCompanyFeatureEnabled() && user?.companyId) {
112
80
  query.andWhere('company_branch.company_id = :companyId', {
113
81
  companyId: user.companyId
114
82
  });
115
83
  }
116
84
  return result;
117
85
  }
118
- // NOTE: @Inject() required for bundled code - type metadata may be lost during esbuild
119
86
  constructor(cacheManager, utilsService, dataSourceProvider, authConfig){
120
- // Repository will be set asynchronously by RequestScopedApiService
121
- super('company_branch', null, cacheManager, utilsService, BranchService.name, true), _define_property(this, "cacheManager", void 0), _define_property(this, "utilsService", void 0), _define_property(this, "dataSourceProvider", void 0), _define_property(this, "authConfig", void 0), _define_property(this, "logger", void 0), this.cacheManager = cacheManager, this.utilsService = utilsService, this.dataSourceProvider = dataSourceProvider, this.authConfig = authConfig, this.logger = new _common.Logger(BranchService.name);
87
+ super('company_branch', null, cacheManager, utilsService, BranchService.name, true), _define_property(this, "cacheManager", void 0), _define_property(this, "utilsService", void 0), _define_property(this, "dataSourceProvider", void 0), _define_property(this, "authConfig", void 0), this.cacheManager = cacheManager, this.utilsService = utilsService, this.dataSourceProvider = dataSourceProvider, this.authConfig = authConfig;
122
88
  }
123
89
  };
124
90
  BranchService = _ts_decorate([
@@ -41,41 +41,15 @@ function _ts_param(paramIndex, decorator) {
41
41
  };
42
42
  }
43
43
  let CompanyService = class CompanyService extends _classes.RequestScopedApiService {
44
- /**
45
- * Resolve entity class for this service
46
- * @returns Company entity class
47
- */ resolveEntity() {
44
+ resolveEntity() {
48
45
  return _companyentity.Company;
49
46
  }
50
- /**
51
- * Get DataSource provider for this service
52
- * @returns AuthDataSourceProvider instance
53
- */ getDataSourceProvider() {
47
+ getDataSourceProvider() {
54
48
  return this.dataSourceProvider;
55
49
  }
56
- // ==================== Entity Conversion ====================
57
- async convertSingleDtoToEntity(dto, _user) {
58
- // Repository initialization is handled automatically by RequestScopedApiService
59
- let company = new _companyentity.Company();
60
- if ('id' in dto && dto.id && typeof dto.id === 'string') {
61
- const dbData = await this.repository.findOne({
62
- where: {
63
- id: dto.id
64
- }
65
- });
66
- if (!dbData) {
67
- throw new _common.NotFoundException('Company not found');
68
- }
69
- company = dbData;
70
- }
71
- return {
72
- ...company,
73
- ...dto
74
- };
75
- }
50
+ // Query Customization
76
51
  async getSelectQuery(query, _user, select) {
77
- // Repository initialization is handled automatically by RequestScopedApiService
78
- if (!select || !select.length) {
52
+ if (!select?.length) {
79
53
  select = [
80
54
  'id',
81
55
  'name',
@@ -84,14 +58,13 @@ let CompanyService = class CompanyService extends _classes.RequestScopedApiServi
84
58
  'createdAt'
85
59
  ];
86
60
  }
87
- const selectFields = select.map((field)=>`${this.entityName}.${field}`);
88
- query.select(selectFields);
61
+ query.select(select.map((f)=>`${this.entityName}.${f}`));
89
62
  return {
90
63
  query,
91
64
  isRaw: false
92
65
  };
93
66
  }
94
- async getGlobalSearchQuery(query, search, _user) {
67
+ async getGlobalSearchQuery(query, search) {
95
68
  query.andWhere('(company.name LIKE :search OR company.slug LIKE :search OR company.email LIKE :search)', {
96
69
  search: `%${search}%`
97
70
  });
@@ -100,10 +73,8 @@ let CompanyService = class CompanyService extends _classes.RequestScopedApiServi
100
73
  isRaw: false
101
74
  };
102
75
  }
103
- // NOTE: @Inject() required for bundled code - type metadata may be lost during esbuild
104
76
  constructor(cacheManager, utilsService, dataSourceProvider){
105
- // Repository will be set asynchronously by RequestScopedApiService
106
- super('company', null, cacheManager, utilsService, CompanyService.name, true), _define_property(this, "cacheManager", void 0), _define_property(this, "utilsService", void 0), _define_property(this, "dataSourceProvider", void 0), _define_property(this, "logger", void 0), this.cacheManager = cacheManager, this.utilsService = utilsService, this.dataSourceProvider = dataSourceProvider, this.logger = new _common.Logger(CompanyService.name);
77
+ super('company', null, cacheManager, utilsService, CompanyService.name, true), _define_property(this, "cacheManager", void 0), _define_property(this, "utilsService", void 0), _define_property(this, "dataSourceProvider", void 0), this.cacheManager = cacheManager, this.utilsService = utilsService, this.dataSourceProvider = dataSourceProvider;
107
78
  }
108
79
  };
109
80
  CompanyService = _ts_decorate([
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", {
3
3
  value: true
4
4
  });
5
5
  _export_star(require("./authentication.service"), exports);
6
+ _export_star(require("./auth-email.service"), exports);
6
7
  _export_star(require("./company-selection-session.service"), exports);
7
8
  _export_star(require("./user.service"), exports);
8
9
  _export_star(require("./company.service"), exports);
@@ -39,20 +39,14 @@ function _ts_param(paramIndex, decorator) {
39
39
  };
40
40
  }
41
41
  let UserPermissionService = class UserPermissionService {
42
- /**
43
- * Get repository lazily - initialized on first access
44
- */ async getRepository() {
42
+ /** Get repository lazily - initialized on first access */ async getRepository() {
45
43
  if (!this._repository) {
46
44
  this._repository = await this.dataSourceProvider.getRepository(_usercompanypermissionentity.UserCompanyPermission);
47
45
  }
48
46
  return this._repository;
49
47
  }
50
- // ==========================================================================
51
- // COMPANY PERMISSIONS
52
- // ==========================================================================
53
- /**
54
- * Find all companies a user has access to
55
- */ async findUserCompanies(userId) {
48
+ // Company Permissions
49
+ /** Find all companies a user has access to */ async findUserCompanies(userId) {
56
50
  const repository = await this.getRepository();
57
51
  return repository.find({
58
52
  where: {
@@ -67,9 +61,7 @@ let UserPermissionService = class UserPermissionService {
67
61
  }
68
62
  });
69
63
  }
70
- /**
71
- * Assign user to a company
72
- */ async assignUserToCompany(dto) {
64
+ /** Assign user to a company */ async assignUserToCompany(dto) {
73
65
  const repository = await this.getRepository();
74
66
  // Check if already exists
75
67
  const existing = await repository.findOne({
@@ -90,9 +82,7 @@ let UserPermissionService = class UserPermissionService {
90
82
  });
91
83
  await repository.save(permission);
92
84
  }
93
- /**
94
- * Revoke user access to a company
95
- */ async revokeCompanyAccess(userId, companyId) {
85
+ /** Revoke user access to a company */ async revokeCompanyAccess(userId, companyId) {
96
86
  const repository = await this.getRepository();
97
87
  const permission = await repository.findOne({
98
88
  where: {
@@ -106,12 +96,8 @@ let UserPermissionService = class UserPermissionService {
106
96
  }
107
97
  await repository.remove(permission);
108
98
  }
109
- // ==========================================================================
110
- // BRANCH PERMISSIONS
111
- // ==========================================================================
112
- /**
113
- * Find all branches a user has access to
114
- */ async findUserBranches(userId) {
99
+ // Branch Permissions
100
+ /** Find all branches a user has access to */ async findUserBranches(userId) {
115
101
  const repository = await this.getRepository();
116
102
  return repository.find({
117
103
  where: {
@@ -126,9 +112,7 @@ let UserPermissionService = class UserPermissionService {
126
112
  }
127
113
  });
128
114
  }
129
- /**
130
- * Assign user to a branch
131
- */ async assignUserToBranch(dto) {
115
+ /** Assign user to a branch */ async assignUserToBranch(dto) {
132
116
  const repository = await this.getRepository();
133
117
  // Check if already exists
134
118
  const existing = await repository.findOne({
@@ -149,9 +133,7 @@ let UserPermissionService = class UserPermissionService {
149
133
  });
150
134
  await repository.save(permission);
151
135
  }
152
- /**
153
- * Revoke user access to a branch
154
- */ async revokeBranchAccess(userId, branchId) {
136
+ /** Revoke user access to a branch */ async revokeBranchAccess(userId, branchId) {
155
137
  const repository = await this.getRepository();
156
138
  const permission = await repository.findOne({
157
139
  where: {
@@ -15,7 +15,9 @@ const _common = require("@nestjs/common");
15
15
  const _bcrypt = /*#__PURE__*/ _interop_require_wildcard(require("bcrypt"));
16
16
  const _authconfigservice = require("./auth-config.service");
17
17
  const _authdatasourceprovider = require("./auth-datasource.provider");
18
+ const _config = require("../config");
18
19
  const _enums = require("../enums");
20
+ const _userenricherinterface = require("../interfaces/user-enricher.interface");
19
21
  const _usercompanypermissionentity = require("../entities/user-company-permission.entity");
20
22
  const _userentity = require("../entities/user.entity");
21
23
  function _define_property(obj, key, value) {
@@ -87,22 +89,13 @@ function _ts_param(paramIndex, decorator) {
87
89
  };
88
90
  }
89
91
  let UserService = class UserService extends _classes.RequestScopedApiService {
90
- /**
91
- * Resolve entity class for this service
92
- * @returns User entity class
93
- */ resolveEntity() {
92
+ resolveEntity() {
94
93
  return _userentity.User;
95
94
  }
96
- /**
97
- * Get DataSource provider for this service
98
- * @returns AuthDataSourceProvider instance
99
- */ getDataSourceProvider() {
95
+ getDataSourceProvider() {
100
96
  return this.dataSourceProvider;
101
97
  }
102
- /**
103
- * Override ensureRepositoryInitialized to also initialize permission repository and dataSource
104
- * CRITICAL: Also initializes permissionRepository and dataSource for company feature support
105
- */ async ensureRepositoryInitialized() {
98
+ async ensureRepositoryInitialized() {
106
99
  await super.ensureRepositoryInitialized();
107
100
  // Initialize permission repository if company feature is enabled
108
101
  const isCompanyFeatureEnabled = this.authConfig.isCompanyFeatureEnabled();
@@ -116,26 +109,8 @@ let UserService = class UserService extends _classes.RequestScopedApiService {
116
109
  this.dataSource = await this.getDataSourceForService();
117
110
  }
118
111
  }
119
- // ==========================================================================
120
- // CUSTOM METHODS (Beyond RequestScopedApiService)
121
- // ==========================================================================
122
- /**
123
- * Update last login timestamp
124
- */ async updateLastLogin(id) {
125
- // Ensure repository is initialized for custom method
126
- await this.ensureRepositoryInitialized();
127
- await this.repository.update(id, {
128
- lastLoginAt: new Date()
129
- });
130
- await this.clearCacheForId([
131
- {
132
- id
133
- }
134
- ]);
135
- }
136
- /**
137
- * Verify email
138
- */ async verifyEmail(id) {
112
+ // Custom Methods
113
+ /** Verify email */ async verifyEmail(id) {
139
114
  // Ensure repository is initialized for custom method
140
115
  await this.ensureRepositoryInitialized();
141
116
  await this.repository.update(id, {
@@ -148,9 +123,7 @@ let UserService = class UserService extends _classes.RequestScopedApiService {
148
123
  }
149
124
  ]);
150
125
  }
151
- /**
152
- * Verify phone
153
- */ async verifyPhone(id) {
126
+ /** Verify phone */ async verifyPhone(id) {
154
127
  // Ensure repository is initialized for custom method
155
128
  await this.ensureRepositoryInitialized();
156
129
  await this.repository.update(id, {
@@ -163,12 +136,7 @@ let UserService = class UserService extends _classes.RequestScopedApiService {
163
136
  }
164
137
  ]);
165
138
  }
166
- /**
167
- * Update user status
168
- *
169
- * When company feature is disabled: Updates User.isActive directly
170
- * When company feature is enabled: Updates UserCompanyPermission.isActive for the logged user's company
171
- */ async updateStatus(id, isActive, user) {
139
+ /** Update user status. When company feature enabled, updates permission isActive for logged user's company. */ async updateStatus(id, isActive, user) {
172
140
  // Ensure repository is initialized for custom method
173
141
  await this.ensureRepositoryInitialized();
174
142
  const isCompanyFeatureEnabled = this.authConfig.isCompanyFeatureEnabled();
@@ -199,50 +167,43 @@ let UserService = class UserService extends _classes.RequestScopedApiService {
199
167
  }
200
168
  ]);
201
169
  }
202
- // ==========================================================================
203
- // APISERVICE OVERRIDES (Customize behavior)
204
- // ==========================================================================
205
- /**
206
- * Override: Convert DTO to entity (hash password before saving)
207
- */ async convertSingleDtoToEntity(dto, user) {
170
+ // ApiService Overrides
171
+ /** Convert DTO to entity (hash password before saving) */ async convertSingleDtoToEntity(dto, user) {
208
172
  // Repository initialization is handled automatically by RequestScopedApiService
209
173
  const entity = await super.convertSingleDtoToEntity(dto, user);
210
- // Hash password if provided
211
- if (dto.password) {
212
- entity.password = await _bcrypt.hash(dto.password, 10);
174
+ // Hash password if provided (CreateUserDto has password field)
175
+ if ('password' in dto && dto.password) {
176
+ entity.password = await _bcrypt.hash(dto.password, _config.BCRYPT_SALT_ROUNDS);
213
177
  }
214
178
  return entity;
215
179
  }
216
- /**
217
- * Override: Convert entity to response DTO (remove password!)
218
- *
219
- * When company feature is enabled and raw mode is used, isActive comes from
220
- * the permission table (user_companyIsActive). Otherwise, it comes from User entity.
221
- */ convertEntityToResponseDto(entity, isRaw) {
180
+ /** Convert entity to response DTO (removes password). In raw mode with company feature, isActive comes from permission table. */ convertEntityToResponseDto(entity, isRaw) {
222
181
  // When raw mode is used with company feature, isActive comes from computed field
223
- const isActive = isRaw && entity.user_companyIsActive !== undefined ? Boolean(entity.user_companyIsActive) : entity.isActive;
182
+ // Raw mode uses snake_case column names (due to SnakeNamingStrategy)
183
+ const isActive = isRaw && entity.user_companyIsActive !== undefined ? Boolean(entity.user_companyIsActive) : isRaw ? entity.user_is_active : entity.isActive;
224
184
  return {
225
185
  id: isRaw ? entity.user_id : entity.id,
226
186
  name: isRaw ? entity.user_name : entity.name,
227
187
  phone: isRaw ? entity.user_phone : entity.phone,
228
188
  email: isRaw ? entity.user_email : entity.email,
229
- profilePictureId: isRaw ? entity.user_profilePictureId : entity.profilePictureId,
189
+ profilePictureId: isRaw ? entity.user_profile_picture_id : entity.profilePictureId,
230
190
  isActive,
231
- emailVerified: isRaw ? entity.user_emailVerified : entity.emailVerified,
232
- phoneVerified: isRaw ? entity.user_phoneVerified : entity.phoneVerified,
233
- lastLoginAt: isRaw ? entity.user_lastLoginAt : entity.lastLoginAt,
234
- additionalFields: isRaw ? entity.user_additionalFields : entity.additionalFields,
235
- createdAt: isRaw ? entity.user_createdAt : entity.createdAt,
236
- updatedAt: isRaw ? entity.user_updatedAt : entity.updatedAt,
237
- deletedAt: isRaw ? entity.user_deletedAt : entity.deletedAt,
238
- createdById: isRaw ? entity.user_createdById : entity.createdById,
239
- updatedById: isRaw ? entity.user_updatedById : entity.updatedById,
240
- deletedById: isRaw ? entity.user_deletedById : entity.deletedById
191
+ emailVerified: isRaw ? entity.user_email_verified : entity.emailVerified,
192
+ phoneVerified: isRaw ? entity.user_phone_verified : entity.phoneVerified,
193
+ lastLoginAt: isRaw ? entity.user_last_login_at : entity.lastLoginAt,
194
+ additionalFields: isRaw ? entity.user_additional_fields : entity.additionalFields,
195
+ createdAt: isRaw ? entity.user_created_at : entity.createdAt,
196
+ updatedAt: isRaw ? entity.user_updated_at : entity.updatedAt,
197
+ deletedAt: isRaw ? entity.user_deleted_at : entity.deletedAt,
198
+ createdById: isRaw ? entity.user_created_by_id : entity.createdById,
199
+ updatedById: isRaw ? entity.user_updated_by_id : entity.updatedById,
200
+ deletedById: isRaw ? entity.user_deleted_by_id : entity.deletedById
241
201
  };
242
202
  }
243
- /**
244
- * Override: Global search - search by name, email, or phone
245
- */ async getGlobalSearchQuery(query, search, _user) {
203
+ /** Convert entity list to response DTO list */ convertEntityListToResponseListDto(entities, isRaw) {
204
+ return entities.map((entity)=>this.convertEntityToResponseDto(entity, isRaw));
205
+ }
206
+ /** Global search - search by name, email, or phone */ async getGlobalSearchQuery(query, search, _user) {
246
207
  query.andWhere('(user.name LIKE :search OR user.email LIKE :search OR user.phone LIKE :search)', {
247
208
  search: `%${search}%`
248
209
  });
@@ -251,18 +212,13 @@ let UserService = class UserService extends _classes.RequestScopedApiService {
251
212
  isRaw: false
252
213
  };
253
214
  }
254
- /**
255
- * Override: getExtraManipulateQuery - Add company permission-based isActive check when company feature enabled
256
- *
257
- * Note: When company feature is disabled at bootstrap, this method returns without modifications.
258
- * All company-related filtering is skipped, and users are returned normally with isActive from User entity.
259
- *
260
- * When company feature is enabled:
261
- * - isActive is computed from UserCompanyPermission table
262
- * - Returns isRaw: true to use raw query results
263
- */ async getExtraManipulateQuery(query, filterAndPaginationDto, user) {
215
+ /** Add company permission-based isActive check when company feature enabled. Returns isRaw: true for computed fields. */ async getExtraManipulateQuery(query, filterAndPaginationDto, user) {
264
216
  // Repository initialization is handled automatically by RequestScopedApiService
265
- const result = await super.getExtraManipulateQuery(query, filterAndPaginationDto, user);
217
+ let result = await super.getExtraManipulateQuery(query, filterAndPaginationDto, user);
218
+ // Apply user enricher query modifications if available
219
+ if (this.userEnricher?.enrichListQuery) {
220
+ await this.userEnricher.enrichListQuery(query, user);
221
+ }
266
222
  // Only apply company filtering if:
267
223
  // 1. Company feature is enabled at bootstrap
268
224
  // 2. User has company context
@@ -315,12 +271,7 @@ let UserService = class UserService extends _classes.RequestScopedApiService {
315
271
  }
316
272
  return result;
317
273
  }
318
- /**
319
- * Override: insert - Create company permission when company feature is enabled
320
- *
321
- * Note: When company feature is disabled at bootstrap, this method falls back to parent implementation.
322
- * No company permissions are created, and user is inserted normally.
323
- */ async insert(dto, user) {
274
+ /** Insert user. Creates company permission when company feature is enabled. */ async insert(dto, user) {
324
275
  // Repository initialization is handled automatically by RequestScopedApiService
325
276
  // Check if company feature is enabled
326
277
  const isCompanyFeatureEnabled = this.authConfig.isCompanyFeatureEnabled();
@@ -388,17 +339,139 @@ let UserService = class UserService extends _classes.RequestScopedApiService {
388
339
  }
389
340
  }
390
341
  /**
391
- * Update user profile with password handling
392
- */ async profile(dto, user) {
393
- // Repository initialization is handled automatically by RequestScopedApiService
342
+ * Get user profile with extras from enricher
343
+ * Returns base user data plus any extra data provided by enricher (roles, permissions, etc.)
344
+ */ async getProfileWithExtras(userId, user) {
345
+ await this.ensureRepositoryInitialized();
346
+ const userEntity = await this.repository.findOne({
347
+ where: {
348
+ id: userId
349
+ }
350
+ });
351
+ if (!userEntity) {
352
+ throw new _common.NotFoundException('User not found');
353
+ }
354
+ const userData = this.convertEntityToResponseDto(userEntity, false);
355
+ // Get extras from enricher if available
356
+ let extras;
357
+ if (this.userEnricher?.getProfileExtras) {
358
+ extras = await this.userEnricher.getProfileExtras(userId, user);
359
+ }
360
+ return {
361
+ user: userData,
362
+ extras
363
+ };
364
+ }
365
+ /**
366
+ * Get enriched user list
367
+ * Applies enricher transformations after query execution
368
+ */ async getEnrichedList(users, user) {
369
+ if (this.userEnricher?.enrichListItems) {
370
+ return this.userEnricher.enrichListItems(users, user);
371
+ }
372
+ return users;
373
+ }
374
+ // ============================================
375
+ // Multi-Step Profile Methods (via Enricher)
376
+ // ============================================
377
+ /**
378
+ * Get profile section definitions from enricher
379
+ */ async getProfileSections() {
380
+ if (this.userEnricher?.getProfileSections) {
381
+ return this.userEnricher.getProfileSections();
382
+ }
383
+ return [];
384
+ }
385
+ /**
386
+ * Get specific profile section data
387
+ */ async getProfileSectionData(userId, sectionId, user) {
388
+ await this.ensureRepositoryInitialized();
389
+ // Verify user exists
390
+ const userEntity = await this.repository.findOne({
391
+ where: {
392
+ id: userId
393
+ }
394
+ });
395
+ if (!userEntity) {
396
+ throw new _common.NotFoundException('User not found');
397
+ }
398
+ if (this.userEnricher?.getProfileSectionData) {
399
+ return this.userEnricher.getProfileSectionData(userId, sectionId, user);
400
+ }
401
+ return null;
402
+ }
403
+ /**
404
+ * Update specific profile section
405
+ */ async updateProfileSection(userId, sectionId, data, user) {
406
+ await this.ensureRepositoryInitialized();
407
+ if (!this.dataSource) {
408
+ throw new _common.BadRequestException('DataSource not available');
409
+ }
410
+ // Verify user exists
411
+ const userEntity = await this.repository.findOne({
412
+ where: {
413
+ id: userId
414
+ }
415
+ });
416
+ if (!userEntity) {
417
+ throw new _common.NotFoundException('User not found');
418
+ }
419
+ if (!this.userEnricher?.updateProfileSection) {
420
+ throw new _common.BadRequestException('Profile sections not supported');
421
+ }
422
+ const queryRunner = this.dataSource.createQueryRunner();
423
+ await queryRunner.connect();
424
+ await queryRunner.startTransaction();
425
+ try {
426
+ await this.userEnricher.updateProfileSection(userId, sectionId, data, queryRunner);
427
+ await queryRunner.commitTransaction();
428
+ await this.clearCacheForId([
429
+ userEntity
430
+ ]);
431
+ } catch (error) {
432
+ await queryRunner.rollbackTransaction();
433
+ _utils.ErrorHandler.logError(this.logger, error, 'updateProfileSection', {
434
+ entity: this.entityName,
435
+ sectionId
436
+ });
437
+ _utils.ErrorHandler.rethrowError(error);
438
+ } finally{
439
+ await queryRunner.release();
440
+ }
441
+ }
442
+ /**
443
+ * Get profile completion percentage
444
+ */ async getProfileCompletion(userId, user) {
445
+ await this.ensureRepositoryInitialized();
446
+ // Verify user exists
447
+ const userEntity = await this.repository.findOne({
448
+ where: {
449
+ id: userId
450
+ }
451
+ });
452
+ if (!userEntity) {
453
+ throw new _common.NotFoundException('User not found');
454
+ }
455
+ if (this.userEnricher?.calculateProfileCompletion) {
456
+ return this.userEnricher.calculateProfileCompletion(userId, user);
457
+ }
458
+ return 100; // Default to 100% if no enricher
459
+ }
460
+ /** Update user profile with password handling and extras */ async profile(dto, user) {
461
+ // Ensure repository and dataSource are initialized
462
+ await this.ensureRepositoryInitialized();
394
463
  if (!this.dataSource) {
395
464
  throw new _common.BadRequestException('DataSource not available');
396
465
  }
466
+ const userId = dto.id ?? user.id;
467
+ // Validate extras if enricher provides validation
468
+ if (dto.extras && this.userEnricher?.validateProfileExtras) {
469
+ await this.userEnricher.validateProfileExtras(userId, dto.extras, user);
470
+ }
397
471
  const queryRunner = this.dataSource.createQueryRunner();
398
472
  await queryRunner.connect();
399
473
  await queryRunner.startTransaction();
400
474
  try {
401
- const userId = dto.id ?? user.id;
402
475
  const findUser = await queryRunner.manager.findOne(this.repository.target, {
403
476
  where: {
404
477
  id: userId
@@ -417,10 +490,14 @@ let UserService = class UserService extends _classes.RequestScopedApiService {
417
490
  const salt = await _bcrypt.genSalt();
418
491
  findUser.password = await _bcrypt.hash(dto.password, salt);
419
492
  }
420
- // Merge other fields (excluding password)
421
- const { password, ...restDto } = dto;
493
+ // Merge other fields (excluding password and extras)
494
+ const { password, extras, ...restDto } = dto;
422
495
  Object.assign(findUser, restDto);
423
496
  await queryRunner.manager.save(this.repository.target, findUser);
497
+ // Handle extras update via enricher
498
+ if (extras && this.userEnricher?.updateProfileExtras) {
499
+ await this.userEnricher.updateProfileExtras(userId, extras, queryRunner);
500
+ }
424
501
  await queryRunner.commitTransaction();
425
502
  await this.clearCacheForId([
426
503
  findUser
@@ -436,10 +513,8 @@ let UserService = class UserService extends _classes.RequestScopedApiService {
436
513
  await queryRunner.release();
437
514
  }
438
515
  }
439
- // NOTE: @Inject() required for bundled code - type metadata may be lost during esbuild
440
- constructor(cacheManager, utilsService, dataSourceProvider, authConfig){
441
- // Repository will be set asynchronously by RequestScopedApiService
442
- super('user', null, cacheManager, utilsService, UserService.name, true), _define_property(this, "cacheManager", void 0), _define_property(this, "utilsService", void 0), _define_property(this, "dataSourceProvider", void 0), _define_property(this, "authConfig", void 0), _define_property(this, "logger", void 0), _define_property(this, "permissionRepository", void 0), _define_property(this, "dataSource", void 0), this.cacheManager = cacheManager, this.utilsService = utilsService, this.dataSourceProvider = dataSourceProvider, this.authConfig = authConfig, this.logger = new _common.Logger(UserService.name);
516
+ constructor(cacheManager, utilsService, dataSourceProvider, authConfig, userEnricher){
517
+ super('user', null, cacheManager, utilsService, UserService.name, true), _define_property(this, "cacheManager", void 0), _define_property(this, "utilsService", void 0), _define_property(this, "dataSourceProvider", void 0), _define_property(this, "authConfig", void 0), _define_property(this, "userEnricher", void 0), _define_property(this, "permissionRepository", void 0), _define_property(this, "dataSource", void 0), this.cacheManager = cacheManager, this.utilsService = utilsService, this.dataSourceProvider = dataSourceProvider, this.authConfig = authConfig, this.userEnricher = userEnricher;
443
518
  }
444
519
  };
445
520
  UserService = _ts_decorate([
@@ -450,11 +525,14 @@ UserService = _ts_decorate([
450
525
  _ts_param(1, (0, _common.Inject)(_modules.UtilsService)),
451
526
  _ts_param(2, (0, _common.Inject)(_authdatasourceprovider.AuthDataSourceProvider)),
452
527
  _ts_param(3, (0, _common.Inject)(_authconfigservice.AuthConfigService)),
528
+ _ts_param(4, (0, _common.Optional)()),
529
+ _ts_param(4, (0, _common.Inject)(_config.USER_ENRICHER)),
453
530
  _ts_metadata("design:type", Function),
454
531
  _ts_metadata("design:paramtypes", [
455
532
  typeof _classes.HybridCache === "undefined" ? Object : _classes.HybridCache,
456
533
  typeof _modules.UtilsService === "undefined" ? Object : _modules.UtilsService,
457
534
  typeof _authdatasourceprovider.AuthDataSourceProvider === "undefined" ? Object : _authdatasourceprovider.AuthDataSourceProvider,
458
- typeof _authconfigservice.AuthConfigService === "undefined" ? Object : _authconfigservice.AuthConfigService
535
+ typeof _authconfigservice.AuthConfigService === "undefined" ? Object : _authconfigservice.AuthConfigService,
536
+ typeof _userenricherinterface.IUserEnricher === "undefined" ? Object : _userenricherinterface.IUserEnricher
459
537
  ])
460
538
  ], UserService);