@flusys/nestjs-task-manager 7.0.2 → 8.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (163) hide show
  1. package/config/index.d.ts +3 -3
  2. package/controllers/index.d.ts +5 -5
  3. package/controllers/task-board.controller.d.ts +3 -3
  4. package/controllers/task-comment.controller.d.ts +2 -2
  5. package/controllers/task-label.controller.d.ts +2 -2
  6. package/controllers/task-list.controller.d.ts +3 -3
  7. package/controllers/task.controller.d.ts +4 -4
  8. package/docs/index.d.ts +1 -1
  9. package/docs/task-manager-swagger.config.d.ts +1 -1
  10. package/dtos/index.d.ts +5 -5
  11. package/dtos/task-board.dto.d.ts +1 -1
  12. package/dtos/task.dto.d.ts +2 -2
  13. package/entities/index.d.ts +19 -19
  14. package/entities/task-board-with-company.entity.d.ts +1 -1
  15. package/fesm/234.js +1393 -0
  16. package/fesm/431.js +981 -0
  17. package/fesm/827.js +1054 -0
  18. package/fesm/988.js +881 -0
  19. package/fesm/config/index.js +156 -3
  20. package/fesm/controllers/index.js +21 -5
  21. package/fesm/docs/index.js +58 -1
  22. package/fesm/dtos/index.js +280 -5
  23. package/fesm/entities/index.js +26 -46
  24. package/fesm/gateways/index.js +288 -1
  25. package/fesm/index.js +440 -9
  26. package/fesm/interfaces/index.js +16 -2
  27. package/fesm/modules/index.js +210 -1
  28. package/fesm/runtime.js +85 -0
  29. package/fesm/services/index.js +53 -8
  30. package/gateways/index.d.ts +1 -1
  31. package/gateways/task-board.gateway.d.ts +3 -3
  32. package/index.d.ts +9 -9
  33. package/interfaces/index.d.ts +2 -2
  34. package/interfaces/task-manager-module.interface.d.ts +3 -3
  35. package/interfaces/task.interface.d.ts +2 -2
  36. package/modules/index.d.ts +1 -1
  37. package/modules/task-manager.module.d.ts +2 -2
  38. package/package.json +26 -36
  39. package/services/index.d.ts +8 -8
  40. package/services/task-activity.service.d.ts +1 -1
  41. package/services/task-board.service.d.ts +5 -5
  42. package/services/task-comment.service.d.ts +4 -4
  43. package/services/task-label.service.d.ts +4 -4
  44. package/services/task-list.service.d.ts +4 -4
  45. package/services/task-manager-config.service.d.ts +3 -3
  46. package/services/task-manager-datasource.provider.d.ts +3 -3
  47. package/services/task.service.d.ts +7 -7
  48. package/cjs/config/event-actions.js +0 -37
  49. package/cjs/config/index.js +0 -20
  50. package/cjs/config/message-keys.js +0 -92
  51. package/cjs/config/task-manager.constants.js +0 -11
  52. package/cjs/controllers/index.js +0 -22
  53. package/cjs/controllers/task-board.controller.js +0 -219
  54. package/cjs/controllers/task-board.controller.spec.js +0 -132
  55. package/cjs/controllers/task-comment.controller.js +0 -103
  56. package/cjs/controllers/task-comment.controller.spec.js +0 -86
  57. package/cjs/controllers/task-label.controller.js +0 -112
  58. package/cjs/controllers/task-label.controller.spec.js +0 -88
  59. package/cjs/controllers/task-list.controller.js +0 -158
  60. package/cjs/controllers/task-list.controller.spec.js +0 -112
  61. package/cjs/controllers/task.controller.js +0 -199
  62. package/cjs/controllers/task.controller.spec.js +0 -131
  63. package/cjs/docs/index.js +0 -18
  64. package/cjs/docs/task-manager-swagger.config.js +0 -43
  65. package/cjs/docs/task-manager-swagger.config.spec.js +0 -28
  66. package/cjs/dtos/index.js +0 -22
  67. package/cjs/dtos/task-board.dto.js +0 -310
  68. package/cjs/dtos/task-comment.dto.js +0 -131
  69. package/cjs/dtos/task-label.dto.js +0 -120
  70. package/cjs/dtos/task-list.dto.js +0 -198
  71. package/cjs/dtos/task.dto.js +0 -367
  72. package/cjs/entities/index.js +0 -72
  73. package/cjs/entities/index.spec.js +0 -30
  74. package/cjs/entities/task-activity.entity.js +0 -98
  75. package/cjs/entities/task-assignee.entity.js +0 -67
  76. package/cjs/entities/task-board-member.entity.js +0 -94
  77. package/cjs/entities/task-board-with-company.entity.js +0 -57
  78. package/cjs/entities/task-board.entity.js +0 -88
  79. package/cjs/entities/task-comment.entity.js +0 -80
  80. package/cjs/entities/task-label.entity.js +0 -72
  81. package/cjs/entities/task-list.entity.js +0 -104
  82. package/cjs/entities/task-task-label.entity.js +0 -67
  83. package/cjs/entities/task.entity.js +0 -151
  84. package/cjs/gateways/index.js +0 -18
  85. package/cjs/gateways/task-board.gateway.js +0 -167
  86. package/cjs/gateways/task-board.gateway.spec.js +0 -243
  87. package/cjs/index.js +0 -26
  88. package/cjs/interfaces/index.js +0 -19
  89. package/cjs/interfaces/task-manager-module.interface.js +0 -4
  90. package/cjs/interfaces/task.interface.js +0 -4
  91. package/cjs/modules/index.js +0 -18
  92. package/cjs/modules/task-manager.module.js +0 -135
  93. package/cjs/modules/task-manager.module.spec.js +0 -144
  94. package/cjs/services/index.js +0 -25
  95. package/cjs/services/task-activity.service.js +0 -76
  96. package/cjs/services/task-activity.service.spec.js +0 -125
  97. package/cjs/services/task-board.service.js +0 -247
  98. package/cjs/services/task-board.service.spec.js +0 -465
  99. package/cjs/services/task-comment.service.js +0 -103
  100. package/cjs/services/task-comment.service.spec.js +0 -160
  101. package/cjs/services/task-label.service.js +0 -97
  102. package/cjs/services/task-label.service.spec.js +0 -138
  103. package/cjs/services/task-list.service.js +0 -130
  104. package/cjs/services/task-list.service.spec.js +0 -284
  105. package/cjs/services/task-manager-config.service.js +0 -69
  106. package/cjs/services/task-manager-config.service.spec.js +0 -98
  107. package/cjs/services/task-manager-datasource.provider.js +0 -84
  108. package/cjs/services/task-manager-datasource.provider.spec.js +0 -131
  109. package/cjs/services/task.service.js +0 -398
  110. package/cjs/services/task.service.spec.js +0 -700
  111. package/fesm/config/event-actions.js +0 -16
  112. package/fesm/config/message-keys.js +0 -65
  113. package/fesm/config/task-manager.constants.js +0 -1
  114. package/fesm/controllers/task-board.controller.js +0 -209
  115. package/fesm/controllers/task-board.controller.spec.js +0 -128
  116. package/fesm/controllers/task-comment.controller.js +0 -93
  117. package/fesm/controllers/task-comment.controller.spec.js +0 -82
  118. package/fesm/controllers/task-label.controller.js +0 -102
  119. package/fesm/controllers/task-label.controller.spec.js +0 -84
  120. package/fesm/controllers/task-list.controller.js +0 -148
  121. package/fesm/controllers/task-list.controller.spec.js +0 -108
  122. package/fesm/controllers/task.controller.js +0 -189
  123. package/fesm/controllers/task.controller.spec.js +0 -127
  124. package/fesm/docs/task-manager-swagger.config.js +0 -33
  125. package/fesm/docs/task-manager-swagger.config.spec.js +0 -24
  126. package/fesm/dtos/task-board.dto.js +0 -277
  127. package/fesm/dtos/task-comment.dto.js +0 -110
  128. package/fesm/dtos/task-label.dto.js +0 -99
  129. package/fesm/dtos/task-list.dto.js +0 -174
  130. package/fesm/dtos/task.dto.js +0 -337
  131. package/fesm/entities/index.spec.js +0 -26
  132. package/fesm/entities/task-activity.entity.js +0 -88
  133. package/fesm/entities/task-assignee.entity.js +0 -57
  134. package/fesm/entities/task-board-member.entity.js +0 -76
  135. package/fesm/entities/task-board-with-company.entity.js +0 -47
  136. package/fesm/entities/task-board.entity.js +0 -78
  137. package/fesm/entities/task-comment.entity.js +0 -70
  138. package/fesm/entities/task-label.entity.js +0 -62
  139. package/fesm/entities/task-list.entity.js +0 -94
  140. package/fesm/entities/task-task-label.entity.js +0 -57
  141. package/fesm/entities/task.entity.js +0 -133
  142. package/fesm/gateways/task-board.gateway.js +0 -157
  143. package/fesm/gateways/task-board.gateway.spec.js +0 -239
  144. package/fesm/interfaces/task-manager-module.interface.js +0 -1
  145. package/fesm/interfaces/task.interface.js +0 -1
  146. package/fesm/modules/task-manager.module.js +0 -125
  147. package/fesm/modules/task-manager.module.spec.js +0 -140
  148. package/fesm/services/task-activity.service.js +0 -66
  149. package/fesm/services/task-activity.service.spec.js +0 -121
  150. package/fesm/services/task-board.service.js +0 -237
  151. package/fesm/services/task-board.service.spec.js +0 -461
  152. package/fesm/services/task-comment.service.js +0 -93
  153. package/fesm/services/task-comment.service.spec.js +0 -156
  154. package/fesm/services/task-label.service.js +0 -87
  155. package/fesm/services/task-label.service.spec.js +0 -134
  156. package/fesm/services/task-list.service.js +0 -120
  157. package/fesm/services/task-list.service.spec.js +0 -280
  158. package/fesm/services/task-manager-config.service.js +0 -59
  159. package/fesm/services/task-manager-config.service.spec.js +0 -94
  160. package/fesm/services/task-manager-datasource.provider.js +0 -74
  161. package/fesm/services/task-manager-datasource.provider.spec.js +0 -127
  162. package/fesm/services/task.service.js +0 -388
  163. package/fesm/services/task.service.spec.js +0 -696
