@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.
- package/config/index.d.ts +3 -3
- package/controllers/index.d.ts +5 -5
- package/controllers/task-board.controller.d.ts +3 -3
- package/controllers/task-comment.controller.d.ts +2 -2
- package/controllers/task-label.controller.d.ts +2 -2
- package/controllers/task-list.controller.d.ts +3 -3
- package/controllers/task.controller.d.ts +4 -4
- package/docs/index.d.ts +1 -1
- package/docs/task-manager-swagger.config.d.ts +1 -1
- package/dtos/index.d.ts +5 -5
- package/dtos/task-board.dto.d.ts +1 -1
- package/dtos/task.dto.d.ts +2 -2
- package/entities/index.d.ts +19 -19
- package/entities/task-board-with-company.entity.d.ts +1 -1
- package/fesm/234.js +1393 -0
- package/fesm/431.js +981 -0
- package/fesm/827.js +1054 -0
- package/fesm/988.js +881 -0
- package/fesm/config/index.js +156 -3
- package/fesm/controllers/index.js +21 -5
- package/fesm/docs/index.js +58 -1
- package/fesm/dtos/index.js +280 -5
- package/fesm/entities/index.js +26 -46
- package/fesm/gateways/index.js +288 -1
- package/fesm/index.js +440 -9
- package/fesm/interfaces/index.js +16 -2
- package/fesm/modules/index.js +210 -1
- package/fesm/runtime.js +85 -0
- package/fesm/services/index.js +53 -8
- package/gateways/index.d.ts +1 -1
- package/gateways/task-board.gateway.d.ts +3 -3
- package/index.d.ts +9 -9
- package/interfaces/index.d.ts +2 -2
- package/interfaces/task-manager-module.interface.d.ts +3 -3
- package/interfaces/task.interface.d.ts +2 -2
- package/modules/index.d.ts +1 -1
- package/modules/task-manager.module.d.ts +2 -2
- package/package.json +26 -36
- package/services/index.d.ts +8 -8
- package/services/task-activity.service.d.ts +1 -1
- package/services/task-board.service.d.ts +5 -5
- package/services/task-comment.service.d.ts +4 -4
- package/services/task-label.service.d.ts +4 -4
- package/services/task-list.service.d.ts +4 -4
- package/services/task-manager-config.service.d.ts +3 -3
- package/services/task-manager-datasource.provider.d.ts +3 -3
- package/services/task.service.d.ts +7 -7
- package/cjs/config/event-actions.js +0 -37
- package/cjs/config/index.js +0 -20
- package/cjs/config/message-keys.js +0 -92
- package/cjs/config/task-manager.constants.js +0 -11
- package/cjs/controllers/index.js +0 -22
- package/cjs/controllers/task-board.controller.js +0 -219
- package/cjs/controllers/task-board.controller.spec.js +0 -132
- package/cjs/controllers/task-comment.controller.js +0 -103
- package/cjs/controllers/task-comment.controller.spec.js +0 -86
- package/cjs/controllers/task-label.controller.js +0 -112
- package/cjs/controllers/task-label.controller.spec.js +0 -88
- package/cjs/controllers/task-list.controller.js +0 -158
- package/cjs/controllers/task-list.controller.spec.js +0 -112
- package/cjs/controllers/task.controller.js +0 -199
- package/cjs/controllers/task.controller.spec.js +0 -131
- package/cjs/docs/index.js +0 -18
- package/cjs/docs/task-manager-swagger.config.js +0 -43
- package/cjs/docs/task-manager-swagger.config.spec.js +0 -28
- package/cjs/dtos/index.js +0 -22
- package/cjs/dtos/task-board.dto.js +0 -310
- package/cjs/dtos/task-comment.dto.js +0 -131
- package/cjs/dtos/task-label.dto.js +0 -120
- package/cjs/dtos/task-list.dto.js +0 -198
- package/cjs/dtos/task.dto.js +0 -367
- package/cjs/entities/index.js +0 -72
- package/cjs/entities/index.spec.js +0 -30
- package/cjs/entities/task-activity.entity.js +0 -98
- package/cjs/entities/task-assignee.entity.js +0 -67
- package/cjs/entities/task-board-member.entity.js +0 -94
- package/cjs/entities/task-board-with-company.entity.js +0 -57
- package/cjs/entities/task-board.entity.js +0 -88
- package/cjs/entities/task-comment.entity.js +0 -80
- package/cjs/entities/task-label.entity.js +0 -72
- package/cjs/entities/task-list.entity.js +0 -104
- package/cjs/entities/task-task-label.entity.js +0 -67
- package/cjs/entities/task.entity.js +0 -151
- package/cjs/gateways/index.js +0 -18
- package/cjs/gateways/task-board.gateway.js +0 -167
- package/cjs/gateways/task-board.gateway.spec.js +0 -243
- package/cjs/index.js +0 -26
- package/cjs/interfaces/index.js +0 -19
- package/cjs/interfaces/task-manager-module.interface.js +0 -4
- package/cjs/interfaces/task.interface.js +0 -4
- package/cjs/modules/index.js +0 -18
- package/cjs/modules/task-manager.module.js +0 -135
- package/cjs/modules/task-manager.module.spec.js +0 -144
- package/cjs/services/index.js +0 -25
- package/cjs/services/task-activity.service.js +0 -76
- package/cjs/services/task-activity.service.spec.js +0 -125
- package/cjs/services/task-board.service.js +0 -247
- package/cjs/services/task-board.service.spec.js +0 -465
- package/cjs/services/task-comment.service.js +0 -103
- package/cjs/services/task-comment.service.spec.js +0 -160
- package/cjs/services/task-label.service.js +0 -97
- package/cjs/services/task-label.service.spec.js +0 -138
- package/cjs/services/task-list.service.js +0 -130
- package/cjs/services/task-list.service.spec.js +0 -284
- package/cjs/services/task-manager-config.service.js +0 -69
- package/cjs/services/task-manager-config.service.spec.js +0 -98
- package/cjs/services/task-manager-datasource.provider.js +0 -84
- package/cjs/services/task-manager-datasource.provider.spec.js +0 -131
- package/cjs/services/task.service.js +0 -398
- package/cjs/services/task.service.spec.js +0 -700
- package/fesm/config/event-actions.js +0 -16
- package/fesm/config/message-keys.js +0 -65
- package/fesm/config/task-manager.constants.js +0 -1
- package/fesm/controllers/task-board.controller.js +0 -209
- package/fesm/controllers/task-board.controller.spec.js +0 -128
- package/fesm/controllers/task-comment.controller.js +0 -93
- package/fesm/controllers/task-comment.controller.spec.js +0 -82
- package/fesm/controllers/task-label.controller.js +0 -102
- package/fesm/controllers/task-label.controller.spec.js +0 -84
- package/fesm/controllers/task-list.controller.js +0 -148
- package/fesm/controllers/task-list.controller.spec.js +0 -108
- package/fesm/controllers/task.controller.js +0 -189
- package/fesm/controllers/task.controller.spec.js +0 -127
- package/fesm/docs/task-manager-swagger.config.js +0 -33
- package/fesm/docs/task-manager-swagger.config.spec.js +0 -24
- package/fesm/dtos/task-board.dto.js +0 -277
- package/fesm/dtos/task-comment.dto.js +0 -110
- package/fesm/dtos/task-label.dto.js +0 -99
- package/fesm/dtos/task-list.dto.js +0 -174
- package/fesm/dtos/task.dto.js +0 -337
- package/fesm/entities/index.spec.js +0 -26
- package/fesm/entities/task-activity.entity.js +0 -88
- package/fesm/entities/task-assignee.entity.js +0 -57
- package/fesm/entities/task-board-member.entity.js +0 -76
- package/fesm/entities/task-board-with-company.entity.js +0 -47
- package/fesm/entities/task-board.entity.js +0 -78
- package/fesm/entities/task-comment.entity.js +0 -70
- package/fesm/entities/task-label.entity.js +0 -62
- package/fesm/entities/task-list.entity.js +0 -94
- package/fesm/entities/task-task-label.entity.js +0 -57
- package/fesm/entities/task.entity.js +0 -133
- package/fesm/gateways/task-board.gateway.js +0 -157
- package/fesm/gateways/task-board.gateway.spec.js +0 -239
- package/fesm/interfaces/task-manager-module.interface.js +0 -1
- package/fesm/interfaces/task.interface.js +0 -1
- package/fesm/modules/task-manager.module.js +0 -125
- package/fesm/modules/task-manager.module.spec.js +0 -140
- package/fesm/services/task-activity.service.js +0 -66
- package/fesm/services/task-activity.service.spec.js +0 -121
- package/fesm/services/task-board.service.js +0 -237
- package/fesm/services/task-board.service.spec.js +0 -461
- package/fesm/services/task-comment.service.js +0 -93
- package/fesm/services/task-comment.service.spec.js +0 -156
- package/fesm/services/task-label.service.js +0 -87
- package/fesm/services/task-label.service.spec.js +0 -134
- package/fesm/services/task-list.service.js +0 -120
- package/fesm/services/task-list.service.spec.js +0 -280
- package/fesm/services/task-manager-config.service.js +0 -59
- package/fesm/services/task-manager-config.service.spec.js +0 -94
- package/fesm/services/task-manager-datasource.provider.js +0 -74
- package/fesm/services/task-manager-datasource.provider.spec.js +0 -127
- package/fesm/services/task.service.js +0 -388
- package/fesm/services/task.service.spec.js +0 -696
|
@@ -1,157 +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 } from '@nestjs/common';
|
|
29
|
-
import { JwtService } from '@nestjs/jwt';
|
|
30
|
-
import { ConnectedSocket, MessageBody, SubscribeMessage, WebSocketGateway, WebSocketServer } from '@nestjs/websockets';
|
|
31
|
-
import { Server, Socket } from 'socket.io';
|
|
32
|
-
import { TaskManagerConfigService } from '../services/task-manager-config.service';
|
|
33
|
-
export class TaskBoardGateway {
|
|
34
|
-
async handleConnection(client) {
|
|
35
|
-
try {
|
|
36
|
-
const token = this.extractToken(client);
|
|
37
|
-
if (!token) {
|
|
38
|
-
client.disconnect();
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
const payload = this.verifyToken(token);
|
|
42
|
-
if (!payload) {
|
|
43
|
-
client.disconnect();
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
client.data.userId = payload.id;
|
|
47
|
-
client.data.companyId = payload.companyId;
|
|
48
|
-
if (!this.connectedUsers.has(payload.id)) {
|
|
49
|
-
this.connectedUsers.set(payload.id, new Set());
|
|
50
|
-
}
|
|
51
|
-
this.connectedUsers.get(payload.id).add(client.id);
|
|
52
|
-
client.join(`user:${payload.id}`);
|
|
53
|
-
} catch {
|
|
54
|
-
client.disconnect();
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
handleDisconnect(client) {
|
|
58
|
-
const userId = client.data.userId;
|
|
59
|
-
if (userId) {
|
|
60
|
-
const sockets = this.connectedUsers.get(userId);
|
|
61
|
-
sockets?.delete(client.id);
|
|
62
|
-
if (sockets?.size === 0) {
|
|
63
|
-
this.connectedUsers.delete(userId);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
handleJoinBoard(client, data) {
|
|
68
|
-
if (data?.boardId) {
|
|
69
|
-
client.join(`board:${data.boardId}`);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
handleLeaveBoard(client, data) {
|
|
73
|
-
if (data?.boardId) {
|
|
74
|
-
client.leave(`board:${data.boardId}`);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
emitToBoardRoom(boardId, event) {
|
|
78
|
-
this.server?.to(`board:${boardId}`).emit('board:event', event);
|
|
79
|
-
}
|
|
80
|
-
isUserOnline(userId) {
|
|
81
|
-
return this.connectedUsers.has(userId);
|
|
82
|
-
}
|
|
83
|
-
extractToken(client) {
|
|
84
|
-
const authToken = client.handshake.auth?.token;
|
|
85
|
-
if (authToken) return authToken;
|
|
86
|
-
const queryToken = client.handshake.query?.token;
|
|
87
|
-
if (typeof queryToken === 'string') return queryToken;
|
|
88
|
-
const authHeader = client.handshake.headers?.authorization;
|
|
89
|
-
if (authHeader?.startsWith('Bearer ')) return authHeader.substring(7);
|
|
90
|
-
return null;
|
|
91
|
-
}
|
|
92
|
-
verifyToken(token) {
|
|
93
|
-
try {
|
|
94
|
-
const secret = this.configService.getJwtSecret();
|
|
95
|
-
if (!secret) return null;
|
|
96
|
-
return this.jwtService.verify(token, {
|
|
97
|
-
secret,
|
|
98
|
-
algorithms: [
|
|
99
|
-
'HS256'
|
|
100
|
-
]
|
|
101
|
-
});
|
|
102
|
-
} catch {
|
|
103
|
-
return null;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
constructor(configService, jwtService){
|
|
107
|
-
_define_property(this, "configService", void 0);
|
|
108
|
-
_define_property(this, "jwtService", void 0);
|
|
109
|
-
_define_property(this, "connectedUsers", void 0);
|
|
110
|
-
_define_property(this, "server", void 0);
|
|
111
|
-
this.configService = configService;
|
|
112
|
-
this.jwtService = jwtService;
|
|
113
|
-
this.connectedUsers = new Map();
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
_ts_decorate([
|
|
117
|
-
WebSocketServer(),
|
|
118
|
-
_ts_metadata("design:type", typeof Server === "undefined" ? Object : Server)
|
|
119
|
-
], TaskBoardGateway.prototype, "server", void 0);
|
|
120
|
-
_ts_decorate([
|
|
121
|
-
SubscribeMessage('board:join'),
|
|
122
|
-
_ts_param(0, ConnectedSocket()),
|
|
123
|
-
_ts_param(1, MessageBody()),
|
|
124
|
-
_ts_metadata("design:type", Function),
|
|
125
|
-
_ts_metadata("design:paramtypes", [
|
|
126
|
-
typeof Socket === "undefined" ? Object : Socket,
|
|
127
|
-
Object
|
|
128
|
-
]),
|
|
129
|
-
_ts_metadata("design:returntype", void 0)
|
|
130
|
-
], TaskBoardGateway.prototype, "handleJoinBoard", null);
|
|
131
|
-
_ts_decorate([
|
|
132
|
-
SubscribeMessage('board:leave'),
|
|
133
|
-
_ts_param(0, ConnectedSocket()),
|
|
134
|
-
_ts_param(1, MessageBody()),
|
|
135
|
-
_ts_metadata("design:type", Function),
|
|
136
|
-
_ts_metadata("design:paramtypes", [
|
|
137
|
-
typeof Socket === "undefined" ? Object : Socket,
|
|
138
|
-
Object
|
|
139
|
-
]),
|
|
140
|
-
_ts_metadata("design:returntype", void 0)
|
|
141
|
-
], TaskBoardGateway.prototype, "handleLeaveBoard", null);
|
|
142
|
-
TaskBoardGateway = _ts_decorate([
|
|
143
|
-
WebSocketGateway({
|
|
144
|
-
namespace: '/task-boards',
|
|
145
|
-
cors: {
|
|
146
|
-
origin: '*',
|
|
147
|
-
credentials: true
|
|
148
|
-
}
|
|
149
|
-
}),
|
|
150
|
-
_ts_param(0, Inject(TaskManagerConfigService)),
|
|
151
|
-
_ts_param(1, Inject(JwtService)),
|
|
152
|
-
_ts_metadata("design:type", Function),
|
|
153
|
-
_ts_metadata("design:paramtypes", [
|
|
154
|
-
typeof TaskManagerConfigService === "undefined" ? Object : TaskManagerConfigService,
|
|
155
|
-
typeof JwtService === "undefined" ? Object : JwtService
|
|
156
|
-
])
|
|
157
|
-
], TaskBoardGateway);
|
|
@@ -1,239 +0,0 @@
|
|
|
1
|
-
import { TaskBoardGateway } from './task-board.gateway';
|
|
2
|
-
function buildSocket(overrides = {}) {
|
|
3
|
-
return {
|
|
4
|
-
id: overrides.id ?? 'socket-1',
|
|
5
|
-
handshake: {
|
|
6
|
-
auth: overrides.authToken !== undefined ? {
|
|
7
|
-
token: overrides.authToken
|
|
8
|
-
} : {},
|
|
9
|
-
query: overrides.queryToken !== undefined ? {
|
|
10
|
-
token: overrides.queryToken
|
|
11
|
-
} : {},
|
|
12
|
-
headers: overrides.authHeader !== undefined ? {
|
|
13
|
-
authorization: overrides.authHeader
|
|
14
|
-
} : {}
|
|
15
|
-
},
|
|
16
|
-
data: {},
|
|
17
|
-
join: jest.fn(),
|
|
18
|
-
leave: jest.fn(),
|
|
19
|
-
disconnect: jest.fn()
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
describe('TaskBoardGateway', ()=>{
|
|
23
|
-
let gateway;
|
|
24
|
-
let mockConfigService;
|
|
25
|
-
let mockJwtService;
|
|
26
|
-
let mockServer;
|
|
27
|
-
beforeEach(()=>{
|
|
28
|
-
mockConfigService = {
|
|
29
|
-
getJwtSecret: jest.fn().mockReturnValue('test-secret')
|
|
30
|
-
};
|
|
31
|
-
mockJwtService = {
|
|
32
|
-
verify: jest.fn()
|
|
33
|
-
};
|
|
34
|
-
gateway = new TaskBoardGateway(mockConfigService, mockJwtService);
|
|
35
|
-
mockServer = {
|
|
36
|
-
to: jest.fn().mockReturnThis(),
|
|
37
|
-
emit: jest.fn()
|
|
38
|
-
};
|
|
39
|
-
gateway.server = mockServer;
|
|
40
|
-
});
|
|
41
|
-
describe('handleConnection', ()=>{
|
|
42
|
-
it('authenticates via handshake.auth.token, joins the user room and tracks the socket', async ()=>{
|
|
43
|
-
const socket = buildSocket({
|
|
44
|
-
id: 'socket-1',
|
|
45
|
-
authToken: 'valid-token'
|
|
46
|
-
});
|
|
47
|
-
mockJwtService.verify.mockReturnValue({
|
|
48
|
-
id: 'user-1',
|
|
49
|
-
companyId: 'company-1'
|
|
50
|
-
});
|
|
51
|
-
await gateway.handleConnection(socket);
|
|
52
|
-
expect(mockJwtService.verify).toHaveBeenCalledWith('valid-token', {
|
|
53
|
-
secret: 'test-secret',
|
|
54
|
-
algorithms: [
|
|
55
|
-
'HS256'
|
|
56
|
-
]
|
|
57
|
-
});
|
|
58
|
-
expect(socket.data.userId).toBe('user-1');
|
|
59
|
-
expect(socket.data.companyId).toBe('company-1');
|
|
60
|
-
expect(socket.join).toHaveBeenCalledWith('user:user-1');
|
|
61
|
-
expect(gateway.isUserOnline('user-1')).toBe(true);
|
|
62
|
-
expect(socket.disconnect).not.toHaveBeenCalled();
|
|
63
|
-
});
|
|
64
|
-
it('falls back to handshake.query.token when auth.token is absent', async ()=>{
|
|
65
|
-
const socket = buildSocket({
|
|
66
|
-
queryToken: 'query-token'
|
|
67
|
-
});
|
|
68
|
-
mockJwtService.verify.mockReturnValue({
|
|
69
|
-
id: 'user-2'
|
|
70
|
-
});
|
|
71
|
-
await gateway.handleConnection(socket);
|
|
72
|
-
expect(mockJwtService.verify).toHaveBeenCalledWith('query-token', expect.anything());
|
|
73
|
-
expect(socket.data.userId).toBe('user-2');
|
|
74
|
-
});
|
|
75
|
-
it('falls back to the Authorization header (Bearer token) when other sources are absent', async ()=>{
|
|
76
|
-
const socket = buildSocket({
|
|
77
|
-
authHeader: 'Bearer header-token'
|
|
78
|
-
});
|
|
79
|
-
mockJwtService.verify.mockReturnValue({
|
|
80
|
-
id: 'user-3'
|
|
81
|
-
});
|
|
82
|
-
await gateway.handleConnection(socket);
|
|
83
|
-
expect(mockJwtService.verify).toHaveBeenCalledWith('header-token', expect.anything());
|
|
84
|
-
expect(socket.data.userId).toBe('user-3');
|
|
85
|
-
});
|
|
86
|
-
it('disconnects the socket when no token is present on the handshake', async ()=>{
|
|
87
|
-
const socket = buildSocket();
|
|
88
|
-
await gateway.handleConnection(socket);
|
|
89
|
-
expect(socket.disconnect).toHaveBeenCalled();
|
|
90
|
-
expect(mockJwtService.verify).not.toHaveBeenCalled();
|
|
91
|
-
});
|
|
92
|
-
it('disconnects the socket when the JWT secret is not configured', async ()=>{
|
|
93
|
-
mockConfigService.getJwtSecret.mockReturnValue(undefined);
|
|
94
|
-
const socket = buildSocket({
|
|
95
|
-
authToken: 'some-token'
|
|
96
|
-
});
|
|
97
|
-
await gateway.handleConnection(socket);
|
|
98
|
-
expect(socket.disconnect).toHaveBeenCalled();
|
|
99
|
-
});
|
|
100
|
-
it('disconnects the socket when token verification throws (invalid/expired token)', async ()=>{
|
|
101
|
-
const socket = buildSocket({
|
|
102
|
-
authToken: 'garbage-token'
|
|
103
|
-
});
|
|
104
|
-
mockJwtService.verify.mockImplementation(()=>{
|
|
105
|
-
throw new Error('invalid signature');
|
|
106
|
-
});
|
|
107
|
-
await gateway.handleConnection(socket);
|
|
108
|
-
expect(socket.disconnect).toHaveBeenCalled();
|
|
109
|
-
});
|
|
110
|
-
it('tracks multiple concurrent sockets for the same authenticated user', async ()=>{
|
|
111
|
-
const socketA = buildSocket({
|
|
112
|
-
id: 'socket-a',
|
|
113
|
-
authToken: 'token'
|
|
114
|
-
});
|
|
115
|
-
const socketB = buildSocket({
|
|
116
|
-
id: 'socket-b',
|
|
117
|
-
authToken: 'token'
|
|
118
|
-
});
|
|
119
|
-
mockJwtService.verify.mockReturnValue({
|
|
120
|
-
id: 'user-multi'
|
|
121
|
-
});
|
|
122
|
-
await gateway.handleConnection(socketA);
|
|
123
|
-
await gateway.handleConnection(socketB);
|
|
124
|
-
expect(gateway.isUserOnline('user-multi')).toBe(true);
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
|
-
describe('handleDisconnect', ()=>{
|
|
128
|
-
it('removes the socket from the connected-users map and marks the user offline once all sockets are gone', async ()=>{
|
|
129
|
-
const socket = buildSocket({
|
|
130
|
-
id: 'socket-1',
|
|
131
|
-
authToken: 'valid-token'
|
|
132
|
-
});
|
|
133
|
-
mockJwtService.verify.mockReturnValue({
|
|
134
|
-
id: 'user-1'
|
|
135
|
-
});
|
|
136
|
-
await gateway.handleConnection(socket);
|
|
137
|
-
expect(gateway.isUserOnline('user-1')).toBe(true);
|
|
138
|
-
gateway.handleDisconnect(socket);
|
|
139
|
-
expect(gateway.isUserOnline('user-1')).toBe(false);
|
|
140
|
-
});
|
|
141
|
-
it('keeps the user marked online while other sockets for that user remain connected', async ()=>{
|
|
142
|
-
const socketA = buildSocket({
|
|
143
|
-
id: 'socket-a',
|
|
144
|
-
authToken: 'token'
|
|
145
|
-
});
|
|
146
|
-
const socketB = buildSocket({
|
|
147
|
-
id: 'socket-b',
|
|
148
|
-
authToken: 'token'
|
|
149
|
-
});
|
|
150
|
-
mockJwtService.verify.mockReturnValue({
|
|
151
|
-
id: 'user-1'
|
|
152
|
-
});
|
|
153
|
-
await gateway.handleConnection(socketA);
|
|
154
|
-
await gateway.handleConnection(socketB);
|
|
155
|
-
gateway.handleDisconnect(socketA);
|
|
156
|
-
expect(gateway.isUserOnline('user-1')).toBe(true);
|
|
157
|
-
});
|
|
158
|
-
it('does nothing when the disconnecting socket has no tracked userId', ()=>{
|
|
159
|
-
const socket = buildSocket({
|
|
160
|
-
id: 'never-connected'
|
|
161
|
-
});
|
|
162
|
-
expect(()=>gateway.handleDisconnect(socket)).not.toThrow();
|
|
163
|
-
});
|
|
164
|
-
});
|
|
165
|
-
describe('handleJoinBoard / handleLeaveBoard', ()=>{
|
|
166
|
-
it('joins the board room when a boardId is provided', ()=>{
|
|
167
|
-
const socket = buildSocket();
|
|
168
|
-
gateway.handleJoinBoard(socket, {
|
|
169
|
-
boardId: 'board-1'
|
|
170
|
-
});
|
|
171
|
-
expect(socket.join).toHaveBeenCalledWith('board:board-1');
|
|
172
|
-
});
|
|
173
|
-
it('does not attempt to join a room when boardId is missing', ()=>{
|
|
174
|
-
const socket = buildSocket();
|
|
175
|
-
gateway.handleJoinBoard(socket, {});
|
|
176
|
-
expect(socket.join).not.toHaveBeenCalled();
|
|
177
|
-
});
|
|
178
|
-
it('leaves the board room when a boardId is provided', ()=>{
|
|
179
|
-
const socket = buildSocket();
|
|
180
|
-
gateway.handleLeaveBoard(socket, {
|
|
181
|
-
boardId: 'board-1'
|
|
182
|
-
});
|
|
183
|
-
expect(socket.leave).toHaveBeenCalledWith('board:board-1');
|
|
184
|
-
});
|
|
185
|
-
it('does not attempt to leave a room when boardId is missing', ()=>{
|
|
186
|
-
const socket = buildSocket();
|
|
187
|
-
gateway.handleLeaveBoard(socket, {});
|
|
188
|
-
expect(socket.leave).not.toHaveBeenCalled();
|
|
189
|
-
});
|
|
190
|
-
});
|
|
191
|
-
describe('emitToBoardRoom', ()=>{
|
|
192
|
-
it('broadcasts the event to the board room', ()=>{
|
|
193
|
-
gateway.emitToBoardRoom('board-1', {
|
|
194
|
-
type: 'task:moved',
|
|
195
|
-
boardId: 'board-1',
|
|
196
|
-
payload: {
|
|
197
|
-
taskId: 'task-1'
|
|
198
|
-
}
|
|
199
|
-
});
|
|
200
|
-
expect(mockServer.to).toHaveBeenCalledWith('board:board-1');
|
|
201
|
-
expect(mockServer.emit).toHaveBeenCalledWith('board:event', {
|
|
202
|
-
type: 'task:moved',
|
|
203
|
-
boardId: 'board-1',
|
|
204
|
-
payload: {
|
|
205
|
-
taskId: 'task-1'
|
|
206
|
-
}
|
|
207
|
-
});
|
|
208
|
-
});
|
|
209
|
-
it('broadcasts a list:reordered event', ()=>{
|
|
210
|
-
gateway.emitToBoardRoom('board-2', {
|
|
211
|
-
type: 'list:reordered',
|
|
212
|
-
boardId: 'board-2',
|
|
213
|
-
payload: {
|
|
214
|
-
orderedIds: [
|
|
215
|
-
'a',
|
|
216
|
-
'b'
|
|
217
|
-
]
|
|
218
|
-
}
|
|
219
|
-
});
|
|
220
|
-
expect(mockServer.to).toHaveBeenCalledWith('board:board-2');
|
|
221
|
-
expect(mockServer.emit).toHaveBeenCalledWith('board:event', expect.objectContaining({
|
|
222
|
-
type: 'list:reordered'
|
|
223
|
-
}));
|
|
224
|
-
});
|
|
225
|
-
it('does not throw when the server instance is not yet attached', ()=>{
|
|
226
|
-
gateway.server = undefined;
|
|
227
|
-
expect(()=>gateway.emitToBoardRoom('board-1', {
|
|
228
|
-
type: 'task:updated',
|
|
229
|
-
boardId: 'board-1',
|
|
230
|
-
payload: {}
|
|
231
|
-
})).not.toThrow();
|
|
232
|
-
});
|
|
233
|
-
});
|
|
234
|
-
describe('isUserOnline', ()=>{
|
|
235
|
-
it('returns false for a user that never connected', ()=>{
|
|
236
|
-
expect(gateway.isUserOnline('never-seen')).toBe(false);
|
|
237
|
-
});
|
|
238
|
-
});
|
|
239
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { };
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { };
|
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
function _ts_decorate(decorators, target, key, desc) {
|
|
2
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
-
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;
|
|
5
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
-
}
|
|
7
|
-
import { CacheModule, UtilsModule, createModuleEventsProvider } from '@flusys/nestjs-shared/modules';
|
|
8
|
-
import { Module } from '@nestjs/common';
|
|
9
|
-
import { JwtService } from '@nestjs/jwt';
|
|
10
|
-
import { TASK_MANAGER_MODULE_OPTIONS } from '../config/task-manager.constants';
|
|
11
|
-
import { TaskBoardController, TaskCommentController, TaskLabelController, TaskListController, TaskController } from '../controllers';
|
|
12
|
-
import { TaskBoardGateway } from '../gateways/task-board.gateway';
|
|
13
|
-
import { TaskActivityService, TaskBoardService, TaskCommentService, TaskLabelService, TaskListService, TaskManagerConfigService, TaskManagerDataSourceProvider, TaskService } from '../services';
|
|
14
|
-
const CONTROLLERS = [
|
|
15
|
-
TaskBoardController,
|
|
16
|
-
TaskListController,
|
|
17
|
-
TaskController,
|
|
18
|
-
TaskLabelController,
|
|
19
|
-
TaskCommentController
|
|
20
|
-
];
|
|
21
|
-
const EXPORTED_SERVICES = [
|
|
22
|
-
TaskManagerConfigService,
|
|
23
|
-
TaskManagerDataSourceProvider,
|
|
24
|
-
TaskBoardService,
|
|
25
|
-
TaskListService,
|
|
26
|
-
TaskService,
|
|
27
|
-
TaskLabelService,
|
|
28
|
-
TaskCommentService,
|
|
29
|
-
TaskActivityService
|
|
30
|
-
];
|
|
31
|
-
export class TaskManagerModule {
|
|
32
|
-
static forRoot(options) {
|
|
33
|
-
return {
|
|
34
|
-
module: TaskManagerModule,
|
|
35
|
-
global: options.global ?? false,
|
|
36
|
-
imports: [
|
|
37
|
-
CacheModule,
|
|
38
|
-
UtilsModule
|
|
39
|
-
],
|
|
40
|
-
controllers: options.includeController !== false ? CONTROLLERS : [],
|
|
41
|
-
providers: [
|
|
42
|
-
...this.buildProviders(options),
|
|
43
|
-
JwtService,
|
|
44
|
-
TaskBoardGateway
|
|
45
|
-
],
|
|
46
|
-
exports: [
|
|
47
|
-
...EXPORTED_SERVICES,
|
|
48
|
-
TaskBoardGateway
|
|
49
|
-
]
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
static forRootAsync(options) {
|
|
53
|
-
return {
|
|
54
|
-
module: TaskManagerModule,
|
|
55
|
-
global: options.global ?? false,
|
|
56
|
-
imports: [
|
|
57
|
-
...options.imports || [],
|
|
58
|
-
CacheModule,
|
|
59
|
-
UtilsModule
|
|
60
|
-
],
|
|
61
|
-
controllers: options.includeController !== false ? CONTROLLERS : [],
|
|
62
|
-
providers: [
|
|
63
|
-
...this.createAsyncProviders(options),
|
|
64
|
-
...this.buildProviders(),
|
|
65
|
-
JwtService,
|
|
66
|
-
TaskBoardGateway
|
|
67
|
-
],
|
|
68
|
-
exports: [
|
|
69
|
-
...EXPORTED_SERVICES,
|
|
70
|
-
TaskBoardGateway
|
|
71
|
-
]
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
static buildProviders(options) {
|
|
75
|
-
const providers = [
|
|
76
|
-
...EXPORTED_SERVICES
|
|
77
|
-
];
|
|
78
|
-
if (options) {
|
|
79
|
-
providers.unshift({
|
|
80
|
-
provide: TASK_MANAGER_MODULE_OPTIONS,
|
|
81
|
-
useValue: options
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
providers.push(createModuleEventsProvider('task-manager', TASK_MANAGER_MODULE_OPTIONS));
|
|
85
|
-
return providers;
|
|
86
|
-
}
|
|
87
|
-
static createAsyncProviders(options) {
|
|
88
|
-
if (options.useFactory) {
|
|
89
|
-
return [
|
|
90
|
-
{
|
|
91
|
-
provide: TASK_MANAGER_MODULE_OPTIONS,
|
|
92
|
-
useFactory: async (...args)=>({
|
|
93
|
-
...options,
|
|
94
|
-
config: await options.useFactory(...args)
|
|
95
|
-
}),
|
|
96
|
-
inject: options.inject || []
|
|
97
|
-
}
|
|
98
|
-
];
|
|
99
|
-
}
|
|
100
|
-
const factoryClass = options.useClass || options.useExisting;
|
|
101
|
-
if (!factoryClass) return [];
|
|
102
|
-
const providers = [
|
|
103
|
-
{
|
|
104
|
-
provide: TASK_MANAGER_MODULE_OPTIONS,
|
|
105
|
-
useFactory: async (factory)=>({
|
|
106
|
-
...options,
|
|
107
|
-
config: await factory.createTaskManagerOptions()
|
|
108
|
-
}),
|
|
109
|
-
inject: [
|
|
110
|
-
factoryClass
|
|
111
|
-
]
|
|
112
|
-
}
|
|
113
|
-
];
|
|
114
|
-
if (options.useClass) {
|
|
115
|
-
providers.push({
|
|
116
|
-
provide: options.useClass,
|
|
117
|
-
useClass: options.useClass
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
return providers;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
TaskManagerModule = _ts_decorate([
|
|
124
|
-
Module({})
|
|
125
|
-
], TaskManagerModule);
|
|
@@ -1,140 +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
|
-
import { TaskManagerModule } from './task-manager.module';
|
|
15
|
-
import { TASK_MANAGER_MODULE_OPTIONS } from '../config/task-manager.constants';
|
|
16
|
-
import { TaskBoardController, TaskCommentController, TaskLabelController, TaskListController, TaskController } from '../controllers';
|
|
17
|
-
import { TaskBoardGateway } from '../gateways/task-board.gateway';
|
|
18
|
-
function providerTokens(providers) {
|
|
19
|
-
return providers.map((p)=>typeof p === 'function' ? p : p.provide);
|
|
20
|
-
}
|
|
21
|
-
describe('TaskManagerModule', ()=>{
|
|
22
|
-
describe('forRoot', ()=>{
|
|
23
|
-
it('should default to a non-global module with all controllers registered', ()=>{
|
|
24
|
-
const dynamicModule = TaskManagerModule.forRoot({});
|
|
25
|
-
expect(dynamicModule.module).toBe(TaskManagerModule);
|
|
26
|
-
expect(dynamicModule.global).toBe(false);
|
|
27
|
-
expect(dynamicModule.controllers).toEqual([
|
|
28
|
-
TaskBoardController,
|
|
29
|
-
TaskListController,
|
|
30
|
-
TaskController,
|
|
31
|
-
TaskLabelController,
|
|
32
|
-
TaskCommentController
|
|
33
|
-
]);
|
|
34
|
-
});
|
|
35
|
-
it('should respect global: true', ()=>{
|
|
36
|
-
expect(TaskManagerModule.forRoot({
|
|
37
|
-
global: true
|
|
38
|
-
}).global).toBe(true);
|
|
39
|
-
});
|
|
40
|
-
it('should register no controllers when includeController is false', ()=>{
|
|
41
|
-
expect(TaskManagerModule.forRoot({
|
|
42
|
-
includeController: false
|
|
43
|
-
}).controllers).toEqual([]);
|
|
44
|
-
});
|
|
45
|
-
it('should register TaskBoardGateway as both a provider and an export', ()=>{
|
|
46
|
-
const dynamicModule = TaskManagerModule.forRoot({});
|
|
47
|
-
expect(providerTokens(dynamicModule.providers)).toContain(TaskBoardGateway);
|
|
48
|
-
expect(dynamicModule.exports).toContain(TaskBoardGateway);
|
|
49
|
-
});
|
|
50
|
-
it('should register a TASK_MANAGER_MODULE_OPTIONS provider carrying the given options', ()=>{
|
|
51
|
-
const options = {
|
|
52
|
-
bootstrapAppConfig: {}
|
|
53
|
-
};
|
|
54
|
-
const dynamicModule = TaskManagerModule.forRoot(options);
|
|
55
|
-
const provider = dynamicModule.providers.find((p)=>p.provide === TASK_MANAGER_MODULE_OPTIONS);
|
|
56
|
-
expect(provider.useValue).toBe(options);
|
|
57
|
-
});
|
|
58
|
-
});
|
|
59
|
-
describe('forRootAsync', ()=>{
|
|
60
|
-
it('should forward external imports alongside CacheModule/UtilsModule', ()=>{
|
|
61
|
-
let FakeImportedModule = class FakeImportedModule {
|
|
62
|
-
};
|
|
63
|
-
const dynamicModule = TaskManagerModule.forRootAsync({
|
|
64
|
-
imports: [
|
|
65
|
-
FakeImportedModule
|
|
66
|
-
]
|
|
67
|
-
});
|
|
68
|
-
expect(dynamicModule.imports).toEqual(expect.arrayContaining([
|
|
69
|
-
FakeImportedModule
|
|
70
|
-
]));
|
|
71
|
-
});
|
|
72
|
-
it('should still register TaskBoardGateway when using async registration', ()=>{
|
|
73
|
-
const dynamicModule = TaskManagerModule.forRootAsync({
|
|
74
|
-
useFactory: jest.fn()
|
|
75
|
-
});
|
|
76
|
-
expect(providerTokens(dynamicModule.providers)).toContain(TaskBoardGateway);
|
|
77
|
-
expect(dynamicModule.exports).toContain(TaskBoardGateway);
|
|
78
|
-
});
|
|
79
|
-
it('useFactory: builds a TASK_MANAGER_MODULE_OPTIONS provider merging resolved config', async ()=>{
|
|
80
|
-
const useFactory = jest.fn().mockResolvedValue({
|
|
81
|
-
foo: 'bar'
|
|
82
|
-
});
|
|
83
|
-
const dynamicModule = TaskManagerModule.forRootAsync({
|
|
84
|
-
useFactory,
|
|
85
|
-
inject: [
|
|
86
|
-
'SOME_TOKEN'
|
|
87
|
-
]
|
|
88
|
-
});
|
|
89
|
-
const provider = dynamicModule.providers.find((p)=>p.provide === TASK_MANAGER_MODULE_OPTIONS);
|
|
90
|
-
expect(provider.inject).toEqual([
|
|
91
|
-
'SOME_TOKEN'
|
|
92
|
-
]);
|
|
93
|
-
const resolved = await provider.useFactory('injected');
|
|
94
|
-
expect(useFactory).toHaveBeenCalledWith('injected');
|
|
95
|
-
expect(resolved.config).toEqual({
|
|
96
|
-
foo: 'bar'
|
|
97
|
-
});
|
|
98
|
-
});
|
|
99
|
-
it('useClass: registers both the class provider and an options provider calling createTaskManagerOptions()', async ()=>{
|
|
100
|
-
let MyOptionsFactory = class MyOptionsFactory {
|
|
101
|
-
constructor(){
|
|
102
|
-
_define_property(this, "createTaskManagerOptions", jest.fn().mockResolvedValue({
|
|
103
|
-
foo: 'from-class'
|
|
104
|
-
}));
|
|
105
|
-
}
|
|
106
|
-
};
|
|
107
|
-
const dynamicModule = TaskManagerModule.forRootAsync({
|
|
108
|
-
useClass: MyOptionsFactory
|
|
109
|
-
});
|
|
110
|
-
const classProvider = dynamicModule.providers.find((p)=>p.provide === MyOptionsFactory);
|
|
111
|
-
expect(classProvider.useClass).toBe(MyOptionsFactory);
|
|
112
|
-
const optionsProvider = dynamicModule.providers.find((p)=>p.provide === TASK_MANAGER_MODULE_OPTIONS);
|
|
113
|
-
const instance = new MyOptionsFactory();
|
|
114
|
-
const resolved = await optionsProvider.useFactory(instance);
|
|
115
|
-
expect(instance.createTaskManagerOptions).toHaveBeenCalled();
|
|
116
|
-
expect(resolved.config).toEqual({
|
|
117
|
-
foo: 'from-class'
|
|
118
|
-
});
|
|
119
|
-
});
|
|
120
|
-
it('useExisting: registers a single options provider without a duplicate class registration', ()=>{
|
|
121
|
-
let MyOptionsFactory = class MyOptionsFactory {
|
|
122
|
-
constructor(){
|
|
123
|
-
_define_property(this, "createTaskManagerOptions", jest.fn());
|
|
124
|
-
}
|
|
125
|
-
};
|
|
126
|
-
const dynamicModule = TaskManagerModule.forRootAsync({
|
|
127
|
-
useExisting: MyOptionsFactory
|
|
128
|
-
});
|
|
129
|
-
expect(dynamicModule.providers.filter((p)=>p.provide === MyOptionsFactory)).toHaveLength(0);
|
|
130
|
-
const optionsProvider = dynamicModule.providers.find((p)=>p.provide === TASK_MANAGER_MODULE_OPTIONS);
|
|
131
|
-
expect(optionsProvider.inject).toEqual([
|
|
132
|
-
MyOptionsFactory
|
|
133
|
-
]);
|
|
134
|
-
});
|
|
135
|
-
it('should register no TASK_MANAGER_MODULE_OPTIONS provider when neither useFactory, useClass, nor useExisting is given', ()=>{
|
|
136
|
-
const dynamicModule = TaskManagerModule.forRootAsync({});
|
|
137
|
-
expect(providerTokens(dynamicModule.providers)).not.toContain(TASK_MANAGER_MODULE_OPTIONS);
|
|
138
|
-
});
|
|
139
|
-
});
|
|
140
|
-
});
|