@flusys/nestjs-task-manager 6.3.0 → 6.4.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.
- package/cjs/services/task-board.service.js +17 -2
- package/cjs/services/task-board.service.spec.js +38 -5
- package/cjs/services/task-list.service.js +10 -1
- package/cjs/services/task-list.service.spec.js +23 -5
- package/cjs/services/task.service.js +16 -0
- package/cjs/services/task.service.spec.js +29 -5
- package/fesm/services/task-board.service.js +17 -2
- package/fesm/services/task-board.service.spec.js +38 -5
- package/fesm/services/task-list.service.js +10 -1
- package/fesm/services/task-list.service.spec.js +23 -5
- package/fesm/services/task.service.js +16 -0
- package/fesm/services/task.service.spec.js +29 -5
- package/package.json +3 -3
- package/services/task-board.service.d.ts +1 -0
- package/services/task-list.service.d.ts +1 -0
- package/services/task.service.d.ts +1 -0
|
@@ -142,11 +142,13 @@ let TaskBoardService = class TaskBoardService extends _classes.ApiService {
|
|
|
142
142
|
}, {
|
|
143
143
|
role: dto.role ?? _entities.BoardMemberRole.MEMBER
|
|
144
144
|
});
|
|
145
|
-
|
|
145
|
+
const restored = await memberRepo.findOne({
|
|
146
146
|
where: {
|
|
147
147
|
id: softDeleted.id
|
|
148
148
|
}
|
|
149
149
|
});
|
|
150
|
+
await this.invalidateBoardCache(dto.boardId);
|
|
151
|
+
return restored;
|
|
150
152
|
}
|
|
151
153
|
const member = memberRepo.create({
|
|
152
154
|
boardId: dto.boardId,
|
|
@@ -154,7 +156,9 @@ let TaskBoardService = class TaskBoardService extends _classes.ApiService {
|
|
|
154
156
|
role: dto.role ?? _entities.BoardMemberRole.MEMBER,
|
|
155
157
|
createdById: user.id
|
|
156
158
|
});
|
|
157
|
-
|
|
159
|
+
const saved = await memberRepo.save(member);
|
|
160
|
+
await this.invalidateBoardCache(dto.boardId);
|
|
161
|
+
return saved;
|
|
158
162
|
}
|
|
159
163
|
async removeMember(dto) {
|
|
160
164
|
const memberRepo = await this.ensureMemberRepository();
|
|
@@ -174,6 +178,7 @@ let TaskBoardService = class TaskBoardService extends _classes.ApiService {
|
|
|
174
178
|
}, {
|
|
175
179
|
deletedAt: new Date()
|
|
176
180
|
});
|
|
181
|
+
await this.invalidateBoardCache(dto.boardId);
|
|
177
182
|
}
|
|
178
183
|
async getMembers(boardId) {
|
|
179
184
|
const memberRepo = await this.ensureMemberRepository();
|
|
@@ -207,6 +212,16 @@ let TaskBoardService = class TaskBoardService extends _classes.ApiService {
|
|
|
207
212
|
createdById: user.id
|
|
208
213
|
})));
|
|
209
214
|
}
|
|
215
|
+
async invalidateBoardCache(boardId) {
|
|
216
|
+
await Promise.all([
|
|
217
|
+
this.clearCacheForAll(),
|
|
218
|
+
this.clearCacheForId([
|
|
219
|
+
{
|
|
220
|
+
id: boardId
|
|
221
|
+
}
|
|
222
|
+
])
|
|
223
|
+
]);
|
|
224
|
+
}
|
|
210
225
|
async ensureMemberRepository() {
|
|
211
226
|
if (!this.memberRepository) {
|
|
212
227
|
this.memberRepository = await this.dataSourceProvider.getRepository(_entities.TaskBoardMember);
|
|
@@ -57,6 +57,15 @@ function mockQueryRunner(repo) {
|
|
|
57
57
|
return qr;
|
|
58
58
|
}
|
|
59
59
|
describe('TaskBoardService', ()=>{
|
|
60
|
+
const mockUtilsService = {
|
|
61
|
+
clearCache: jest.fn(),
|
|
62
|
+
getCacheKey: jest.fn().mockReturnValue('cache-key'),
|
|
63
|
+
trackCacheKey: jest.fn()
|
|
64
|
+
};
|
|
65
|
+
// Call history is shared across tests in this file; reset it so each cache assertion is exact.
|
|
66
|
+
beforeEach(()=>{
|
|
67
|
+
mockUtilsService.clearCache.mockClear();
|
|
68
|
+
});
|
|
60
69
|
let service;
|
|
61
70
|
let mockRepo;
|
|
62
71
|
let mockMemberRepo;
|
|
@@ -84,11 +93,7 @@ describe('TaskBoardService', ()=>{
|
|
|
84
93
|
},
|
|
85
94
|
{
|
|
86
95
|
provide: _modules.UtilsService,
|
|
87
|
-
useValue:
|
|
88
|
-
clearCache: jest.fn(),
|
|
89
|
-
getCacheKey: jest.fn(),
|
|
90
|
-
trackCacheKey: jest.fn()
|
|
91
|
-
}
|
|
96
|
+
useValue: mockUtilsService
|
|
92
97
|
},
|
|
93
98
|
{
|
|
94
99
|
provide: _taskmanagerconfigservice.TaskManagerConfigService,
|
|
@@ -264,6 +269,23 @@ describe('TaskBoardService', ()=>{
|
|
|
264
269
|
}));
|
|
265
270
|
expect(result).toEqual(created);
|
|
266
271
|
});
|
|
272
|
+
it('invalidates the board list cache and the board entry (membership drives visibility and total_members)', async ()=>{
|
|
273
|
+
mockRepo.findOne.mockResolvedValue(buildBoard({
|
|
274
|
+
id: 'board-1'
|
|
275
|
+
}));
|
|
276
|
+
mockMemberRepo.findOne.mockResolvedValueOnce(null);
|
|
277
|
+
mockMemberRepo.findOne.mockResolvedValueOnce(null);
|
|
278
|
+
mockMemberRepo.create.mockImplementation((data)=>data);
|
|
279
|
+
mockMemberRepo.save.mockResolvedValue(buildMember({
|
|
280
|
+
boardId: 'board-1'
|
|
281
|
+
}));
|
|
282
|
+
await service.addMember({
|
|
283
|
+
boardId: 'board-1',
|
|
284
|
+
userId: 'user-2'
|
|
285
|
+
}, (0, _loggedusermock.buildMockUser)());
|
|
286
|
+
expect(mockUtilsService.clearCache).toHaveBeenCalledWith('task_board', expect.anything());
|
|
287
|
+
expect(mockUtilsService.clearCache).toHaveBeenCalledWith('task_board', expect.anything(), 'board-1');
|
|
288
|
+
});
|
|
267
289
|
it('defaults the role to MEMBER when none is given', async ()=>{
|
|
268
290
|
mockRepo.findOne.mockResolvedValue(buildBoard({
|
|
269
291
|
id: 'board-1'
|
|
@@ -347,6 +369,17 @@ describe('TaskBoardService', ()=>{
|
|
|
347
369
|
deletedAt: expect.any(Date)
|
|
348
370
|
});
|
|
349
371
|
});
|
|
372
|
+
it('invalidates the board list cache and the board entry', async ()=>{
|
|
373
|
+
mockMemberRepo.findOne.mockResolvedValue(buildMember({
|
|
374
|
+
id: 'member-1'
|
|
375
|
+
}));
|
|
376
|
+
await service.removeMember({
|
|
377
|
+
boardId: 'board-1',
|
|
378
|
+
userId: 'user-uuid-1'
|
|
379
|
+
});
|
|
380
|
+
expect(mockUtilsService.clearCache).toHaveBeenCalledWith('task_board', expect.anything());
|
|
381
|
+
expect(mockUtilsService.clearCache).toHaveBeenCalledWith('task_board', expect.anything(), 'board-1');
|
|
382
|
+
});
|
|
350
383
|
it('throws NotFoundException when the member is not found', async ()=>{
|
|
351
384
|
mockMemberRepo.findOne.mockResolvedValue(null);
|
|
352
385
|
await expect(service.removeMember({
|
|
@@ -42,6 +42,10 @@ function _ts_param(paramIndex, decorator) {
|
|
|
42
42
|
};
|
|
43
43
|
}
|
|
44
44
|
let TaskListService = class TaskListService extends _classes.ApiService {
|
|
45
|
+
/** Boards cache denormalized `total_tasks` / `total_lists`, so board caches go stale on any write here. */ async clearCacheForAll() {
|
|
46
|
+
await super.clearCacheForAll();
|
|
47
|
+
await this.utilsService.clearCache('task_board', this.cacheManager);
|
|
48
|
+
}
|
|
45
49
|
async convertSingleDtoToEntity(dto, _user) {
|
|
46
50
|
await this.ensureDataSourceRepository();
|
|
47
51
|
let entity = {};
|
|
@@ -93,7 +97,12 @@ let TaskListService = class TaskListService extends _classes.ApiService {
|
|
|
93
97
|
}, {
|
|
94
98
|
position: i * 100
|
|
95
99
|
})));
|
|
96
|
-
await
|
|
100
|
+
await Promise.all([
|
|
101
|
+
this.clearCacheForAll(),
|
|
102
|
+
this.clearCacheForId(dto.orderedIds.map((id)=>({
|
|
103
|
+
id
|
|
104
|
+
})))
|
|
105
|
+
]);
|
|
97
106
|
}
|
|
98
107
|
constructor(cacheManager, utilsService, dataSourceProvider){
|
|
99
108
|
super('task_list', cacheManager, utilsService, TaskListService.name, true, 'task-manager', _entities.TaskList, dataSourceProvider), _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;
|
|
@@ -41,6 +41,15 @@ function mockQueryRunner(repo) {
|
|
|
41
41
|
return qr;
|
|
42
42
|
}
|
|
43
43
|
describe('TaskListService', ()=>{
|
|
44
|
+
const mockUtilsService = {
|
|
45
|
+
clearCache: jest.fn(),
|
|
46
|
+
getCacheKey: jest.fn().mockReturnValue('cache-key'),
|
|
47
|
+
trackCacheKey: jest.fn()
|
|
48
|
+
};
|
|
49
|
+
// Call history is shared across tests in this file; reset it so each cache assertion is exact.
|
|
50
|
+
beforeEach(()=>{
|
|
51
|
+
mockUtilsService.clearCache.mockClear();
|
|
52
|
+
});
|
|
44
53
|
let service;
|
|
45
54
|
let mockRepo;
|
|
46
55
|
beforeEach(async ()=>{
|
|
@@ -55,11 +64,7 @@ describe('TaskListService', ()=>{
|
|
|
55
64
|
},
|
|
56
65
|
{
|
|
57
66
|
provide: _modules.UtilsService,
|
|
58
|
-
useValue:
|
|
59
|
-
clearCache: jest.fn(),
|
|
60
|
-
getCacheKey: jest.fn(),
|
|
61
|
-
trackCacheKey: jest.fn()
|
|
62
|
-
}
|
|
67
|
+
useValue: mockUtilsService
|
|
63
68
|
},
|
|
64
69
|
{
|
|
65
70
|
provide: _taskmanagerdatasourceprovider.TaskManagerDataSourceProvider,
|
|
@@ -188,6 +193,19 @@ describe('TaskListService', ()=>{
|
|
|
188
193
|
position: 200
|
|
189
194
|
});
|
|
190
195
|
});
|
|
196
|
+
it('invalidates the list cache, each reordered entry, and the parent board cache', async ()=>{
|
|
197
|
+
await service.reorderLists({
|
|
198
|
+
boardId: 'board-1',
|
|
199
|
+
orderedIds: [
|
|
200
|
+
'list-a',
|
|
201
|
+
'list-b'
|
|
202
|
+
]
|
|
203
|
+
});
|
|
204
|
+
expect(mockUtilsService.clearCache).toHaveBeenCalledWith('task_list', expect.anything());
|
|
205
|
+
expect(mockUtilsService.clearCache).toHaveBeenCalledWith('task_list', expect.anything(), 'list-a');
|
|
206
|
+
expect(mockUtilsService.clearCache).toHaveBeenCalledWith('task_list', expect.anything(), 'list-b');
|
|
207
|
+
expect(mockUtilsService.clearCache).toHaveBeenCalledWith('task_board', expect.anything());
|
|
208
|
+
});
|
|
191
209
|
it('handles reordering a single-item list (position 0)', async ()=>{
|
|
192
210
|
await service.reorderLists({
|
|
193
211
|
boardId: 'board-1',
|
|
@@ -45,6 +45,10 @@ function _ts_param(paramIndex, decorator) {
|
|
|
45
45
|
};
|
|
46
46
|
}
|
|
47
47
|
let TaskService = class TaskService extends _classes.ApiService {
|
|
48
|
+
/** Boards cache denormalized `total_tasks` / `total_lists`, so board caches go stale on any write here. */ async clearCacheForAll() {
|
|
49
|
+
await super.clearCacheForAll();
|
|
50
|
+
await this.utilsService.clearCache('task_board', this.cacheManager);
|
|
51
|
+
}
|
|
48
52
|
async convertSingleDtoToEntity(dto, user) {
|
|
49
53
|
await this.ensureDataSourceRepository();
|
|
50
54
|
let entity = {};
|
|
@@ -185,6 +189,12 @@ let TaskService = class TaskService extends _classes.ApiService {
|
|
|
185
189
|
}, {
|
|
186
190
|
listId: dto.targetListId
|
|
187
191
|
});
|
|
192
|
+
await Promise.all([
|
|
193
|
+
this.clearCacheForAll(),
|
|
194
|
+
this.clearCacheForId([
|
|
195
|
+
saved
|
|
196
|
+
])
|
|
197
|
+
]);
|
|
188
198
|
return saved;
|
|
189
199
|
}
|
|
190
200
|
async assignTask(dto, user) {
|
|
@@ -221,6 +231,12 @@ let TaskService = class TaskService extends _classes.ApiService {
|
|
|
221
231
|
}, {
|
|
222
232
|
assigneeIds: dto.assigneeIds
|
|
223
233
|
});
|
|
234
|
+
await Promise.all([
|
|
235
|
+
this.clearCacheForAll(),
|
|
236
|
+
this.clearCacheForId([
|
|
237
|
+
saved
|
|
238
|
+
])
|
|
239
|
+
]);
|
|
224
240
|
saved.assignees = await this.loadAssigneesForTask(saved.id);
|
|
225
241
|
return saved;
|
|
226
242
|
}
|
|
@@ -49,6 +49,15 @@ function mockQueryRunner(repo) {
|
|
|
49
49
|
return qr;
|
|
50
50
|
}
|
|
51
51
|
describe('TaskService', ()=>{
|
|
52
|
+
const mockUtilsService = {
|
|
53
|
+
clearCache: jest.fn(),
|
|
54
|
+
getCacheKey: jest.fn().mockReturnValue('cache-key'),
|
|
55
|
+
trackCacheKey: jest.fn()
|
|
56
|
+
};
|
|
57
|
+
// Call history is shared across tests in this file; reset it so each cache assertion is exact.
|
|
58
|
+
beforeEach(()=>{
|
|
59
|
+
mockUtilsService.clearCache.mockClear();
|
|
60
|
+
});
|
|
52
61
|
let service;
|
|
53
62
|
let mockRepo;
|
|
54
63
|
let mockLabelJunctionRepo;
|
|
@@ -96,11 +105,7 @@ describe('TaskService', ()=>{
|
|
|
96
105
|
},
|
|
97
106
|
{
|
|
98
107
|
provide: _modules.UtilsService,
|
|
99
|
-
useValue:
|
|
100
|
-
clearCache: jest.fn(),
|
|
101
|
-
getCacheKey: jest.fn(),
|
|
102
|
-
trackCacheKey: jest.fn()
|
|
103
|
-
}
|
|
108
|
+
useValue: mockUtilsService
|
|
104
109
|
},
|
|
105
110
|
{
|
|
106
111
|
provide: _taskmanagerdatasourceprovider.TaskManagerDataSourceProvider,
|
|
@@ -368,6 +373,25 @@ describe('TaskService', ()=>{
|
|
|
368
373
|
listId: 'list-new'
|
|
369
374
|
});
|
|
370
375
|
});
|
|
376
|
+
it('invalidates the task list cache and the moved task entry', async ()=>{
|
|
377
|
+
const task = buildTask({
|
|
378
|
+
id: 'task-1',
|
|
379
|
+
listId: 'list-old',
|
|
380
|
+
boardId: 'board-1'
|
|
381
|
+
});
|
|
382
|
+
mockRepo.findOne.mockResolvedValue(task);
|
|
383
|
+
mockRepo.save.mockImplementation((t)=>Promise.resolve(t));
|
|
384
|
+
await service.moveTask({
|
|
385
|
+
taskId: 'task-1',
|
|
386
|
+
targetListId: 'list-new',
|
|
387
|
+
position: 1
|
|
388
|
+
}, (0, _loggedusermock.buildMockUser)({
|
|
389
|
+
id: 'mover-1'
|
|
390
|
+
}));
|
|
391
|
+
expect(mockUtilsService.clearCache).toHaveBeenCalledWith('task', expect.anything());
|
|
392
|
+
expect(mockUtilsService.clearCache).toHaveBeenCalledWith('task', expect.anything(), 'task-1');
|
|
393
|
+
expect(mockUtilsService.clearCache).toHaveBeenCalledWith('task_board', expect.anything());
|
|
394
|
+
});
|
|
371
395
|
it('defaults position to 0 (first position) when no position is given', async ()=>{
|
|
372
396
|
const task = buildTask({
|
|
373
397
|
id: 'task-1',
|
|
@@ -132,11 +132,13 @@ export class TaskBoardService extends ApiService {
|
|
|
132
132
|
}, {
|
|
133
133
|
role: dto.role ?? BoardMemberRole.MEMBER
|
|
134
134
|
});
|
|
135
|
-
|
|
135
|
+
const restored = await memberRepo.findOne({
|
|
136
136
|
where: {
|
|
137
137
|
id: softDeleted.id
|
|
138
138
|
}
|
|
139
139
|
});
|
|
140
|
+
await this.invalidateBoardCache(dto.boardId);
|
|
141
|
+
return restored;
|
|
140
142
|
}
|
|
141
143
|
const member = memberRepo.create({
|
|
142
144
|
boardId: dto.boardId,
|
|
@@ -144,7 +146,9 @@ export class TaskBoardService extends ApiService {
|
|
|
144
146
|
role: dto.role ?? BoardMemberRole.MEMBER,
|
|
145
147
|
createdById: user.id
|
|
146
148
|
});
|
|
147
|
-
|
|
149
|
+
const saved = await memberRepo.save(member);
|
|
150
|
+
await this.invalidateBoardCache(dto.boardId);
|
|
151
|
+
return saved;
|
|
148
152
|
}
|
|
149
153
|
async removeMember(dto) {
|
|
150
154
|
const memberRepo = await this.ensureMemberRepository();
|
|
@@ -164,6 +168,7 @@ export class TaskBoardService extends ApiService {
|
|
|
164
168
|
}, {
|
|
165
169
|
deletedAt: new Date()
|
|
166
170
|
});
|
|
171
|
+
await this.invalidateBoardCache(dto.boardId);
|
|
167
172
|
}
|
|
168
173
|
async getMembers(boardId) {
|
|
169
174
|
const memberRepo = await this.ensureMemberRepository();
|
|
@@ -197,6 +202,16 @@ export class TaskBoardService extends ApiService {
|
|
|
197
202
|
createdById: user.id
|
|
198
203
|
})));
|
|
199
204
|
}
|
|
205
|
+
async invalidateBoardCache(boardId) {
|
|
206
|
+
await Promise.all([
|
|
207
|
+
this.clearCacheForAll(),
|
|
208
|
+
this.clearCacheForId([
|
|
209
|
+
{
|
|
210
|
+
id: boardId
|
|
211
|
+
}
|
|
212
|
+
])
|
|
213
|
+
]);
|
|
214
|
+
}
|
|
200
215
|
async ensureMemberRepository() {
|
|
201
216
|
if (!this.memberRepository) {
|
|
202
217
|
this.memberRepository = await this.dataSourceProvider.getRepository(TaskBoardMember);
|
|
@@ -53,6 +53,15 @@ function mockQueryRunner(repo) {
|
|
|
53
53
|
return qr;
|
|
54
54
|
}
|
|
55
55
|
describe('TaskBoardService', ()=>{
|
|
56
|
+
const mockUtilsService = {
|
|
57
|
+
clearCache: jest.fn(),
|
|
58
|
+
getCacheKey: jest.fn().mockReturnValue('cache-key'),
|
|
59
|
+
trackCacheKey: jest.fn()
|
|
60
|
+
};
|
|
61
|
+
// Call history is shared across tests in this file; reset it so each cache assertion is exact.
|
|
62
|
+
beforeEach(()=>{
|
|
63
|
+
mockUtilsService.clearCache.mockClear();
|
|
64
|
+
});
|
|
56
65
|
let service;
|
|
57
66
|
let mockRepo;
|
|
58
67
|
let mockMemberRepo;
|
|
@@ -80,11 +89,7 @@ describe('TaskBoardService', ()=>{
|
|
|
80
89
|
},
|
|
81
90
|
{
|
|
82
91
|
provide: UtilsService,
|
|
83
|
-
useValue:
|
|
84
|
-
clearCache: jest.fn(),
|
|
85
|
-
getCacheKey: jest.fn(),
|
|
86
|
-
trackCacheKey: jest.fn()
|
|
87
|
-
}
|
|
92
|
+
useValue: mockUtilsService
|
|
88
93
|
},
|
|
89
94
|
{
|
|
90
95
|
provide: TaskManagerConfigService,
|
|
@@ -260,6 +265,23 @@ describe('TaskBoardService', ()=>{
|
|
|
260
265
|
}));
|
|
261
266
|
expect(result).toEqual(created);
|
|
262
267
|
});
|
|
268
|
+
it('invalidates the board list cache and the board entry (membership drives visibility and total_members)', async ()=>{
|
|
269
|
+
mockRepo.findOne.mockResolvedValue(buildBoard({
|
|
270
|
+
id: 'board-1'
|
|
271
|
+
}));
|
|
272
|
+
mockMemberRepo.findOne.mockResolvedValueOnce(null);
|
|
273
|
+
mockMemberRepo.findOne.mockResolvedValueOnce(null);
|
|
274
|
+
mockMemberRepo.create.mockImplementation((data)=>data);
|
|
275
|
+
mockMemberRepo.save.mockResolvedValue(buildMember({
|
|
276
|
+
boardId: 'board-1'
|
|
277
|
+
}));
|
|
278
|
+
await service.addMember({
|
|
279
|
+
boardId: 'board-1',
|
|
280
|
+
userId: 'user-2'
|
|
281
|
+
}, buildMockUser());
|
|
282
|
+
expect(mockUtilsService.clearCache).toHaveBeenCalledWith('task_board', expect.anything());
|
|
283
|
+
expect(mockUtilsService.clearCache).toHaveBeenCalledWith('task_board', expect.anything(), 'board-1');
|
|
284
|
+
});
|
|
263
285
|
it('defaults the role to MEMBER when none is given', async ()=>{
|
|
264
286
|
mockRepo.findOne.mockResolvedValue(buildBoard({
|
|
265
287
|
id: 'board-1'
|
|
@@ -343,6 +365,17 @@ describe('TaskBoardService', ()=>{
|
|
|
343
365
|
deletedAt: expect.any(Date)
|
|
344
366
|
});
|
|
345
367
|
});
|
|
368
|
+
it('invalidates the board list cache and the board entry', async ()=>{
|
|
369
|
+
mockMemberRepo.findOne.mockResolvedValue(buildMember({
|
|
370
|
+
id: 'member-1'
|
|
371
|
+
}));
|
|
372
|
+
await service.removeMember({
|
|
373
|
+
boardId: 'board-1',
|
|
374
|
+
userId: 'user-uuid-1'
|
|
375
|
+
});
|
|
376
|
+
expect(mockUtilsService.clearCache).toHaveBeenCalledWith('task_board', expect.anything());
|
|
377
|
+
expect(mockUtilsService.clearCache).toHaveBeenCalledWith('task_board', expect.anything(), 'board-1');
|
|
378
|
+
});
|
|
346
379
|
it('throws NotFoundException when the member is not found', async ()=>{
|
|
347
380
|
mockMemberRepo.findOne.mockResolvedValue(null);
|
|
348
381
|
await expect(service.removeMember({
|
|
@@ -32,6 +32,10 @@ import { TASK_LIST_MESSAGES } from '../config';
|
|
|
32
32
|
import { TaskList } from '../entities';
|
|
33
33
|
import { TaskManagerDataSourceProvider } from './task-manager-datasource.provider';
|
|
34
34
|
export class TaskListService extends ApiService {
|
|
35
|
+
/** Boards cache denormalized `total_tasks` / `total_lists`, so board caches go stale on any write here. */ async clearCacheForAll() {
|
|
36
|
+
await super.clearCacheForAll();
|
|
37
|
+
await this.utilsService.clearCache('task_board', this.cacheManager);
|
|
38
|
+
}
|
|
35
39
|
async convertSingleDtoToEntity(dto, _user) {
|
|
36
40
|
await this.ensureDataSourceRepository();
|
|
37
41
|
let entity = {};
|
|
@@ -83,7 +87,12 @@ export class TaskListService extends ApiService {
|
|
|
83
87
|
}, {
|
|
84
88
|
position: i * 100
|
|
85
89
|
})));
|
|
86
|
-
await
|
|
90
|
+
await Promise.all([
|
|
91
|
+
this.clearCacheForAll(),
|
|
92
|
+
this.clearCacheForId(dto.orderedIds.map((id)=>({
|
|
93
|
+
id
|
|
94
|
+
})))
|
|
95
|
+
]);
|
|
87
96
|
}
|
|
88
97
|
constructor(cacheManager, utilsService, dataSourceProvider){
|
|
89
98
|
super('task_list', cacheManager, utilsService, TaskListService.name, true, 'task-manager', TaskList, dataSourceProvider), _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;
|
|
@@ -37,6 +37,15 @@ function mockQueryRunner(repo) {
|
|
|
37
37
|
return qr;
|
|
38
38
|
}
|
|
39
39
|
describe('TaskListService', ()=>{
|
|
40
|
+
const mockUtilsService = {
|
|
41
|
+
clearCache: jest.fn(),
|
|
42
|
+
getCacheKey: jest.fn().mockReturnValue('cache-key'),
|
|
43
|
+
trackCacheKey: jest.fn()
|
|
44
|
+
};
|
|
45
|
+
// Call history is shared across tests in this file; reset it so each cache assertion is exact.
|
|
46
|
+
beforeEach(()=>{
|
|
47
|
+
mockUtilsService.clearCache.mockClear();
|
|
48
|
+
});
|
|
40
49
|
let service;
|
|
41
50
|
let mockRepo;
|
|
42
51
|
beforeEach(async ()=>{
|
|
@@ -51,11 +60,7 @@ describe('TaskListService', ()=>{
|
|
|
51
60
|
},
|
|
52
61
|
{
|
|
53
62
|
provide: UtilsService,
|
|
54
|
-
useValue:
|
|
55
|
-
clearCache: jest.fn(),
|
|
56
|
-
getCacheKey: jest.fn(),
|
|
57
|
-
trackCacheKey: jest.fn()
|
|
58
|
-
}
|
|
63
|
+
useValue: mockUtilsService
|
|
59
64
|
},
|
|
60
65
|
{
|
|
61
66
|
provide: TaskManagerDataSourceProvider,
|
|
@@ -184,6 +189,19 @@ describe('TaskListService', ()=>{
|
|
|
184
189
|
position: 200
|
|
185
190
|
});
|
|
186
191
|
});
|
|
192
|
+
it('invalidates the list cache, each reordered entry, and the parent board cache', async ()=>{
|
|
193
|
+
await service.reorderLists({
|
|
194
|
+
boardId: 'board-1',
|
|
195
|
+
orderedIds: [
|
|
196
|
+
'list-a',
|
|
197
|
+
'list-b'
|
|
198
|
+
]
|
|
199
|
+
});
|
|
200
|
+
expect(mockUtilsService.clearCache).toHaveBeenCalledWith('task_list', expect.anything());
|
|
201
|
+
expect(mockUtilsService.clearCache).toHaveBeenCalledWith('task_list', expect.anything(), 'list-a');
|
|
202
|
+
expect(mockUtilsService.clearCache).toHaveBeenCalledWith('task_list', expect.anything(), 'list-b');
|
|
203
|
+
expect(mockUtilsService.clearCache).toHaveBeenCalledWith('task_board', expect.anything());
|
|
204
|
+
});
|
|
187
205
|
it('handles reordering a single-item list (position 0)', async ()=>{
|
|
188
206
|
await service.reorderLists({
|
|
189
207
|
boardId: 'board-1',
|
|
@@ -35,6 +35,10 @@ import { TaskActivityService } from './task-activity.service';
|
|
|
35
35
|
import { TaskBoardService } from './task-board.service';
|
|
36
36
|
import { TaskManagerDataSourceProvider } from './task-manager-datasource.provider';
|
|
37
37
|
export class TaskService extends ApiService {
|
|
38
|
+
/** Boards cache denormalized `total_tasks` / `total_lists`, so board caches go stale on any write here. */ async clearCacheForAll() {
|
|
39
|
+
await super.clearCacheForAll();
|
|
40
|
+
await this.utilsService.clearCache('task_board', this.cacheManager);
|
|
41
|
+
}
|
|
38
42
|
async convertSingleDtoToEntity(dto, user) {
|
|
39
43
|
await this.ensureDataSourceRepository();
|
|
40
44
|
let entity = {};
|
|
@@ -175,6 +179,12 @@ export class TaskService extends ApiService {
|
|
|
175
179
|
}, {
|
|
176
180
|
listId: dto.targetListId
|
|
177
181
|
});
|
|
182
|
+
await Promise.all([
|
|
183
|
+
this.clearCacheForAll(),
|
|
184
|
+
this.clearCacheForId([
|
|
185
|
+
saved
|
|
186
|
+
])
|
|
187
|
+
]);
|
|
178
188
|
return saved;
|
|
179
189
|
}
|
|
180
190
|
async assignTask(dto, user) {
|
|
@@ -211,6 +221,12 @@ export class TaskService extends ApiService {
|
|
|
211
221
|
}, {
|
|
212
222
|
assigneeIds: dto.assigneeIds
|
|
213
223
|
});
|
|
224
|
+
await Promise.all([
|
|
225
|
+
this.clearCacheForAll(),
|
|
226
|
+
this.clearCacheForId([
|
|
227
|
+
saved
|
|
228
|
+
])
|
|
229
|
+
]);
|
|
214
230
|
saved.assignees = await this.loadAssigneesForTask(saved.id);
|
|
215
231
|
return saved;
|
|
216
232
|
}
|
|
@@ -45,6 +45,15 @@ function mockQueryRunner(repo) {
|
|
|
45
45
|
return qr;
|
|
46
46
|
}
|
|
47
47
|
describe('TaskService', ()=>{
|
|
48
|
+
const mockUtilsService = {
|
|
49
|
+
clearCache: jest.fn(),
|
|
50
|
+
getCacheKey: jest.fn().mockReturnValue('cache-key'),
|
|
51
|
+
trackCacheKey: jest.fn()
|
|
52
|
+
};
|
|
53
|
+
// Call history is shared across tests in this file; reset it so each cache assertion is exact.
|
|
54
|
+
beforeEach(()=>{
|
|
55
|
+
mockUtilsService.clearCache.mockClear();
|
|
56
|
+
});
|
|
48
57
|
let service;
|
|
49
58
|
let mockRepo;
|
|
50
59
|
let mockLabelJunctionRepo;
|
|
@@ -92,11 +101,7 @@ describe('TaskService', ()=>{
|
|
|
92
101
|
},
|
|
93
102
|
{
|
|
94
103
|
provide: UtilsService,
|
|
95
|
-
useValue:
|
|
96
|
-
clearCache: jest.fn(),
|
|
97
|
-
getCacheKey: jest.fn(),
|
|
98
|
-
trackCacheKey: jest.fn()
|
|
99
|
-
}
|
|
104
|
+
useValue: mockUtilsService
|
|
100
105
|
},
|
|
101
106
|
{
|
|
102
107
|
provide: TaskManagerDataSourceProvider,
|
|
@@ -364,6 +369,25 @@ describe('TaskService', ()=>{
|
|
|
364
369
|
listId: 'list-new'
|
|
365
370
|
});
|
|
366
371
|
});
|
|
372
|
+
it('invalidates the task list cache and the moved task entry', async ()=>{
|
|
373
|
+
const task = buildTask({
|
|
374
|
+
id: 'task-1',
|
|
375
|
+
listId: 'list-old',
|
|
376
|
+
boardId: 'board-1'
|
|
377
|
+
});
|
|
378
|
+
mockRepo.findOne.mockResolvedValue(task);
|
|
379
|
+
mockRepo.save.mockImplementation((t)=>Promise.resolve(t));
|
|
380
|
+
await service.moveTask({
|
|
381
|
+
taskId: 'task-1',
|
|
382
|
+
targetListId: 'list-new',
|
|
383
|
+
position: 1
|
|
384
|
+
}, buildMockUser({
|
|
385
|
+
id: 'mover-1'
|
|
386
|
+
}));
|
|
387
|
+
expect(mockUtilsService.clearCache).toHaveBeenCalledWith('task', expect.anything());
|
|
388
|
+
expect(mockUtilsService.clearCache).toHaveBeenCalledWith('task', expect.anything(), 'task-1');
|
|
389
|
+
expect(mockUtilsService.clearCache).toHaveBeenCalledWith('task_board', expect.anything());
|
|
390
|
+
});
|
|
367
391
|
it('defaults position to 0 (first position) when no position is given', async ()=>{
|
|
368
392
|
const task = buildTask({
|
|
369
393
|
id: 'task-1',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flusys/nestjs-task-manager",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.4.1",
|
|
4
4
|
"description": "Task management module with dynamic boards and real-time collaboration",
|
|
5
5
|
"main": "cjs/index.js",
|
|
6
6
|
"module": "fesm/index.js",
|
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
"socket.io": "^4.8.0"
|
|
86
86
|
},
|
|
87
87
|
"dependencies": {
|
|
88
|
-
"@flusys/nestjs-core": "6.
|
|
89
|
-
"@flusys/nestjs-shared": "6.
|
|
88
|
+
"@flusys/nestjs-core": "6.4.1",
|
|
89
|
+
"@flusys/nestjs-shared": "6.4.1"
|
|
90
90
|
}
|
|
91
91
|
}
|
|
@@ -28,5 +28,6 @@ export declare class TaskBoardService extends ApiService<CreateTaskBoardDto, Upd
|
|
|
28
28
|
})[]>;
|
|
29
29
|
isBoardMember(boardId: string, userId: string): Promise<boolean>;
|
|
30
30
|
protected afterInsertOperation(entities: TaskBoardBase[], _dto: CreateTaskBoardDto | Array<CreateTaskBoardDto>, user: ILoggedUserInfo | null, _qr: any): Promise<void>;
|
|
31
|
+
private invalidateBoardCache;
|
|
31
32
|
private ensureMemberRepository;
|
|
32
33
|
}
|
|
@@ -10,6 +10,7 @@ export declare class TaskListService extends ApiService<CreateTaskListDto, Updat
|
|
|
10
10
|
protected utilsService: UtilsService;
|
|
11
11
|
private readonly dataSourceProvider;
|
|
12
12
|
constructor(cacheManager: HybridCache, utilsService: UtilsService, dataSourceProvider: TaskManagerDataSourceProvider);
|
|
13
|
+
clearCacheForAll(): Promise<void>;
|
|
13
14
|
convertSingleDtoToEntity(dto: CreateTaskListDto | UpdateTaskListDto, _user: ILoggedUserInfo | null): Promise<TaskList>;
|
|
14
15
|
protected getFilterQuery(query: SelectQueryBuilder<TaskList>, filter: Record<string, unknown>, _user: ILoggedUserInfo | null): Promise<{
|
|
15
16
|
query: SelectQueryBuilder<TaskList>;
|
|
@@ -20,6 +20,7 @@ export declare class TaskService extends ApiService<CreateTaskDto, UpdateTaskDto
|
|
|
20
20
|
private pendingLabelIds;
|
|
21
21
|
private pendingAssigneeIds;
|
|
22
22
|
constructor(cacheManager: HybridCache, utilsService: UtilsService, dataSourceProvider: TaskManagerDataSourceProvider, boardService: TaskBoardService, activityService: TaskActivityService);
|
|
23
|
+
clearCacheForAll(): Promise<void>;
|
|
23
24
|
convertSingleDtoToEntity(dto: CreateTaskDto | UpdateTaskDto, user: ILoggedUserInfo | null): Promise<Task>;
|
|
24
25
|
protected afterInsertOperation(entities: Task[], _dto: CreateTaskDto | Array<CreateTaskDto>, user: ILoggedUserInfo | null, _qr: QueryRunner): Promise<void>;
|
|
25
26
|
protected afterUpdateOperation(entities: Task[], _dto: UpdateTaskDto | Array<UpdateTaskDto>, _user: ILoggedUserInfo | null, _qr: QueryRunner): Promise<void>;
|