@@ -1,280 +0,0 @@
1
- import { Test } from '@nestjs/testing';
2
- import { EventBusRegistry } from '@flusys/nestjs-shared/classes';
3
- import { UtilsService } from '@flusys/nestjs-shared/modules';
4
- import { createMockDataSourceProvider, createMockQueryBuilder, createMockRepository } from '@test-utils/mocks/repository.mock';
5
- import { buildMockUser } from '@test-utils/mocks/logged-user.mock';
6
- import { TaskListService } from './task-list.service';
7
- import { TaskManagerDataSourceProvider } from './task-manager-datasource.provider';
8
- function buildTaskList(overrides = {}) {
9
- return {
10
- id: 'list-uuid-1',
11
- boardId: 'board-uuid-1',
12
- name: 'To Do',
13
- color: '#6B7280',
14
- position: 0,
15
- isActive: true,
16
- isDoneList: false,
17
- createdAt: new Date(),
18
- updatedAt: new Date(),
19
- deletedAt: null,
20
- createdById: null,
21
- updatedById: null,
22
- deletedById: null,
23
- ...overrides
24
- };
25
- }
26
- function mockQueryRunner(repo) {
27
- const qr = {
28
- connect: jest.fn().mockResolvedValue(undefined),
29
- startTransaction: jest.fn().mockResolvedValue(undefined),
30
- commitTransaction: jest.fn().mockResolvedValue(undefined),
31
- rollbackTransaction: jest.fn().mockResolvedValue(undefined),
32
- release: jest.fn().mockResolvedValue(undefined),
33
- manager: {
34
- save: jest.fn((_target, entities)=>Promise.resolve(entities))
35
- }
36
- };
37
- repo.manager.connection.createQueryRunner.mockReturnValue(qr);
38
- return qr;
39
- }
40
- describe('TaskListService', ()=>{
41
- const mockUtilsService = {
42
- clearCache: jest.fn(),
43
- getCacheKey: jest.fn().mockReturnValue('cache-key'),
44
- trackCacheKey: jest.fn()
45
- };
46
- // Call history is shared across tests in this file; reset it so each cache assertion is exact.
47
- beforeEach(()=>{
48
- mockUtilsService.clearCache.mockClear();
49
- });
50
- let service;
51
- let mockRepo;
52
- beforeEach(async ()=>{
53
- mockRepo = createMockRepository();
54
- const mockDataSourceProvider = createMockDataSourceProvider(mockRepo);
55
- const module = await Test.createTestingModule({
56
- providers: [
57
- TaskListService,
58
- {
59
- provide: 'CACHE_INSTANCE',
60
- useValue: {}
61
- },
62
- {
63
- provide: UtilsService,
64
- useValue: mockUtilsService
65
- },
66
- {
67
- provide: TaskManagerDataSourceProvider,
68
- useValue: mockDataSourceProvider
69
- }
70
- ]
71
- }).compile();
72
- service = await module.resolve(TaskListService);
73
- });
74
- describe('convertSingleDtoToEntity (via insert)', ()=>{
75
- it('creates a list applying default color, position and flags', async ()=>{
76
- mockQueryRunner(mockRepo);
77
- const result = await service.insert({
78
- boardId: 'board-1',
79
- name: 'Backlog'
80
- }, buildMockUser());
81
- expect(result).toEqual(expect.objectContaining({
82
- boardId: 'board-1',
83
- name: 'Backlog',
84
- color: '#6B7280',
85
- position: 0,
86
- isDoneList: false,
87
- isActive: true
88
- }));
89
- });
90
- it('respects explicit color, position and isDoneList on create', async ()=>{
91
- mockQueryRunner(mockRepo);
92
- const result = await service.insert({
93
- boardId: 'board-1',
94
- name: 'Done',
95
- color: '#22C55E',
96
- position: 3,
97
- isDoneList: true
98
- }, buildMockUser());
99
- expect(result).toEqual(expect.objectContaining({
100
- color: '#22C55E',
101
- position: 3,
102
- isDoneList: true
103
- }));
104
- });
105
- it('throws NotFoundException on update when the list does not exist', async ()=>{
106
- mockQueryRunner(mockRepo);
107
- mockRepo.findOne.mockResolvedValue(null);
108
- await expect(service.update({
109
- id: 'missing-id',
110
- name: 'Renamed'
111
- }, buildMockUser())).rejects.toThrow();
112
- });
113
- it('merges update fields onto the existing entity, preserving unspecified fields', async ()=>{
114
- mockQueryRunner(mockRepo);
115
- const existing = buildTaskList({
116
- id: 'list-1',
117
- name: 'Old Name',
118
- color: '#111111',
119
- position: 5
120
- });
121
- mockRepo.findOne.mockResolvedValue(existing);
122
- const result = await service.update({
123
- id: 'list-1',
124
- name: 'New Name'
125
- }, buildMockUser());
126
- expect(result).toEqual(expect.objectContaining({
127
- id: 'list-1',
128
- name: 'New Name',
129
- color: '#111111',
130
- position: 5
131
- }));
132
- });
133
- });
134
- describe('getFilterQuery', ()=>{
135
- it('filters by boardId, filters by isActive and always orders by position ascending', async ()=>{
136
- const qb = createMockQueryBuilder();
137
- const { query, isRaw } = await service.getFilterQuery(qb, {
138
- boardId: 'board-1',
139
- isActive: true
140
- }, buildMockUser());
141
- expect(qb.andWhere).toHaveBeenCalledWith('task_list.boardId = :boardId', {
142
- boardId: 'board-1'
143
- });
144
- expect(qb.andWhere).toHaveBeenCalledWith('task_list.isActive = :isActive', {
145
- isActive: true
146
- });
147
- expect(qb.orderBy).toHaveBeenCalledWith('task_list.position', 'ASC');
148
- expect(isRaw).toBe(false);
149
- expect(query).toBe(qb);
150
- });
151
- it('ignores a non-boolean isActive filter value', async ()=>{
152
- const qb = createMockQueryBuilder();
153
- await service.getFilterQuery(qb, {
154
- isActive: 'not-a-boolean'
155
- }, buildMockUser());
156
- expect(qb.andWhere).not.toHaveBeenCalledWith(expect.stringContaining('isActive'), expect.anything());
157
- });
158
- it('orders by position even without any filters supplied', async ()=>{
159
- const qb = createMockQueryBuilder();
160
- await service.getFilterQuery(qb, {}, buildMockUser());
161
- expect(qb.orderBy).toHaveBeenCalledWith('task_list.position', 'ASC');
162
- });
163
- });
164
- describe('reorderLists', ()=>{
165
- it('assigns positions in steps of 100 following the given order', async ()=>{
166
- await service.reorderLists({
167
- boardId: 'board-1',
168
- orderedIds: [
169
- 'list-a',
170
- 'list-b',
171
- 'list-c'
172
- ]
173
- });
174
- expect(mockRepo.update).toHaveBeenNthCalledWith(1, {
175
- id: 'list-a',
176
- boardId: 'board-1'
177
- }, {
178
- position: 0
179
- });
180
- expect(mockRepo.update).toHaveBeenNthCalledWith(2, {
181
- id: 'list-b',
182
- boardId: 'board-1'
183
- }, {
184
- position: 100
185
- });
186
- expect(mockRepo.update).toHaveBeenNthCalledWith(3, {
187
- id: 'list-c',
188
- boardId: 'board-1'
189
- }, {
190
- position: 200
191
- });
192
- });
193
- it('invalidates the list cache, each reordered entry, and the parent board cache', async ()=>{
194
- await service.reorderLists({
195
- boardId: 'board-1',
196
- orderedIds: [
197
- 'list-a',
198
- 'list-b'
199
- ]
200
- });
201
- expect(mockUtilsService.clearCache).toHaveBeenCalledWith('task_list', expect.anything());
202
- expect(mockUtilsService.clearCache).toHaveBeenCalledWith('task_list', expect.anything(), 'list-a');
203
- expect(mockUtilsService.clearCache).toHaveBeenCalledWith('task_list', expect.anything(), 'list-b');
204
- expect(mockUtilsService.clearCache).toHaveBeenCalledWith('task_board', expect.anything());
205
- });
206
- it('handles reordering a single-item list (position 0)', async ()=>{
207
- await service.reorderLists({
208
- boardId: 'board-1',
209
- orderedIds: [
210
- 'only-list'
211
- ]
212
- });
213
- expect(mockRepo.update).toHaveBeenCalledWith({
214
- id: 'only-list',
215
- boardId: 'board-1'
216
- }, {
217
- position: 0
218
- });
219
- expect(mockRepo.update).toHaveBeenCalledTimes(1);
220
- });
221
- it('does nothing (no updates) for an empty ordered list', async ()=>{
222
- await service.reorderLists({
223
- boardId: 'board-1',
224
- orderedIds: []
225
- });
226
- expect(mockRepo.update).not.toHaveBeenCalled();
227
- });
228
- });
229
- describe('domain events', ()=>{
230
- let publish;
231
- beforeEach(()=>{
232
- publish = jest.fn().mockResolvedValue(undefined);
233
- EventBusRegistry.reset();
234
- EventBusRegistry.setBus({
235
- publish,
236
- subscribe: jest.fn(),
237
- close: jest.fn()
238
- });
239
- EventBusRegistry.configureModule('task-manager', {
240
- enabled: true
241
- });
242
- });
243
- afterEach(()=>{
244
- EventBusRegistry.reset();
245
- });
246
- it('publishes task-manager.task_list.reordered with every list id', async ()=>{
247
- await service.reorderLists({
248
- boardId: 'board-1',
249
- orderedIds: [
250
- 'list-a',
251
- 'list-b'
252
- ]
253
- });
254
- expect(publish).toHaveBeenCalledWith(expect.objectContaining({
255
- name: 'task-manager.task_list.reordered',
256
- payload: expect.objectContaining({
257
- ids: [
258
- 'list-a',
259
- 'list-b'
260
- ],
261
- metadata: {
262
- boardId: 'board-1'
263
- }
264
- })
265
- }));
266
- });
267
- it('does not publish when the task manager module has events disabled', async ()=>{
268
- EventBusRegistry.configureModule('task-manager', {
269
- enabled: false
270
- });
271
- await service.reorderLists({
272
- boardId: 'board-1',
273
- orderedIds: [
274
- 'list-a'
275
- ]
276
- });
277
- expect(publish).not.toHaveBeenCalled();
278
- });
279
- });
280
- });
@@ -1,59 +0,0 @@
1
- function _define_property(obj, key, value) {
2
- if (key in obj) {
3
- Object.defineProperty(obj, key, {
4
- value: value,
5
- enumerable: true,
6
- configurable: true,
7
- writable: true
8
- });
9
- } else {
10
- obj[key] = value;
11
- }
12
- return obj;
13
- }
14
- function _ts_decorate(decorators, target, key, desc) {
15
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
16
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
17
- else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
18
- return c > 3 && r && Object.defineProperty(target, key, r), r;
19
- }
20
- function _ts_metadata(k, v) {
21
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
22
- }
23
- function _ts_param(paramIndex, decorator) {
24
- return function(target, key) {
25
- decorator(target, key, paramIndex);
26
- };
27
- }
28
- import { Inject, Injectable } from '@nestjs/common';
29
- import { TASK_MANAGER_MODULE_OPTIONS } from '../config/task-manager.constants';
30
- import { TaskManagerModuleOptions } from '../interfaces';
31
- export class TaskManagerConfigService {
32
- isCompanyFeatureEnabled() {
33
- return this.options.bootstrapAppConfig?.enableCompanyFeature ?? false;
34
- }
35
- getDatabaseMode() {
36
- return this.options.bootstrapAppConfig?.databaseMode ?? 'single';
37
- }
38
- isMultiTenant() {
39
- return this.getDatabaseMode() === 'multi-tenant';
40
- }
41
- getJwtSecret() {
42
- return this.options.config?.jwtSecret;
43
- }
44
- getOptions() {
45
- return this.options;
46
- }
47
- constructor(options){
48
- _define_property(this, "options", void 0);
49
- this.options = options;
50
- }
51
- }
52
- TaskManagerConfigService = _ts_decorate([
53
- Injectable(),
54
- _ts_param(0, Inject(TASK_MANAGER_MODULE_OPTIONS)),
55
- _ts_metadata("design:type", Function),
56
- _ts_metadata("design:paramtypes", [
57
- typeof TaskManagerModuleOptions === "undefined" ? Object : TaskManagerModuleOptions
58
- ])
59
- ], TaskManagerConfigService);
@@ -1,94 +0,0 @@
1
- import { TaskManagerConfigService } from './task-manager-config.service';
2
- function buildOptions(overrides = {}) {
3
- return {
4
- bootstrapAppConfig: undefined,
5
- config: undefined,
6
- ...overrides
7
- };
8
- }
9
- describe('TaskManagerConfigService', ()=>{
10
- describe('isCompanyFeatureEnabled', ()=>{
11
- it('returns true when bootstrapAppConfig enables the company feature', ()=>{
12
- const service = new TaskManagerConfigService(buildOptions({
13
- bootstrapAppConfig: {
14
- databaseMode: 'single',
15
- enableCompanyFeature: true
16
- }
17
- }));
18
- expect(service.isCompanyFeatureEnabled()).toBe(true);
19
- });
20
- it('defaults to false when bootstrapAppConfig is missing', ()=>{
21
- const service = new TaskManagerConfigService(buildOptions({
22
- bootstrapAppConfig: undefined
23
- }));
24
- expect(service.isCompanyFeatureEnabled()).toBe(false);
25
- });
26
- it('defaults to false when enableCompanyFeature is not set on bootstrapAppConfig', ()=>{
27
- const service = new TaskManagerConfigService(buildOptions({
28
- bootstrapAppConfig: {}
29
- }));
30
- expect(service.isCompanyFeatureEnabled()).toBe(false);
31
- });
32
- });
33
- describe('getDatabaseMode', ()=>{
34
- it('returns the configured database mode', ()=>{
35
- const service = new TaskManagerConfigService(buildOptions({
36
- bootstrapAppConfig: {
37
- databaseMode: 'multi-tenant'
38
- }
39
- }));
40
- expect(service.getDatabaseMode()).toBe('multi-tenant');
41
- });
42
- it('defaults to "single" when bootstrapAppConfig is missing', ()=>{
43
- const service = new TaskManagerConfigService(buildOptions({
44
- bootstrapAppConfig: undefined
45
- }));
46
- expect(service.getDatabaseMode()).toBe('single');
47
- });
48
- });
49
- describe('isMultiTenant', ()=>{
50
- it('returns true when database mode is multi-tenant', ()=>{
51
- const service = new TaskManagerConfigService(buildOptions({
52
- bootstrapAppConfig: {
53
- databaseMode: 'multi-tenant'
54
- }
55
- }));
56
- expect(service.isMultiTenant()).toBe(true);
57
- });
58
- it('returns false when database mode is single', ()=>{
59
- const service = new TaskManagerConfigService(buildOptions({
60
- bootstrapAppConfig: {
61
- databaseMode: 'single'
62
- }
63
- }));
64
- expect(service.isMultiTenant()).toBe(false);
65
- });
66
- });
67
- describe('getJwtSecret', ()=>{
68
- it('returns the configured jwt secret', ()=>{
69
- const service = new TaskManagerConfigService(buildOptions({
70
- config: {
71
- jwtSecret: 'super-secret'
72
- }
73
- }));
74
- expect(service.getJwtSecret()).toBe('super-secret');
75
- });
76
- it('returns undefined when config is missing', ()=>{
77
- const service = new TaskManagerConfigService(buildOptions({
78
- config: undefined
79
- }));
80
- expect(service.getJwtSecret()).toBeUndefined();
81
- });
82
- });
83
- describe('getOptions', ()=>{
84
- it('returns the exact options object it was constructed with', ()=>{
85
- const options = buildOptions({
86
- config: {
87
- jwtSecret: 'secret'
88
- }
89
- });
90
- const service = new TaskManagerConfigService(options);
91
- expect(service.getOptions()).toBe(options);
92
- });
93
- });
94
- });
@@ -1,74 +0,0 @@
1
- function _define_property(obj, key, value) {
2
- if (key in obj) {
3
- Object.defineProperty(obj, key, {
4
- value: value,
5
- enumerable: true,
6
- configurable: true,
7
- writable: true
8
- });
9
- } else {
10
- obj[key] = value;
11
- }
12
- return obj;
13
- }
14
- function _ts_decorate(decorators, target, key, desc) {
15
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
16
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
17
- else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
18
- return c > 3 && r && Object.defineProperty(target, key, r), r;
19
- }
20
- function _ts_metadata(k, v) {
21
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
22
- }
23
- function _ts_param(paramIndex, decorator) {
24
- return function(target, key) {
25
- decorator(target, key, paramIndex);
26
- };
27
- }
28
- import { MultiTenantDataSourceService } from '@flusys/nestjs-shared/modules';
29
- import { Inject, Injectable, Optional, Scope } from '@nestjs/common';
30
- import { REQUEST } from '@nestjs/core';
31
- import { Request } from 'express';
32
- import { getTaskManagerEntitiesByConfig } from '../entities';
33
- import { TaskManagerConfigService } from './task-manager-config.service';
34
- export class TaskManagerDataSourceProvider extends MultiTenantDataSourceService {
35
- static buildParentOptions(options) {
36
- return {
37
- bootstrapAppConfig: options.bootstrapAppConfig,
38
- defaultDatabaseConfig: options.config?.defaultDatabaseConfig,
39
- tenantDefaultDatabaseConfig: options.config?.tenantDefaultDatabaseConfig,
40
- tenants: options.config?.tenants
41
- };
42
- }
43
- getEnableCompanyFeatureForTenant(tenant) {
44
- return tenant?.enableCompanyFeature ?? this.configService.isCompanyFeatureEnabled();
45
- }
46
- async createDataSourceFromConfig(config) {
47
- const currentTenant = this.getCurrentTenant();
48
- const enableCompanyFeature = this.getEnableCompanyFeatureForTenant(currentTenant ?? undefined);
49
- const entities = getTaskManagerEntitiesByConfig(enableCompanyFeature);
50
- return super.createDataSourceFromConfig(config, entities);
51
- }
52
- constructor(configService, request){
53
- super(TaskManagerDataSourceProvider.buildParentOptions(configService.getOptions()), request), _define_property(this, "configService", void 0), this.configService = configService;
54
- }
55
- }
56
- _define_property(TaskManagerDataSourceProvider, "tenantConnections", new Map());
57
- _define_property(TaskManagerDataSourceProvider, "singleDataSource", null);
58
- _define_property(TaskManagerDataSourceProvider, "tenantsRegistry", new Map());
59
- _define_property(TaskManagerDataSourceProvider, "initialized", false);
60
- _define_property(TaskManagerDataSourceProvider, "connectionLocks", new Map());
61
- _define_property(TaskManagerDataSourceProvider, "singleConnectionLock", null);
62
- TaskManagerDataSourceProvider = _ts_decorate([
63
- Injectable({
64
- scope: Scope.REQUEST
65
- }),
66
- _ts_param(0, Inject(TaskManagerConfigService)),
67
- _ts_param(1, Optional()),
68
- _ts_param(1, Inject(REQUEST)),
69
- _ts_metadata("design:type", Function),
70
- _ts_metadata("design:paramtypes", [
71
- typeof TaskManagerConfigService === "undefined" ? Object : TaskManagerConfigService,
72
- typeof Request === "undefined" ? Object : Request
73
- ])
74
- ], TaskManagerDataSourceProvider);
@@ -1,127 +0,0 @@
1
- import { TaskManagerDataSourceProvider } from './task-manager-datasource.provider';
2
- import { getTaskManagerEntitiesByConfig } from '../entities';
3
- function buildConfigService(overrides = {}) {
4
- const options = {
5
- bootstrapAppConfig: {
6
- databaseMode: 'single',
7
- enableCompanyFeature: false
8
- },
9
- config: {
10
- defaultDatabaseConfig: {
11
- type: 'mysql',
12
- host: 'localhost',
13
- port: 3306,
14
- username: 'root',
15
- password: 'password',
16
- database: 'flusys_test'
17
- },
18
- tenantDefaultDatabaseConfig: undefined,
19
- tenants: []
20
- },
21
- ...overrides
22
- };
23
- return {
24
- getOptions: jest.fn().mockReturnValue(options),
25
- isCompanyFeatureEnabled: jest.fn().mockReturnValue(options.bootstrapAppConfig?.enableCompanyFeature ?? false)
26
- };
27
- }
28
- describe('TaskManagerDataSourceProvider', ()=>{
29
- afterEach(()=>{
30
- // Reset the isolated static caches this provider owns so tests don't leak state between runs.
31
- TaskManagerDataSourceProvider.tenantConnections = new Map();
32
- TaskManagerDataSourceProvider.singleDataSource = null;
33
- TaskManagerDataSourceProvider.tenantsRegistry = new Map();
34
- TaskManagerDataSourceProvider.initialized = false;
35
- TaskManagerDataSourceProvider.connectionLocks = new Map();
36
- TaskManagerDataSourceProvider.singleConnectionLock = null;
37
- });
38
- it('constructs and forwards bootstrap/database config to the base class', ()=>{
39
- const configService = buildConfigService();
40
- const provider = new TaskManagerDataSourceProvider(configService, undefined);
41
- expect(configService.getOptions).toHaveBeenCalled();
42
- expect(provider.getDatabaseMode()).toBe('single');
43
- expect(provider.isMultiTenant()).toBe(false);
44
- });
45
- it('reflects multi-tenant mode from bootstrapAppConfig', ()=>{
46
- const configService = buildConfigService({
47
- bootstrapAppConfig: {
48
- databaseMode: 'multi-tenant'
49
- }
50
- });
51
- const provider = new TaskManagerDataSourceProvider(configService, undefined);
52
- expect(provider.isMultiTenant()).toBe(true);
53
- });
54
- it('owns isolated static caches, separate from the base MultiTenantDataSourceService', ()=>{
55
- // Each DataSource Provider must override BOTH static cache properties and the
56
- // methods that read them — otherwise modules would share connections (see FLUSYS_NEST/CLAUDE.md).
57
- expect(TaskManagerDataSourceProvider.tenantConnections).not.toBe(Object.getPrototypeOf(TaskManagerDataSourceProvider).tenantConnections);
58
- });
59
- describe('getEnableCompanyFeatureForTenant', ()=>{
60
- it('uses the tenant override when the tenant explicitly sets enableCompanyFeature', ()=>{
61
- const configService = buildConfigService();
62
- configService.isCompanyFeatureEnabled.mockReturnValue(false);
63
- const provider = new TaskManagerDataSourceProvider(configService, undefined);
64
- const result = provider.getEnableCompanyFeatureForTenant({
65
- enableCompanyFeature: true
66
- });
67
- expect(result).toBe(true);
68
- });
69
- it('falls back to the module-level config when tenant does not override', ()=>{
70
- const configService = buildConfigService();
71
- configService.isCompanyFeatureEnabled.mockReturnValue(true);
72
- const provider = new TaskManagerDataSourceProvider(configService, undefined);
73
- const result = provider.getEnableCompanyFeatureForTenant(undefined);
74
- expect(result).toBe(true);
75
- expect(configService.isCompanyFeatureEnabled).toHaveBeenCalled();
76
- });
77
- it('falls back to module-level config when tenant is provided without the flag set', ()=>{
78
- const configService = buildConfigService();
79
- configService.isCompanyFeatureEnabled.mockReturnValue(false);
80
- const provider = new TaskManagerDataSourceProvider(configService, undefined);
81
- const result = provider.getEnableCompanyFeatureForTenant({});
82
- expect(result).toBe(false);
83
- });
84
- });
85
- describe('createDataSourceFromConfig', ()=>{
86
- it('builds a DataSource using core entities when company feature is disabled', async ()=>{
87
- const configService = buildConfigService();
88
- configService.isCompanyFeatureEnabled.mockReturnValue(false);
89
- const provider = new TaskManagerDataSourceProvider(configService, undefined);
90
- const fakeDataSource = {
91
- isInitialized: true
92
- };
93
- const superSpy = jest.spyOn(Object.getPrototypeOf(Object.getPrototypeOf(provider)), 'createDataSourceFromConfig').mockResolvedValue(fakeDataSource);
94
- const config = {
95
- type: 'mysql',
96
- host: 'localhost',
97
- port: 3306,
98
- username: 'root',
99
- password: 'password',
100
- database: 'flusys_test'
101
- };
102
- const result = await provider.createDataSourceFromConfig(config);
103
- expect(superSpy).toHaveBeenCalledWith(config, getTaskManagerEntitiesByConfig(false));
104
- expect(result).toBe(fakeDataSource);
105
- superSpy.mockRestore();
106
- });
107
- it('builds a DataSource using company entities when the tenant enables the company feature', async ()=>{
108
- const configService = buildConfigService();
109
- configService.isCompanyFeatureEnabled.mockReturnValue(false);
110
- const provider = new TaskManagerDataSourceProvider(configService, undefined);
111
- jest.spyOn(provider, 'getCurrentTenant').mockReturnValue({
112
- enableCompanyFeature: true
113
- });
114
- const fakeDataSource = {
115
- isInitialized: true
116
- };
117
- const superSpy = jest.spyOn(Object.getPrototypeOf(Object.getPrototypeOf(provider)), 'createDataSourceFromConfig').mockResolvedValue(fakeDataSource);
118
- const config = {
119
- type: 'mysql',
120
- database: 'flusys_test'
121
- };
122
- await provider.createDataSourceFromConfig(config);
123
- expect(superSpy).toHaveBeenCalledWith(config, getTaskManagerEntitiesByConfig(true));
124
- superSpy.mockRestore();
125
- });
126
- });
127
- });