@flusys/nestjs-task-manager 5.3.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 (137) hide show
  1. package/README.md +245 -0
  2. package/cjs/config/index.js +19 -0
  3. package/cjs/config/message-keys.js +92 -0
  4. package/cjs/config/task-manager.constants.js +11 -0
  5. package/cjs/controllers/index.js +22 -0
  6. package/cjs/controllers/task-board.controller.js +219 -0
  7. package/cjs/controllers/task-comment.controller.js +103 -0
  8. package/cjs/controllers/task-label.controller.js +112 -0
  9. package/cjs/controllers/task-list.controller.js +158 -0
  10. package/cjs/controllers/task.controller.js +199 -0
  11. package/cjs/docs/index.js +18 -0
  12. package/cjs/docs/task-manager-swagger.config.js +43 -0
  13. package/cjs/dtos/index.js +22 -0
  14. package/cjs/dtos/task-board.dto.js +310 -0
  15. package/cjs/dtos/task-comment.dto.js +131 -0
  16. package/cjs/dtos/task-label.dto.js +120 -0
  17. package/cjs/dtos/task-list.dto.js +198 -0
  18. package/cjs/dtos/task.dto.js +367 -0
  19. package/cjs/entities/index.js +72 -0
  20. package/cjs/entities/task-activity.entity.js +98 -0
  21. package/cjs/entities/task-assignee.entity.js +67 -0
  22. package/cjs/entities/task-board-member.entity.js +94 -0
  23. package/cjs/entities/task-board-with-company.entity.js +57 -0
  24. package/cjs/entities/task-board.entity.js +88 -0
  25. package/cjs/entities/task-comment.entity.js +80 -0
  26. package/cjs/entities/task-label.entity.js +72 -0
  27. package/cjs/entities/task-list.entity.js +104 -0
  28. package/cjs/entities/task-task-label.entity.js +67 -0
  29. package/cjs/entities/task.entity.js +151 -0
  30. package/cjs/gateways/index.js +18 -0
  31. package/cjs/gateways/task-board.gateway.js +199 -0
  32. package/cjs/index.js +26 -0
  33. package/cjs/interfaces/index.js +19 -0
  34. package/cjs/interfaces/task-manager-module.interface.js +4 -0
  35. package/cjs/interfaces/task.interface.js +4 -0
  36. package/cjs/modules/index.js +18 -0
  37. package/cjs/modules/task-manager.module.js +131 -0
  38. package/cjs/services/index.js +25 -0
  39. package/cjs/services/task-activity.service.js +76 -0
  40. package/cjs/services/task-board.service.js +235 -0
  41. package/cjs/services/task-comment.service.js +103 -0
  42. package/cjs/services/task-label.service.js +97 -0
  43. package/cjs/services/task-list.service.js +115 -0
  44. package/cjs/services/task-manager-config.service.js +69 -0
  45. package/cjs/services/task-manager-datasource.provider.js +84 -0
  46. package/cjs/services/task.service.js +359 -0
  47. package/config/index.d.ts +2 -0
  48. package/config/message-keys.d.ts +65 -0
  49. package/config/task-manager.constants.d.ts +1 -0
  50. package/controllers/index.d.ts +5 -0
  51. package/controllers/task-board.controller.d.ts +27 -0
  52. package/controllers/task-comment.controller.d.ts +22 -0
  53. package/controllers/task-label.controller.d.ts +22 -0
  54. package/controllers/task-list.controller.d.ts +26 -0
  55. package/controllers/task.controller.d.ts +28 -0
  56. package/docs/index.d.ts +1 -0
  57. package/docs/task-manager-swagger.config.d.ts +2 -0
  58. package/dtos/index.d.ts +5 -0
  59. package/dtos/task-board.dto.d.ts +44 -0
  60. package/dtos/task-comment.dto.d.ts +16 -0
  61. package/dtos/task-label.dto.d.ts +16 -0
  62. package/dtos/task-list.dto.d.ts +26 -0
  63. package/dtos/task.dto.d.ts +48 -0
  64. package/entities/index.d.ts +22 -0
  65. package/entities/task-activity.entity.d.ts +9 -0
  66. package/entities/task-assignee.entity.d.ts +5 -0
  67. package/entities/task-board-member.entity.d.ts +12 -0
  68. package/entities/task-board-with-company.entity.d.ts +4 -0
  69. package/entities/task-board.entity.d.ts +8 -0
  70. package/entities/task-comment.entity.d.ts +7 -0
  71. package/entities/task-label.entity.d.ts +6 -0
  72. package/entities/task-list.entity.d.ts +9 -0
  73. package/entities/task-task-label.entity.d.ts +5 -0
  74. package/entities/task.entity.d.ts +23 -0
  75. package/fesm/config/index.js +2 -0
  76. package/fesm/config/message-keys.js +65 -0
  77. package/fesm/config/task-manager.constants.js +1 -0
  78. package/fesm/controllers/index.js +5 -0
  79. package/fesm/controllers/task-board.controller.js +209 -0
  80. package/fesm/controllers/task-comment.controller.js +93 -0
  81. package/fesm/controllers/task-label.controller.js +102 -0
  82. package/fesm/controllers/task-list.controller.js +148 -0
  83. package/fesm/controllers/task.controller.js +189 -0
  84. package/fesm/docs/index.js +1 -0
  85. package/fesm/docs/task-manager-swagger.config.js +33 -0
  86. package/fesm/dtos/index.js +5 -0
  87. package/fesm/dtos/task-board.dto.js +277 -0
  88. package/fesm/dtos/task-comment.dto.js +110 -0
  89. package/fesm/dtos/task-label.dto.js +99 -0
  90. package/fesm/dtos/task-list.dto.js +174 -0
  91. package/fesm/dtos/task.dto.js +337 -0
  92. package/fesm/entities/index.js +46 -0
  93. package/fesm/entities/task-activity.entity.js +88 -0
  94. package/fesm/entities/task-assignee.entity.js +57 -0
  95. package/fesm/entities/task-board-member.entity.js +76 -0
  96. package/fesm/entities/task-board-with-company.entity.js +47 -0
  97. package/fesm/entities/task-board.entity.js +78 -0
  98. package/fesm/entities/task-comment.entity.js +70 -0
  99. package/fesm/entities/task-label.entity.js +62 -0
  100. package/fesm/entities/task-list.entity.js +94 -0
  101. package/fesm/entities/task-task-label.entity.js +57 -0
  102. package/fesm/entities/task.entity.js +133 -0
  103. package/fesm/gateways/index.js +1 -0
  104. package/fesm/gateways/task-board.gateway.js +148 -0
  105. package/fesm/index.js +9 -0
  106. package/fesm/interfaces/index.js +2 -0
  107. package/fesm/interfaces/task-manager-module.interface.js +1 -0
  108. package/fesm/interfaces/task.interface.js +1 -0
  109. package/fesm/modules/index.js +1 -0
  110. package/fesm/modules/task-manager.module.js +121 -0
  111. package/fesm/services/index.js +8 -0
  112. package/fesm/services/task-activity.service.js +66 -0
  113. package/fesm/services/task-board.service.js +225 -0
  114. package/fesm/services/task-comment.service.js +93 -0
  115. package/fesm/services/task-label.service.js +87 -0
  116. package/fesm/services/task-list.service.js +105 -0
  117. package/fesm/services/task-manager-config.service.js +59 -0
  118. package/fesm/services/task-manager-datasource.provider.js +74 -0
  119. package/fesm/services/task.service.js +349 -0
  120. package/gateways/index.d.ts +1 -0
  121. package/gateways/task-board.gateway.d.ts +22 -0
  122. package/index.d.ts +9 -0
  123. package/interfaces/index.d.ts +2 -0
  124. package/interfaces/task-manager-module.interface.d.ts +19 -0
  125. package/interfaces/task.interface.d.ts +52 -0
  126. package/modules/index.d.ts +1 -0
  127. package/modules/task-manager.module.d.ts +8 -0
  128. package/package.json +91 -0
  129. package/services/index.d.ts +8 -0
  130. package/services/task-activity.service.d.ts +8 -0
  131. package/services/task-board.service.d.ts +32 -0
  132. package/services/task-comment.service.d.ts +18 -0
  133. package/services/task-label.service.d.ts +18 -0
  134. package/services/task-list.service.d.ts +19 -0
  135. package/services/task-manager-config.service.d.ts +12 -0
  136. package/services/task-manager-datasource.provider.d.ts +18 -0
  137. package/services/task.service.d.ts +44 -0
