@flusys/nestjs-notification 6.4.1 → 7.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/cjs/config/event-actions.js +33 -0
- package/cjs/config/index.js +2 -1
- package/cjs/modules/notification.module.js +1 -0
- package/cjs/services/notification-helper.service.js +18 -0
- package/cjs/services/notification-helper.service.spec.js +86 -0
- package/cjs/services/notification.service.js +12 -0
- package/cjs/services/notification.service.spec.js +69 -0
- package/config/event-actions.d.ts +9 -0
- package/config/index.d.ts +2 -1
- package/fesm/config/event-actions.js +12 -0
- package/fesm/config/index.js +2 -1
- package/fesm/modules/notification.module.js +2 -1
- package/fesm/services/notification-helper.service.js +19 -1
- package/fesm/services/notification-helper.service.spec.js +86 -0
- package/fesm/services/notification.service.js +13 -1
- package/fesm/services/notification.service.spec.js +69 -0
- package/interfaces/notification-module.interface.d.ts +2 -0
- package/package.json +3 -3
- package/services/notification-helper.service.d.ts +1 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Domain event actions published by the notification module, alongside the CRUD
|
|
3
|
+
* lifecycle actions every ApiService emits.
|
|
4
|
+
*/ "use strict";
|
|
5
|
+
Object.defineProperty(exports, "__esModule", {
|
|
6
|
+
value: true
|
|
7
|
+
});
|
|
8
|
+
function _export(target, all) {
|
|
9
|
+
for(var name in all)Object.defineProperty(target, name, {
|
|
10
|
+
enumerable: true,
|
|
11
|
+
get: Object.getOwnPropertyDescriptor(all, name).get
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
_export(exports, {
|
|
15
|
+
get NOTIFICATION_EVENT_ACTIONS () {
|
|
16
|
+
return NOTIFICATION_EVENT_ACTIONS;
|
|
17
|
+
},
|
|
18
|
+
get NOTIFICATION_EVENT_ENTITIES () {
|
|
19
|
+
return NOTIFICATION_EVENT_ENTITIES;
|
|
20
|
+
},
|
|
21
|
+
get NOTIFICATION_EVENT_MODULE () {
|
|
22
|
+
return NOTIFICATION_EVENT_MODULE;
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
const NOTIFICATION_EVENT_ACTIONS = {
|
|
26
|
+
SENT: 'sent',
|
|
27
|
+
READ: 'read',
|
|
28
|
+
ALL_READ: 'all-read'
|
|
29
|
+
};
|
|
30
|
+
const NOTIFICATION_EVENT_ENTITIES = {
|
|
31
|
+
NOTIFICATION: 'notification'
|
|
32
|
+
};
|
|
33
|
+
const NOTIFICATION_EVENT_MODULE = 'notification';
|
package/cjs/config/index.js
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", {
|
|
3
3
|
value: true
|
|
4
4
|
});
|
|
5
|
-
_export_star(require("./
|
|
5
|
+
_export_star(require("./event-actions"), exports);
|
|
6
6
|
_export_star(require("./message-keys"), exports);
|
|
7
|
+
_export_star(require("./notification.constants"), exports);
|
|
7
8
|
function _export_star(from, to) {
|
|
8
9
|
Object.keys(from).forEach(function(k) {
|
|
9
10
|
if (k !== "default" && !Object.prototype.hasOwnProperty.call(to, k)) {
|
|
@@ -95,6 +95,7 @@ let NotificationModule = class NotificationModule {
|
|
|
95
95
|
useValue: options
|
|
96
96
|
});
|
|
97
97
|
}
|
|
98
|
+
providers.push((0, _modules.createModuleEventsProvider)('notification', _notificationconstants.NOTIFICATION_MODULE_OPTIONS));
|
|
98
99
|
return providers;
|
|
99
100
|
}
|
|
100
101
|
static createAsyncProviders(options) {
|
|
@@ -13,6 +13,7 @@ const _classes = require("@flusys/nestjs-shared/classes");
|
|
|
13
13
|
const _enums = require("@flusys/nestjs-shared/enums");
|
|
14
14
|
const _modules = require("@flusys/nestjs-shared/modules");
|
|
15
15
|
const _common = require("@nestjs/common");
|
|
16
|
+
const _config = require("../config");
|
|
16
17
|
const _entities = require("../entities");
|
|
17
18
|
const _gateways = require("../gateways");
|
|
18
19
|
const _notificationconfigservice = require("./notification-config.service");
|
|
@@ -51,6 +52,11 @@ let NotificationHelperService = class NotificationHelperService {
|
|
|
51
52
|
const saved = await repo.save(entity);
|
|
52
53
|
await this.clearCache();
|
|
53
54
|
this.emitToUser(saved, options.realtime !== false);
|
|
55
|
+
await this.publishSentEvent([
|
|
56
|
+
saved
|
|
57
|
+
], [
|
|
58
|
+
options.userId
|
|
59
|
+
]);
|
|
54
60
|
return saved;
|
|
55
61
|
}
|
|
56
62
|
async sendToMany(options) {
|
|
@@ -65,6 +71,7 @@ let NotificationHelperService = class NotificationHelperService {
|
|
|
65
71
|
}
|
|
66
72
|
});
|
|
67
73
|
}
|
|
74
|
+
await this.publishSentEvent(saved, options.userIds);
|
|
68
75
|
return saved;
|
|
69
76
|
}
|
|
70
77
|
isUserOnline(userId) {
|
|
@@ -73,6 +80,17 @@ let NotificationHelperService = class NotificationHelperService {
|
|
|
73
80
|
getOnlineUserCount() {
|
|
74
81
|
return this.gateway?.getOnlineUserCount() ?? 0;
|
|
75
82
|
}
|
|
83
|
+
async publishSentEvent(notifications, userIds) {
|
|
84
|
+
await (0, _classes.publishDomainEvent)({
|
|
85
|
+
module: _config.NOTIFICATION_EVENT_MODULE,
|
|
86
|
+
entity: _config.NOTIFICATION_EVENT_ENTITIES.NOTIFICATION,
|
|
87
|
+
action: _config.NOTIFICATION_EVENT_ACTIONS.SENT,
|
|
88
|
+
ids: notifications.map((notification)=>notification.id),
|
|
89
|
+
metadata: {
|
|
90
|
+
recipientCount: userIds.length
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}
|
|
76
94
|
async getDataSource() {
|
|
77
95
|
if (!this.dataSource) {
|
|
78
96
|
this.dataSource = await this.dataSourceProvider.getDataSource();
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
3
3
|
value: true
|
|
4
4
|
});
|
|
5
5
|
const _testing = require("@nestjs/testing");
|
|
6
|
+
const _classes = require("@flusys/nestjs-shared/classes");
|
|
6
7
|
const _modules = require("@flusys/nestjs-shared/modules");
|
|
7
8
|
const _enums = require("@flusys/nestjs-shared/enums");
|
|
8
9
|
const _notificationhelperservice = require("./notification-helper.service");
|
|
@@ -303,4 +304,89 @@ describe('NotificationHelperService', ()=>{
|
|
|
303
304
|
expect(service.getOnlineUserCount()).toBe(0);
|
|
304
305
|
});
|
|
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
|
+
});
|
|
306
392
|
});
|
|
@@ -171,6 +171,12 @@ let NotificationService = class NotificationService extends _classes.ApiService
|
|
|
171
171
|
this.emitToUser(user.id, ext.companyId ?? user.companyId, 'notification:read', {
|
|
172
172
|
id
|
|
173
173
|
});
|
|
174
|
+
await this.publishDomainAction(_config.NOTIFICATION_EVENT_ACTIONS.READ, {
|
|
175
|
+
ids: [
|
|
176
|
+
notification.id
|
|
177
|
+
],
|
|
178
|
+
user
|
|
179
|
+
});
|
|
174
180
|
return notification;
|
|
175
181
|
}
|
|
176
182
|
async markAllAsRead(user) {
|
|
@@ -189,6 +195,12 @@ let NotificationService = class NotificationService extends _classes.ApiService
|
|
|
189
195
|
await this.clearCacheForAll();
|
|
190
196
|
// Emit to user's sockets in the current company context
|
|
191
197
|
this.emitToUser(user.id, user.companyId, 'notification:read-all', {});
|
|
198
|
+
await this.publishDomainAction(_config.NOTIFICATION_EVENT_ACTIONS.ALL_READ, {
|
|
199
|
+
metadata: {
|
|
200
|
+
count: result.affected ?? 0
|
|
201
|
+
},
|
|
202
|
+
user
|
|
203
|
+
});
|
|
192
204
|
return result.affected ?? 0;
|
|
193
205
|
}
|
|
194
206
|
async getUnreadCount(user) {
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
3
3
|
value: true
|
|
4
4
|
});
|
|
5
5
|
const _testing = require("@nestjs/testing");
|
|
6
|
+
const _classes = require("@flusys/nestjs-shared/classes");
|
|
6
7
|
const _modules = require("@flusys/nestjs-shared/modules");
|
|
7
8
|
const _repositorymock = require("@test-utils/mocks/repository.mock");
|
|
8
9
|
const _loggedusermock = require("@test-utils/mocks/logged-user.mock");
|
|
@@ -487,4 +488,72 @@ describe('NotificationService', ()=>{
|
|
|
487
488
|
await expect(noGatewayService.markAsRead('notif-1', user)).resolves.toBeDefined();
|
|
488
489
|
});
|
|
489
490
|
});
|
|
491
|
+
describe('domain events', ()=>{
|
|
492
|
+
let publish;
|
|
493
|
+
beforeEach(()=>{
|
|
494
|
+
publish = jest.fn().mockResolvedValue(undefined);
|
|
495
|
+
_classes.EventBusRegistry.reset();
|
|
496
|
+
_classes.EventBusRegistry.setBus({
|
|
497
|
+
publish,
|
|
498
|
+
subscribe: jest.fn(),
|
|
499
|
+
close: jest.fn()
|
|
500
|
+
});
|
|
501
|
+
_classes.EventBusRegistry.configureModule('notification', {
|
|
502
|
+
enabled: true
|
|
503
|
+
});
|
|
504
|
+
});
|
|
505
|
+
afterEach(()=>{
|
|
506
|
+
_classes.EventBusRegistry.reset();
|
|
507
|
+
});
|
|
508
|
+
it('publishes notification.notification.read when one is marked read', async ()=>{
|
|
509
|
+
const user = (0, _loggedusermock.buildMockUser)();
|
|
510
|
+
const notification = buildNotification({
|
|
511
|
+
id: 'notif-1',
|
|
512
|
+
userId: user.id,
|
|
513
|
+
isRead: false
|
|
514
|
+
});
|
|
515
|
+
mockRepo.findOne.mockResolvedValue(notification);
|
|
516
|
+
mockRepo.save.mockResolvedValue({
|
|
517
|
+
...notification,
|
|
518
|
+
isRead: true
|
|
519
|
+
});
|
|
520
|
+
await service.markAsRead('notif-1', user);
|
|
521
|
+
expect(publish).toHaveBeenCalledWith(expect.objectContaining({
|
|
522
|
+
name: 'notification.notification.read',
|
|
523
|
+
payload: expect.objectContaining({
|
|
524
|
+
ids: [
|
|
525
|
+
'notif-1'
|
|
526
|
+
]
|
|
527
|
+
})
|
|
528
|
+
}));
|
|
529
|
+
});
|
|
530
|
+
it('publishes notification.notification.all-read with the affected count', async ()=>{
|
|
531
|
+
mockRepo.update.mockResolvedValue({
|
|
532
|
+
affected: 4,
|
|
533
|
+
raw: {},
|
|
534
|
+
generatedMaps: []
|
|
535
|
+
});
|
|
536
|
+
await service.markAllAsRead((0, _loggedusermock.buildMockUser)());
|
|
537
|
+
expect(publish).toHaveBeenCalledWith(expect.objectContaining({
|
|
538
|
+
name: 'notification.notification.all-read',
|
|
539
|
+
payload: expect.objectContaining({
|
|
540
|
+
metadata: {
|
|
541
|
+
count: 4
|
|
542
|
+
}
|
|
543
|
+
})
|
|
544
|
+
}));
|
|
545
|
+
});
|
|
546
|
+
it('does not publish when the notification module has events disabled', async ()=>{
|
|
547
|
+
_classes.EventBusRegistry.configureModule('notification', {
|
|
548
|
+
enabled: false
|
|
549
|
+
});
|
|
550
|
+
mockRepo.update.mockResolvedValue({
|
|
551
|
+
affected: 1,
|
|
552
|
+
raw: {},
|
|
553
|
+
generatedMaps: []
|
|
554
|
+
});
|
|
555
|
+
await service.markAllAsRead((0, _loggedusermock.buildMockUser)());
|
|
556
|
+
expect(publish).not.toHaveBeenCalled();
|
|
557
|
+
});
|
|
558
|
+
});
|
|
490
559
|
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const NOTIFICATION_EVENT_ACTIONS: {
|
|
2
|
+
readonly SENT: "sent";
|
|
3
|
+
readonly READ: "read";
|
|
4
|
+
readonly ALL_READ: "all-read";
|
|
5
|
+
};
|
|
6
|
+
export declare const NOTIFICATION_EVENT_ENTITIES: {
|
|
7
|
+
readonly NOTIFICATION: "notification";
|
|
8
|
+
};
|
|
9
|
+
export declare const NOTIFICATION_EVENT_MODULE = "notification";
|
package/config/index.d.ts
CHANGED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Domain event actions published by the notification module, alongside the CRUD
|
|
3
|
+
* lifecycle actions every ApiService emits.
|
|
4
|
+
*/ export const NOTIFICATION_EVENT_ACTIONS = {
|
|
5
|
+
SENT: 'sent',
|
|
6
|
+
READ: 'read',
|
|
7
|
+
ALL_READ: 'all-read'
|
|
8
|
+
};
|
|
9
|
+
export const NOTIFICATION_EVENT_ENTITIES = {
|
|
10
|
+
NOTIFICATION: 'notification'
|
|
11
|
+
};
|
|
12
|
+
export const NOTIFICATION_EVENT_MODULE = 'notification';
|
package/fesm/config/index.js
CHANGED
|
@@ -4,7 +4,7 @@ function _ts_decorate(decorators, target, key, desc) {
|
|
|
4
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
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
}
|
|
7
|
-
import { CacheModule, UtilsModule } from '@flusys/nestjs-shared/modules';
|
|
7
|
+
import { CacheModule, UtilsModule, createModuleEventsProvider } from '@flusys/nestjs-shared/modules';
|
|
8
8
|
import { NOTIFICATION_ADAPTER } from '@flusys/nestjs-shared/interfaces';
|
|
9
9
|
import { Module } from '@nestjs/common';
|
|
10
10
|
import { JwtService } from '@nestjs/jwt';
|
|
@@ -85,6 +85,7 @@ export class NotificationModule {
|
|
|
85
85
|
useValue: options
|
|
86
86
|
});
|
|
87
87
|
}
|
|
88
|
+
providers.push(createModuleEventsProvider('notification', NOTIFICATION_MODULE_OPTIONS));
|
|
88
89
|
return providers;
|
|
89
90
|
}
|
|
90
91
|
static createAsyncProviders(options) {
|
|
@@ -26,10 +26,11 @@ function _ts_param(paramIndex, decorator) {
|
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
28
|
import { INotificationBulkSendOptions, INotificationSendOptions, LogAction } from '@flusys/nestjs-shared';
|
|
29
|
-
import { HybridCache } from '@flusys/nestjs-shared/classes';
|
|
29
|
+
import { HybridCache, publishDomainEvent } from '@flusys/nestjs-shared/classes';
|
|
30
30
|
import { NotificationType } from '@flusys/nestjs-shared/enums';
|
|
31
31
|
import { UtilsService } from '@flusys/nestjs-shared/modules';
|
|
32
32
|
import { Inject, Injectable, Optional } from '@nestjs/common';
|
|
33
|
+
import { NOTIFICATION_EVENT_ACTIONS, NOTIFICATION_EVENT_ENTITIES, NOTIFICATION_EVENT_MODULE } from '../config';
|
|
33
34
|
import { Notification, NotificationWithCompany } from '../entities';
|
|
34
35
|
import { NotificationGateway } from '../gateways';
|
|
35
36
|
import { NotificationConfigService } from './notification-config.service';
|
|
@@ -41,6 +42,11 @@ export class NotificationHelperService {
|
|
|
41
42
|
const saved = await repo.save(entity);
|
|
42
43
|
await this.clearCache();
|
|
43
44
|
this.emitToUser(saved, options.realtime !== false);
|
|
45
|
+
await this.publishSentEvent([
|
|
46
|
+
saved
|
|
47
|
+
], [
|
|
48
|
+
options.userId
|
|
49
|
+
]);
|
|
44
50
|
return saved;
|
|
45
51
|
}
|
|
46
52
|
async sendToMany(options) {
|
|
@@ -55,6 +61,7 @@ export class NotificationHelperService {
|
|
|
55
61
|
}
|
|
56
62
|
});
|
|
57
63
|
}
|
|
64
|
+
await this.publishSentEvent(saved, options.userIds);
|
|
58
65
|
return saved;
|
|
59
66
|
}
|
|
60
67
|
isUserOnline(userId) {
|
|
@@ -63,6 +70,17 @@ export class NotificationHelperService {
|
|
|
63
70
|
getOnlineUserCount() {
|
|
64
71
|
return this.gateway?.getOnlineUserCount() ?? 0;
|
|
65
72
|
}
|
|
73
|
+
async publishSentEvent(notifications, userIds) {
|
|
74
|
+
await publishDomainEvent({
|
|
75
|
+
module: NOTIFICATION_EVENT_MODULE,
|
|
76
|
+
entity: NOTIFICATION_EVENT_ENTITIES.NOTIFICATION,
|
|
77
|
+
action: NOTIFICATION_EVENT_ACTIONS.SENT,
|
|
78
|
+
ids: notifications.map((notification)=>notification.id),
|
|
79
|
+
metadata: {
|
|
80
|
+
recipientCount: userIds.length
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
}
|
|
66
84
|
async getDataSource() {
|
|
67
85
|
if (!this.dataSource) {
|
|
68
86
|
this.dataSource = await this.dataSourceProvider.getDataSource();
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Test } from '@nestjs/testing';
|
|
2
|
+
import { EventBusRegistry } from '@flusys/nestjs-shared/classes';
|
|
2
3
|
import { UtilsService } from '@flusys/nestjs-shared/modules';
|
|
3
4
|
import { NotificationType } from '@flusys/nestjs-shared/enums';
|
|
4
5
|
import { NotificationHelperService } from './notification-helper.service';
|
|
@@ -299,4 +300,89 @@ describe('NotificationHelperService', ()=>{
|
|
|
299
300
|
expect(service.getOnlineUserCount()).toBe(0);
|
|
300
301
|
});
|
|
301
302
|
});
|
|
303
|
+
describe('domain events', ()=>{
|
|
304
|
+
let publish;
|
|
305
|
+
beforeEach(()=>{
|
|
306
|
+
publish = jest.fn().mockResolvedValue(undefined);
|
|
307
|
+
EventBusRegistry.reset();
|
|
308
|
+
EventBusRegistry.setBus({
|
|
309
|
+
publish,
|
|
310
|
+
subscribe: jest.fn(),
|
|
311
|
+
close: jest.fn()
|
|
312
|
+
});
|
|
313
|
+
EventBusRegistry.configureModule('notification', {
|
|
314
|
+
enabled: true
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
afterEach(()=>{
|
|
318
|
+
EventBusRegistry.reset();
|
|
319
|
+
});
|
|
320
|
+
it('publishes notification.notification.sent for a single send', async ()=>{
|
|
321
|
+
const service = await buildService();
|
|
322
|
+
mockRepo.save.mockResolvedValue({
|
|
323
|
+
id: 'notif-1',
|
|
324
|
+
userId: 'user-1'
|
|
325
|
+
});
|
|
326
|
+
await service.send({
|
|
327
|
+
userId: 'user-1',
|
|
328
|
+
title: 'Hi'
|
|
329
|
+
});
|
|
330
|
+
expect(publish).toHaveBeenCalledWith(expect.objectContaining({
|
|
331
|
+
name: 'notification.notification.sent',
|
|
332
|
+
payload: expect.objectContaining({
|
|
333
|
+
ids: [
|
|
334
|
+
'notif-1'
|
|
335
|
+
],
|
|
336
|
+
metadata: expect.objectContaining({
|
|
337
|
+
recipientCount: 1
|
|
338
|
+
})
|
|
339
|
+
})
|
|
340
|
+
}));
|
|
341
|
+
});
|
|
342
|
+
it('reports the recipient count for a bulk send', async ()=>{
|
|
343
|
+
const service = await buildService();
|
|
344
|
+
mockRepo.save.mockResolvedValue([
|
|
345
|
+
{
|
|
346
|
+
id: 'notif-1',
|
|
347
|
+
userId: 'user-1'
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
id: 'notif-2',
|
|
351
|
+
userId: 'user-2'
|
|
352
|
+
}
|
|
353
|
+
]);
|
|
354
|
+
await service.sendToMany({
|
|
355
|
+
userIds: [
|
|
356
|
+
'user-1',
|
|
357
|
+
'user-2'
|
|
358
|
+
],
|
|
359
|
+
title: 'Hi',
|
|
360
|
+
realtime: false
|
|
361
|
+
});
|
|
362
|
+
expect(publish.mock.calls[0][0].payload).toEqual(expect.objectContaining({
|
|
363
|
+
ids: [
|
|
364
|
+
'notif-1',
|
|
365
|
+
'notif-2'
|
|
366
|
+
],
|
|
367
|
+
metadata: expect.objectContaining({
|
|
368
|
+
recipientCount: 2
|
|
369
|
+
})
|
|
370
|
+
}));
|
|
371
|
+
});
|
|
372
|
+
it('does not publish when the notification module has events disabled', async ()=>{
|
|
373
|
+
EventBusRegistry.configureModule('notification', {
|
|
374
|
+
enabled: false
|
|
375
|
+
});
|
|
376
|
+
const service = await buildService();
|
|
377
|
+
mockRepo.save.mockResolvedValue({
|
|
378
|
+
id: 'notif-1',
|
|
379
|
+
userId: 'user-1'
|
|
380
|
+
});
|
|
381
|
+
await service.send({
|
|
382
|
+
userId: 'user-1',
|
|
383
|
+
title: 'Hi'
|
|
384
|
+
});
|
|
385
|
+
expect(publish).not.toHaveBeenCalled();
|
|
386
|
+
});
|
|
387
|
+
});
|
|
302
388
|
});
|
|
@@ -31,7 +31,7 @@ import { ILoggedUserInfo } from '@flusys/nestjs-shared/interfaces';
|
|
|
31
31
|
import { UtilsService } from '@flusys/nestjs-shared/modules';
|
|
32
32
|
import { applyCompanyFilter } from '@flusys/nestjs-shared/utils';
|
|
33
33
|
import { Inject, Injectable, NotFoundException, Optional, Scope } from '@nestjs/common';
|
|
34
|
-
import { NOTIFICATION_MESSAGES } from '../config';
|
|
34
|
+
import { NOTIFICATION_EVENT_ACTIONS, NOTIFICATION_MESSAGES } from '../config';
|
|
35
35
|
import { Notification, NotificationWithCompany } from '../entities';
|
|
36
36
|
import { NotificationGateway } from '../gateways';
|
|
37
37
|
import { NotificationConfigService } from './notification-config.service';
|
|
@@ -161,6 +161,12 @@ export class NotificationService extends ApiService {
|
|
|
161
161
|
this.emitToUser(user.id, ext.companyId ?? user.companyId, 'notification:read', {
|
|
162
162
|
id
|
|
163
163
|
});
|
|
164
|
+
await this.publishDomainAction(NOTIFICATION_EVENT_ACTIONS.READ, {
|
|
165
|
+
ids: [
|
|
166
|
+
notification.id
|
|
167
|
+
],
|
|
168
|
+
user
|
|
169
|
+
});
|
|
164
170
|
return notification;
|
|
165
171
|
}
|
|
166
172
|
async markAllAsRead(user) {
|
|
@@ -179,6 +185,12 @@ export class NotificationService extends ApiService {
|
|
|
179
185
|
await this.clearCacheForAll();
|
|
180
186
|
// Emit to user's sockets in the current company context
|
|
181
187
|
this.emitToUser(user.id, user.companyId, 'notification:read-all', {});
|
|
188
|
+
await this.publishDomainAction(NOTIFICATION_EVENT_ACTIONS.ALL_READ, {
|
|
189
|
+
metadata: {
|
|
190
|
+
count: result.affected ?? 0
|
|
191
|
+
},
|
|
192
|
+
user
|
|
193
|
+
});
|
|
182
194
|
return result.affected ?? 0;
|
|
183
195
|
}
|
|
184
196
|
async getUnreadCount(user) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Test } from '@nestjs/testing';
|
|
2
|
+
import { EventBusRegistry } from '@flusys/nestjs-shared/classes';
|
|
2
3
|
import { UtilsService } from '@flusys/nestjs-shared/modules';
|
|
3
4
|
import { createMockDataSourceProvider, createMockRepository } from '@test-utils/mocks/repository.mock';
|
|
4
5
|
import { buildMockUser } from '@test-utils/mocks/logged-user.mock';
|
|
@@ -483,4 +484,72 @@ describe('NotificationService', ()=>{
|
|
|
483
484
|
await expect(noGatewayService.markAsRead('notif-1', user)).resolves.toBeDefined();
|
|
484
485
|
});
|
|
485
486
|
});
|
|
487
|
+
describe('domain events', ()=>{
|
|
488
|
+
let publish;
|
|
489
|
+
beforeEach(()=>{
|
|
490
|
+
publish = jest.fn().mockResolvedValue(undefined);
|
|
491
|
+
EventBusRegistry.reset();
|
|
492
|
+
EventBusRegistry.setBus({
|
|
493
|
+
publish,
|
|
494
|
+
subscribe: jest.fn(),
|
|
495
|
+
close: jest.fn()
|
|
496
|
+
});
|
|
497
|
+
EventBusRegistry.configureModule('notification', {
|
|
498
|
+
enabled: true
|
|
499
|
+
});
|
|
500
|
+
});
|
|
501
|
+
afterEach(()=>{
|
|
502
|
+
EventBusRegistry.reset();
|
|
503
|
+
});
|
|
504
|
+
it('publishes notification.notification.read when one is marked read', async ()=>{
|
|
505
|
+
const user = buildMockUser();
|
|
506
|
+
const notification = buildNotification({
|
|
507
|
+
id: 'notif-1',
|
|
508
|
+
userId: user.id,
|
|
509
|
+
isRead: false
|
|
510
|
+
});
|
|
511
|
+
mockRepo.findOne.mockResolvedValue(notification);
|
|
512
|
+
mockRepo.save.mockResolvedValue({
|
|
513
|
+
...notification,
|
|
514
|
+
isRead: true
|
|
515
|
+
});
|
|
516
|
+
await service.markAsRead('notif-1', user);
|
|
517
|
+
expect(publish).toHaveBeenCalledWith(expect.objectContaining({
|
|
518
|
+
name: 'notification.notification.read',
|
|
519
|
+
payload: expect.objectContaining({
|
|
520
|
+
ids: [
|
|
521
|
+
'notif-1'
|
|
522
|
+
]
|
|
523
|
+
})
|
|
524
|
+
}));
|
|
525
|
+
});
|
|
526
|
+
it('publishes notification.notification.all-read with the affected count', async ()=>{
|
|
527
|
+
mockRepo.update.mockResolvedValue({
|
|
528
|
+
affected: 4,
|
|
529
|
+
raw: {},
|
|
530
|
+
generatedMaps: []
|
|
531
|
+
});
|
|
532
|
+
await service.markAllAsRead(buildMockUser());
|
|
533
|
+
expect(publish).toHaveBeenCalledWith(expect.objectContaining({
|
|
534
|
+
name: 'notification.notification.all-read',
|
|
535
|
+
payload: expect.objectContaining({
|
|
536
|
+
metadata: {
|
|
537
|
+
count: 4
|
|
538
|
+
}
|
|
539
|
+
})
|
|
540
|
+
}));
|
|
541
|
+
});
|
|
542
|
+
it('does not publish when the notification module has events disabled', async ()=>{
|
|
543
|
+
EventBusRegistry.configureModule('notification', {
|
|
544
|
+
enabled: false
|
|
545
|
+
});
|
|
546
|
+
mockRepo.update.mockResolvedValue({
|
|
547
|
+
affected: 1,
|
|
548
|
+
raw: {},
|
|
549
|
+
generatedMaps: []
|
|
550
|
+
});
|
|
551
|
+
await service.markAllAsRead(buildMockUser());
|
|
552
|
+
expect(publish).not.toHaveBeenCalled();
|
|
553
|
+
});
|
|
554
|
+
});
|
|
486
555
|
});
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { IBootstrapAppConfig, IDataSourceServiceOptions, IDynamicModuleConfig, IModuleOptionsFactory } from '@flusys/nestjs-core';
|
|
2
|
+
import { IModuleEventsConfig } from '@flusys/nestjs-shared/interfaces';
|
|
2
3
|
import { ModuleMetadata, Type } from '@nestjs/common';
|
|
3
4
|
export interface INotificationModuleConfig extends IDataSourceServiceOptions {
|
|
4
5
|
jwtSecret?: string;
|
|
6
|
+
events?: IModuleEventsConfig;
|
|
5
7
|
}
|
|
6
8
|
export interface NotificationModuleOptions extends IDynamicModuleConfig {
|
|
7
9
|
bootstrapAppConfig?: IBootstrapAppConfig;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flusys/nestjs-notification",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "7.0.0-beta.1",
|
|
4
4
|
"description": "Notification module with real-time Socket.IO support",
|
|
5
5
|
"main": "cjs/index.js",
|
|
6
6
|
"module": "fesm/index.js",
|
|
@@ -93,7 +93,7 @@
|
|
|
93
93
|
"express": "^5.0.0"
|
|
94
94
|
},
|
|
95
95
|
"dependencies": {
|
|
96
|
-
"@flusys/nestjs-core": "
|
|
97
|
-
"@flusys/nestjs-shared": "
|
|
96
|
+
"@flusys/nestjs-core": "7.0.0-beta.1",
|
|
97
|
+
"@flusys/nestjs-shared": "7.0.0-beta.1"
|
|
98
98
|
}
|
|
99
99
|
}
|
|
@@ -17,6 +17,7 @@ export declare class NotificationHelperService {
|
|
|
17
17
|
sendToMany(options: INotificationBulkSendOptions): Promise<NotificationBase[]>;
|
|
18
18
|
isUserOnline(userId: string): boolean;
|
|
19
19
|
getOnlineUserCount(): number;
|
|
20
|
+
private publishSentEvent;
|
|
20
21
|
private getDataSource;
|
|
21
22
|
private getRepository;
|
|
22
23
|
private buildEntity;
|