@flusys/nestjs-auth 6.0.1 → 6.1.0

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 (83) hide show
  1. package/cjs/controllers/authentication.controller.spec.js +219 -0
  2. package/cjs/controllers/branch.controller.spec.js +134 -0
  3. package/cjs/controllers/company-selection.controller.spec.js +84 -0
  4. package/cjs/controllers/company.controller.spec.js +131 -0
  5. package/cjs/controllers/email-verification.controller.spec.js +142 -0
  6. package/cjs/controllers/user-permission.controller.spec.js +138 -0
  7. package/cjs/controllers/user.controller.spec.js +204 -0
  8. package/cjs/docs/auth-swagger.config.spec.js +130 -0
  9. package/cjs/entities/company-branch.entity.js +2 -3
  10. package/cjs/entities/index.spec.js +73 -0
  11. package/cjs/helpers/auth-response.mapper.js +46 -0
  12. package/cjs/helpers/auth-response.mapper.spec.js +139 -0
  13. package/cjs/helpers/index.js +19 -0
  14. package/cjs/helpers/token.helper.js +58 -0
  15. package/cjs/helpers/token.helper.spec.js +86 -0
  16. package/cjs/index.js +1 -0
  17. package/cjs/interceptors/clear-token.interceptor.spec.js +107 -0
  18. package/cjs/interceptors/set-token.interceptor.spec.js +165 -0
  19. package/cjs/modules/auth.module.spec.js +243 -0
  20. package/cjs/services/auth-config.service.spec.js +97 -0
  21. package/cjs/services/auth-datasource.provider.spec.js +161 -0
  22. package/cjs/services/auth-email.service.spec.js +404 -0
  23. package/cjs/services/authentication.service.js +37 -109
  24. package/cjs/services/authentication.service.spec.js +904 -0
  25. package/cjs/services/branch.service.js +30 -3
  26. package/cjs/services/branch.service.spec.js +154 -0
  27. package/cjs/services/company-selection-session.service.spec.js +90 -0
  28. package/cjs/services/company.service.js +34 -3
  29. package/cjs/services/company.service.spec.js +113 -0
  30. package/cjs/services/user-permission.service.js +35 -4
  31. package/cjs/services/user-permission.service.spec.js +202 -0
  32. package/cjs/services/user.service.spec.js +704 -0
  33. package/cjs/strategies/jwt.strategy.js +1 -1
  34. package/cjs/strategies/jwt.strategy.spec.js +166 -0
  35. package/cjs/validators/password.validator.spec.js +161 -0
  36. package/entities/company-branch.entity.d.ts +1 -1
  37. package/entities/pending-registration.entity.d.ts +2 -7
  38. package/fesm/controllers/authentication.controller.spec.js +215 -0
  39. package/fesm/controllers/branch.controller.spec.js +130 -0
  40. package/fesm/controllers/company-selection.controller.spec.js +80 -0
  41. package/fesm/controllers/company.controller.spec.js +127 -0
  42. package/fesm/controllers/email-verification.controller.spec.js +138 -0
  43. package/fesm/controllers/user-permission.controller.spec.js +134 -0
  44. package/fesm/controllers/user.controller.spec.js +200 -0
  45. package/fesm/docs/auth-swagger.config.spec.js +126 -0
  46. package/fesm/entities/company-branch.entity.js +2 -3
  47. package/fesm/entities/index.spec.js +69 -0
  48. package/fesm/helpers/auth-response.mapper.js +25 -0
  49. package/fesm/helpers/auth-response.mapper.spec.js +135 -0
  50. package/fesm/helpers/index.js +2 -0
  51. package/fesm/helpers/token.helper.js +40 -0
  52. package/fesm/helpers/token.helper.spec.js +82 -0
  53. package/fesm/index.js +1 -0
  54. package/fesm/interceptors/clear-token.interceptor.spec.js +103 -0
  55. package/fesm/interceptors/set-token.interceptor.spec.js +161 -0
  56. package/fesm/interfaces/authentication.interface.js +1 -1
  57. package/fesm/modules/auth.module.spec.js +239 -0
  58. package/fesm/services/auth-config.service.spec.js +93 -0
  59. package/fesm/services/auth-datasource.provider.spec.js +157 -0
  60. package/fesm/services/auth-email.service.spec.js +359 -0
  61. package/fesm/services/authentication.service.js +37 -109
  62. package/fesm/services/authentication.service.spec.js +859 -0
  63. package/fesm/services/branch.service.js +32 -5
  64. package/fesm/services/branch.service.spec.js +150 -0
  65. package/fesm/services/company-selection-session.service.spec.js +86 -0
  66. package/fesm/services/company.service.js +36 -5
  67. package/fesm/services/company.service.spec.js +109 -0
  68. package/fesm/services/user-permission.service.js +36 -5
  69. package/fesm/services/user-permission.service.spec.js +198 -0
  70. package/fesm/services/user.service.spec.js +659 -0
  71. package/fesm/strategies/jwt.strategy.js +1 -1
  72. package/fesm/strategies/jwt.strategy.spec.js +162 -0
  73. package/fesm/validators/password.validator.spec.js +157 -0
  74. package/helpers/auth-response.mapper.d.ts +22 -0
  75. package/helpers/index.d.ts +2 -0
  76. package/helpers/token.helper.d.ts +8 -0
  77. package/index.d.ts +1 -0
  78. package/interfaces/authentication.interface.d.ts +7 -0
  79. package/package.json +8 -3
  80. package/services/authentication.service.d.ts +1 -8
  81. package/services/branch.service.d.ts +7 -5
  82. package/services/company.service.d.ts +7 -4
  83. package/services/user-permission.service.d.ts +4 -1
