@flusys/nestjs-notification 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/adapters/index.d.ts +1 -1
- package/adapters/notification.adapter.d.ts +2 -2
- package/config/index.d.ts +3 -3
- package/controllers/index.d.ts +1 -1
- package/controllers/notification.controller.d.ts +4 -4
- package/docs/index.d.ts +1 -1
- package/docs/notification-swagger.config.d.ts +2 -2
- package/dtos/index.d.ts +1 -1
- package/entities/index.d.ts +5 -5
- package/entities/notification-with-company.entity.d.ts +1 -1
- package/fesm/244.js +816 -0
- package/fesm/446.js +939 -0
- package/fesm/adapters/index.js +79 -1
- package/fesm/config/index.js +84 -3
- package/fesm/controllers/index.js +13 -1
- package/fesm/docs/index.js +112 -1
- package/fesm/dtos/index.js +311 -1
- package/fesm/entities/index.js +211 -8
- package/fesm/gateways/index.js +286 -1
- package/fesm/index.js +467 -10
- package/fesm/interfaces/index.js +16 -2
- package/fesm/modules/index.js +269 -1
- package/fesm/runtime.js +85 -0
- package/fesm/services/index.js +357 -4
- package/gateways/index.d.ts +1 -1
- package/gateways/notification.gateway.d.ts +2 -2
- package/index.d.ts +10 -10
- package/interfaces/index.d.ts +2 -2
- package/interfaces/notification-module.interface.d.ts +3 -3
- package/interfaces/notification.interface.d.ts +1 -1
- package/modules/index.d.ts +1 -1
- package/modules/notification.module.d.ts +2 -2
- package/package.json +33 -44
- package/services/index.d.ts +4 -4
- package/services/notification-config.service.d.ts +3 -3
- package/services/notification-datasource.provider.d.ts +3 -3
- package/services/notification-helper.service.d.ts +5 -5
- package/services/notification.service.d.ts +8 -8
- package/cjs/adapters/index.js +0 -18
- package/cjs/adapters/notification.adapter.js +0 -62
- package/cjs/adapters/notification.adapter.spec.js +0 -73
- package/cjs/config/event-actions.js +0 -33
- package/cjs/config/index.js +0 -20
- package/cjs/config/message-keys.js +0 -19
- package/cjs/config/notification.constants.js +0 -11
- package/cjs/controllers/index.js +0 -18
- package/cjs/controllers/notification.controller.js +0 -299
- package/cjs/controllers/notification.controller.spec.js +0 -214
- package/cjs/docs/index.js +0 -18
- package/cjs/docs/notification-swagger.config.js +0 -97
- package/cjs/docs/notification-swagger.config.spec.js +0 -68
- package/cjs/dtos/index.js +0 -18
- package/cjs/dtos/notification.dto.js +0 -295
- package/cjs/entities/index.js +0 -48
- package/cjs/entities/index.spec.js +0 -13
- package/cjs/entities/notification-with-company.entity.js +0 -57
- package/cjs/entities/notification.entity.js +0 -112
- package/cjs/gateways/index.js +0 -18
- package/cjs/gateways/notification.gateway.js +0 -172
- package/cjs/gateways/notification.gateway.spec.js +0 -359
- package/cjs/index.js +0 -28
- package/cjs/interfaces/index.js +0 -19
- package/cjs/interfaces/notification-module.interface.js +0 -4
- package/cjs/interfaces/notification.interface.js +0 -4
- package/cjs/modules/index.js +0 -18
- package/cjs/modules/notification.module.js +0 -139
- package/cjs/modules/notification.module.spec.js +0 -152
- package/cjs/services/index.js +0 -21
- package/cjs/services/notification-config.service.js +0 -69
- package/cjs/services/notification-config.service.spec.js +0 -113
- package/cjs/services/notification-datasource.provider.js +0 -133
- package/cjs/services/notification-datasource.provider.spec.js +0 -136
- package/cjs/services/notification-helper.service.js +0 -197
- package/cjs/services/notification-helper.service.spec.js +0 -392
- package/cjs/services/notification.service.js +0 -281
- package/cjs/services/notification.service.spec.js +0 -560
- package/fesm/adapters/notification.adapter.js +0 -52
- package/fesm/adapters/notification.adapter.spec.js +0 -69
- package/fesm/config/event-actions.js +0 -12
- package/fesm/config/message-keys.js +0 -9
- package/fesm/config/notification.constants.js +0 -1
- package/fesm/controllers/notification.controller.js +0 -289
- package/fesm/controllers/notification.controller.spec.js +0 -210
- package/fesm/docs/notification-swagger.config.js +0 -87
- package/fesm/docs/notification-swagger.config.spec.js +0 -64
- package/fesm/dtos/notification.dto.js +0 -262
- package/fesm/entities/index.spec.js +0 -9
- package/fesm/entities/notification-with-company.entity.js +0 -47
- package/fesm/entities/notification.entity.js +0 -102
- package/fesm/gateways/notification.gateway.js +0 -162
- package/fesm/gateways/notification.gateway.spec.js +0 -355
- package/fesm/interfaces/notification-module.interface.js +0 -1
- package/fesm/interfaces/notification.interface.js +0 -1
- package/fesm/modules/notification.module.js +0 -129
- package/fesm/modules/notification.module.spec.js +0 -148
- package/fesm/services/notification-config.service.js +0 -59
- package/fesm/services/notification-config.service.spec.js +0 -109
- package/fesm/services/notification-datasource.provider.js +0 -82
- package/fesm/services/notification-datasource.provider.spec.js +0 -132
- package/fesm/services/notification-helper.service.js +0 -187
- package/fesm/services/notification-helper.service.spec.js +0 -388
- package/fesm/services/notification.service.js +0 -271
- package/fesm/services/notification.service.spec.js +0 -556
|
@@ -1,392 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", {
|
|
3
|
-
value: true
|
|
4
|
-
});
|
|
5
|
-
const _testing = require("@nestjs/testing");
|
|
6
|
-
const _classes = require("@flusys/nestjs-shared/classes");
|
|
7
|
-
const _modules = require("@flusys/nestjs-shared/modules");
|
|
8
|
-
const _enums = require("@flusys/nestjs-shared/enums");
|
|
9
|
-
const _notificationhelperservice = require("./notification-helper.service");
|
|
10
|
-
const _notificationconfigservice = require("./notification-config.service");
|
|
11
|
-
const _notificationdatasourceprovider = require("./notification-datasource.provider");
|
|
12
|
-
const _gateways = require("../gateways");
|
|
13
|
-
const _entities = require("../entities");
|
|
14
|
-
function createMockRepo() {
|
|
15
|
-
return {
|
|
16
|
-
save: jest.fn()
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
function createMockDataSource(repo) {
|
|
20
|
-
return {
|
|
21
|
-
getRepository: jest.fn().mockReturnValue(repo)
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
describe('NotificationHelperService', ()=>{
|
|
25
|
-
let mockCacheManager;
|
|
26
|
-
let mockUtilsService;
|
|
27
|
-
let mockConfigService;
|
|
28
|
-
let mockDataSourceProvider;
|
|
29
|
-
let mockGateway;
|
|
30
|
-
let mockRepo;
|
|
31
|
-
let mockDataSource;
|
|
32
|
-
async function buildService(withGateway = true) {
|
|
33
|
-
const providers = [
|
|
34
|
-
_notificationhelperservice.NotificationHelperService,
|
|
35
|
-
{
|
|
36
|
-
provide: 'CACHE_INSTANCE',
|
|
37
|
-
useValue: mockCacheManager
|
|
38
|
-
},
|
|
39
|
-
{
|
|
40
|
-
provide: _modules.UtilsService,
|
|
41
|
-
useValue: mockUtilsService
|
|
42
|
-
},
|
|
43
|
-
{
|
|
44
|
-
provide: _notificationconfigservice.NotificationConfigService,
|
|
45
|
-
useValue: mockConfigService
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
provide: _notificationdatasourceprovider.NotificationDataSourceProvider,
|
|
49
|
-
useValue: mockDataSourceProvider
|
|
50
|
-
}
|
|
51
|
-
];
|
|
52
|
-
if (withGateway) {
|
|
53
|
-
providers.push({
|
|
54
|
-
provide: _gateways.NotificationGateway,
|
|
55
|
-
useValue: mockGateway
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
const module = await _testing.Test.createTestingModule({
|
|
59
|
-
providers
|
|
60
|
-
}).compile();
|
|
61
|
-
return module.get(_notificationhelperservice.NotificationHelperService);
|
|
62
|
-
}
|
|
63
|
-
beforeEach(()=>{
|
|
64
|
-
mockRepo = createMockRepo();
|
|
65
|
-
mockDataSource = createMockDataSource(mockRepo);
|
|
66
|
-
mockCacheManager = {};
|
|
67
|
-
mockUtilsService = {
|
|
68
|
-
clearCache: jest.fn().mockResolvedValue(undefined)
|
|
69
|
-
};
|
|
70
|
-
mockConfigService = {
|
|
71
|
-
isCompanyFeatureEnabled: jest.fn().mockReturnValue(false)
|
|
72
|
-
};
|
|
73
|
-
mockDataSourceProvider = {
|
|
74
|
-
getDataSource: jest.fn().mockResolvedValue(mockDataSource)
|
|
75
|
-
};
|
|
76
|
-
mockGateway = {
|
|
77
|
-
emitToUser: jest.fn(),
|
|
78
|
-
emitToUserInCompany: jest.fn(),
|
|
79
|
-
isUserOnline: jest.fn(),
|
|
80
|
-
getOnlineUserCount: jest.fn()
|
|
81
|
-
};
|
|
82
|
-
});
|
|
83
|
-
describe('send', ()=>{
|
|
84
|
-
it('saves the notification, clears the cache, and emits to the user in real time', async ()=>{
|
|
85
|
-
const service = await buildService();
|
|
86
|
-
const options = {
|
|
87
|
-
userId: 'user-1',
|
|
88
|
-
title: 'Hi',
|
|
89
|
-
message: 'body',
|
|
90
|
-
type: _enums.NotificationType.SUCCESS,
|
|
91
|
-
data: {
|
|
92
|
-
a: 1
|
|
93
|
-
}
|
|
94
|
-
};
|
|
95
|
-
const saved = {
|
|
96
|
-
id: 'notif-1',
|
|
97
|
-
...options,
|
|
98
|
-
isRead: false
|
|
99
|
-
};
|
|
100
|
-
mockRepo.save.mockResolvedValue(saved);
|
|
101
|
-
const result = await service.send(options);
|
|
102
|
-
expect(mockDataSource.getRepository).toHaveBeenCalledWith(_entities.Notification);
|
|
103
|
-
expect(mockRepo.save).toHaveBeenCalledWith(expect.objectContaining({
|
|
104
|
-
userId: 'user-1',
|
|
105
|
-
title: 'Hi',
|
|
106
|
-
isRead: false
|
|
107
|
-
}));
|
|
108
|
-
expect(mockUtilsService.clearCache).toHaveBeenCalledWith('notification', mockCacheManager);
|
|
109
|
-
expect(mockGateway.emitToUser).toHaveBeenCalledWith('user-1', 'notification:new', expect.objectContaining({
|
|
110
|
-
id: 'notif-1',
|
|
111
|
-
title: 'Hi'
|
|
112
|
-
}));
|
|
113
|
-
expect(result).toBe(saved);
|
|
114
|
-
});
|
|
115
|
-
it('defaults type to INFO and message/data to null when omitted', async ()=>{
|
|
116
|
-
const service = await buildService();
|
|
117
|
-
mockRepo.save.mockImplementation(async (entity)=>({
|
|
118
|
-
id: 'notif-2',
|
|
119
|
-
...entity
|
|
120
|
-
}));
|
|
121
|
-
await service.send({
|
|
122
|
-
userId: 'user-1',
|
|
123
|
-
title: 'Only title'
|
|
124
|
-
});
|
|
125
|
-
expect(mockRepo.save).toHaveBeenCalledWith(expect.objectContaining({
|
|
126
|
-
type: _enums.NotificationType.INFO,
|
|
127
|
-
message: null,
|
|
128
|
-
data: null
|
|
129
|
-
}));
|
|
130
|
-
});
|
|
131
|
-
it('does not emit in real time when realtime is explicitly false', async ()=>{
|
|
132
|
-
const service = await buildService();
|
|
133
|
-
mockRepo.save.mockResolvedValue({
|
|
134
|
-
id: 'notif-3',
|
|
135
|
-
userId: 'user-1',
|
|
136
|
-
title: 'Silent'
|
|
137
|
-
});
|
|
138
|
-
await service.send({
|
|
139
|
-
userId: 'user-1',
|
|
140
|
-
title: 'Silent',
|
|
141
|
-
realtime: false
|
|
142
|
-
});
|
|
143
|
-
expect(mockGateway.emitToUser).not.toHaveBeenCalled();
|
|
144
|
-
expect(mockGateway.emitToUserInCompany).not.toHaveBeenCalled();
|
|
145
|
-
});
|
|
146
|
-
it('does not throw when no gateway is bound (gateway not registered)', async ()=>{
|
|
147
|
-
const service = await buildService(false);
|
|
148
|
-
mockRepo.save.mockResolvedValue({
|
|
149
|
-
id: 'notif-4',
|
|
150
|
-
userId: 'user-1',
|
|
151
|
-
title: 'No gateway'
|
|
152
|
-
});
|
|
153
|
-
await expect(service.send({
|
|
154
|
-
userId: 'user-1',
|
|
155
|
-
title: 'No gateway'
|
|
156
|
-
})).resolves.toBeDefined();
|
|
157
|
-
});
|
|
158
|
-
it('uses NotificationWithCompany and includes companyId when the company feature is enabled', async ()=>{
|
|
159
|
-
mockConfigService.isCompanyFeatureEnabled.mockReturnValue(true);
|
|
160
|
-
const service = await buildService();
|
|
161
|
-
mockRepo.save.mockImplementation(async (entity)=>({
|
|
162
|
-
id: 'notif-5',
|
|
163
|
-
...entity
|
|
164
|
-
}));
|
|
165
|
-
await service.send({
|
|
166
|
-
userId: 'user-1',
|
|
167
|
-
title: 'Scoped',
|
|
168
|
-
companyId: 'company-1'
|
|
169
|
-
});
|
|
170
|
-
expect(mockDataSource.getRepository).toHaveBeenCalledWith(_entities.NotificationWithCompany);
|
|
171
|
-
expect(mockRepo.save).toHaveBeenCalledWith(expect.objectContaining({
|
|
172
|
-
companyId: 'company-1'
|
|
173
|
-
}));
|
|
174
|
-
expect(mockGateway.emitToUserInCompany).toHaveBeenCalledWith('user-1', 'company-1', 'notification:new', expect.any(Object));
|
|
175
|
-
});
|
|
176
|
-
it('omits companyId when the company feature is enabled but none was provided', async ()=>{
|
|
177
|
-
mockConfigService.isCompanyFeatureEnabled.mockReturnValue(true);
|
|
178
|
-
const service = await buildService();
|
|
179
|
-
mockRepo.save.mockImplementation(async (entity)=>({
|
|
180
|
-
id: 'notif-6',
|
|
181
|
-
...entity
|
|
182
|
-
}));
|
|
183
|
-
await service.send({
|
|
184
|
-
userId: 'user-1',
|
|
185
|
-
title: 'No company'
|
|
186
|
-
});
|
|
187
|
-
const savedArg = mockRepo.save.mock.calls[0][0];
|
|
188
|
-
expect(savedArg.companyId).toBeUndefined();
|
|
189
|
-
});
|
|
190
|
-
it('reuses the same cached data source across multiple calls', async ()=>{
|
|
191
|
-
const service = await buildService();
|
|
192
|
-
mockRepo.save.mockResolvedValue({
|
|
193
|
-
id: 'notif-7',
|
|
194
|
-
userId: 'user-1',
|
|
195
|
-
title: 'A'
|
|
196
|
-
});
|
|
197
|
-
await service.send({
|
|
198
|
-
userId: 'user-1',
|
|
199
|
-
title: 'A'
|
|
200
|
-
});
|
|
201
|
-
await service.send({
|
|
202
|
-
userId: 'user-1',
|
|
203
|
-
title: 'B'
|
|
204
|
-
});
|
|
205
|
-
expect(mockDataSourceProvider.getDataSource).toHaveBeenCalledTimes(1);
|
|
206
|
-
});
|
|
207
|
-
});
|
|
208
|
-
describe('sendToMany', ()=>{
|
|
209
|
-
it('saves a notification for every user id and clears the cache once', async ()=>{
|
|
210
|
-
const service = await buildService();
|
|
211
|
-
const options = {
|
|
212
|
-
userIds: [
|
|
213
|
-
'user-1',
|
|
214
|
-
'user-2',
|
|
215
|
-
'user-3'
|
|
216
|
-
],
|
|
217
|
-
title: 'Broadcast'
|
|
218
|
-
};
|
|
219
|
-
mockRepo.save.mockImplementation(async (entities)=>entities.map((e, i)=>({
|
|
220
|
-
id: `notif-${i}`,
|
|
221
|
-
...e
|
|
222
|
-
})));
|
|
223
|
-
const result = await service.sendToMany(options);
|
|
224
|
-
expect(mockRepo.save).toHaveBeenCalledWith([
|
|
225
|
-
expect.objectContaining({
|
|
226
|
-
userId: 'user-1'
|
|
227
|
-
}),
|
|
228
|
-
expect.objectContaining({
|
|
229
|
-
userId: 'user-2'
|
|
230
|
-
}),
|
|
231
|
-
expect.objectContaining({
|
|
232
|
-
userId: 'user-3'
|
|
233
|
-
})
|
|
234
|
-
]);
|
|
235
|
-
expect(mockUtilsService.clearCache).toHaveBeenCalledTimes(1);
|
|
236
|
-
expect(result).toHaveLength(3);
|
|
237
|
-
});
|
|
238
|
-
it('handles an empty userIds array without saving or emitting', async ()=>{
|
|
239
|
-
const service = await buildService();
|
|
240
|
-
mockRepo.save.mockResolvedValue([]);
|
|
241
|
-
const result = await service.sendToMany({
|
|
242
|
-
userIds: [],
|
|
243
|
-
title: 'Nobody'
|
|
244
|
-
});
|
|
245
|
-
expect(mockRepo.save).toHaveBeenCalledWith([]);
|
|
246
|
-
expect(result).toEqual([]);
|
|
247
|
-
});
|
|
248
|
-
it('emits to each user asynchronously (after the microtask queue flushes) when realtime', async ()=>{
|
|
249
|
-
const service = await buildService();
|
|
250
|
-
mockRepo.save.mockImplementation(async (entities)=>entities.map((e, i)=>({
|
|
251
|
-
id: `notif-${i}`,
|
|
252
|
-
...e
|
|
253
|
-
})));
|
|
254
|
-
await service.sendToMany({
|
|
255
|
-
userIds: [
|
|
256
|
-
'user-1',
|
|
257
|
-
'user-2'
|
|
258
|
-
],
|
|
259
|
-
title: 'Broadcast'
|
|
260
|
-
});
|
|
261
|
-
expect(mockGateway.emitToUser).not.toHaveBeenCalled();
|
|
262
|
-
await new Promise((resolve)=>setImmediate(resolve));
|
|
263
|
-
expect(mockGateway.emitToUser).toHaveBeenCalledTimes(2);
|
|
264
|
-
expect(mockGateway.emitToUser).toHaveBeenCalledWith('user-1', 'notification:new', expect.any(Object));
|
|
265
|
-
expect(mockGateway.emitToUser).toHaveBeenCalledWith('user-2', 'notification:new', expect.any(Object));
|
|
266
|
-
});
|
|
267
|
-
it('does not schedule emits when realtime is false', async ()=>{
|
|
268
|
-
const service = await buildService();
|
|
269
|
-
mockRepo.save.mockImplementation(async (entities)=>entities.map((e, i)=>({
|
|
270
|
-
id: `notif-${i}`,
|
|
271
|
-
...e
|
|
272
|
-
})));
|
|
273
|
-
await service.sendToMany({
|
|
274
|
-
userIds: [
|
|
275
|
-
'user-1'
|
|
276
|
-
],
|
|
277
|
-
title: 'Silent',
|
|
278
|
-
realtime: false
|
|
279
|
-
});
|
|
280
|
-
await new Promise((resolve)=>setImmediate(resolve));
|
|
281
|
-
expect(mockGateway.emitToUser).not.toHaveBeenCalled();
|
|
282
|
-
});
|
|
283
|
-
});
|
|
284
|
-
describe('isUserOnline', ()=>{
|
|
285
|
-
it('delegates to the gateway when present', async ()=>{
|
|
286
|
-
const service = await buildService();
|
|
287
|
-
mockGateway.isUserOnline.mockReturnValue(true);
|
|
288
|
-
expect(service.isUserOnline('user-1')).toBe(true);
|
|
289
|
-
expect(mockGateway.isUserOnline).toHaveBeenCalledWith('user-1');
|
|
290
|
-
});
|
|
291
|
-
it('returns false when no gateway is bound', async ()=>{
|
|
292
|
-
const service = await buildService(false);
|
|
293
|
-
expect(service.isUserOnline('user-1')).toBe(false);
|
|
294
|
-
});
|
|
295
|
-
});
|
|
296
|
-
describe('getOnlineUserCount', ()=>{
|
|
297
|
-
it('delegates to the gateway when present', async ()=>{
|
|
298
|
-
const service = await buildService();
|
|
299
|
-
mockGateway.getOnlineUserCount.mockReturnValue(5);
|
|
300
|
-
expect(service.getOnlineUserCount()).toBe(5);
|
|
301
|
-
});
|
|
302
|
-
it('returns 0 when no gateway is bound', async ()=>{
|
|
303
|
-
const service = await buildService(false);
|
|
304
|
-
expect(service.getOnlineUserCount()).toBe(0);
|
|
305
|
-
});
|
|
306
|
-
});
|
|
307
|
-
describe('domain events', ()=>{
|
|
308
|
-
let publish;
|
|
309
|
-
beforeEach(()=>{
|
|
310
|
-
publish = jest.fn().mockResolvedValue(undefined);
|
|
311
|
-
_classes.EventBusRegistry.reset();
|
|
312
|
-
_classes.EventBusRegistry.setBus({
|
|
313
|
-
publish,
|
|
314
|
-
subscribe: jest.fn(),
|
|
315
|
-
close: jest.fn()
|
|
316
|
-
});
|
|
317
|
-
_classes.EventBusRegistry.configureModule('notification', {
|
|
318
|
-
enabled: true
|
|
319
|
-
});
|
|
320
|
-
});
|
|
321
|
-
afterEach(()=>{
|
|
322
|
-
_classes.EventBusRegistry.reset();
|
|
323
|
-
});
|
|
324
|
-
it('publishes notification.notification.sent for a single send', async ()=>{
|
|
325
|
-
const service = await buildService();
|
|
326
|
-
mockRepo.save.mockResolvedValue({
|
|
327
|
-
id: 'notif-1',
|
|
328
|
-
userId: 'user-1'
|
|
329
|
-
});
|
|
330
|
-
await service.send({
|
|
331
|
-
userId: 'user-1',
|
|
332
|
-
title: 'Hi'
|
|
333
|
-
});
|
|
334
|
-
expect(publish).toHaveBeenCalledWith(expect.objectContaining({
|
|
335
|
-
name: 'notification.notification.sent',
|
|
336
|
-
payload: expect.objectContaining({
|
|
337
|
-
ids: [
|
|
338
|
-
'notif-1'
|
|
339
|
-
],
|
|
340
|
-
metadata: expect.objectContaining({
|
|
341
|
-
recipientCount: 1
|
|
342
|
-
})
|
|
343
|
-
})
|
|
344
|
-
}));
|
|
345
|
-
});
|
|
346
|
-
it('reports the recipient count for a bulk send', async ()=>{
|
|
347
|
-
const service = await buildService();
|
|
348
|
-
mockRepo.save.mockResolvedValue([
|
|
349
|
-
{
|
|
350
|
-
id: 'notif-1',
|
|
351
|
-
userId: 'user-1'
|
|
352
|
-
},
|
|
353
|
-
{
|
|
354
|
-
id: 'notif-2',
|
|
355
|
-
userId: 'user-2'
|
|
356
|
-
}
|
|
357
|
-
]);
|
|
358
|
-
await service.sendToMany({
|
|
359
|
-
userIds: [
|
|
360
|
-
'user-1',
|
|
361
|
-
'user-2'
|
|
362
|
-
],
|
|
363
|
-
title: 'Hi',
|
|
364
|
-
realtime: false
|
|
365
|
-
});
|
|
366
|
-
expect(publish.mock.calls[0][0].payload).toEqual(expect.objectContaining({
|
|
367
|
-
ids: [
|
|
368
|
-
'notif-1',
|
|
369
|
-
'notif-2'
|
|
370
|
-
],
|
|
371
|
-
metadata: expect.objectContaining({
|
|
372
|
-
recipientCount: 2
|
|
373
|
-
})
|
|
374
|
-
}));
|
|
375
|
-
});
|
|
376
|
-
it('does not publish when the notification module has events disabled', async ()=>{
|
|
377
|
-
_classes.EventBusRegistry.configureModule('notification', {
|
|
378
|
-
enabled: false
|
|
379
|
-
});
|
|
380
|
-
const service = await buildService();
|
|
381
|
-
mockRepo.save.mockResolvedValue({
|
|
382
|
-
id: 'notif-1',
|
|
383
|
-
userId: 'user-1'
|
|
384
|
-
});
|
|
385
|
-
await service.send({
|
|
386
|
-
userId: 'user-1',
|
|
387
|
-
title: 'Hi'
|
|
388
|
-
});
|
|
389
|
-
expect(publish).not.toHaveBeenCalled();
|
|
390
|
-
});
|
|
391
|
-
});
|
|
392
|
-
});
|
|
@@ -1,281 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", {
|
|
3
|
-
value: true
|
|
4
|
-
});
|
|
5
|
-
Object.defineProperty(exports, "NotificationService", {
|
|
6
|
-
enumerable: true,
|
|
7
|
-
get: function() {
|
|
8
|
-
return NotificationService;
|
|
9
|
-
}
|
|
10
|
-
});
|
|
11
|
-
const _nestjsshared = require("@flusys/nestjs-shared");
|
|
12
|
-
const _classes = require("@flusys/nestjs-shared/classes");
|
|
13
|
-
const _interfaces = require("@flusys/nestjs-shared/interfaces");
|
|
14
|
-
const _modules = require("@flusys/nestjs-shared/modules");
|
|
15
|
-
const _utils = require("@flusys/nestjs-shared/utils");
|
|
16
|
-
const _common = require("@nestjs/common");
|
|
17
|
-
const _config = require("../config");
|
|
18
|
-
const _entities = require("../entities");
|
|
19
|
-
const _gateways = require("../gateways");
|
|
20
|
-
const _notificationconfigservice = require("./notification-config.service");
|
|
21
|
-
const _notificationdatasourceprovider = require("./notification-datasource.provider");
|
|
22
|
-
function _define_property(obj, key, value) {
|
|
23
|
-
if (key in obj) {
|
|
24
|
-
Object.defineProperty(obj, key, {
|
|
25
|
-
value: value,
|
|
26
|
-
enumerable: true,
|
|
27
|
-
configurable: true,
|
|
28
|
-
writable: true
|
|
29
|
-
});
|
|
30
|
-
} else {
|
|
31
|
-
obj[key] = value;
|
|
32
|
-
}
|
|
33
|
-
return obj;
|
|
34
|
-
}
|
|
35
|
-
function _ts_decorate(decorators, target, key, desc) {
|
|
36
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
37
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
38
|
-
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;
|
|
39
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
40
|
-
}
|
|
41
|
-
function _ts_metadata(k, v) {
|
|
42
|
-
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
43
|
-
}
|
|
44
|
-
function _ts_param(paramIndex, decorator) {
|
|
45
|
-
return function(target, key) {
|
|
46
|
-
decorator(target, key, paramIndex);
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
let NotificationService = class NotificationService extends _classes.ApiService {
|
|
50
|
-
async convertSingleDtoToEntity(dto, user) {
|
|
51
|
-
let entity = {};
|
|
52
|
-
if ('id' in dto && dto.id) {
|
|
53
|
-
const existing = await this.repository.findOne({
|
|
54
|
-
where: {
|
|
55
|
-
id: dto.id
|
|
56
|
-
}
|
|
57
|
-
});
|
|
58
|
-
if (!existing) {
|
|
59
|
-
throw new _common.NotFoundException({
|
|
60
|
-
message: 'Notification not found',
|
|
61
|
-
messageKey: _config.NOTIFICATION_MESSAGES.NOT_FOUND
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
entity = existing;
|
|
65
|
-
}
|
|
66
|
-
const merged = {
|
|
67
|
-
...entity,
|
|
68
|
-
userId: user?.id ?? entity.userId,
|
|
69
|
-
type: dto.type ?? entity.type ?? 'info',
|
|
70
|
-
title: dto.title ?? entity.title,
|
|
71
|
-
message: dto.message ?? entity.message,
|
|
72
|
-
data: dto.data ?? entity.data,
|
|
73
|
-
isRead: entity.isRead ?? false,
|
|
74
|
-
readAt: entity.readAt
|
|
75
|
-
};
|
|
76
|
-
if (this.notificationConfig.isCompanyFeatureEnabled()) {
|
|
77
|
-
merged.companyId = dto.companyId ?? user?.companyId ?? entity.companyId ?? null;
|
|
78
|
-
}
|
|
79
|
-
return merged;
|
|
80
|
-
}
|
|
81
|
-
async getSelectQuery(query, _user, select) {
|
|
82
|
-
const fields = select && select.length ? select : [];
|
|
83
|
-
const always = [
|
|
84
|
-
'createdAt',
|
|
85
|
-
'updatedAt'
|
|
86
|
-
];
|
|
87
|
-
const merged = [
|
|
88
|
-
...new Set([
|
|
89
|
-
...fields,
|
|
90
|
-
...always
|
|
91
|
-
])
|
|
92
|
-
];
|
|
93
|
-
query.addSelect(merged.map((f)=>`${this.entityName}.${f}`));
|
|
94
|
-
return {
|
|
95
|
-
query,
|
|
96
|
-
isRaw: false
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
async getFilterQuery(query, filter, _user) {
|
|
100
|
-
for (const [key, value] of Object.entries(filter)){
|
|
101
|
-
if (key === 'isRead' && typeof value === 'boolean') {
|
|
102
|
-
query.andWhere(`${this.entityName}.isRead = :isRead`, {
|
|
103
|
-
isRead: value
|
|
104
|
-
});
|
|
105
|
-
} else if (key === 'type' && value) {
|
|
106
|
-
query.andWhere(`${this.entityName}.type = :type`, {
|
|
107
|
-
type: value
|
|
108
|
-
});
|
|
109
|
-
} else if (key === 'userId' && value) {
|
|
110
|
-
query.andWhere(`${this.entityName}.userId = :userId`, {
|
|
111
|
-
userId: value
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
return {
|
|
116
|
-
query,
|
|
117
|
-
isRaw: false
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
async getExtraManipulateQuery(query, filterDto, user) {
|
|
121
|
-
const result = await super.getExtraManipulateQuery(query, filterDto, user);
|
|
122
|
-
if (user?.id) {
|
|
123
|
-
query.andWhere(`${this.entityName}.userId = :currentUserId`, {
|
|
124
|
-
currentUserId: user.id
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
(0, _utils.applyCompanyFilter)(query, {
|
|
128
|
-
isCompanyFeatureEnabled: this.notificationConfig.isCompanyFeatureEnabled(),
|
|
129
|
-
entityAlias: this.entityName
|
|
130
|
-
}, user);
|
|
131
|
-
const filterSort = filterDto?.sort;
|
|
132
|
-
if (!filterSort || Object.keys(filterSort).length === 0) {
|
|
133
|
-
query.orderBy(`${this.entityName}.createdAt`, 'DESC');
|
|
134
|
-
}
|
|
135
|
-
return result;
|
|
136
|
-
}
|
|
137
|
-
async afterInsertOperation(entities, _dto, _user, _qr) {
|
|
138
|
-
for (const entity of entities){
|
|
139
|
-
const ext = entity;
|
|
140
|
-
this.emitToUser(entity.userId, ext.companyId, 'notification:new', this.toPayload(entity));
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
async markAsRead(id, user) {
|
|
144
|
-
await this.ensureDataSourceRepository();
|
|
145
|
-
const notification = await this.repository.findOne({
|
|
146
|
-
where: {
|
|
147
|
-
id,
|
|
148
|
-
userId: user.id
|
|
149
|
-
}
|
|
150
|
-
});
|
|
151
|
-
if (!notification) {
|
|
152
|
-
throw new _common.NotFoundException({
|
|
153
|
-
message: 'Notification not found',
|
|
154
|
-
messageKey: _config.NOTIFICATION_MESSAGES.NOT_FOUND
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
notification.isRead = true;
|
|
158
|
-
notification.readAt = new Date();
|
|
159
|
-
await this.repository.save(notification);
|
|
160
|
-
await Promise.all([
|
|
161
|
-
this.clearCacheForAll(),
|
|
162
|
-
this.clearCacheForId([
|
|
163
|
-
notification
|
|
164
|
-
])
|
|
165
|
-
]);
|
|
166
|
-
// Emit to user's sockets in the same company context
|
|
167
|
-
const ext = notification;
|
|
168
|
-
this.emitToUser(user.id, ext.companyId ?? user.companyId, 'notification:read', {
|
|
169
|
-
id
|
|
170
|
-
});
|
|
171
|
-
await this.publishDomainAction(_config.NOTIFICATION_EVENT_ACTIONS.READ, {
|
|
172
|
-
ids: [
|
|
173
|
-
notification.id
|
|
174
|
-
],
|
|
175
|
-
user
|
|
176
|
-
});
|
|
177
|
-
return notification;
|
|
178
|
-
}
|
|
179
|
-
async markAllAsRead(user) {
|
|
180
|
-
await this.ensureDataSourceRepository();
|
|
181
|
-
const where = {
|
|
182
|
-
userId: user.id,
|
|
183
|
-
isRead: false
|
|
184
|
-
};
|
|
185
|
-
if (this.notificationConfig.isCompanyFeatureEnabled() && user.companyId) {
|
|
186
|
-
where.companyId = user.companyId;
|
|
187
|
-
}
|
|
188
|
-
const result = await this.repository.update(where, {
|
|
189
|
-
isRead: true,
|
|
190
|
-
readAt: new Date()
|
|
191
|
-
});
|
|
192
|
-
await this.clearCacheForAll();
|
|
193
|
-
// Emit to user's sockets in the current company context
|
|
194
|
-
this.emitToUser(user.id, user.companyId, 'notification:read-all', {});
|
|
195
|
-
await this.publishDomainAction(_config.NOTIFICATION_EVENT_ACTIONS.ALL_READ, {
|
|
196
|
-
metadata: {
|
|
197
|
-
count: result.affected ?? 0
|
|
198
|
-
},
|
|
199
|
-
user
|
|
200
|
-
});
|
|
201
|
-
return result.affected ?? 0;
|
|
202
|
-
}
|
|
203
|
-
async getUnreadCount(user) {
|
|
204
|
-
await this.ensureDataSourceRepository();
|
|
205
|
-
const where = {
|
|
206
|
-
userId: user.id,
|
|
207
|
-
isRead: false
|
|
208
|
-
};
|
|
209
|
-
if (this.notificationConfig.isCompanyFeatureEnabled() && user.companyId) {
|
|
210
|
-
where.companyId = user.companyId;
|
|
211
|
-
}
|
|
212
|
-
return this.repository.count({
|
|
213
|
-
where
|
|
214
|
-
});
|
|
215
|
-
}
|
|
216
|
-
toPayload(entity) {
|
|
217
|
-
return {
|
|
218
|
-
id: entity.id,
|
|
219
|
-
type: entity.type,
|
|
220
|
-
title: entity.title,
|
|
221
|
-
message: entity.message,
|
|
222
|
-
data: entity.data,
|
|
223
|
-
createdAt: entity.createdAt
|
|
224
|
-
};
|
|
225
|
-
}
|
|
226
|
-
emitToUser(userId, companyId, event, payload) {
|
|
227
|
-
if (!this.gateway) {
|
|
228
|
-
return;
|
|
229
|
-
}
|
|
230
|
-
if (this.notificationConfig.isCompanyFeatureEnabled() && companyId) {
|
|
231
|
-
this.gateway.emitToUserInCompany(userId, companyId, event, payload);
|
|
232
|
-
} else {
|
|
233
|
-
this.gateway.emitToUser(userId, event, payload);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
constructor(cacheManager, utilsService, notificationConfig, dataSourceProvider, gateway){
|
|
237
|
-
super(_config.NOTIFICATION_EVENT_ENTITIES.NOTIFICATION, cacheManager, utilsService, NotificationService.name, false, _config.NOTIFICATION_EVENT_MODULE, dataSourceProvider, notificationConfig.isCompanyFeatureEnabled() ? _entities.NotificationWithCompany : _entities.Notification), _define_property(this, "cacheManager", void 0), _define_property(this, "utilsService", void 0), _define_property(this, "notificationConfig", void 0), _define_property(this, "gateway", void 0), this.cacheManager = cacheManager, this.utilsService = utilsService, this.notificationConfig = notificationConfig, this.gateway = gateway;
|
|
238
|
-
}
|
|
239
|
-
};
|
|
240
|
-
_ts_decorate([
|
|
241
|
-
(0, _nestjsshared.LogAction)({
|
|
242
|
-
action: 'notification.markAsRead',
|
|
243
|
-
module: 'notification'
|
|
244
|
-
}),
|
|
245
|
-
_ts_metadata("design:type", Function),
|
|
246
|
-
_ts_metadata("design:paramtypes", [
|
|
247
|
-
String,
|
|
248
|
-
typeof _interfaces.ILoggedUserInfo === "undefined" ? Object : _interfaces.ILoggedUserInfo
|
|
249
|
-
]),
|
|
250
|
-
_ts_metadata("design:returntype", Promise)
|
|
251
|
-
], NotificationService.prototype, "markAsRead", null);
|
|
252
|
-
_ts_decorate([
|
|
253
|
-
(0, _nestjsshared.LogAction)({
|
|
254
|
-
action: 'notification.markAllAsRead',
|
|
255
|
-
module: 'notification'
|
|
256
|
-
}),
|
|
257
|
-
_ts_metadata("design:type", Function),
|
|
258
|
-
_ts_metadata("design:paramtypes", [
|
|
259
|
-
typeof _interfaces.ILoggedUserInfo === "undefined" ? Object : _interfaces.ILoggedUserInfo
|
|
260
|
-
]),
|
|
261
|
-
_ts_metadata("design:returntype", Promise)
|
|
262
|
-
], NotificationService.prototype, "markAllAsRead", null);
|
|
263
|
-
NotificationService = _ts_decorate([
|
|
264
|
-
(0, _common.Injectable)({
|
|
265
|
-
scope: _common.Scope.REQUEST
|
|
266
|
-
}),
|
|
267
|
-
_ts_param(0, (0, _common.Inject)('CACHE_INSTANCE')),
|
|
268
|
-
_ts_param(1, (0, _common.Inject)(_modules.UtilsService)),
|
|
269
|
-
_ts_param(2, (0, _common.Inject)(_notificationconfigservice.NotificationConfigService)),
|
|
270
|
-
_ts_param(3, (0, _common.Inject)(_notificationdatasourceprovider.NotificationDataSourceProvider)),
|
|
271
|
-
_ts_param(4, (0, _common.Optional)()),
|
|
272
|
-
_ts_param(4, (0, _common.Inject)(_gateways.NotificationGateway)),
|
|
273
|
-
_ts_metadata("design:type", Function),
|
|
274
|
-
_ts_metadata("design:paramtypes", [
|
|
275
|
-
typeof _classes.HybridCache === "undefined" ? Object : _classes.HybridCache,
|
|
276
|
-
typeof _modules.UtilsService === "undefined" ? Object : _modules.UtilsService,
|
|
277
|
-
typeof _notificationconfigservice.NotificationConfigService === "undefined" ? Object : _notificationconfigservice.NotificationConfigService,
|
|
278
|
-
typeof _notificationdatasourceprovider.NotificationDataSourceProvider === "undefined" ? Object : _notificationdatasourceprovider.NotificationDataSourceProvider,
|
|
279
|
-
typeof _gateways.NotificationGateway === "undefined" ? Object : _gateways.NotificationGateway
|
|
280
|
-
])
|
|
281
|
-
], NotificationService);
|