@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.
- package/README.md +1 -11
- package/cjs/config/auth.constants.js +14 -4
- package/cjs/controllers/authentication.controller.js +173 -36
- package/cjs/controllers/branch.controller.js +3 -4
- package/cjs/controllers/company.controller.js +3 -4
- package/cjs/controllers/user-permission.controller.js +6 -18
- package/cjs/controllers/user.controller.js +168 -22
- package/cjs/dtos/authentication.dto.js +106 -3
- package/cjs/entities/index.js +5 -1
- package/cjs/entities/pending-registration.entity.js +125 -0
- package/cjs/entities/user-token.entity.js +129 -0
- package/cjs/interfaces/auth-email-provider.interface.js +45 -0
- package/cjs/interfaces/authentication.interface.js +0 -3
- package/cjs/interfaces/index.js +2 -0
- package/cjs/interfaces/user-enricher.interface.js +4 -0
- package/cjs/modules/auth.module.js +8 -2
- package/cjs/services/auth-config.service.js +22 -44
- package/cjs/services/auth-datasource.provider.js +17 -33
- package/cjs/services/auth-email.service.js +452 -0
- package/cjs/services/authentication.service.js +229 -115
- package/cjs/services/branch.service.js +10 -44
- package/cjs/services/company.service.js +7 -36
- package/cjs/services/index.js +1 -0
- package/cjs/services/user-permission.service.js +9 -27
- package/cjs/services/user.service.js +177 -99
- package/cjs/strategies/jwt.strategy.js +14 -1
- package/config/auth.constants.d.ts +3 -0
- package/controllers/authentication.controller.d.ts +13 -9
- package/controllers/user.controller.d.ts +27 -0
- package/dtos/authentication.dto.d.ts +21 -3
- package/dtos/user.dto.d.ts +1 -0
- package/entities/index.d.ts +7 -3
- package/entities/pending-registration.entity.d.ts +17 -0
- package/entities/user-token.entity.d.ts +15 -0
- package/fesm/config/auth.constants.js +18 -8
- package/fesm/controllers/authentication.controller.js +174 -37
- package/fesm/controllers/branch.controller.js +5 -6
- package/fesm/controllers/company.controller.js +5 -6
- package/fesm/controllers/user-permission.controller.js +6 -18
- package/fesm/controllers/user.controller.js +168 -22
- package/fesm/dtos/authentication.dto.js +109 -100
- package/fesm/entities/index.js +7 -1
- package/fesm/entities/pending-registration.entity.js +115 -0
- package/fesm/entities/user-token.entity.js +113 -0
- package/fesm/interfaces/auth-email-provider.interface.js +38 -0
- package/fesm/interfaces/authentication.interface.js +1 -7
- package/fesm/interfaces/index.js +2 -0
- package/fesm/interfaces/user-enricher.interface.js +26 -0
- package/fesm/modules/auth.module.js +9 -3
- package/fesm/services/auth-config.service.js +22 -44
- package/fesm/services/auth-datasource.provider.js +17 -33
- package/fesm/services/auth-email.service.js +401 -0
- package/fesm/services/authentication.service.js +227 -113
- package/fesm/services/branch.service.js +11 -45
- package/fesm/services/company.service.js +8 -37
- package/fesm/services/index.js +1 -0
- package/fesm/services/user-permission.service.js +9 -27
- package/fesm/services/user.service.js +178 -100
- package/fesm/strategies/jwt.strategy.js +14 -1
- package/interfaces/auth-email-provider.interface.d.ts +6 -0
- package/interfaces/auth-module-options.interface.d.ts +4 -1
- package/interfaces/authentication.interface.d.ts +14 -0
- package/interfaces/index.d.ts +2 -0
- package/interfaces/user-enricher.interface.d.ts +67 -0
- package/interfaces/user.interface.d.ts +1 -0
- package/package.json +3 -3
- package/services/auth-config.service.d.ts +1 -0
- package/services/auth-email.service.d.ts +50 -0
- package/services/authentication.service.d.ts +21 -5
- package/services/branch.service.d.ts +2 -5
- package/services/company.service.d.ts +1 -4
- package/services/index.d.ts +1 -0
- package/services/user.service.d.ts +17 -6
|
@@ -27,46 +27,20 @@ function _ts_param(paramIndex, decorator) {
|
|
|
27
27
|
}
|
|
28
28
|
import { RequestScopedApiService, HybridCache } from '@flusys/nestjs-shared/classes';
|
|
29
29
|
import { UtilsService } from '@flusys/nestjs-shared/modules';
|
|
30
|
-
import { Inject, Injectable,
|
|
30
|
+
import { Inject, Injectable, Scope } from '@nestjs/common';
|
|
31
31
|
import { CompanyBranch } from '../entities/company-branch.entity';
|
|
32
32
|
import { AuthConfigService } from './auth-config.service';
|
|
33
33
|
import { AuthDataSourceProvider } from './auth-datasource.provider';
|
|
34
34
|
export class BranchService extends RequestScopedApiService {
|
|
35
|
-
|
|
36
|
-
* Resolve entity class for this service
|
|
37
|
-
* @returns CompanyBranch entity class
|
|
38
|
-
*/ resolveEntity() {
|
|
35
|
+
resolveEntity() {
|
|
39
36
|
return CompanyBranch;
|
|
40
37
|
}
|
|
41
|
-
|
|
42
|
-
* Get DataSource provider for this service
|
|
43
|
-
* @returns AuthDataSourceProvider instance
|
|
44
|
-
*/ getDataSourceProvider() {
|
|
38
|
+
getDataSourceProvider() {
|
|
45
39
|
return this.dataSourceProvider;
|
|
46
40
|
}
|
|
47
|
-
//
|
|
48
|
-
async convertSingleDtoToEntity(dto, _user) {
|
|
49
|
-
// Repository initialization is handled automatically by RequestScopedApiService
|
|
50
|
-
let branch = new CompanyBranch();
|
|
51
|
-
if ('id' in dto && dto.id && typeof dto.id === 'string') {
|
|
52
|
-
const dbData = await this.repository.findOne({
|
|
53
|
-
where: {
|
|
54
|
-
id: dto.id
|
|
55
|
-
}
|
|
56
|
-
});
|
|
57
|
-
if (!dbData) {
|
|
58
|
-
throw new NotFoundException('Branch not found');
|
|
59
|
-
}
|
|
60
|
-
branch = dbData;
|
|
61
|
-
}
|
|
62
|
-
return {
|
|
63
|
-
...branch,
|
|
64
|
-
...dto
|
|
65
|
-
};
|
|
66
|
-
}
|
|
41
|
+
// Query Customization
|
|
67
42
|
async getSelectQuery(query, _user, select) {
|
|
68
|
-
|
|
69
|
-
if (!select || !select.length) {
|
|
43
|
+
if (!select?.length) {
|
|
70
44
|
select = [
|
|
71
45
|
'id',
|
|
72
46
|
'name',
|
|
@@ -75,14 +49,13 @@ export class BranchService extends RequestScopedApiService {
|
|
|
75
49
|
'createdAt'
|
|
76
50
|
];
|
|
77
51
|
}
|
|
78
|
-
|
|
79
|
-
query.select(selectFields);
|
|
52
|
+
query.select(select.map((f)=>`${this.entityName}.${f}`));
|
|
80
53
|
return {
|
|
81
54
|
query,
|
|
82
55
|
isRaw: false
|
|
83
56
|
};
|
|
84
57
|
}
|
|
85
|
-
async getGlobalSearchQuery(query, search
|
|
58
|
+
async getGlobalSearchQuery(query, search) {
|
|
86
59
|
query.andWhere('(company_branch.name LIKE :search OR company_branch.slug LIKE :search OR company_branch.email LIKE :search)', {
|
|
87
60
|
search: `%${search}%`
|
|
88
61
|
});
|
|
@@ -91,24 +64,17 @@ export class BranchService extends RequestScopedApiService {
|
|
|
91
64
|
isRaw: false
|
|
92
65
|
};
|
|
93
66
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
*/ async getExtraManipulateQuery(query, _filterAndPaginationDto, user) {
|
|
98
|
-
const result = await super.getExtraManipulateQuery(query, _filterAndPaginationDto, user);
|
|
99
|
-
// Only apply company filtering if company feature is enabled and user has company context
|
|
100
|
-
const isCompanyFeatureEnabled = this.authConfig.isCompanyFeatureEnabled();
|
|
101
|
-
if (isCompanyFeatureEnabled && user?.companyId) {
|
|
67
|
+
async getExtraManipulateQuery(query, filterDto, user) {
|
|
68
|
+
const result = await super.getExtraManipulateQuery(query, filterDto, user);
|
|
69
|
+
if (this.authConfig.isCompanyFeatureEnabled() && user?.companyId) {
|
|
102
70
|
query.andWhere('company_branch.company_id = :companyId', {
|
|
103
71
|
companyId: user.companyId
|
|
104
72
|
});
|
|
105
73
|
}
|
|
106
74
|
return result;
|
|
107
75
|
}
|
|
108
|
-
// NOTE: @Inject() required for bundled code - type metadata may be lost during esbuild
|
|
109
76
|
constructor(cacheManager, utilsService, dataSourceProvider, authConfig){
|
|
110
|
-
|
|
111
|
-
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 Logger(BranchService.name);
|
|
77
|
+
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;
|
|
112
78
|
}
|
|
113
79
|
}
|
|
114
80
|
BranchService = _ts_decorate([
|
|
@@ -27,45 +27,19 @@ function _ts_param(paramIndex, decorator) {
|
|
|
27
27
|
}
|
|
28
28
|
import { RequestScopedApiService, HybridCache } from '@flusys/nestjs-shared/classes';
|
|
29
29
|
import { UtilsService } from '@flusys/nestjs-shared/modules';
|
|
30
|
-
import { Inject, Injectable,
|
|
30
|
+
import { Inject, Injectable, Scope } from '@nestjs/common';
|
|
31
31
|
import { Company } from '../entities/company.entity';
|
|
32
32
|
import { AuthDataSourceProvider } from './auth-datasource.provider';
|
|
33
33
|
export class CompanyService extends RequestScopedApiService {
|
|
34
|
-
|
|
35
|
-
* Resolve entity class for this service
|
|
36
|
-
* @returns Company entity class
|
|
37
|
-
*/ resolveEntity() {
|
|
34
|
+
resolveEntity() {
|
|
38
35
|
return Company;
|
|
39
36
|
}
|
|
40
|
-
|
|
41
|
-
* Get DataSource provider for this service
|
|
42
|
-
* @returns AuthDataSourceProvider instance
|
|
43
|
-
*/ getDataSourceProvider() {
|
|
37
|
+
getDataSourceProvider() {
|
|
44
38
|
return this.dataSourceProvider;
|
|
45
39
|
}
|
|
46
|
-
//
|
|
47
|
-
async convertSingleDtoToEntity(dto, _user) {
|
|
48
|
-
// Repository initialization is handled automatically by RequestScopedApiService
|
|
49
|
-
let company = new Company();
|
|
50
|
-
if ('id' in dto && dto.id && typeof dto.id === 'string') {
|
|
51
|
-
const dbData = await this.repository.findOne({
|
|
52
|
-
where: {
|
|
53
|
-
id: dto.id
|
|
54
|
-
}
|
|
55
|
-
});
|
|
56
|
-
if (!dbData) {
|
|
57
|
-
throw new NotFoundException('Company not found');
|
|
58
|
-
}
|
|
59
|
-
company = dbData;
|
|
60
|
-
}
|
|
61
|
-
return {
|
|
62
|
-
...company,
|
|
63
|
-
...dto
|
|
64
|
-
};
|
|
65
|
-
}
|
|
40
|
+
// Query Customization
|
|
66
41
|
async getSelectQuery(query, _user, select) {
|
|
67
|
-
|
|
68
|
-
if (!select || !select.length) {
|
|
42
|
+
if (!select?.length) {
|
|
69
43
|
select = [
|
|
70
44
|
'id',
|
|
71
45
|
'name',
|
|
@@ -74,14 +48,13 @@ export class CompanyService extends RequestScopedApiService {
|
|
|
74
48
|
'createdAt'
|
|
75
49
|
];
|
|
76
50
|
}
|
|
77
|
-
|
|
78
|
-
query.select(selectFields);
|
|
51
|
+
query.select(select.map((f)=>`${this.entityName}.${f}`));
|
|
79
52
|
return {
|
|
80
53
|
query,
|
|
81
54
|
isRaw: false
|
|
82
55
|
};
|
|
83
56
|
}
|
|
84
|
-
async getGlobalSearchQuery(query, search
|
|
57
|
+
async getGlobalSearchQuery(query, search) {
|
|
85
58
|
query.andWhere('(company.name LIKE :search OR company.slug LIKE :search OR company.email LIKE :search)', {
|
|
86
59
|
search: `%${search}%`
|
|
87
60
|
});
|
|
@@ -90,10 +63,8 @@ export class CompanyService extends RequestScopedApiService {
|
|
|
90
63
|
isRaw: false
|
|
91
64
|
};
|
|
92
65
|
}
|
|
93
|
-
// NOTE: @Inject() required for bundled code - type metadata may be lost during esbuild
|
|
94
66
|
constructor(cacheManager, utilsService, dataSourceProvider){
|
|
95
|
-
|
|
96
|
-
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 Logger(CompanyService.name);
|
|
67
|
+
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;
|
|
97
68
|
}
|
|
98
69
|
}
|
|
99
70
|
CompanyService = _ts_decorate([
|
package/fesm/services/index.js
CHANGED
|
@@ -29,20 +29,14 @@ import { BadRequestException, Inject, Injectable, Logger, NotFoundException, Sco
|
|
|
29
29
|
import { PermissionType, UserCompanyPermission } from '../entities/user-company-permission.entity';
|
|
30
30
|
import { AuthDataSourceProvider } from './auth-datasource.provider';
|
|
31
31
|
export class UserPermissionService {
|
|
32
|
-
/**
|
|
33
|
-
* Get repository lazily - initialized on first access
|
|
34
|
-
*/ async getRepository() {
|
|
32
|
+
/** Get repository lazily - initialized on first access */ async getRepository() {
|
|
35
33
|
if (!this._repository) {
|
|
36
34
|
this._repository = await this.dataSourceProvider.getRepository(UserCompanyPermission);
|
|
37
35
|
}
|
|
38
36
|
return this._repository;
|
|
39
37
|
}
|
|
40
|
-
//
|
|
41
|
-
|
|
42
|
-
// ==========================================================================
|
|
43
|
-
/**
|
|
44
|
-
* Find all companies a user has access to
|
|
45
|
-
*/ async findUserCompanies(userId) {
|
|
38
|
+
// Company Permissions
|
|
39
|
+
/** Find all companies a user has access to */ async findUserCompanies(userId) {
|
|
46
40
|
const repository = await this.getRepository();
|
|
47
41
|
return repository.find({
|
|
48
42
|
where: {
|
|
@@ -57,9 +51,7 @@ export class UserPermissionService {
|
|
|
57
51
|
}
|
|
58
52
|
});
|
|
59
53
|
}
|
|
60
|
-
/**
|
|
61
|
-
* Assign user to a company
|
|
62
|
-
*/ async assignUserToCompany(dto) {
|
|
54
|
+
/** Assign user to a company */ async assignUserToCompany(dto) {
|
|
63
55
|
const repository = await this.getRepository();
|
|
64
56
|
// Check if already exists
|
|
65
57
|
const existing = await repository.findOne({
|
|
@@ -80,9 +72,7 @@ export class UserPermissionService {
|
|
|
80
72
|
});
|
|
81
73
|
await repository.save(permission);
|
|
82
74
|
}
|
|
83
|
-
/**
|
|
84
|
-
* Revoke user access to a company
|
|
85
|
-
*/ async revokeCompanyAccess(userId, companyId) {
|
|
75
|
+
/** Revoke user access to a company */ async revokeCompanyAccess(userId, companyId) {
|
|
86
76
|
const repository = await this.getRepository();
|
|
87
77
|
const permission = await repository.findOne({
|
|
88
78
|
where: {
|
|
@@ -96,12 +86,8 @@ export class UserPermissionService {
|
|
|
96
86
|
}
|
|
97
87
|
await repository.remove(permission);
|
|
98
88
|
}
|
|
99
|
-
//
|
|
100
|
-
|
|
101
|
-
// ==========================================================================
|
|
102
|
-
/**
|
|
103
|
-
* Find all branches a user has access to
|
|
104
|
-
*/ async findUserBranches(userId) {
|
|
89
|
+
// Branch Permissions
|
|
90
|
+
/** Find all branches a user has access to */ async findUserBranches(userId) {
|
|
105
91
|
const repository = await this.getRepository();
|
|
106
92
|
return repository.find({
|
|
107
93
|
where: {
|
|
@@ -116,9 +102,7 @@ export class UserPermissionService {
|
|
|
116
102
|
}
|
|
117
103
|
});
|
|
118
104
|
}
|
|
119
|
-
/**
|
|
120
|
-
* Assign user to a branch
|
|
121
|
-
*/ async assignUserToBranch(dto) {
|
|
105
|
+
/** Assign user to a branch */ async assignUserToBranch(dto) {
|
|
122
106
|
const repository = await this.getRepository();
|
|
123
107
|
// Check if already exists
|
|
124
108
|
const existing = await repository.findOne({
|
|
@@ -139,9 +123,7 @@ export class UserPermissionService {
|
|
|
139
123
|
});
|
|
140
124
|
await repository.save(permission);
|
|
141
125
|
}
|
|
142
|
-
/**
|
|
143
|
-
* Revoke user access to a branch
|
|
144
|
-
*/ async revokeBranchAccess(userId, branchId) {
|
|
126
|
+
/** Revoke user access to a branch */ async revokeBranchAccess(userId, branchId) {
|
|
145
127
|
const repository = await this.getRepository();
|
|
146
128
|
const permission = await repository.findOne({
|
|
147
129
|
where: {
|
|
@@ -28,30 +28,23 @@ function _ts_param(paramIndex, decorator) {
|
|
|
28
28
|
import { RequestScopedApiService, HybridCache } from '@flusys/nestjs-shared/classes';
|
|
29
29
|
import { UtilsService } from '@flusys/nestjs-shared/modules';
|
|
30
30
|
import { ErrorHandler } from '@flusys/nestjs-shared/utils';
|
|
31
|
-
import { BadRequestException, Inject, Injectable,
|
|
31
|
+
import { BadRequestException, Inject, Injectable, NotFoundException, Optional, Scope } from '@nestjs/common';
|
|
32
32
|
import * as bcrypt from 'bcrypt';
|
|
33
33
|
import { AuthConfigService } from './auth-config.service';
|
|
34
34
|
import { AuthDataSourceProvider } from './auth-datasource.provider';
|
|
35
|
+
import { BCRYPT_SALT_ROUNDS, USER_ENRICHER } from '../config';
|
|
35
36
|
import { PermissionType } from '../enums';
|
|
37
|
+
import { IUserEnricher } from '../interfaces/user-enricher.interface';
|
|
36
38
|
import { UserCompanyPermission } from '../entities/user-company-permission.entity';
|
|
37
39
|
import { User } from '../entities/user.entity';
|
|
38
40
|
export class UserService extends RequestScopedApiService {
|
|
39
|
-
|
|
40
|
-
* Resolve entity class for this service
|
|
41
|
-
* @returns User entity class
|
|
42
|
-
*/ resolveEntity() {
|
|
41
|
+
resolveEntity() {
|
|
43
42
|
return User;
|
|
44
43
|
}
|
|
45
|
-
|
|
46
|
-
* Get DataSource provider for this service
|
|
47
|
-
* @returns AuthDataSourceProvider instance
|
|
48
|
-
*/ getDataSourceProvider() {
|
|
44
|
+
getDataSourceProvider() {
|
|
49
45
|
return this.dataSourceProvider;
|
|
50
46
|
}
|
|
51
|
-
|
|
52
|
-
* Override ensureRepositoryInitialized to also initialize permission repository and dataSource
|
|
53
|
-
* CRITICAL: Also initializes permissionRepository and dataSource for company feature support
|
|
54
|
-
*/ async ensureRepositoryInitialized() {
|
|
47
|
+
async ensureRepositoryInitialized() {
|
|
55
48
|
await super.ensureRepositoryInitialized();
|
|
56
49
|
// Initialize permission repository if company feature is enabled
|
|
57
50
|
const isCompanyFeatureEnabled = this.authConfig.isCompanyFeatureEnabled();
|
|
@@ -65,26 +58,8 @@ export class UserService extends RequestScopedApiService {
|
|
|
65
58
|
this.dataSource = await this.getDataSourceForService();
|
|
66
59
|
}
|
|
67
60
|
}
|
|
68
|
-
//
|
|
69
|
-
|
|
70
|
-
// ==========================================================================
|
|
71
|
-
/**
|
|
72
|
-
* Update last login timestamp
|
|
73
|
-
*/ async updateLastLogin(id) {
|
|
74
|
-
// Ensure repository is initialized for custom method
|
|
75
|
-
await this.ensureRepositoryInitialized();
|
|
76
|
-
await this.repository.update(id, {
|
|
77
|
-
lastLoginAt: new Date()
|
|
78
|
-
});
|
|
79
|
-
await this.clearCacheForId([
|
|
80
|
-
{
|
|
81
|
-
id
|
|
82
|
-
}
|
|
83
|
-
]);
|
|
84
|
-
}
|
|
85
|
-
/**
|
|
86
|
-
* Verify email
|
|
87
|
-
*/ async verifyEmail(id) {
|
|
61
|
+
// Custom Methods
|
|
62
|
+
/** Verify email */ async verifyEmail(id) {
|
|
88
63
|
// Ensure repository is initialized for custom method
|
|
89
64
|
await this.ensureRepositoryInitialized();
|
|
90
65
|
await this.repository.update(id, {
|
|
@@ -97,9 +72,7 @@ export class UserService extends RequestScopedApiService {
|
|
|
97
72
|
}
|
|
98
73
|
]);
|
|
99
74
|
}
|
|
100
|
-
/**
|
|
101
|
-
* Verify phone
|
|
102
|
-
*/ async verifyPhone(id) {
|
|
75
|
+
/** Verify phone */ async verifyPhone(id) {
|
|
103
76
|
// Ensure repository is initialized for custom method
|
|
104
77
|
await this.ensureRepositoryInitialized();
|
|
105
78
|
await this.repository.update(id, {
|
|
@@ -112,12 +85,7 @@ export class UserService extends RequestScopedApiService {
|
|
|
112
85
|
}
|
|
113
86
|
]);
|
|
114
87
|
}
|
|
115
|
-
/**
|
|
116
|
-
* Update user status
|
|
117
|
-
*
|
|
118
|
-
* When company feature is disabled: Updates User.isActive directly
|
|
119
|
-
* When company feature is enabled: Updates UserCompanyPermission.isActive for the logged user's company
|
|
120
|
-
*/ async updateStatus(id, isActive, user) {
|
|
88
|
+
/** Update user status. When company feature enabled, updates permission isActive for logged user's company. */ async updateStatus(id, isActive, user) {
|
|
121
89
|
// Ensure repository is initialized for custom method
|
|
122
90
|
await this.ensureRepositoryInitialized();
|
|
123
91
|
const isCompanyFeatureEnabled = this.authConfig.isCompanyFeatureEnabled();
|
|
@@ -148,50 +116,43 @@ export class UserService extends RequestScopedApiService {
|
|
|
148
116
|
}
|
|
149
117
|
]);
|
|
150
118
|
}
|
|
151
|
-
//
|
|
152
|
-
|
|
153
|
-
// ==========================================================================
|
|
154
|
-
/**
|
|
155
|
-
* Override: Convert DTO to entity (hash password before saving)
|
|
156
|
-
*/ async convertSingleDtoToEntity(dto, user) {
|
|
119
|
+
// ApiService Overrides
|
|
120
|
+
/** Convert DTO to entity (hash password before saving) */ async convertSingleDtoToEntity(dto, user) {
|
|
157
121
|
// Repository initialization is handled automatically by RequestScopedApiService
|
|
158
122
|
const entity = await super.convertSingleDtoToEntity(dto, user);
|
|
159
|
-
// Hash password if provided
|
|
160
|
-
if (dto.password) {
|
|
161
|
-
entity.password = await bcrypt.hash(dto.password,
|
|
123
|
+
// Hash password if provided (CreateUserDto has password field)
|
|
124
|
+
if ('password' in dto && dto.password) {
|
|
125
|
+
entity.password = await bcrypt.hash(dto.password, BCRYPT_SALT_ROUNDS);
|
|
162
126
|
}
|
|
163
127
|
return entity;
|
|
164
128
|
}
|
|
165
|
-
/**
|
|
166
|
-
* Override: Convert entity to response DTO (remove password!)
|
|
167
|
-
*
|
|
168
|
-
* When company feature is enabled and raw mode is used, isActive comes from
|
|
169
|
-
* the permission table (user_companyIsActive). Otherwise, it comes from User entity.
|
|
170
|
-
*/ convertEntityToResponseDto(entity, isRaw) {
|
|
129
|
+
/** Convert entity to response DTO (removes password). In raw mode with company feature, isActive comes from permission table. */ convertEntityToResponseDto(entity, isRaw) {
|
|
171
130
|
// When raw mode is used with company feature, isActive comes from computed field
|
|
172
|
-
|
|
131
|
+
// Raw mode uses snake_case column names (due to SnakeNamingStrategy)
|
|
132
|
+
const isActive = isRaw && entity.user_companyIsActive !== undefined ? Boolean(entity.user_companyIsActive) : isRaw ? entity.user_is_active : entity.isActive;
|
|
173
133
|
return {
|
|
174
134
|
id: isRaw ? entity.user_id : entity.id,
|
|
175
135
|
name: isRaw ? entity.user_name : entity.name,
|
|
176
136
|
phone: isRaw ? entity.user_phone : entity.phone,
|
|
177
137
|
email: isRaw ? entity.user_email : entity.email,
|
|
178
|
-
profilePictureId: isRaw ? entity.
|
|
138
|
+
profilePictureId: isRaw ? entity.user_profile_picture_id : entity.profilePictureId,
|
|
179
139
|
isActive,
|
|
180
|
-
emailVerified: isRaw ? entity.
|
|
181
|
-
phoneVerified: isRaw ? entity.
|
|
182
|
-
lastLoginAt: isRaw ? entity.
|
|
183
|
-
additionalFields: isRaw ? entity.
|
|
184
|
-
createdAt: isRaw ? entity.
|
|
185
|
-
updatedAt: isRaw ? entity.
|
|
186
|
-
deletedAt: isRaw ? entity.
|
|
187
|
-
createdById: isRaw ? entity.
|
|
188
|
-
updatedById: isRaw ? entity.
|
|
189
|
-
deletedById: isRaw ? entity.
|
|
140
|
+
emailVerified: isRaw ? entity.user_email_verified : entity.emailVerified,
|
|
141
|
+
phoneVerified: isRaw ? entity.user_phone_verified : entity.phoneVerified,
|
|
142
|
+
lastLoginAt: isRaw ? entity.user_last_login_at : entity.lastLoginAt,
|
|
143
|
+
additionalFields: isRaw ? entity.user_additional_fields : entity.additionalFields,
|
|
144
|
+
createdAt: isRaw ? entity.user_created_at : entity.createdAt,
|
|
145
|
+
updatedAt: isRaw ? entity.user_updated_at : entity.updatedAt,
|
|
146
|
+
deletedAt: isRaw ? entity.user_deleted_at : entity.deletedAt,
|
|
147
|
+
createdById: isRaw ? entity.user_created_by_id : entity.createdById,
|
|
148
|
+
updatedById: isRaw ? entity.user_updated_by_id : entity.updatedById,
|
|
149
|
+
deletedById: isRaw ? entity.user_deleted_by_id : entity.deletedById
|
|
190
150
|
};
|
|
191
151
|
}
|
|
192
|
-
/**
|
|
193
|
-
|
|
194
|
-
|
|
152
|
+
/** Convert entity list to response DTO list */ convertEntityListToResponseListDto(entities, isRaw) {
|
|
153
|
+
return entities.map((entity)=>this.convertEntityToResponseDto(entity, isRaw));
|
|
154
|
+
}
|
|
155
|
+
/** Global search - search by name, email, or phone */ async getGlobalSearchQuery(query, search, _user) {
|
|
195
156
|
query.andWhere('(user.name LIKE :search OR user.email LIKE :search OR user.phone LIKE :search)', {
|
|
196
157
|
search: `%${search}%`
|
|
197
158
|
});
|
|
@@ -200,18 +161,13 @@ export class UserService extends RequestScopedApiService {
|
|
|
200
161
|
isRaw: false
|
|
201
162
|
};
|
|
202
163
|
}
|
|
203
|
-
/**
|
|
204
|
-
* Override: getExtraManipulateQuery - Add company permission-based isActive check when company feature enabled
|
|
205
|
-
*
|
|
206
|
-
* Note: When company feature is disabled at bootstrap, this method returns without modifications.
|
|
207
|
-
* All company-related filtering is skipped, and users are returned normally with isActive from User entity.
|
|
208
|
-
*
|
|
209
|
-
* When company feature is enabled:
|
|
210
|
-
* - isActive is computed from UserCompanyPermission table
|
|
211
|
-
* - Returns isRaw: true to use raw query results
|
|
212
|
-
*/ async getExtraManipulateQuery(query, filterAndPaginationDto, user) {
|
|
164
|
+
/** Add company permission-based isActive check when company feature enabled. Returns isRaw: true for computed fields. */ async getExtraManipulateQuery(query, filterAndPaginationDto, user) {
|
|
213
165
|
// Repository initialization is handled automatically by RequestScopedApiService
|
|
214
|
-
|
|
166
|
+
let result = await super.getExtraManipulateQuery(query, filterAndPaginationDto, user);
|
|
167
|
+
// Apply user enricher query modifications if available
|
|
168
|
+
if (this.userEnricher?.enrichListQuery) {
|
|
169
|
+
await this.userEnricher.enrichListQuery(query, user);
|
|
170
|
+
}
|
|
215
171
|
// Only apply company filtering if:
|
|
216
172
|
// 1. Company feature is enabled at bootstrap
|
|
217
173
|
// 2. User has company context
|
|
@@ -264,12 +220,7 @@ export class UserService extends RequestScopedApiService {
|
|
|
264
220
|
}
|
|
265
221
|
return result;
|
|
266
222
|
}
|
|
267
|
-
/**
|
|
268
|
-
* Override: insert - Create company permission when company feature is enabled
|
|
269
|
-
*
|
|
270
|
-
* Note: When company feature is disabled at bootstrap, this method falls back to parent implementation.
|
|
271
|
-
* No company permissions are created, and user is inserted normally.
|
|
272
|
-
*/ async insert(dto, user) {
|
|
223
|
+
/** Insert user. Creates company permission when company feature is enabled. */ async insert(dto, user) {
|
|
273
224
|
// Repository initialization is handled automatically by RequestScopedApiService
|
|
274
225
|
// Check if company feature is enabled
|
|
275
226
|
const isCompanyFeatureEnabled = this.authConfig.isCompanyFeatureEnabled();
|
|
@@ -337,17 +288,139 @@ export class UserService extends RequestScopedApiService {
|
|
|
337
288
|
}
|
|
338
289
|
}
|
|
339
290
|
/**
|
|
340
|
-
*
|
|
341
|
-
|
|
342
|
-
|
|
291
|
+
* Get user profile with extras from enricher
|
|
292
|
+
* Returns base user data plus any extra data provided by enricher (roles, permissions, etc.)
|
|
293
|
+
*/ async getProfileWithExtras(userId, user) {
|
|
294
|
+
await this.ensureRepositoryInitialized();
|
|
295
|
+
const userEntity = await this.repository.findOne({
|
|
296
|
+
where: {
|
|
297
|
+
id: userId
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
if (!userEntity) {
|
|
301
|
+
throw new NotFoundException('User not found');
|
|
302
|
+
}
|
|
303
|
+
const userData = this.convertEntityToResponseDto(userEntity, false);
|
|
304
|
+
// Get extras from enricher if available
|
|
305
|
+
let extras;
|
|
306
|
+
if (this.userEnricher?.getProfileExtras) {
|
|
307
|
+
extras = await this.userEnricher.getProfileExtras(userId, user);
|
|
308
|
+
}
|
|
309
|
+
return {
|
|
310
|
+
user: userData,
|
|
311
|
+
extras
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Get enriched user list
|
|
316
|
+
* Applies enricher transformations after query execution
|
|
317
|
+
*/ async getEnrichedList(users, user) {
|
|
318
|
+
if (this.userEnricher?.enrichListItems) {
|
|
319
|
+
return this.userEnricher.enrichListItems(users, user);
|
|
320
|
+
}
|
|
321
|
+
return users;
|
|
322
|
+
}
|
|
323
|
+
// ============================================
|
|
324
|
+
// Multi-Step Profile Methods (via Enricher)
|
|
325
|
+
// ============================================
|
|
326
|
+
/**
|
|
327
|
+
* Get profile section definitions from enricher
|
|
328
|
+
*/ async getProfileSections() {
|
|
329
|
+
if (this.userEnricher?.getProfileSections) {
|
|
330
|
+
return this.userEnricher.getProfileSections();
|
|
331
|
+
}
|
|
332
|
+
return [];
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Get specific profile section data
|
|
336
|
+
*/ async getProfileSectionData(userId, sectionId, user) {
|
|
337
|
+
await this.ensureRepositoryInitialized();
|
|
338
|
+
// Verify user exists
|
|
339
|
+
const userEntity = await this.repository.findOne({
|
|
340
|
+
where: {
|
|
341
|
+
id: userId
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
if (!userEntity) {
|
|
345
|
+
throw new NotFoundException('User not found');
|
|
346
|
+
}
|
|
347
|
+
if (this.userEnricher?.getProfileSectionData) {
|
|
348
|
+
return this.userEnricher.getProfileSectionData(userId, sectionId, user);
|
|
349
|
+
}
|
|
350
|
+
return null;
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Update specific profile section
|
|
354
|
+
*/ async updateProfileSection(userId, sectionId, data, user) {
|
|
355
|
+
await this.ensureRepositoryInitialized();
|
|
356
|
+
if (!this.dataSource) {
|
|
357
|
+
throw new BadRequestException('DataSource not available');
|
|
358
|
+
}
|
|
359
|
+
// Verify user exists
|
|
360
|
+
const userEntity = await this.repository.findOne({
|
|
361
|
+
where: {
|
|
362
|
+
id: userId
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
if (!userEntity) {
|
|
366
|
+
throw new NotFoundException('User not found');
|
|
367
|
+
}
|
|
368
|
+
if (!this.userEnricher?.updateProfileSection) {
|
|
369
|
+
throw new BadRequestException('Profile sections not supported');
|
|
370
|
+
}
|
|
371
|
+
const queryRunner = this.dataSource.createQueryRunner();
|
|
372
|
+
await queryRunner.connect();
|
|
373
|
+
await queryRunner.startTransaction();
|
|
374
|
+
try {
|
|
375
|
+
await this.userEnricher.updateProfileSection(userId, sectionId, data, queryRunner);
|
|
376
|
+
await queryRunner.commitTransaction();
|
|
377
|
+
await this.clearCacheForId([
|
|
378
|
+
userEntity
|
|
379
|
+
]);
|
|
380
|
+
} catch (error) {
|
|
381
|
+
await queryRunner.rollbackTransaction();
|
|
382
|
+
ErrorHandler.logError(this.logger, error, 'updateProfileSection', {
|
|
383
|
+
entity: this.entityName,
|
|
384
|
+
sectionId
|
|
385
|
+
});
|
|
386
|
+
ErrorHandler.rethrowError(error);
|
|
387
|
+
} finally{
|
|
388
|
+
await queryRunner.release();
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Get profile completion percentage
|
|
393
|
+
*/ async getProfileCompletion(userId, user) {
|
|
394
|
+
await this.ensureRepositoryInitialized();
|
|
395
|
+
// Verify user exists
|
|
396
|
+
const userEntity = await this.repository.findOne({
|
|
397
|
+
where: {
|
|
398
|
+
id: userId
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
if (!userEntity) {
|
|
402
|
+
throw new NotFoundException('User not found');
|
|
403
|
+
}
|
|
404
|
+
if (this.userEnricher?.calculateProfileCompletion) {
|
|
405
|
+
return this.userEnricher.calculateProfileCompletion(userId, user);
|
|
406
|
+
}
|
|
407
|
+
return 100; // Default to 100% if no enricher
|
|
408
|
+
}
|
|
409
|
+
/** Update user profile with password handling and extras */ async profile(dto, user) {
|
|
410
|
+
// Ensure repository and dataSource are initialized
|
|
411
|
+
await this.ensureRepositoryInitialized();
|
|
343
412
|
if (!this.dataSource) {
|
|
344
413
|
throw new BadRequestException('DataSource not available');
|
|
345
414
|
}
|
|
415
|
+
const userId = dto.id ?? user.id;
|
|
416
|
+
// Validate extras if enricher provides validation
|
|
417
|
+
if (dto.extras && this.userEnricher?.validateProfileExtras) {
|
|
418
|
+
await this.userEnricher.validateProfileExtras(userId, dto.extras, user);
|
|
419
|
+
}
|
|
346
420
|
const queryRunner = this.dataSource.createQueryRunner();
|
|
347
421
|
await queryRunner.connect();
|
|
348
422
|
await queryRunner.startTransaction();
|
|
349
423
|
try {
|
|
350
|
-
const userId = dto.id ?? user.id;
|
|
351
424
|
const findUser = await queryRunner.manager.findOne(this.repository.target, {
|
|
352
425
|
where: {
|
|
353
426
|
id: userId
|
|
@@ -366,10 +439,14 @@ export class UserService extends RequestScopedApiService {
|
|
|
366
439
|
const salt = await bcrypt.genSalt();
|
|
367
440
|
findUser.password = await bcrypt.hash(dto.password, salt);
|
|
368
441
|
}
|
|
369
|
-
// Merge other fields (excluding password)
|
|
370
|
-
const { password, ...restDto } = dto;
|
|
442
|
+
// Merge other fields (excluding password and extras)
|
|
443
|
+
const { password, extras, ...restDto } = dto;
|
|
371
444
|
Object.assign(findUser, restDto);
|
|
372
445
|
await queryRunner.manager.save(this.repository.target, findUser);
|
|
446
|
+
// Handle extras update via enricher
|
|
447
|
+
if (extras && this.userEnricher?.updateProfileExtras) {
|
|
448
|
+
await this.userEnricher.updateProfileExtras(userId, extras, queryRunner);
|
|
449
|
+
}
|
|
373
450
|
await queryRunner.commitTransaction();
|
|
374
451
|
await this.clearCacheForId([
|
|
375
452
|
findUser
|
|
@@ -385,10 +462,8 @@ export class UserService extends RequestScopedApiService {
|
|
|
385
462
|
await queryRunner.release();
|
|
386
463
|
}
|
|
387
464
|
}
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
// Repository will be set asynchronously by RequestScopedApiService
|
|
391
|
-
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 Logger(UserService.name);
|
|
465
|
+
constructor(cacheManager, utilsService, dataSourceProvider, authConfig, userEnricher){
|
|
466
|
+
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;
|
|
392
467
|
}
|
|
393
468
|
}
|
|
394
469
|
UserService = _ts_decorate([
|
|
@@ -399,11 +474,14 @@ UserService = _ts_decorate([
|
|
|
399
474
|
_ts_param(1, Inject(UtilsService)),
|
|
400
475
|
_ts_param(2, Inject(AuthDataSourceProvider)),
|
|
401
476
|
_ts_param(3, Inject(AuthConfigService)),
|
|
477
|
+
_ts_param(4, Optional()),
|
|
478
|
+
_ts_param(4, Inject(USER_ENRICHER)),
|
|
402
479
|
_ts_metadata("design:type", Function),
|
|
403
480
|
_ts_metadata("design:paramtypes", [
|
|
404
481
|
typeof HybridCache === "undefined" ? Object : HybridCache,
|
|
405
482
|
typeof UtilsService === "undefined" ? Object : UtilsService,
|
|
406
483
|
typeof AuthDataSourceProvider === "undefined" ? Object : AuthDataSourceProvider,
|
|
407
|
-
typeof AuthConfigService === "undefined" ? Object : AuthConfigService
|
|
484
|
+
typeof AuthConfigService === "undefined" ? Object : AuthConfigService,
|
|
485
|
+
typeof IUserEnricher === "undefined" ? Object : IUserEnricher
|
|
408
486
|
])
|
|
409
487
|
], UserService);
|