@@ -0,0 +1,349 @@
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 { ApiService, HybridCache } from '@flusys/nestjs-shared/classes';
29
+ import { UtilsService } from '@flusys/nestjs-shared/modules';
30
+ import { BadRequestException, Inject, Injectable, NotFoundException, Scope } from '@nestjs/common';
31
+ import { In, IsNull } from 'typeorm';
32
+ import { TASK_MESSAGES } from '../config';
33
+ import { Task, TaskAssignee, TaskLabel, TaskTaskLabel } from '../entities';
34
+ import { TaskActivityService } from './task-activity.service';
35
+ import { TaskBoardService } from './task-board.service';
36
+ import { TaskManagerDataSourceProvider } from './task-manager-datasource.provider';
37
+ export class TaskService extends ApiService {
38
+ async convertSingleDtoToEntity(dto, user) {
39
+ await this.ensureDataSourceRepository();
40
+ let entity = {};
41
+ if ('id' in dto && dto.id) {
42
+ const existing = await this.repository.findOne({
43
+ where: {
44
+ id: dto.id
45
+ }
46
+ });
47
+ if (!existing) throw new NotFoundException({
48
+ message: 'Task not found',
49
+ messageKey: TASK_MESSAGES.NOT_FOUND
50
+ });
51
+ entity = existing;
52
+ }
53
+ if (dto.assigneeIds?.length) {
54
+ const boardId = dto.boardId ?? entity.boardId;
55
+ const memberChecks = await Promise.all(dto.assigneeIds.map((id)=>this.boardService.isBoardMember(boardId, id)));
56
+ if (memberChecks.some((isMember)=>!isMember)) {
57
+ throw new BadRequestException({
58
+ message: 'Assignee is not a board member',
59
+ messageKey: TASK_MESSAGES.ASSIGNEE_NOT_BOARD_MEMBER
60
+ });
61
+ }
62
+ }
63
+ if ('labelIds' in dto && dto.labelIds !== undefined) {
64
+ this.pendingLabelIds.push(dto.labelIds ?? []);
65
+ }
66
+ if ('assigneeIds' in dto && dto.assigneeIds !== undefined) {
67
+ this.pendingAssigneeIds.push(dto.assigneeIds ?? []);
68
+ } else {
69
+ this.pendingAssigneeIds.push(undefined);
70
+ }
71
+ return {
72
+ ...entity,
73
+ boardId: dto.boardId ?? entity.boardId,
74
+ listId: dto.listId ?? entity.listId,
75
+ title: dto.title ?? entity.title,
76
+ description: dto.description !== undefined ? dto.description ?? null : entity.description ?? null,
77
+ priority: dto.priority ?? entity.priority ?? 'MEDIUM',
78
+ dueDate: dto.dueDate !== undefined ? dto.dueDate ?? null : entity.dueDate ?? null,
79
+ position: entity.position ?? 0,
80
+ reporterId: entity.reporterId ?? user?.id ?? null,
81
+ parentTaskId: dto.parentTaskId !== undefined ? dto.parentTaskId ?? null : entity.parentTaskId ?? null,
82
+ estimatedHours: dto.estimatedHours !== undefined ? dto.estimatedHours ?? null : entity.estimatedHours ?? null
83
+ };
84
+ }
85
+ async afterInsertOperation(entities, user, _qr) {
86
+ await Promise.all(entities.map(async (entity, i)=>{
87
+ const assigneeIds = this.pendingAssigneeIds[i] ?? [];
88
+ const syncOps = [
89
+ this.syncLabels(entity.id, this.pendingLabelIds[i] ?? [])
90
+ ];
91
+ if (assigneeIds.length > 0) {
92
+ syncOps.push(this.syncAssignees(entity.id, assigneeIds));
93
+ }
94
+ await Promise.all(syncOps);
95
+ }));
96
+ await Promise.all([
97
+ this.loadLabelsForTasks(entities),
98
+ this.loadAssigneesForTasks(entities)
99
+ ]);
100
+ await Promise.all(entities.map((entity)=>this.activityService.log(entity.id, entity.boardId, user?.id ?? null, 'task.created', null, {
101
+ title: entity.title
102
+ })));
103
+ this.pendingLabelIds = [];
104
+ this.pendingAssigneeIds = [];
105
+ }
106
+ async afterUpdateOperation(entities, _user, _qr) {
107
+ await Promise.all(entities.map(async (entity, i)=>{
108
+ const syncOps = [];
109
+ if (this.pendingLabelIds[i] !== undefined) {
110
+ syncOps.push(this.syncLabels(entity.id, this.pendingLabelIds[i]));
111
+ }
112
+ if (this.pendingAssigneeIds[i] !== undefined) {
113
+ syncOps.push(this.syncAssignees(entity.id, this.pendingAssigneeIds[i]));
114
+ }
115
+ if (syncOps.length > 0) await Promise.all(syncOps);
116
+ }));
117
+ await this.loadLabelsForTasks(entities);
118
+ const entitiesWithAssigneeUpdates = entities.filter((_, i)=>this.pendingAssigneeIds[i] !== undefined);
119
+ if (entitiesWithAssigneeUpdates.length > 0) {
120
+ await this.loadAssigneesForTasks(entitiesWithAssigneeUpdates);
121
+ }
122
+ this.pendingLabelIds = [];
123
+ this.pendingAssigneeIds = [];
124
+ }
125
+ async getFilterQuery(query, filter, _user) {
126
+ for (const [key, value] of Object.entries(filter)){
127
+ if (key === 'boardId') {
128
+ query.andWhere('task.boardId = :boardId', {
129
+ boardId: value
130
+ });
131
+ } else if (key === 'listId') {
132
+ query.andWhere('task.listId = :listId', {
133
+ listId: value
134
+ });
135
+ } else if (key === 'assigneeId') {
136
+ query.innerJoin('task_assignee', 'ta', 'ta.task_id = task.id').andWhere('ta.user_id = :assigneeUserId', {
137
+ assigneeUserId: value
138
+ });
139
+ } else if (key === 'priority') {
140
+ query.andWhere('task.priority = :priority', {
141
+ priority: value
142
+ });
143
+ }
144
+ }
145
+ return {
146
+ query,
147
+ isRaw: false
148
+ };
149
+ }
150
+ async getAll(search, filterAndPaginationDto, user) {
151
+ const result = await super.getAll(search, filterAndPaginationDto, user);
152
+ await this.loadLabelsForTasks(result.data);
153
+ await this.loadAssigneesForTasks(result.data);
154
+ return result;
155
+ }
156
+ async moveTask(dto, user) {
157
+ await this.ensureDataSourceRepository();
158
+ const task = await this.repository.findOne({
159
+ where: {
160
+ id: dto.taskId,
161
+ deletedAt: IsNull()
162
+ }
163
+ });
164
+ if (!task) throw new NotFoundException({
165
+ message: 'Task not found',
166
+ messageKey: TASK_MESSAGES.NOT_FOUND
167
+ });
168
+ const oldListId = task.listId;
169
+ task.listId = dto.targetListId;
170
+ task.position = dto.position ?? 0;
171
+ task.updatedById = user.id;
172
+ const saved = await this.repository.save(task);
173
+ await this.activityService.log(task.id, task.boardId, user.id, 'task.moved', {
174
+ listId: oldListId
175
+ }, {
176
+ listId: dto.targetListId
177
+ });
178
+ return saved;
179
+ }
180
+ async assignTask(dto, user) {
181
+ await this.ensureDataSourceRepository();
182
+ const task = await this.repository.findOne({
183
+ where: {
184
+ id: dto.taskId,
185
+ deletedAt: IsNull()
186
+ }
187
+ });
188
+ if (!task) throw new NotFoundException({
189
+ message: 'Task not found',
190
+ messageKey: TASK_MESSAGES.NOT_FOUND
191
+ });
192
+ const memberChecks = await Promise.all(dto.assigneeIds.map((id)=>this.boardService.isBoardMember(task.boardId, id)));
193
+ if (memberChecks.some((isMember)=>!isMember)) {
194
+ throw new BadRequestException({
195
+ message: 'Assignee is not a board member',
196
+ messageKey: TASK_MESSAGES.ASSIGNEE_NOT_BOARD_MEMBER
197
+ });
198
+ }
199
+ const assigneeRepo = await this.ensureAssigneeRepository();
200
+ const oldRows = await assigneeRepo.find({
201
+ where: {
202
+ taskId: dto.taskId
203
+ }
204
+ });
205
+ const oldIds = oldRows.map((r)=>r.userId);
206
+ await this.syncAssignees(dto.taskId, dto.assigneeIds);
207
+ task.updatedById = user.id;
208
+ const saved = await this.repository.save(task);
209
+ await this.activityService.log(task.id, task.boardId, user.id, 'task.assigned', {
210
+ assigneeIds: oldIds
211
+ }, {
212
+ assigneeIds: dto.assigneeIds
213
+ });
214
+ saved.assignees = await this.loadAssigneesForTask(saved.id);
215
+ return saved;
216
+ }
217
+ async syncAssignees(taskId, userIds) {
218
+ const repo = await this.ensureAssigneeRepository();
219
+ await repo.delete({
220
+ taskId
221
+ });
222
+ if (userIds.length > 0) {
223
+ await repo.save(userIds.map((userId)=>repo.create({
224
+ taskId,
225
+ userId
226
+ })));
227
+ }
228
+ }
229
+ async syncLabels(taskId, labelIds) {
230
+ const labelRepo = await this.ensureLabelRepository();
231
+ await labelRepo.delete({
232
+ taskId
233
+ });
234
+ if (labelIds.length > 0) {
235
+ const rows = labelIds.map((labelId)=>labelRepo.create({
236
+ taskId,
237
+ labelId
238
+ }));
239
+ await labelRepo.save(rows);
240
+ }
241
+ }
242
+ async loadLabelsForTasks(tasks) {
243
+ if (tasks.length === 0) return;
244
+ const junctionRepo = await this.ensureLabelRepository();
245
+ const fullRepo = await this.ensureFullLabelRepository();
246
+ const taskIds = tasks.map((t)=>t.id);
247
+ const junctionRows = await junctionRepo.find({
248
+ where: {
249
+ taskId: In(taskIds),
250
+ deletedAt: IsNull()
251
+ }
252
+ });
253
+ const uniqueLabelIds = [
254
+ ...new Set(junctionRows.map((r)=>r.labelId))
255
+ ];
256
+ const fullLabels = uniqueLabelIds.length > 0 ? await fullRepo.find({
257
+ where: {
258
+ id: In(uniqueLabelIds),
259
+ deletedAt: IsNull()
260
+ }
261
+ }) : [];
262
+ const labelById = new Map(fullLabels.map((l)=>[
263
+ l.id,
264
+ l
265
+ ]));
266
+ const byTask = new Map();
267
+ for (const row of junctionRows){
268
+ const label = labelById.get(row.labelId);
269
+ if (!label) continue;
270
+ const list = byTask.get(row.taskId) ?? [];
271
+ list.push(label);
272
+ byTask.set(row.taskId, list);
273
+ }
274
+ for (const task of tasks){
275
+ const taskLabels = byTask.get(task.id) ?? [];
276
+ task.labelIds = taskLabels.map((l)=>l.id);
277
+ task.labels = taskLabels;
278
+ }
279
+ }
280
+ async loadAssigneesForTasks(tasks) {
281
+ if (tasks.length === 0) return;
282
+ const repo = await this.ensureAssigneeRepository();
283
+ const taskIds = tasks.map((t)=>t.id);
284
+ const rows = await repo.createQueryBuilder('ta').select('ta.task_id', 'taskId').addSelect('ta.user_id', 'userId').addSelect('u.name', 'userName').leftJoin('app_user', 'u', 'u.id = ta.user_id').where('ta.task_id IN (:...taskIds)', {
285
+ taskIds
286
+ }).getRawMany();
287
+ const byTask = new Map();
288
+ for (const row of rows){
289
+ const list = byTask.get(row.taskId) ?? [];
290
+ list.push({
291
+ userId: row.userId,
292
+ userName: row.userName ?? null
293
+ });
294
+ byTask.set(row.taskId, list);
295
+ }
296
+ for (const task of tasks){
297
+ task.assignees = byTask.get(task.id) ?? [];
298
+ }
299
+ }
300
+ async loadAssigneesForTask(taskId) {
301
+ const repo = await this.ensureAssigneeRepository();
302
+ const rows = await repo.createQueryBuilder('ta').select('ta.user_id', 'userId').addSelect('u.name', 'userName').leftJoin('app_user', 'u', 'u.id = ta.user_id').where('ta.task_id = :taskId', {
303
+ taskId
304
+ }).getRawMany();
305
+ return rows.map((r)=>({
306
+ userId: r.userId,
307
+ userName: r.userName ?? null
308
+ }));
309
+ }
310
+ async ensureLabelRepository() {
311
+ if (!this.taskLabelRepository) {
312
+ this.taskLabelRepository = await this.dataSourceProvider.getRepository(TaskTaskLabel);
313
+ }
314
+ return this.taskLabelRepository;
315
+ }
316
+ async ensureFullLabelRepository() {
317
+ if (!this.fullLabelRepository) {
318
+ this.fullLabelRepository = await this.dataSourceProvider.getRepository(TaskLabel);
319
+ }
320
+ return this.fullLabelRepository;
321
+ }
322
+ async ensureAssigneeRepository() {
323
+ if (!this.taskAssigneeRepository) {
324
+ this.taskAssigneeRepository = await this.dataSourceProvider.getRepository(TaskAssignee);
325
+ }
326
+ return this.taskAssigneeRepository;
327
+ }
328
+ constructor(cacheManager, utilsService, dataSourceProvider, boardService, activityService){
329
+ super('task', cacheManager, utilsService, TaskService.name, true, 'task-manager', Task, dataSourceProvider), _define_property(this, "cacheManager", void 0), _define_property(this, "utilsService", void 0), _define_property(this, "dataSourceProvider", void 0), _define_property(this, "boardService", void 0), _define_property(this, "activityService", void 0), _define_property(this, "taskLabelRepository", void 0), _define_property(this, "fullLabelRepository", void 0), _define_property(this, "taskAssigneeRepository", void 0), _define_property(this, "pendingLabelIds", void 0), _define_property(this, "pendingAssigneeIds", void 0), this.cacheManager = cacheManager, this.utilsService = utilsService, this.dataSourceProvider = dataSourceProvider, this.boardService = boardService, this.activityService = activityService, this.taskLabelRepository = null, this.fullLabelRepository = null, this.taskAssigneeRepository = null, this.pendingLabelIds = [], this.pendingAssigneeIds = [];
330
+ }
331
+ }
332
+ TaskService = _ts_decorate([
333
+ Injectable({
334
+ scope: Scope.REQUEST
335
+ }),
336
+ _ts_param(0, Inject('CACHE_INSTANCE')),
337
+ _ts_param(1, Inject(UtilsService)),
338
+ _ts_param(2, Inject(TaskManagerDataSourceProvider)),
339
+ _ts_param(3, Inject(TaskBoardService)),
340
+ _ts_param(4, Inject(TaskActivityService)),
341
+ _ts_metadata("design:type", Function),
342
+ _ts_metadata("design:paramtypes", [
343
+ typeof HybridCache === "undefined" ? Object : HybridCache,
344
+ typeof UtilsService === "undefined" ? Object : UtilsService,
345
+ typeof TaskManagerDataSourceProvider === "undefined" ? Object : TaskManagerDataSourceProvider,
346
+ typeof TaskBoardService === "undefined" ? Object : TaskBoardService,
347
+ typeof TaskActivityService === "undefined" ? Object : TaskActivityService
348
+ ])
349
+ ], TaskService);
@@ -0,0 +1 @@
1
+ export * from './task-board.gateway';
@@ -0,0 +1,22 @@
1
+ import { OnGatewayConnection, OnGatewayDisconnect } from '@nestjs/websockets';
2
+ import { Server, Socket } from 'socket.io';
3
+ import { ITaskBoardSocketEvent } from '../interfaces';
4
+ import { TaskManagerConfigService } from '../services/task-manager-config.service';
5
+ export declare class TaskBoardGateway implements OnGatewayConnection, OnGatewayDisconnect {
6
+ private readonly configService;
7
+ private readonly connectedUsers;
8
+ server: Server;
9
+ constructor(configService: TaskManagerConfigService);
10
+ handleConnection(client: Socket): Promise<void>;
11
+ handleDisconnect(client: Socket): void;
12
+ handleJoinBoard(client: Socket, data: {
13
+ boardId: string;
14
+ }): void;
15
+ handleLeaveBoard(client: Socket, data: {
16
+ boardId: string;
17
+ }): void;
18
+ emitToBoardRoom(boardId: string, event: ITaskBoardSocketEvent): void;
19
+ isUserOnline(userId: string): boolean;
20
+ private extractToken;
21
+ private verifyToken;
22
+ }
package/index.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ export * from './config';
2
+ export * from './controllers';
3
+ export * from './docs';
4
+ export * from './dtos';
5
+ export * from './entities';
6
+ export * from './gateways';
7
+ export * from './interfaces';
8
+ export * from './modules';
9
+ export * from './services';
@@ -0,0 +1,2 @@
1
+ export * from './task-manager-module.interface';
2
+ export * from './task.interface';
@@ -0,0 +1,19 @@
1
+ import { IBootstrapAppConfig, IDataSourceServiceOptions, IDynamicModuleConfig, IModuleOptionsFactory } from '@flusys/nestjs-core';
2
+ import { ModuleMetadata, Type } from '@nestjs/common';
3
+ export interface ITaskManagerModuleConfig extends IDataSourceServiceOptions {
4
+ jwtSecret?: string;
5
+ }
6
+ export interface TaskManagerModuleOptions extends IDynamicModuleConfig {
7
+ bootstrapAppConfig?: IBootstrapAppConfig;
8
+ config?: ITaskManagerModuleConfig;
9
+ }
10
+ export interface TaskManagerOptionsFactory extends IModuleOptionsFactory<ITaskManagerModuleConfig> {
11
+ createTaskManagerOptions(): Promise<ITaskManagerModuleConfig> | ITaskManagerModuleConfig;
12
+ }
13
+ export interface TaskManagerModuleAsyncOptions extends Pick<ModuleMetadata, 'imports'>, IDynamicModuleConfig {
14
+ bootstrapAppConfig: IBootstrapAppConfig;
15
+ useFactory?: (...args: any[]) => Promise<ITaskManagerModuleConfig> | ITaskManagerModuleConfig;
16
+ inject?: any[];
17
+ useClass?: Type<TaskManagerOptionsFactory>;
18
+ useExisting?: Type<TaskManagerOptionsFactory>;
19
+ }
@@ -0,0 +1,52 @@
1
+ import { IIdentity } from '@flusys/nestjs-shared/interfaces';
2
+ import { TaskPriority } from '../entities/task.entity';
3
+ export interface ITaskBoard extends IIdentity {
4
+ name: string;
5
+ description: string | null;
6
+ color: string;
7
+ isActive: boolean;
8
+ settings: Record<string, unknown> | null;
9
+ companyId?: string | null;
10
+ }
11
+ export interface ITaskList extends IIdentity {
12
+ boardId: string;
13
+ name: string;
14
+ color: string;
15
+ position: number;
16
+ isActive: boolean;
17
+ isDoneList: boolean;
18
+ }
19
+ export interface ITaskAssignee {
20
+ userId: string;
21
+ userName: string | null;
22
+ }
23
+ export interface ITask extends IIdentity {
24
+ boardId: string;
25
+ listId: string;
26
+ title: string;
27
+ description: string | null;
28
+ priority: TaskPriority;
29
+ dueDate: string | null;
30
+ position: number;
31
+ assignees: ITaskAssignee[];
32
+ reporterId: string | null;
33
+ parentTaskId: string | null;
34
+ estimatedHours: number | null;
35
+ labels?: ITaskLabel[];
36
+ }
37
+ export interface ITaskLabel extends IIdentity {
38
+ boardId: string;
39
+ name: string;
40
+ color: string;
41
+ }
42
+ export interface ITaskComment extends IIdentity {
43
+ taskId: string;
44
+ userId: string;
45
+ content: string;
46
+ parentCommentId: string | null;
47
+ }
48
+ export interface ITaskBoardSocketEvent {
49
+ type: 'task:created' | 'task:updated' | 'task:deleted' | 'task:moved' | 'list:created' | 'list:updated' | 'list:deleted' | 'list:reordered';
50
+ boardId: string;
51
+ payload: unknown;
52
+ }
@@ -0,0 +1 @@
1
+ export * from './task-manager.module';
@@ -0,0 +1,8 @@
1
+ import { DynamicModule } from '@nestjs/common';
2
+ import { TaskManagerModuleAsyncOptions, TaskManagerModuleOptions } from '../interfaces';
3
+ export declare class TaskManagerModule {
4
+ static forRoot(options: TaskManagerModuleOptions): DynamicModule;
5
+ static forRootAsync(options: TaskManagerModuleAsyncOptions): DynamicModule;
6
+ private static buildProviders;
7
+ private static createAsyncProviders;
8
+ }
package/package.json ADDED
@@ -0,0 +1,91 @@
1
+ {
2
+ "name": "@flusys/nestjs-task-manager",
3
+ "version": "5.3.0",
4
+ "description": "Task management module with dynamic boards and real-time collaboration",
5
+ "main": "cjs/index.js",
6
+ "module": "fesm/index.js",
7
+ "types": "index.d.ts",
8
+ "sideEffects": false,
9
+ "keywords": [
10
+ "nestjs",
11
+ "task-manager",
12
+ "kanban",
13
+ "socket.io",
14
+ "real-time",
15
+ "@flusys"
16
+ ],
17
+ "license": "UNLICENSED",
18
+ "exports": {
19
+ ".": {
20
+ "types": "./index.d.ts",
21
+ "import": "./fesm/index.js",
22
+ "require": "./cjs/index.js"
23
+ },
24
+ "./config": {
25
+ "types": "./config/index.d.ts",
26
+ "import": "./fesm/config/index.js",
27
+ "require": "./cjs/config/index.js"
28
+ },
29
+ "./controllers": {
30
+ "types": "./controllers/index.d.ts",
31
+ "import": "./fesm/controllers/index.js",
32
+ "require": "./cjs/controllers/index.js"
33
+ },
34
+ "./docs": {
35
+ "types": "./docs/index.d.ts",
36
+ "import": "./fesm/docs/index.js",
37
+ "require": "./cjs/docs/index.js"
38
+ },
39
+ "./dtos": {
40
+ "types": "./dtos/index.d.ts",
41
+ "import": "./fesm/dtos/index.js",
42
+ "require": "./cjs/dtos/index.js"
43
+ },
44
+ "./entities": {
45
+ "types": "./entities/index.d.ts",
46
+ "import": "./fesm/entities/index.js",
47
+ "require": "./cjs/entities/index.js"
48
+ },
49
+ "./gateways": {
50
+ "types": "./gateways/index.d.ts",
51
+ "import": "./fesm/gateways/index.js",
52
+ "require": "./cjs/gateways/index.js"
53
+ },
54
+ "./interfaces": {
55
+ "types": "./interfaces/index.d.ts",
56
+ "import": "./fesm/interfaces/index.js",
57
+ "require": "./cjs/interfaces/index.js"
58
+ },
59
+ "./modules": {
60
+ "types": "./modules/index.d.ts",
61
+ "import": "./fesm/modules/index.js",
62
+ "require": "./cjs/modules/index.js"
63
+ },
64
+ "./services": {
65
+ "types": "./services/index.d.ts",
66
+ "import": "./fesm/services/index.js",
67
+ "require": "./cjs/services/index.js"
68
+ },
69
+ "./*": {
70
+ "types": "./*.d.ts",
71
+ "import": "./*.js",
72
+ "require": "./*.js"
73
+ }
74
+ },
75
+ "peerDependencies": {
76
+ "@nestjs/common": "^11.0.0",
77
+ "@nestjs/core": "^11.0.0",
78
+ "@nestjs/swagger": "^11.0.0",
79
+ "@nestjs/websockets": "^11.0.0",
80
+ "@nestjs/platform-socket.io": "^11.0.0",
81
+ "typeorm": "^0.3.0",
82
+ "class-transformer": "^0.5.1",
83
+ "class-validator": "^0.14.0 || ^0.15.0",
84
+ "socket.io": "^4.8.0"
85
+ },
86
+ "dependencies": {
87
+ "@flusys/nestjs-core": "5.3.0",
88
+ "@flusys/nestjs-shared": "5.3.0",
89
+ "jsonwebtoken": "^9.0.0"
90
+ }
91
+ }
@@ -0,0 +1,8 @@
1
+ export * from './task-manager-config.service';
2
+ export * from './task-manager-datasource.provider';
3
+ export * from './task-board.service';
4
+ export * from './task-list.service';
5
+ export * from './task-activity.service';
6
+ export * from './task-label.service';
7
+ export * from './task-comment.service';
8
+ export * from './task.service';
@@ -0,0 +1,8 @@
1
+ import { TaskManagerDataSourceProvider } from './task-manager-datasource.provider';
2
+ export declare class TaskActivityService {
3
+ private readonly dataSourceProvider;
4
+ private repository;
5
+ constructor(dataSourceProvider: TaskManagerDataSourceProvider);
6
+ log(taskId: string, boardId: string, userId: string | null, action: string, oldValue: Record<string, unknown> | null, newValue: Record<string, unknown> | null): Promise<void>;
7
+ private ensureRepository;
8
+ }
@@ -0,0 +1,32 @@
1
+ import { HybridCache, ApiService } from '@flusys/nestjs-shared/classes';
2
+ import { ILoggedUserInfo } from '@flusys/nestjs-shared/interfaces';
3
+ import { UtilsService } from '@flusys/nestjs-shared/modules';
4
+ import { EntityTarget, SelectQueryBuilder } from 'typeorm';
5
+ import { AddBoardMemberDto, CreateTaskBoardDto, RemoveBoardMemberDto, UpdateTaskBoardDto } from '../dtos';
6
+ import { TaskBoardMember, TaskBoardBase } from '../entities';
7
+ import { TaskManagerConfigService } from './task-manager-config.service';
8
+ import { TaskManagerDataSourceProvider } from './task-manager-datasource.provider';
9
+ export declare class TaskBoardService extends ApiService<CreateTaskBoardDto, UpdateTaskBoardDto, TaskBoardBase, TaskBoardBase> {
10
+ protected cacheManager: HybridCache;
11
+ protected utilsService: UtilsService;
12
+ private readonly config;
13
+ private readonly dataSourceProvider;
14
+ private memberRepository;
15
+ constructor(cacheManager: HybridCache, utilsService: UtilsService, config: TaskManagerConfigService, dataSourceProvider: TaskManagerDataSourceProvider);
16
+ protected resolveEntity(): EntityTarget<TaskBoardBase>;
17
+ convertSingleDtoToEntity(dto: CreateTaskBoardDto | UpdateTaskBoardDto, user: ILoggedUserInfo | null): Promise<TaskBoardBase>;
18
+ protected getExtraManipulateQuery(query: SelectQueryBuilder<TaskBoardBase>, filterDto: Record<string, unknown>, user: ILoggedUserInfo | null): Promise<{
19
+ query: SelectQueryBuilder<TaskBoardBase>;
20
+ isRaw: boolean;
21
+ }>;
22
+ protected convertEntityToResponseDto(entity: any, isRaw: boolean): any;
23
+ addMember(dto: AddBoardMemberDto, user: ILoggedUserInfo): Promise<TaskBoardMember>;
24
+ removeMember(dto: RemoveBoardMemberDto): Promise<void>;
25
+ getMembers(boardId: string): Promise<(TaskBoardMember & {
26
+ userName: string | null;
27
+ userEmail: string | null;
28
+ })[]>;
29
+ isBoardMember(boardId: string, userId: string): Promise<boolean>;
30
+ protected afterInsertOperation(entities: TaskBoardBase[], user: ILoggedUserInfo | null, _qr: any): Promise<void>;
31
+ private ensureMemberRepository;
32
+ }
@@ -0,0 +1,18 @@
1
+ import { HybridCache, ApiService } from '@flusys/nestjs-shared/classes';
2
+ import { ILoggedUserInfo } from '@flusys/nestjs-shared/interfaces';
3
+ import { UtilsService } from '@flusys/nestjs-shared/modules';
4
+ import { SelectQueryBuilder } from 'typeorm';
5
+ import { CreateTaskCommentDto, UpdateTaskCommentDto } from '../dtos';
6
+ import { TaskComment } from '../entities';
7
+ import { TaskManagerDataSourceProvider } from './task-manager-datasource.provider';
8
+ export declare class TaskCommentService extends ApiService<CreateTaskCommentDto, UpdateTaskCommentDto, TaskComment, TaskComment> {
9
+ protected cacheManager: HybridCache;
10
+ protected utilsService: UtilsService;
11
+ private readonly dataSourceProvider;
12
+ constructor(cacheManager: HybridCache, utilsService: UtilsService, dataSourceProvider: TaskManagerDataSourceProvider);
13
+ convertSingleDtoToEntity(dto: CreateTaskCommentDto | UpdateTaskCommentDto, user: ILoggedUserInfo | null): Promise<TaskComment>;
14
+ protected getFilterQuery(query: SelectQueryBuilder<TaskComment>, filter: Record<string, unknown>, _user: ILoggedUserInfo | null): Promise<{
15
+ query: SelectQueryBuilder<TaskComment>;
16
+ isRaw: boolean;
17
+ }>;
18
+ }
@@ -0,0 +1,18 @@
1
+ import { ApiService, HybridCache } from '@flusys/nestjs-shared/classes';
2
+ import { ILoggedUserInfo } from '@flusys/nestjs-shared/interfaces';
3
+ import { UtilsService } from '@flusys/nestjs-shared/modules';
4
+ import { SelectQueryBuilder } from 'typeorm';
5
+ import { CreateTaskLabelDto, UpdateTaskLabelDto } from '../dtos';
6
+ import { TaskLabel } from '../entities';
7
+ import { TaskManagerDataSourceProvider } from './task-manager-datasource.provider';
8
+ export declare class TaskLabelService extends ApiService<CreateTaskLabelDto, UpdateTaskLabelDto, TaskLabel, TaskLabel> {
9
+ protected cacheManager: HybridCache;
10
+ protected utilsService: UtilsService;
11
+ private readonly dataSourceProvider;
12
+ constructor(cacheManager: HybridCache, utilsService: UtilsService, dataSourceProvider: TaskManagerDataSourceProvider);
13
+ convertSingleDtoToEntity(dto: CreateTaskLabelDto | UpdateTaskLabelDto, _user: ILoggedUserInfo | null): Promise<TaskLabel>;
14
+ protected getFilterQuery(query: SelectQueryBuilder<TaskLabel>, filter: Record<string, unknown>, _user: ILoggedUserInfo | null): Promise<{
15
+ query: SelectQueryBuilder<TaskLabel>;
16
+ isRaw: boolean;
17
+ }>;
18
+ }