@@ -9,10 +9,13 @@ Object.defineProperty(exports, "BranchService", {
9
9
  }
10
10
  });
11
11
  const _classes = require("@flusys/nestjs-shared/classes");
12
+ const _interfaces = require("@flusys/nestjs-shared/interfaces");
12
13
  const _modules = require("@flusys/nestjs-shared/modules");
13
14
  const _utils = require("@flusys/nestjs-shared/utils");
14
15
  const _common = require("@nestjs/common");
16
+ const _typeorm = require("typeorm");
15
17
  const _companybranchentity = require("../entities/company-branch.entity");
18
+ const _usercompanypermissionentity = require("../entities/user-company-permission.entity");
16
19
  const _authconfigservice = require("./auth-config.service");
17
20
  const _authdatasourceprovider = require("./auth-datasource.provider");
18
21
  function _define_property(obj, key, value) {
@@ -43,6 +46,27 @@ function _ts_param(paramIndex, decorator) {
43
46
  };
44
47
  }
45
48
  let BranchService = class BranchService extends _classes.ApiService {
49
+ async beforeDeleteOperation(dto, user, queryRunner) {
50
+ const branchIds = Array.isArray(dto.id) ? dto.id : [
51
+ dto.id
52
+ ];
53
+ await queryRunner.manager.getRepository(_usercompanypermissionentity.UserCompanyPermission).delete({
54
+ targetId: (0, _typeorm.In)(branchIds)
55
+ });
56
+ if (!this.permissionSyncAdapter?.onBranchDeleted) {
57
+ return;
58
+ }
59
+ const rows = await queryRunner.manager.getRepository(_companybranchentity.CompanyBranch).find({
60
+ where: {
61
+ id: (0, _typeorm.In)(branchIds)
62
+ },
63
+ select: [
64
+ 'id',
65
+ 'companyId'
66
+ ]
67
+ });
68
+ await Promise.all(rows.map((row)=>this.permissionSyncAdapter.onBranchDeleted(row.id, row.companyId)));
69
+ }
46
70
  // Query Customization
47
71
  async getSelectQuery(query, _user, select) {
48
72
  if (!select?.length) {
@@ -84,8 +108,8 @@ let BranchService = class BranchService extends _classes.ApiService {
84
108
  }
85
109
  return result;
86
110
  }
87
- constructor(cacheManager, utilsService, dataSourceProvider, authConfig){
88
- super('company_branch', cacheManager, utilsService, BranchService.name, true, 'auth', _companybranchentity.CompanyBranch, dataSourceProvider), _define_property(this, "cacheManager", void 0), _define_property(this, "utilsService", void 0), _define_property(this, "authConfig", void 0), this.cacheManager = cacheManager, this.utilsService = utilsService, this.authConfig = authConfig;
111
+ constructor(cacheManager, utilsService, dataSourceProvider, authConfig, permissionSyncAdapter){
112
+ super('company_branch', cacheManager, utilsService, BranchService.name, true, 'auth', _companybranchentity.CompanyBranch, dataSourceProvider), _define_property(this, "cacheManager", void 0), _define_property(this, "utilsService", void 0), _define_property(this, "authConfig", void 0), _define_property(this, "permissionSyncAdapter", void 0), this.cacheManager = cacheManager, this.utilsService = utilsService, this.authConfig = authConfig, this.permissionSyncAdapter = permissionSyncAdapter;
89
113
  }
90
114
  };
91
115
  BranchService = _ts_decorate([
@@ -96,11 +120,14 @@ BranchService = _ts_decorate([
96
120
  _ts_param(1, (0, _common.Inject)(_modules.UtilsService)),
97
121
  _ts_param(2, (0, _common.Inject)(_authdatasourceprovider.AuthDataSourceProvider)),
98
122
  _ts_param(3, (0, _common.Inject)(_authconfigservice.AuthConfigService)),
123
+ _ts_param(4, (0, _common.Optional)()),
124
+ _ts_param(4, (0, _common.Inject)(_interfaces.PERMISSION_SYNC_ADAPTER)),
99
125
  _ts_metadata("design:type", Function),
100
126
  _ts_metadata("design:paramtypes", [
101
127
  typeof _classes.HybridCache === "undefined" ? Object : _classes.HybridCache,
102
128
  typeof _modules.UtilsService === "undefined" ? Object : _modules.UtilsService,
103
129
  typeof _authdatasourceprovider.AuthDataSourceProvider === "undefined" ? Object : _authdatasourceprovider.AuthDataSourceProvider,
104
- typeof _authconfigservice.AuthConfigService === "undefined" ? Object : _authconfigservice.AuthConfigService
130
+ typeof _authconfigservice.AuthConfigService === "undefined" ? Object : _authconfigservice.AuthConfigService,
131
+ typeof _interfaces.IPermissionSyncAdapter === "undefined" ? Object : _interfaces.IPermissionSyncAdapter
105
132
  ])
106
133
  ], BranchService);
@@ -0,0 +1,154 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ const _repositorymock = require("@test-utils/mocks/repository.mock");
6
+ const _branchservice = require("./branch.service");
7
+ const _companybranchentity = require("../entities/company-branch.entity");
8
+ const _usercompanypermissionentity = require("../entities/user-company-permission.entity");
9
+ describe('BranchService', ()=>{
10
+ let service;
11
+ let mockRepo;
12
+ let mockAuthConfig;
13
+ let mockPermissionSyncAdapter;
14
+ let mockQueryRunner;
15
+ beforeEach(()=>{
16
+ mockRepo = (0, _repositorymock.createMockRepository)();
17
+ Object.assign(mockRepo.createQueryBuilder(), {
18
+ leftJoinAndSelect: jest.fn().mockReturnThis(),
19
+ addOrderBy: jest.fn().mockReturnThis()
20
+ });
21
+ const mockDataSourceProvider = (0, _repositorymock.createMockDataSourceProvider)(mockRepo);
22
+ mockAuthConfig = {
23
+ isCompanyFeatureEnabled: jest.fn().mockReturnValue(false)
24
+ };
25
+ mockPermissionSyncAdapter = {
26
+ onBranchDeleted: jest.fn()
27
+ };
28
+ service = new _branchservice.BranchService({}, {}, mockDataSourceProvider, mockAuthConfig, mockPermissionSyncAdapter);
29
+ const permissionRepo = (0, _repositorymock.createMockRepository)();
30
+ const branchRepo = (0, _repositorymock.createMockRepository)();
31
+ branchRepo.find.mockResolvedValue([]);
32
+ mockQueryRunner = {
33
+ manager: {
34
+ getRepository: jest.fn().mockImplementation((entity)=>{
35
+ if (entity === _usercompanypermissionentity.UserCompanyPermission) return permissionRepo;
36
+ if (entity === _companybranchentity.CompanyBranch) return branchRepo;
37
+ throw new Error('Unexpected entity');
38
+ })
39
+ }
40
+ };
41
+ });
42
+ describe('beforeDeleteOperation', ()=>{
43
+ it('deletes user permissions targeting the deleted branch ids', async ()=>{
44
+ await service.beforeDeleteOperation({
45
+ id: [
46
+ 'branch-1',
47
+ 'branch-2'
48
+ ]
49
+ }, null, mockQueryRunner);
50
+ const permissionRepo = mockQueryRunner.manager.getRepository(_usercompanypermissionentity.UserCompanyPermission);
51
+ expect(permissionRepo.delete).toHaveBeenCalledWith({
52
+ targetId: expect.anything()
53
+ });
54
+ });
55
+ it('handles a single (non-array) branch id', async ()=>{
56
+ await service.beforeDeleteOperation({
57
+ id: 'branch-1'
58
+ }, null, mockQueryRunner);
59
+ const permissionRepo = mockQueryRunner.manager.getRepository(_usercompanypermissionentity.UserCompanyPermission);
60
+ expect(permissionRepo.delete).toHaveBeenCalled();
61
+ });
62
+ it('does nothing further when no permission sync adapter is configured', async ()=>{
63
+ service = new _branchservice.BranchService({}, {}, (0, _repositorymock.createMockDataSourceProvider)(mockRepo), mockAuthConfig, undefined);
64
+ await expect(service.beforeDeleteOperation({
65
+ id: [
66
+ 'branch-1'
67
+ ]
68
+ }, null, mockQueryRunner)).resolves.toBeUndefined();
69
+ });
70
+ it('notifies the permission sync adapter for each deleted branch', async ()=>{
71
+ const branchRepo = mockQueryRunner.manager.getRepository(_companybranchentity.CompanyBranch);
72
+ branchRepo.find.mockResolvedValue([
73
+ {
74
+ id: 'branch-1',
75
+ companyId: 'company-1'
76
+ }
77
+ ]);
78
+ await service.beforeDeleteOperation({
79
+ id: [
80
+ 'branch-1'
81
+ ]
82
+ }, null, mockQueryRunner);
83
+ expect(mockPermissionSyncAdapter.onBranchDeleted).toHaveBeenCalledWith('branch-1', 'company-1');
84
+ });
85
+ });
86
+ describe('getSelectQuery', ()=>{
87
+ it('applies the default selected fields when none are provided', async ()=>{
88
+ const qb = mockRepo.createQueryBuilder();
89
+ const { isRaw } = await service.getSelectQuery(qb, null);
90
+ expect(qb.select).toHaveBeenCalledWith(expect.arrayContaining([
91
+ 'company_branch.id',
92
+ 'company_branch.name',
93
+ 'company_branch.slug'
94
+ ]));
95
+ expect(isRaw).toBe(false);
96
+ });
97
+ it('honors an explicit select list', async ()=>{
98
+ const qb = mockRepo.createQueryBuilder();
99
+ await service.getSelectQuery(qb, null, [
100
+ 'id',
101
+ 'name'
102
+ ]);
103
+ expect(qb.select).toHaveBeenCalledWith([
104
+ 'company_branch.id',
105
+ 'company_branch.name'
106
+ ]);
107
+ });
108
+ });
109
+ describe('getGlobalSearchQuery', ()=>{
110
+ it('adds a LIKE search clause across name/slug/email', async ()=>{
111
+ const qb = mockRepo.createQueryBuilder();
112
+ await service.getGlobalSearchQuery(qb, 'acme');
113
+ expect(qb.andWhere).toHaveBeenCalledWith(expect.stringContaining('company_branch.name LIKE'), {
114
+ search: '%acme%'
115
+ });
116
+ });
117
+ });
118
+ describe('getExtraManipulateQuery', ()=>{
119
+ it('applies a company filter when company feature is enabled and no explicit companyId filter is present', async ()=>{
120
+ mockAuthConfig.isCompanyFeatureEnabled.mockReturnValue(true);
121
+ const qb = mockRepo.createQueryBuilder();
122
+ await service.getExtraManipulateQuery(qb, {
123
+ filter: {}
124
+ }, {
125
+ id: 'user-1',
126
+ companyId: 'company-1'
127
+ });
128
+ expect(qb.andWhere).toHaveBeenCalledWith('company_branch.company_id = :companyId', {
129
+ companyId: 'company-1'
130
+ });
131
+ });
132
+ it('does not apply a company filter when companyId is explicitly provided in the filter', async ()=>{
133
+ mockAuthConfig.isCompanyFeatureEnabled.mockReturnValue(true);
134
+ const qb = mockRepo.createQueryBuilder();
135
+ await service.getExtraManipulateQuery(qb, {
136
+ filter: {
137
+ companyId: 'explicit-company'
138
+ }
139
+ }, {
140
+ id: 'user-1',
141
+ companyId: 'company-1'
142
+ });
143
+ expect(qb.andWhere).not.toHaveBeenCalledWith('company_branch.company_id = :companyId', expect.anything());
144
+ });
145
+ it('does not apply a company filter when the company feature is disabled', async ()=>{
146
+ mockAuthConfig.isCompanyFeatureEnabled.mockReturnValue(false);
147
+ const qb = mockRepo.createQueryBuilder();
148
+ await service.getExtraManipulateQuery(qb, {
149
+ filter: {}
150
+ }, null);
151
+ expect(qb.andWhere).not.toHaveBeenCalledWith('company_branch.company_id = :companyId', expect.anything());
152
+ });
153
+ });
154
+ });
@@ -0,0 +1,90 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ const _companyselectionsessionservice = require("./company-selection-session.service");
6
+ describe('CompanySelectionSessionService', ()=>{
7
+ let service;
8
+ let mockCacheManager;
9
+ beforeEach(()=>{
10
+ mockCacheManager = {
11
+ set: jest.fn(),
12
+ get: jest.fn(),
13
+ del: jest.fn()
14
+ };
15
+ service = new _companyselectionsessionservice.CompanySelectionSessionService(mockCacheManager);
16
+ });
17
+ describe('createSession', ()=>{
18
+ it('stores a session in the cache and returns a session id', async ()=>{
19
+ const sessionId = await service.createSession('user-1');
20
+ expect(typeof sessionId).toBe('string');
21
+ expect(sessionId.length).toBeGreaterThan(0);
22
+ expect(mockCacheManager.set).toHaveBeenCalledWith(`auth:session:${sessionId}`, expect.objectContaining({
23
+ sessionId,
24
+ userId: 'user-1'
25
+ }), 5 * 60 * 1000);
26
+ });
27
+ it('generates unique session ids on successive calls', async ()=>{
28
+ const first = await service.createSession('user-1');
29
+ const second = await service.createSession('user-1');
30
+ expect(first).not.toBe(second);
31
+ });
32
+ });
33
+ describe('getSession', ()=>{
34
+ it('returns null when no session is cached', async ()=>{
35
+ mockCacheManager.get.mockResolvedValue(undefined);
36
+ const result = await service.getSession('missing-session');
37
+ expect(result).toBeNull();
38
+ });
39
+ it('returns the session when present and not expired', async ()=>{
40
+ const session = {
41
+ sessionId: 'sess-1',
42
+ userId: 'user-1',
43
+ createdAt: new Date(),
44
+ expiresAt: new Date(Date.now() + 60_000)
45
+ };
46
+ mockCacheManager.get.mockResolvedValue(session);
47
+ const result = await service.getSession('sess-1');
48
+ expect(result).toEqual(session);
49
+ expect(mockCacheManager.get).toHaveBeenCalledWith('auth:session:sess-1');
50
+ });
51
+ it('deletes and returns null when the cached session has expired', async ()=>{
52
+ const session = {
53
+ sessionId: 'sess-1',
54
+ userId: 'user-1',
55
+ createdAt: new Date(Date.now() - 10 * 60_000),
56
+ expiresAt: new Date(Date.now() - 60_000)
57
+ };
58
+ mockCacheManager.get.mockResolvedValue(session);
59
+ const result = await service.getSession('sess-1');
60
+ expect(result).toBeNull();
61
+ expect(mockCacheManager.del).toHaveBeenCalledWith('auth:session:sess-1');
62
+ });
63
+ });
64
+ describe('consumeSession', ()=>{
65
+ it('returns and deletes the session when present', async ()=>{
66
+ const session = {
67
+ sessionId: 'sess-1',
68
+ userId: 'user-1',
69
+ createdAt: new Date(),
70
+ expiresAt: new Date(Date.now() + 60_000)
71
+ };
72
+ mockCacheManager.get.mockResolvedValue(session);
73
+ const result = await service.consumeSession('sess-1');
74
+ expect(result).toEqual(session);
75
+ expect(mockCacheManager.del).toHaveBeenCalledWith('auth:session:sess-1');
76
+ });
77
+ it('returns null and does not delete when session is missing', async ()=>{
78
+ mockCacheManager.get.mockResolvedValue(undefined);
79
+ const result = await service.consumeSession('missing');
80
+ expect(result).toBeNull();
81
+ expect(mockCacheManager.del).not.toHaveBeenCalled();
82
+ });
83
+ });
84
+ describe('deleteSession', ()=>{
85
+ it('deletes the session by key', async ()=>{
86
+ await service.deleteSession('sess-1');
87
+ expect(mockCacheManager.del).toHaveBeenCalledWith('auth:session:sess-1');
88
+ });
89
+ });
90
+ });
@@ -9,9 +9,13 @@ Object.defineProperty(exports, "CompanyService", {
9
9
  }
10
10
  });
11
11
  const _classes = require("@flusys/nestjs-shared/classes");
12
+ const _interfaces = require("@flusys/nestjs-shared/interfaces");
12
13
  const _modules = require("@flusys/nestjs-shared/modules");
13
14
  const _common = require("@nestjs/common");
15
+ const _typeorm = require("typeorm");
16
+ const _companybranchentity = require("../entities/company-branch.entity");
14
17
  const _companyentity = require("../entities/company.entity");
18
+ const _usercompanypermissionentity = require("../entities/user-company-permission.entity");
15
19
  const _authdatasourceprovider = require("./auth-datasource.provider");
16
20
  function _define_property(obj, key, value) {
17
21
  if (key in obj) {
@@ -41,6 +45,30 @@ function _ts_param(paramIndex, decorator) {
41
45
  };
42
46
  }
43
47
  let CompanyService = class CompanyService extends _classes.ApiService {
48
+ async beforeDeleteOperation(dto, user, queryRunner) {
49
+ const companyIds = Array.isArray(dto.id) ? dto.id : [
50
+ dto.id
51
+ ];
52
+ const branchRows = await queryRunner.manager.getRepository(_companybranchentity.CompanyBranch).find({
53
+ where: {
54
+ companyId: (0, _typeorm.In)(companyIds)
55
+ },
56
+ select: [
57
+ 'id'
58
+ ]
59
+ });
60
+ const targetIds = [
61
+ ...companyIds,
62
+ ...branchRows.map((row)=>row.id)
63
+ ];
64
+ await queryRunner.manager.getRepository(_usercompanypermissionentity.UserCompanyPermission).delete({
65
+ targetId: (0, _typeorm.In)(targetIds)
66
+ });
67
+ if (!this.permissionSyncAdapter?.onCompanyDeleted) {
68
+ return;
69
+ }
70
+ await Promise.all(companyIds.map((companyId)=>this.permissionSyncAdapter.onCompanyDeleted(companyId)));
71
+ }
44
72
  // Query Customization
45
73
  async getSelectQuery(query, _user, select) {
46
74
  if (!select?.length) {
@@ -69,8 +97,8 @@ let CompanyService = class CompanyService extends _classes.ApiService {
69
97
  isRaw: false
70
98
  };
71
99
  }
72
- constructor(cacheManager, utilsService, dataSourceProvider){
73
- super('company', cacheManager, utilsService, CompanyService.name, true, 'auth', _companyentity.Company, dataSourceProvider), _define_property(this, "cacheManager", void 0), _define_property(this, "utilsService", void 0), this.cacheManager = cacheManager, this.utilsService = utilsService;
100
+ constructor(cacheManager, utilsService, dataSourceProvider, permissionSyncAdapter){
101
+ super('company', cacheManager, utilsService, CompanyService.name, true, 'auth', _companyentity.Company, dataSourceProvider), _define_property(this, "cacheManager", void 0), _define_property(this, "utilsService", void 0), _define_property(this, "permissionSyncAdapter", void 0), this.cacheManager = cacheManager, this.utilsService = utilsService, this.permissionSyncAdapter = permissionSyncAdapter;
74
102
  }
75
103
  };
76
104
  CompanyService = _ts_decorate([
@@ -80,10 +108,13 @@ CompanyService = _ts_decorate([
80
108
  _ts_param(0, (0, _common.Inject)('CACHE_INSTANCE')),
81
109
  _ts_param(1, (0, _common.Inject)(_modules.UtilsService)),
82
110
  _ts_param(2, (0, _common.Inject)(_authdatasourceprovider.AuthDataSourceProvider)),
111
+ _ts_param(3, (0, _common.Optional)()),
112
+ _ts_param(3, (0, _common.Inject)(_interfaces.PERMISSION_SYNC_ADAPTER)),
83
113
  _ts_metadata("design:type", Function),
84
114
  _ts_metadata("design:paramtypes", [
85
115
  typeof _classes.HybridCache === "undefined" ? Object : _classes.HybridCache,
86
116
  typeof _modules.UtilsService === "undefined" ? Object : _modules.UtilsService,
87
- typeof _authdatasourceprovider.AuthDataSourceProvider === "undefined" ? Object : _authdatasourceprovider.AuthDataSourceProvider
117
+ typeof _authdatasourceprovider.AuthDataSourceProvider === "undefined" ? Object : _authdatasourceprovider.AuthDataSourceProvider,
118
+ typeof _interfaces.IPermissionSyncAdapter === "undefined" ? Object : _interfaces.IPermissionSyncAdapter
88
119
  ])
89
120
  ], CompanyService);
@@ -0,0 +1,113 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ const _repositorymock = require("@test-utils/mocks/repository.mock");
6
+ const _companyservice = require("./company.service");
7
+ const _companybranchentity = require("../entities/company-branch.entity");
8
+ const _usercompanypermissionentity = require("../entities/user-company-permission.entity");
9
+ describe('CompanyService', ()=>{
10
+ let service;
11
+ let mockRepo;
12
+ let mockPermissionSyncAdapter;
13
+ let mockQueryRunner;
14
+ let branchRepo;
15
+ let permissionRepo;
16
+ beforeEach(()=>{
17
+ mockRepo = (0, _repositorymock.createMockRepository)();
18
+ const mockDataSourceProvider = (0, _repositorymock.createMockDataSourceProvider)(mockRepo);
19
+ mockPermissionSyncAdapter = {
20
+ onCompanyDeleted: jest.fn()
21
+ };
22
+ service = new _companyservice.CompanyService({}, {}, mockDataSourceProvider, mockPermissionSyncAdapter);
23
+ branchRepo = (0, _repositorymock.createMockRepository)();
24
+ branchRepo.find.mockResolvedValue([]);
25
+ permissionRepo = (0, _repositorymock.createMockRepository)();
26
+ mockQueryRunner = {
27
+ manager: {
28
+ getRepository: jest.fn().mockImplementation((entity)=>{
29
+ if (entity === _companybranchentity.CompanyBranch) return branchRepo;
30
+ if (entity === _usercompanypermissionentity.UserCompanyPermission) return permissionRepo;
31
+ throw new Error('Unexpected entity');
32
+ })
33
+ }
34
+ };
35
+ });
36
+ describe('beforeDeleteOperation', ()=>{
37
+ it('deletes permissions for the companies and their branches', async ()=>{
38
+ branchRepo.find.mockResolvedValue([
39
+ {
40
+ id: 'branch-1'
41
+ }
42
+ ]);
43
+ await service.beforeDeleteOperation({
44
+ id: [
45
+ 'company-1'
46
+ ]
47
+ }, null, mockQueryRunner);
48
+ const deleteArg = permissionRepo.delete.mock.calls[0][0];
49
+ expect(deleteArg.targetId._value).toEqual(expect.arrayContaining([
50
+ 'company-1',
51
+ 'branch-1'
52
+ ]));
53
+ });
54
+ it('handles a single (non-array) company id', async ()=>{
55
+ await service.beforeDeleteOperation({
56
+ id: 'company-1'
57
+ }, null, mockQueryRunner);
58
+ expect(branchRepo.find).toHaveBeenCalledWith(expect.objectContaining({
59
+ where: {
60
+ companyId: expect.anything()
61
+ }
62
+ }));
63
+ });
64
+ it('does nothing further when no permission sync adapter is configured', async ()=>{
65
+ service = new _companyservice.CompanyService({}, {}, (0, _repositorymock.createMockDataSourceProvider)(mockRepo), undefined);
66
+ await expect(service.beforeDeleteOperation({
67
+ id: [
68
+ 'company-1'
69
+ ]
70
+ }, null, mockQueryRunner)).resolves.toBeUndefined();
71
+ });
72
+ it('notifies the permission sync adapter for each deleted company', async ()=>{
73
+ await service.beforeDeleteOperation({
74
+ id: [
75
+ 'company-1',
76
+ 'company-2'
77
+ ]
78
+ }, null, mockQueryRunner);
79
+ expect(mockPermissionSyncAdapter.onCompanyDeleted).toHaveBeenCalledWith('company-1');
80
+ expect(mockPermissionSyncAdapter.onCompanyDeleted).toHaveBeenCalledWith('company-2');
81
+ });
82
+ });
83
+ describe('getSelectQuery', ()=>{
84
+ it('applies the default selected fields when none are provided', async ()=>{
85
+ const qb = mockRepo.createQueryBuilder();
86
+ const { isRaw } = await service.getSelectQuery(qb, null);
87
+ expect(qb.select).toHaveBeenCalledWith(expect.arrayContaining([
88
+ 'company.id',
89
+ 'company.name',
90
+ 'company.slug'
91
+ ]));
92
+ expect(isRaw).toBe(false);
93
+ });
94
+ it('honors an explicit select list', async ()=>{
95
+ const qb = mockRepo.createQueryBuilder();
96
+ await service.getSelectQuery(qb, null, [
97
+ 'id'
98
+ ]);
99
+ expect(qb.select).toHaveBeenCalledWith([
100
+ 'company.id'
101
+ ]);
102
+ });
103
+ });
104
+ describe('getGlobalSearchQuery', ()=>{
105
+ it('adds a LIKE search clause across name/slug/email', async ()=>{
106
+ const qb = mockRepo.createQueryBuilder();
107
+ await service.getGlobalSearchQuery(qb, 'acme');
108
+ expect(qb.andWhere).toHaveBeenCalledWith(expect.stringContaining('company.name LIKE'), {
109
+ search: '%acme%'
110
+ });
111
+ });
112
+ });
113
+ });
@@ -9,6 +9,7 @@ Object.defineProperty(exports, "UserPermissionService", {
9
9
  }
10
10
  });
11
11
  const _nestjsshared = require("@flusys/nestjs-shared");
12
+ const _interfaces = require("@flusys/nestjs-shared/interfaces");
12
13
  const _common = require("@nestjs/common");
13
14
  const _config = require("../config");
14
15
  const _usercompanypermissionentity = require("../entities/user-company-permission.entity");
@@ -102,7 +103,7 @@ let UserPermissionService = class UserPermissionService {
102
103
  throw error;
103
104
  }
104
105
  }
105
- /** Revoke permission from user */ async revokePermission(userId, targetId, type) {
106
+ async revokePermission(userId, targetId, type) {
106
107
  const repository = await this.getRepository();
107
108
  const typeName = type === _usercompanypermissionentity.PermissionType.COMPANY ? 'company' : 'branch';
108
109
  const permission = await repository.findOne({
@@ -110,7 +111,10 @@ let UserPermissionService = class UserPermissionService {
110
111
  userId,
111
112
  targetId,
112
113
  permissionType: type
113
- }
114
+ },
115
+ relations: [
116
+ type === _usercompanypermissionentity.PermissionType.BRANCH ? 'branch' : 'company'
117
+ ]
114
118
  });
115
119
  if (!permission) {
116
120
  throw new _common.NotFoundException({
@@ -121,7 +125,29 @@ let UserPermissionService = class UserPermissionService {
121
125
  }
122
126
  });
123
127
  }
128
+ const companyId = type === _usercompanypermissionentity.PermissionType.COMPANY ? targetId : permission.branch?.companyId;
124
129
  await repository.remove(permission);
130
+ if (type === _usercompanypermissionentity.PermissionType.COMPANY) {
131
+ const revokedBranchIds = await this.revokeBranchPermissionsForCompany(repository, userId, targetId);
132
+ await this.permissionSyncAdapter?.onUserCompanyAccessRevoked?.(userId, targetId);
133
+ await Promise.all(revokedBranchIds.map((branchId)=>this.permissionSyncAdapter?.onUserBranchAccessRevoked?.(userId, branchId, targetId)));
134
+ } else {
135
+ if (companyId) await this.permissionSyncAdapter?.onUserBranchAccessRevoked?.(userId, targetId, companyId);
136
+ }
137
+ }
138
+ async revokeBranchPermissionsForCompany(repository, userId, companyId) {
139
+ const branchPermissions = await repository.createQueryBuilder('permission').innerJoin('permission.branch', 'branch').where('permission.userId = :userId', {
140
+ userId
141
+ }).andWhere('permission.permissionType = :type', {
142
+ type: _usercompanypermissionentity.PermissionType.BRANCH
143
+ }).andWhere('branch.companyId = :companyId', {
144
+ companyId
145
+ }).getMany();
146
+ if (branchPermissions.length === 0) {
147
+ return [];
148
+ }
149
+ await repository.remove(branchPermissions);
150
+ return branchPermissions.map((permission)=>permission.targetId);
125
151
  }
126
152
  /** Find user branch permissions with optional company filter */ async findUserBranches(userId, companyId) {
127
153
  const repository = await this.getRepository();
@@ -137,12 +163,14 @@ let UserPermissionService = class UserPermissionService {
137
163
  }
138
164
  return queryBuilder.getMany();
139
165
  }
140
- constructor(dataSourceProvider){
166
+ constructor(dataSourceProvider, permissionSyncAdapter){
141
167
  _define_property(this, "dataSourceProvider", void 0);
168
+ _define_property(this, "permissionSyncAdapter", void 0);
142
169
  _define_property(this, "_repository", void 0);
143
170
  // Convenience Methods
144
171
  _define_property(this, "findUserCompanies", void 0);
145
172
  this.dataSourceProvider = dataSourceProvider;
173
+ this.permissionSyncAdapter = permissionSyncAdapter;
146
174
  this._repository = null;
147
175
  this.findUserCompanies = (userId)=>this.findUserPermissions(userId, _usercompanypermissionentity.PermissionType.COMPANY);
148
176
  }
@@ -178,8 +206,11 @@ UserPermissionService = _ts_decorate([
178
206
  scope: _common.Scope.REQUEST
179
207
  }),
180
208
  _ts_param(0, (0, _common.Inject)(_authdatasourceprovider.AuthDataSourceProvider)),
209
+ _ts_param(1, (0, _common.Optional)()),
210
+ _ts_param(1, (0, _common.Inject)(_interfaces.PERMISSION_SYNC_ADAPTER)),
181
211
  _ts_metadata("design:type", Function),
182
212
  _ts_metadata("design:paramtypes", [
183
- typeof _authdatasourceprovider.AuthDataSourceProvider === "undefined" ? Object : _authdatasourceprovider.AuthDataSourceProvider
213
+ typeof _authdatasourceprovider.AuthDataSourceProvider === "undefined" ? Object : _authdatasourceprovider.AuthDataSourceProvider,
214
+ typeof _interfaces.IPermissionSyncAdapter === "undefined" ? Object : _interfaces.IPermissionSyncAdapter
184
215
  ])
185
216
  ], UserPermissionService);