@flusys/nestjs-notification 5.1.2 → 5.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/adapters/notification.adapter.d.ts +3 -4
- package/cjs/adapters/notification.adapter.js +2 -19
- package/cjs/modules/notification.module.js +2 -6
- package/cjs/services/notification-config.service.js +0 -3
- package/cjs/services/notification-datasource.provider.js +0 -45
- package/cjs/services/notification-helper.service.js +44 -83
- package/cjs/services/notification.service.js +28 -19
- package/controllers/notification.controller.d.ts +6 -6
- package/fesm/adapters/notification.adapter.js +3 -20
- package/fesm/modules/notification.module.js +2 -6
- package/fesm/services/notification-config.service.js +0 -3
- package/fesm/services/notification-datasource.provider.js +1 -46
- package/fesm/services/notification-helper.service.js +44 -83
- package/fesm/services/notification.service.js +29 -20
- package/interfaces/notification-module.interface.d.ts +0 -1
- package/package.json +15 -15
- package/services/notification-config.service.d.ts +0 -1
- package/services/notification-datasource.provider.d.ts +0 -2
- package/services/notification-helper.service.d.ts +10 -27
- package/services/notification.service.d.ts +8 -6
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { INotificationAdapter,
|
|
1
|
+
import { INotificationAdapter, INotificationBulkSendOptions, INotificationSendOptions } from '@flusys/nestjs-shared';
|
|
2
2
|
import { NotificationHelperService } from '../services/notification-helper.service';
|
|
3
3
|
export declare class NotificationAdapter implements INotificationAdapter {
|
|
4
4
|
private readonly notificationHelper;
|
|
5
5
|
constructor(notificationHelper: NotificationHelperService);
|
|
6
|
-
send(options:
|
|
7
|
-
sendToMany(options:
|
|
8
|
-
broadcastToCompany(companyId: string, title: string, message?: string, type?: NotificationType, data?: Record<string, unknown>): Promise<void>;
|
|
6
|
+
send(options: INotificationSendOptions): Promise<void>;
|
|
7
|
+
sendToMany(options: INotificationBulkSendOptions): Promise<void>;
|
|
9
8
|
isUserOnline(userId: string): boolean;
|
|
10
9
|
}
|
|
@@ -39,27 +39,10 @@ function _ts_param(paramIndex, decorator) {
|
|
|
39
39
|
}
|
|
40
40
|
let NotificationAdapter = class NotificationAdapter {
|
|
41
41
|
async send(options) {
|
|
42
|
-
await this.notificationHelper.send(
|
|
43
|
-
userId: options.userId,
|
|
44
|
-
title: options.title,
|
|
45
|
-
message: options.message,
|
|
46
|
-
type: options.type,
|
|
47
|
-
data: options.data,
|
|
48
|
-
companyId: options.companyId
|
|
49
|
-
});
|
|
42
|
+
await this.notificationHelper.send(options);
|
|
50
43
|
}
|
|
51
44
|
async sendToMany(options) {
|
|
52
|
-
await this.notificationHelper.sendToMany(
|
|
53
|
-
userIds: options.userIds,
|
|
54
|
-
title: options.title,
|
|
55
|
-
message: options.message,
|
|
56
|
-
type: options.type,
|
|
57
|
-
data: options.data,
|
|
58
|
-
companyId: options.companyId
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
async broadcastToCompany(companyId, title, message, type, data) {
|
|
62
|
-
await this.notificationHelper.sendToCompany(companyId, title, message, type, data);
|
|
45
|
+
await this.notificationHelper.sendToMany(options);
|
|
63
46
|
}
|
|
64
47
|
isUserOnline(userId) {
|
|
65
48
|
return this.notificationHelper.isUserOnline(userId);
|
|
@@ -37,10 +37,6 @@ const ADAPTER_PROVIDER = {
|
|
|
37
37
|
};
|
|
38
38
|
let NotificationModule = class NotificationModule {
|
|
39
39
|
static forRoot(options) {
|
|
40
|
-
const enableRealtime = options.config?.enableRealtime !== false;
|
|
41
|
-
const gatewayProviders = enableRealtime ? [
|
|
42
|
-
_gateways.NotificationGateway
|
|
43
|
-
] : [];
|
|
44
40
|
return {
|
|
45
41
|
module: NotificationModule,
|
|
46
42
|
global: options.global ?? false,
|
|
@@ -51,13 +47,13 @@ let NotificationModule = class NotificationModule {
|
|
|
51
47
|
controllers: options.includeController !== false ? CONTROLLERS : [],
|
|
52
48
|
providers: [
|
|
53
49
|
...this.buildProviders(options),
|
|
54
|
-
|
|
50
|
+
_gateways.NotificationGateway,
|
|
55
51
|
_adapters.NotificationAdapter,
|
|
56
52
|
ADAPTER_PROVIDER
|
|
57
53
|
],
|
|
58
54
|
exports: [
|
|
59
55
|
...EXPORTED_SERVICES,
|
|
60
|
-
|
|
56
|
+
_gateways.NotificationGateway,
|
|
61
57
|
_interfaces.NOTIFICATION_ADAPTER
|
|
62
58
|
]
|
|
63
59
|
};
|
|
@@ -48,9 +48,6 @@ let NotificationConfigService = class NotificationConfigService {
|
|
|
48
48
|
isMultiTenant() {
|
|
49
49
|
return this.getDatabaseMode() === 'multi-tenant';
|
|
50
50
|
}
|
|
51
|
-
isRealtimeEnabled() {
|
|
52
|
-
return this.options.config?.enableRealtime !== false;
|
|
53
|
-
}
|
|
54
51
|
getJwtSecret() {
|
|
55
52
|
return this.options.config?.jwtSecret;
|
|
56
53
|
}
|
|
@@ -9,7 +9,6 @@ Object.defineProperty(exports, "NotificationDataSourceProvider", {
|
|
|
9
9
|
}
|
|
10
10
|
});
|
|
11
11
|
const _modules = require("@flusys/nestjs-shared/modules");
|
|
12
|
-
const _constants = require("@flusys/nestjs-shared/constants");
|
|
13
12
|
const _common = require("@nestjs/common");
|
|
14
13
|
const _core = require("@nestjs/core");
|
|
15
14
|
const _express = require("express");
|
|
@@ -109,50 +108,6 @@ let NotificationDataSourceProvider = class NotificationDataSourceProvider extend
|
|
|
109
108
|
const entities = await this.getNotificationEntities(enableCompanyFeature);
|
|
110
109
|
return super.createDataSourceFromConfig(config, entities);
|
|
111
110
|
}
|
|
112
|
-
async getSingleDataSource() {
|
|
113
|
-
if (NotificationDataSourceProvider.singleDataSource?.isInitialized) {
|
|
114
|
-
return NotificationDataSourceProvider.singleDataSource;
|
|
115
|
-
}
|
|
116
|
-
if (NotificationDataSourceProvider.singleConnectionLock) {
|
|
117
|
-
return NotificationDataSourceProvider.singleConnectionLock;
|
|
118
|
-
}
|
|
119
|
-
const config = this.getDefaultDatabaseConfig();
|
|
120
|
-
if (!config) {
|
|
121
|
-
throw new _common.InternalServerErrorException({
|
|
122
|
-
message: 'No database config available. Provide defaultDatabaseConfig or tenantDefaultDatabaseConfig.',
|
|
123
|
-
messageKey: _constants.SYSTEM_MESSAGES.DATABASE_CONFIG_NOT_AVAILABLE
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
const connectionPromise = this.createDataSourceFromConfig(config);
|
|
127
|
-
NotificationDataSourceProvider.singleConnectionLock = connectionPromise;
|
|
128
|
-
try {
|
|
129
|
-
const dataSource = await connectionPromise;
|
|
130
|
-
NotificationDataSourceProvider.singleDataSource = dataSource;
|
|
131
|
-
return dataSource;
|
|
132
|
-
} finally{
|
|
133
|
-
NotificationDataSourceProvider.singleConnectionLock = null;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
async getOrCreateTenantConnection(tenant) {
|
|
137
|
-
const existing = NotificationDataSourceProvider.tenantConnections.get(tenant.id);
|
|
138
|
-
if (existing?.isInitialized) {
|
|
139
|
-
return existing;
|
|
140
|
-
}
|
|
141
|
-
const pendingConnection = NotificationDataSourceProvider.connectionLocks.get(tenant.id);
|
|
142
|
-
if (pendingConnection) {
|
|
143
|
-
return pendingConnection;
|
|
144
|
-
}
|
|
145
|
-
const config = this.buildTenantDatabaseConfig(tenant);
|
|
146
|
-
const connectionPromise = this.createDataSourceFromConfig(config);
|
|
147
|
-
NotificationDataSourceProvider.connectionLocks.set(tenant.id, connectionPromise);
|
|
148
|
-
try {
|
|
149
|
-
const dataSource = await connectionPromise;
|
|
150
|
-
NotificationDataSourceProvider.tenantConnections.set(tenant.id, dataSource);
|
|
151
|
-
return dataSource;
|
|
152
|
-
} finally{
|
|
153
|
-
NotificationDataSourceProvider.connectionLocks.delete(tenant.id);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
111
|
constructor(configService, request){
|
|
157
112
|
super(NotificationDataSourceProvider.buildParentOptions(configService.getOptions()), request), _define_property(this, "configService", void 0), this.configService = configService;
|
|
158
113
|
}
|
|
@@ -8,15 +8,15 @@ Object.defineProperty(exports, "NotificationHelperService", {
|
|
|
8
8
|
return NotificationHelperService;
|
|
9
9
|
}
|
|
10
10
|
});
|
|
11
|
-
const _common = require("@nestjs/common");
|
|
12
|
-
const _classes = require("@flusys/nestjs-shared/classes");
|
|
13
11
|
const _nestjsshared = require("@flusys/nestjs-shared");
|
|
12
|
+
const _classes = require("@flusys/nestjs-shared/classes");
|
|
13
|
+
const _enums = require("@flusys/nestjs-shared/enums");
|
|
14
14
|
const _modules = require("@flusys/nestjs-shared/modules");
|
|
15
|
+
const _common = require("@nestjs/common");
|
|
16
|
+
const _entities = require("../entities");
|
|
17
|
+
const _gateways = require("../gateways");
|
|
15
18
|
const _notificationconfigservice = require("./notification-config.service");
|
|
16
19
|
const _notificationdatasourceprovider = require("./notification-datasource.provider");
|
|
17
|
-
const _gateways = require("../gateways");
|
|
18
|
-
const _entities = require("../entities");
|
|
19
|
-
const _enums = require("@flusys/nestjs-shared/enums");
|
|
20
20
|
function _define_property(obj, key, value) {
|
|
21
21
|
if (key in obj) {
|
|
22
22
|
Object.defineProperty(obj, key, {
|
|
@@ -45,90 +45,68 @@ function _ts_param(paramIndex, decorator) {
|
|
|
45
45
|
};
|
|
46
46
|
}
|
|
47
47
|
let NotificationHelperService = class NotificationHelperService {
|
|
48
|
-
async getDataSource() {
|
|
49
|
-
if (!this.dataSource) {
|
|
50
|
-
this.dataSource = await this.dataSourceProvider.getDataSource();
|
|
51
|
-
}
|
|
52
|
-
return this.dataSource;
|
|
53
|
-
}
|
|
54
|
-
async getRepository() {
|
|
55
|
-
const ds = await this.getDataSource();
|
|
56
|
-
const EntityClass = this.configService.isCompanyFeatureEnabled() ? _entities.NotificationWithCompany : _entities.Notification;
|
|
57
|
-
return ds.getRepository(EntityClass);
|
|
58
|
-
}
|
|
59
48
|
async send(options) {
|
|
60
|
-
const { userId, title, message = null, type = _enums.NotificationType.INFO, data = null, companyId } = options;
|
|
61
49
|
const repo = await this.getRepository();
|
|
62
|
-
const entity =
|
|
63
|
-
userId,
|
|
64
|
-
type,
|
|
65
|
-
title,
|
|
66
|
-
message,
|
|
67
|
-
data,
|
|
68
|
-
isRead: false
|
|
69
|
-
};
|
|
70
|
-
if (this.configService.isCompanyFeatureEnabled() && companyId) {
|
|
71
|
-
entity.companyId = companyId;
|
|
72
|
-
}
|
|
50
|
+
const entity = this.buildEntity(options.userId, options);
|
|
73
51
|
const saved = await repo.save(entity);
|
|
74
52
|
await this.clearCache();
|
|
75
|
-
this.
|
|
53
|
+
this.emitToUser(saved, options.realtime !== false);
|
|
76
54
|
return saved;
|
|
77
55
|
}
|
|
78
56
|
async sendToMany(options) {
|
|
79
|
-
const { userIds, title, message = null, type = _enums.NotificationType.INFO, data = null, companyId } = options;
|
|
80
57
|
const repo = await this.getRepository();
|
|
81
|
-
const entities = userIds.map((userId)=>(
|
|
82
|
-
userId,
|
|
83
|
-
type,
|
|
84
|
-
title,
|
|
85
|
-
message,
|
|
86
|
-
data,
|
|
87
|
-
isRead: false,
|
|
88
|
-
...this.configService.isCompanyFeatureEnabled() && companyId ? {
|
|
89
|
-
companyId
|
|
90
|
-
} : {}
|
|
91
|
-
}));
|
|
58
|
+
const entities = options.userIds.map((userId)=>this.buildEntity(userId, options));
|
|
92
59
|
const saved = await repo.save(entities);
|
|
93
60
|
await this.clearCache();
|
|
94
|
-
|
|
95
|
-
|
|
61
|
+
if (options.realtime !== false) {
|
|
62
|
+
setImmediate(()=>{
|
|
63
|
+
for (const notification of saved){
|
|
64
|
+
this.emitToUser(notification, true);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
96
67
|
}
|
|
97
68
|
return saved;
|
|
98
69
|
}
|
|
99
|
-
async sendToCompany(companyId, title, message, type = _enums.NotificationType.INFO, data) {
|
|
100
|
-
if (!this.configService.isCompanyFeatureEnabled()) {
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
if (this.configService.isRealtimeEnabled() && this.gateway) {
|
|
104
|
-
const payload = {
|
|
105
|
-
id: `broadcast-${Date.now()}`,
|
|
106
|
-
type,
|
|
107
|
-
title,
|
|
108
|
-
message: message ?? null,
|
|
109
|
-
data: data ?? null,
|
|
110
|
-
createdAt: new Date()
|
|
111
|
-
};
|
|
112
|
-
this.gateway.emitToCompany(companyId, 'notification:new', payload);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
70
|
isUserOnline(userId) {
|
|
116
71
|
return this.gateway?.isUserOnline(userId) ?? false;
|
|
117
72
|
}
|
|
118
73
|
getOnlineUserCount() {
|
|
119
74
|
return this.gateway?.getOnlineUserCount() ?? 0;
|
|
120
75
|
}
|
|
121
|
-
|
|
122
|
-
if (!this.
|
|
76
|
+
async getDataSource() {
|
|
77
|
+
if (!this.dataSource) {
|
|
78
|
+
this.dataSource = await this.dataSourceProvider.getDataSource();
|
|
79
|
+
}
|
|
80
|
+
return this.dataSource;
|
|
81
|
+
}
|
|
82
|
+
async getRepository() {
|
|
83
|
+
const ds = await this.getDataSource();
|
|
84
|
+
const EntityClass = this.configService.isCompanyFeatureEnabled() ? _entities.NotificationWithCompany : _entities.Notification;
|
|
85
|
+
return ds.getRepository(EntityClass);
|
|
86
|
+
}
|
|
87
|
+
buildEntity(userId, content) {
|
|
88
|
+
const entity = {
|
|
89
|
+
userId,
|
|
90
|
+
type: content.type ?? _enums.NotificationType.INFO,
|
|
91
|
+
title: content.title,
|
|
92
|
+
message: content.message ?? null,
|
|
93
|
+
data: content.data ?? null,
|
|
94
|
+
isRead: false
|
|
95
|
+
};
|
|
96
|
+
if (this.configService.isCompanyFeatureEnabled() && content.companyId) {
|
|
97
|
+
entity.companyId = content.companyId;
|
|
98
|
+
}
|
|
99
|
+
return entity;
|
|
100
|
+
}
|
|
101
|
+
emitToUser(notification, realtime) {
|
|
102
|
+
if (!realtime || !this.gateway) {
|
|
123
103
|
return;
|
|
124
104
|
}
|
|
125
105
|
const ext = notification;
|
|
126
106
|
const payload = this.toPayload(notification);
|
|
127
107
|
if (this.configService.isCompanyFeatureEnabled() && ext.companyId) {
|
|
128
|
-
// Company feature enabled - emit only to sockets in matching company context
|
|
129
108
|
this.gateway.emitToUserInCompany(notification.userId, ext.companyId, 'notification:new', payload);
|
|
130
109
|
} else {
|
|
131
|
-
// No company feature or no companyId - emit to all user's sockets
|
|
132
110
|
this.gateway.emitToUser(notification.userId, 'notification:new', payload);
|
|
133
111
|
}
|
|
134
112
|
}
|
|
@@ -143,7 +121,7 @@ let NotificationHelperService = class NotificationHelperService {
|
|
|
143
121
|
};
|
|
144
122
|
}
|
|
145
123
|
async clearCache() {
|
|
146
|
-
await this.utilsService.clearCache(
|
|
124
|
+
await this.utilsService.clearCache('notification', this.cacheManager);
|
|
147
125
|
}
|
|
148
126
|
constructor(cacheManager, utilsService, configService, dataSourceProvider, gateway){
|
|
149
127
|
_define_property(this, "cacheManager", void 0);
|
|
@@ -151,14 +129,12 @@ let NotificationHelperService = class NotificationHelperService {
|
|
|
151
129
|
_define_property(this, "configService", void 0);
|
|
152
130
|
_define_property(this, "dataSourceProvider", void 0);
|
|
153
131
|
_define_property(this, "gateway", void 0);
|
|
154
|
-
_define_property(this, "entityName", void 0);
|
|
155
132
|
_define_property(this, "dataSource", void 0);
|
|
156
133
|
this.cacheManager = cacheManager;
|
|
157
134
|
this.utilsService = utilsService;
|
|
158
135
|
this.configService = configService;
|
|
159
136
|
this.dataSourceProvider = dataSourceProvider;
|
|
160
137
|
this.gateway = gateway;
|
|
161
|
-
this.entityName = 'notification';
|
|
162
138
|
this.dataSource = null;
|
|
163
139
|
}
|
|
164
140
|
};
|
|
@@ -169,7 +145,7 @@ _ts_decorate([
|
|
|
169
145
|
}),
|
|
170
146
|
_ts_metadata("design:type", Function),
|
|
171
147
|
_ts_metadata("design:paramtypes", [
|
|
172
|
-
typeof
|
|
148
|
+
typeof _nestjsshared.INotificationSendOptions === "undefined" ? Object : _nestjsshared.INotificationSendOptions
|
|
173
149
|
]),
|
|
174
150
|
_ts_metadata("design:returntype", Promise)
|
|
175
151
|
], NotificationHelperService.prototype, "send", null);
|
|
@@ -180,25 +156,10 @@ _ts_decorate([
|
|
|
180
156
|
}),
|
|
181
157
|
_ts_metadata("design:type", Function),
|
|
182
158
|
_ts_metadata("design:paramtypes", [
|
|
183
|
-
typeof
|
|
159
|
+
typeof _nestjsshared.INotificationBulkSendOptions === "undefined" ? Object : _nestjsshared.INotificationBulkSendOptions
|
|
184
160
|
]),
|
|
185
161
|
_ts_metadata("design:returntype", Promise)
|
|
186
162
|
], NotificationHelperService.prototype, "sendToMany", null);
|
|
187
|
-
_ts_decorate([
|
|
188
|
-
(0, _nestjsshared.LogAction)({
|
|
189
|
-
action: 'notification.sendToCompany',
|
|
190
|
-
module: 'notification'
|
|
191
|
-
}),
|
|
192
|
-
_ts_metadata("design:type", Function),
|
|
193
|
-
_ts_metadata("design:paramtypes", [
|
|
194
|
-
String,
|
|
195
|
-
String,
|
|
196
|
-
Object,
|
|
197
|
-
Object,
|
|
198
|
-
Object
|
|
199
|
-
]),
|
|
200
|
-
_ts_metadata("design:returntype", Promise)
|
|
201
|
-
], NotificationHelperService.prototype, "sendToCompany", null);
|
|
202
163
|
NotificationHelperService = _ts_decorate([
|
|
203
164
|
(0, _common.Injectable)(),
|
|
204
165
|
_ts_param(0, (0, _common.Inject)('CACHE_INSTANCE')),
|
|
@@ -46,13 +46,10 @@ function _ts_param(paramIndex, decorator) {
|
|
|
46
46
|
decorator(target, key, paramIndex);
|
|
47
47
|
};
|
|
48
48
|
}
|
|
49
|
-
let NotificationService = class NotificationService extends _classes.
|
|
49
|
+
let NotificationService = class NotificationService extends _classes.ApiService {
|
|
50
50
|
resolveEntity() {
|
|
51
51
|
return this.notificationConfig.isCompanyFeatureEnabled() ? _entities.NotificationWithCompany : _entities.Notification;
|
|
52
52
|
}
|
|
53
|
-
getDataSourceProvider() {
|
|
54
|
-
return this.dataSourceProvider;
|
|
55
|
-
}
|
|
56
53
|
async convertSingleDtoToEntity(dto, user) {
|
|
57
54
|
let entity = {};
|
|
58
55
|
if ('id' in dto && dto.id) {
|
|
@@ -84,6 +81,24 @@ let NotificationService = class NotificationService extends _classes.RequestScop
|
|
|
84
81
|
}
|
|
85
82
|
return merged;
|
|
86
83
|
}
|
|
84
|
+
async getSelectQuery(query, _user, select) {
|
|
85
|
+
const fields = select && select.length ? select : [];
|
|
86
|
+
const always = [
|
|
87
|
+
'createdAt',
|
|
88
|
+
'updatedAt'
|
|
89
|
+
];
|
|
90
|
+
const merged = [
|
|
91
|
+
...new Set([
|
|
92
|
+
...fields,
|
|
93
|
+
...always
|
|
94
|
+
])
|
|
95
|
+
];
|
|
96
|
+
query.addSelect(merged.map((f)=>`${this.entityName}.${f}`));
|
|
97
|
+
return {
|
|
98
|
+
query,
|
|
99
|
+
isRaw: false
|
|
100
|
+
};
|
|
101
|
+
}
|
|
87
102
|
async getFilterQuery(query, filter, _user) {
|
|
88
103
|
for (const [key, value] of Object.entries(filter)){
|
|
89
104
|
if (key === 'isRead' && typeof value === 'boolean') {
|
|
@@ -125,11 +140,11 @@ let NotificationService = class NotificationService extends _classes.RequestScop
|
|
|
125
140
|
async afterInsertOperation(entities, _user, _qr) {
|
|
126
141
|
for (const entity of entities){
|
|
127
142
|
const ext = entity;
|
|
128
|
-
this.
|
|
143
|
+
this.emitToUser(entity.userId, ext.companyId, 'notification:new', this.toPayload(entity));
|
|
129
144
|
}
|
|
130
145
|
}
|
|
131
146
|
async markAsRead(id, user) {
|
|
132
|
-
await this.
|
|
147
|
+
await this.ensureDataSourceRepository();
|
|
133
148
|
const notification = await this.repository.findOne({
|
|
134
149
|
where: {
|
|
135
150
|
id,
|
|
@@ -153,13 +168,13 @@ let NotificationService = class NotificationService extends _classes.RequestScop
|
|
|
153
168
|
]);
|
|
154
169
|
// Emit to user's sockets in the same company context
|
|
155
170
|
const ext = notification;
|
|
156
|
-
this.
|
|
171
|
+
this.emitToUser(user.id, ext.companyId ?? user.companyId, 'notification:read', {
|
|
157
172
|
id
|
|
158
173
|
});
|
|
159
174
|
return notification;
|
|
160
175
|
}
|
|
161
176
|
async markAllAsRead(user) {
|
|
162
|
-
await this.
|
|
177
|
+
await this.ensureDataSourceRepository();
|
|
163
178
|
const where = {
|
|
164
179
|
userId: user.id,
|
|
165
180
|
isRead: false
|
|
@@ -173,11 +188,11 @@ let NotificationService = class NotificationService extends _classes.RequestScop
|
|
|
173
188
|
});
|
|
174
189
|
await this.clearCacheForAll();
|
|
175
190
|
// Emit to user's sockets in the current company context
|
|
176
|
-
this.
|
|
191
|
+
this.emitToUser(user.id, user.companyId, 'notification:read-all', {});
|
|
177
192
|
return result.affected ?? 0;
|
|
178
193
|
}
|
|
179
194
|
async getUnreadCount(user) {
|
|
180
|
-
await this.
|
|
195
|
+
await this.ensureDataSourceRepository();
|
|
181
196
|
const where = {
|
|
182
197
|
userId: user.id,
|
|
183
198
|
isRead: false
|
|
@@ -199,24 +214,18 @@ let NotificationService = class NotificationService extends _classes.RequestScop
|
|
|
199
214
|
createdAt: entity.createdAt
|
|
200
215
|
};
|
|
201
216
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
* If company feature is enabled and companyId is provided, only sockets
|
|
205
|
-
* connected with that company will receive the event.
|
|
206
|
-
*/ emitToUserInCompanyIfRealtime(userId, companyId, event, payload) {
|
|
207
|
-
if (!this.notificationConfig.isRealtimeEnabled() || !this.gateway) {
|
|
217
|
+
emitToUser(userId, companyId, event, payload) {
|
|
218
|
+
if (!this.gateway) {
|
|
208
219
|
return;
|
|
209
220
|
}
|
|
210
221
|
if (this.notificationConfig.isCompanyFeatureEnabled() && companyId) {
|
|
211
|
-
// Company feature enabled - emit only to sockets in matching company context
|
|
212
222
|
this.gateway.emitToUserInCompany(userId, companyId, event, payload);
|
|
213
223
|
} else {
|
|
214
|
-
// No company feature or no companyId - emit to all user's sockets
|
|
215
224
|
this.gateway.emitToUser(userId, event, payload);
|
|
216
225
|
}
|
|
217
226
|
}
|
|
218
227
|
constructor(cacheManager, utilsService, notificationConfig, dataSourceProvider, gateway){
|
|
219
|
-
super('notification',
|
|
228
|
+
super('notification', cacheManager, utilsService, NotificationService.name, false, 'notification', undefined, dataSourceProvider), _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;
|
|
220
229
|
}
|
|
221
230
|
};
|
|
222
231
|
_ts_decorate([
|
|
@@ -4,19 +4,19 @@ import { CreateNotificationDto, MarkReadDto, NotificationResponseDto, SendBulkNo
|
|
|
4
4
|
import { NotificationHelperService } from '../services/notification-helper.service';
|
|
5
5
|
import { NotificationService } from '../services/notification.service';
|
|
6
6
|
declare const NotificationController_base: abstract new (service: NotificationService) => {
|
|
7
|
-
readonly enabledEndpoints: import("@flusys/nestjs-shared
|
|
7
|
+
readonly enabledEndpoints: import("@flusys/nestjs-shared").ApiEndpoint[] | "all";
|
|
8
8
|
service: NotificationService;
|
|
9
|
-
isEnabled(endpoint: import("@flusys/nestjs-shared
|
|
9
|
+
isEnabled(endpoint: import("@flusys/nestjs-shared").ApiEndpoint): boolean;
|
|
10
10
|
insert(addDto: CreateNotificationDto, user: ILoggedUserInfo | null): Promise<SingleResponseDto<NotificationResponseDto>>;
|
|
11
11
|
insertMany(addDto: CreateNotificationDto[], user: ILoggedUserInfo | null): Promise<BulkResponseDto<NotificationResponseDto>>;
|
|
12
|
-
getById(id: string, body: import("@flusys/nestjs-shared
|
|
13
|
-
getByIds(body: import("@flusys/nestjs-shared
|
|
12
|
+
getById(id: string, body: import("@flusys/nestjs-shared").GetByIdBodyDto, user: ILoggedUserInfo | null): Promise<SingleResponseDto<NotificationResponseDto>>;
|
|
13
|
+
getByIds(body: import("@flusys/nestjs-shared").GetByIdsDto, user: ILoggedUserInfo | null): Promise<import("@flusys/nestjs-shared").ListResponseDto<NotificationResponseDto>>;
|
|
14
14
|
update(updateDto: UpdateNotificationDto, user: ILoggedUserInfo | null): Promise<SingleResponseDto<NotificationResponseDto>>;
|
|
15
15
|
updateMany(updateDtos: UpdateNotificationDto[], user: ILoggedUserInfo | null): Promise<BulkResponseDto<NotificationResponseDto>>;
|
|
16
16
|
bulkUpsert(dtos: (CreateNotificationDto | UpdateNotificationDto)[], user: ILoggedUserInfo | null): Promise<BulkResponseDto<NotificationResponseDto>>;
|
|
17
17
|
getByFilter(filter: Record<string, any>, user: ILoggedUserInfo | null): Promise<SingleResponseDto<NotificationResponseDto>>;
|
|
18
|
-
getAll(filterAndPaginationDto: import("@flusys/nestjs-shared
|
|
19
|
-
delete(deleteDto: import("@flusys/nestjs-shared
|
|
18
|
+
getAll(filterAndPaginationDto: import("@flusys/nestjs-shared").FilterAndPaginationDto, user: ILoggedUserInfo | null, search?: string): Promise<import("@flusys/nestjs-shared").ListResponseDto<NotificationResponseDto>>;
|
|
19
|
+
delete(deleteDto: import("@flusys/nestjs-shared").DeleteDto, user: ILoggedUserInfo | null): Promise<MessageResponseDto>;
|
|
20
20
|
};
|
|
21
21
|
export declare class NotificationController extends NotificationController_base {
|
|
22
22
|
service: NotificationService;
|
|
@@ -25,31 +25,14 @@ function _ts_param(paramIndex, decorator) {
|
|
|
25
25
|
decorator(target, key, paramIndex);
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
|
-
import {
|
|
28
|
+
import { Inject, Injectable } from '@nestjs/common';
|
|
29
29
|
import { NotificationHelperService } from '../services/notification-helper.service';
|
|
30
30
|
export class NotificationAdapter {
|
|
31
31
|
async send(options) {
|
|
32
|
-
await this.notificationHelper.send(
|
|
33
|
-
userId: options.userId,
|
|
34
|
-
title: options.title,
|
|
35
|
-
message: options.message,
|
|
36
|
-
type: options.type,
|
|
37
|
-
data: options.data,
|
|
38
|
-
companyId: options.companyId
|
|
39
|
-
});
|
|
32
|
+
await this.notificationHelper.send(options);
|
|
40
33
|
}
|
|
41
34
|
async sendToMany(options) {
|
|
42
|
-
await this.notificationHelper.sendToMany(
|
|
43
|
-
userIds: options.userIds,
|
|
44
|
-
title: options.title,
|
|
45
|
-
message: options.message,
|
|
46
|
-
type: options.type,
|
|
47
|
-
data: options.data,
|
|
48
|
-
companyId: options.companyId
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
async broadcastToCompany(companyId, title, message, type, data) {
|
|
52
|
-
await this.notificationHelper.sendToCompany(companyId, title, message, type, data);
|
|
35
|
+
await this.notificationHelper.sendToMany(options);
|
|
53
36
|
}
|
|
54
37
|
isUserOnline(userId) {
|
|
55
38
|
return this.notificationHelper.isUserOnline(userId);
|
|
@@ -27,10 +27,6 @@ const ADAPTER_PROVIDER = {
|
|
|
27
27
|
};
|
|
28
28
|
export class NotificationModule {
|
|
29
29
|
static forRoot(options) {
|
|
30
|
-
const enableRealtime = options.config?.enableRealtime !== false;
|
|
31
|
-
const gatewayProviders = enableRealtime ? [
|
|
32
|
-
NotificationGateway
|
|
33
|
-
] : [];
|
|
34
30
|
return {
|
|
35
31
|
module: NotificationModule,
|
|
36
32
|
global: options.global ?? false,
|
|
@@ -41,13 +37,13 @@ export class NotificationModule {
|
|
|
41
37
|
controllers: options.includeController !== false ? CONTROLLERS : [],
|
|
42
38
|
providers: [
|
|
43
39
|
...this.buildProviders(options),
|
|
44
|
-
|
|
40
|
+
NotificationGateway,
|
|
45
41
|
NotificationAdapter,
|
|
46
42
|
ADAPTER_PROVIDER
|
|
47
43
|
],
|
|
48
44
|
exports: [
|
|
49
45
|
...EXPORTED_SERVICES,
|
|
50
|
-
|
|
46
|
+
NotificationGateway,
|
|
51
47
|
NOTIFICATION_ADAPTER
|
|
52
48
|
]
|
|
53
49
|
};
|
|
@@ -38,9 +38,6 @@ export class NotificationConfigService {
|
|
|
38
38
|
isMultiTenant() {
|
|
39
39
|
return this.getDatabaseMode() === 'multi-tenant';
|
|
40
40
|
}
|
|
41
|
-
isRealtimeEnabled() {
|
|
42
|
-
return this.options.config?.enableRealtime !== false;
|
|
43
|
-
}
|
|
44
41
|
getJwtSecret() {
|
|
45
42
|
return this.options.config?.jwtSecret;
|
|
46
43
|
}
|
|
@@ -26,8 +26,7 @@ function _ts_param(paramIndex, decorator) {
|
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
28
|
import { MultiTenantDataSourceService } from '@flusys/nestjs-shared/modules';
|
|
29
|
-
import {
|
|
30
|
-
import { Inject, Injectable, InternalServerErrorException, Optional, Scope } from '@nestjs/common';
|
|
29
|
+
import { Inject, Injectable, Optional, Scope } from '@nestjs/common';
|
|
31
30
|
import { REQUEST } from '@nestjs/core';
|
|
32
31
|
import { Request } from 'express';
|
|
33
32
|
import { NotificationConfigService } from './notification-config.service';
|
|
@@ -58,50 +57,6 @@ export class NotificationDataSourceProvider extends MultiTenantDataSourceService
|
|
|
58
57
|
const entities = await this.getNotificationEntities(enableCompanyFeature);
|
|
59
58
|
return super.createDataSourceFromConfig(config, entities);
|
|
60
59
|
}
|
|
61
|
-
async getSingleDataSource() {
|
|
62
|
-
if (NotificationDataSourceProvider.singleDataSource?.isInitialized) {
|
|
63
|
-
return NotificationDataSourceProvider.singleDataSource;
|
|
64
|
-
}
|
|
65
|
-
if (NotificationDataSourceProvider.singleConnectionLock) {
|
|
66
|
-
return NotificationDataSourceProvider.singleConnectionLock;
|
|
67
|
-
}
|
|
68
|
-
const config = this.getDefaultDatabaseConfig();
|
|
69
|
-
if (!config) {
|
|
70
|
-
throw new InternalServerErrorException({
|
|
71
|
-
message: 'No database config available. Provide defaultDatabaseConfig or tenantDefaultDatabaseConfig.',
|
|
72
|
-
messageKey: SYSTEM_MESSAGES.DATABASE_CONFIG_NOT_AVAILABLE
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
const connectionPromise = this.createDataSourceFromConfig(config);
|
|
76
|
-
NotificationDataSourceProvider.singleConnectionLock = connectionPromise;
|
|
77
|
-
try {
|
|
78
|
-
const dataSource = await connectionPromise;
|
|
79
|
-
NotificationDataSourceProvider.singleDataSource = dataSource;
|
|
80
|
-
return dataSource;
|
|
81
|
-
} finally{
|
|
82
|
-
NotificationDataSourceProvider.singleConnectionLock = null;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
async getOrCreateTenantConnection(tenant) {
|
|
86
|
-
const existing = NotificationDataSourceProvider.tenantConnections.get(tenant.id);
|
|
87
|
-
if (existing?.isInitialized) {
|
|
88
|
-
return existing;
|
|
89
|
-
}
|
|
90
|
-
const pendingConnection = NotificationDataSourceProvider.connectionLocks.get(tenant.id);
|
|
91
|
-
if (pendingConnection) {
|
|
92
|
-
return pendingConnection;
|
|
93
|
-
}
|
|
94
|
-
const config = this.buildTenantDatabaseConfig(tenant);
|
|
95
|
-
const connectionPromise = this.createDataSourceFromConfig(config);
|
|
96
|
-
NotificationDataSourceProvider.connectionLocks.set(tenant.id, connectionPromise);
|
|
97
|
-
try {
|
|
98
|
-
const dataSource = await connectionPromise;
|
|
99
|
-
NotificationDataSourceProvider.tenantConnections.set(tenant.id, dataSource);
|
|
100
|
-
return dataSource;
|
|
101
|
-
} finally{
|
|
102
|
-
NotificationDataSourceProvider.connectionLocks.delete(tenant.id);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
60
|
constructor(configService, request){
|
|
106
61
|
super(NotificationDataSourceProvider.buildParentOptions(configService.getOptions()), request), _define_property(this, "configService", void 0), this.configService = configService;
|
|
107
62
|
}
|
|
@@ -25,100 +25,78 @@ function _ts_param(paramIndex, decorator) {
|
|
|
25
25
|
decorator(target, key, paramIndex);
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
|
-
import {
|
|
28
|
+
import { INotificationBulkSendOptions, INotificationSendOptions, LogAction } from '@flusys/nestjs-shared';
|
|
29
29
|
import { HybridCache } from '@flusys/nestjs-shared/classes';
|
|
30
|
-
import {
|
|
30
|
+
import { NotificationType } from '@flusys/nestjs-shared/enums';
|
|
31
31
|
import { UtilsService } from '@flusys/nestjs-shared/modules';
|
|
32
|
+
import { Inject, Injectable, Optional } from '@nestjs/common';
|
|
33
|
+
import { Notification, NotificationWithCompany } from '../entities';
|
|
34
|
+
import { NotificationGateway } from '../gateways';
|
|
32
35
|
import { NotificationConfigService } from './notification-config.service';
|
|
33
36
|
import { NotificationDataSourceProvider } from './notification-datasource.provider';
|
|
34
|
-
import { NotificationGateway } from '../gateways';
|
|
35
|
-
import { Notification, NotificationWithCompany } from '../entities';
|
|
36
|
-
import { NotificationType } from '@flusys/nestjs-shared/enums';
|
|
37
37
|
export class NotificationHelperService {
|
|
38
|
-
async getDataSource() {
|
|
39
|
-
if (!this.dataSource) {
|
|
40
|
-
this.dataSource = await this.dataSourceProvider.getDataSource();
|
|
41
|
-
}
|
|
42
|
-
return this.dataSource;
|
|
43
|
-
}
|
|
44
|
-
async getRepository() {
|
|
45
|
-
const ds = await this.getDataSource();
|
|
46
|
-
const EntityClass = this.configService.isCompanyFeatureEnabled() ? NotificationWithCompany : Notification;
|
|
47
|
-
return ds.getRepository(EntityClass);
|
|
48
|
-
}
|
|
49
38
|
async send(options) {
|
|
50
|
-
const { userId, title, message = null, type = NotificationType.INFO, data = null, companyId } = options;
|
|
51
39
|
const repo = await this.getRepository();
|
|
52
|
-
const entity =
|
|
53
|
-
userId,
|
|
54
|
-
type,
|
|
55
|
-
title,
|
|
56
|
-
message,
|
|
57
|
-
data,
|
|
58
|
-
isRead: false
|
|
59
|
-
};
|
|
60
|
-
if (this.configService.isCompanyFeatureEnabled() && companyId) {
|
|
61
|
-
entity.companyId = companyId;
|
|
62
|
-
}
|
|
40
|
+
const entity = this.buildEntity(options.userId, options);
|
|
63
41
|
const saved = await repo.save(entity);
|
|
64
42
|
await this.clearCache();
|
|
65
|
-
this.
|
|
43
|
+
this.emitToUser(saved, options.realtime !== false);
|
|
66
44
|
return saved;
|
|
67
45
|
}
|
|
68
46
|
async sendToMany(options) {
|
|
69
|
-
const { userIds, title, message = null, type = NotificationType.INFO, data = null, companyId } = options;
|
|
70
47
|
const repo = await this.getRepository();
|
|
71
|
-
const entities = userIds.map((userId)=>(
|
|
72
|
-
userId,
|
|
73
|
-
type,
|
|
74
|
-
title,
|
|
75
|
-
message,
|
|
76
|
-
data,
|
|
77
|
-
isRead: false,
|
|
78
|
-
...this.configService.isCompanyFeatureEnabled() && companyId ? {
|
|
79
|
-
companyId
|
|
80
|
-
} : {}
|
|
81
|
-
}));
|
|
48
|
+
const entities = options.userIds.map((userId)=>this.buildEntity(userId, options));
|
|
82
49
|
const saved = await repo.save(entities);
|
|
83
50
|
await this.clearCache();
|
|
84
|
-
|
|
85
|
-
|
|
51
|
+
if (options.realtime !== false) {
|
|
52
|
+
setImmediate(()=>{
|
|
53
|
+
for (const notification of saved){
|
|
54
|
+
this.emitToUser(notification, true);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
86
57
|
}
|
|
87
58
|
return saved;
|
|
88
59
|
}
|
|
89
|
-
async sendToCompany(companyId, title, message, type = NotificationType.INFO, data) {
|
|
90
|
-
if (!this.configService.isCompanyFeatureEnabled()) {
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
if (this.configService.isRealtimeEnabled() && this.gateway) {
|
|
94
|
-
const payload = {
|
|
95
|
-
id: `broadcast-${Date.now()}`,
|
|
96
|
-
type,
|
|
97
|
-
title,
|
|
98
|
-
message: message ?? null,
|
|
99
|
-
data: data ?? null,
|
|
100
|
-
createdAt: new Date()
|
|
101
|
-
};
|
|
102
|
-
this.gateway.emitToCompany(companyId, 'notification:new', payload);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
60
|
isUserOnline(userId) {
|
|
106
61
|
return this.gateway?.isUserOnline(userId) ?? false;
|
|
107
62
|
}
|
|
108
63
|
getOnlineUserCount() {
|
|
109
64
|
return this.gateway?.getOnlineUserCount() ?? 0;
|
|
110
65
|
}
|
|
111
|
-
|
|
112
|
-
if (!this.
|
|
66
|
+
async getDataSource() {
|
|
67
|
+
if (!this.dataSource) {
|
|
68
|
+
this.dataSource = await this.dataSourceProvider.getDataSource();
|
|
69
|
+
}
|
|
70
|
+
return this.dataSource;
|
|
71
|
+
}
|
|
72
|
+
async getRepository() {
|
|
73
|
+
const ds = await this.getDataSource();
|
|
74
|
+
const EntityClass = this.configService.isCompanyFeatureEnabled() ? NotificationWithCompany : Notification;
|
|
75
|
+
return ds.getRepository(EntityClass);
|
|
76
|
+
}
|
|
77
|
+
buildEntity(userId, content) {
|
|
78
|
+
const entity = {
|
|
79
|
+
userId,
|
|
80
|
+
type: content.type ?? NotificationType.INFO,
|
|
81
|
+
title: content.title,
|
|
82
|
+
message: content.message ?? null,
|
|
83
|
+
data: content.data ?? null,
|
|
84
|
+
isRead: false
|
|
85
|
+
};
|
|
86
|
+
if (this.configService.isCompanyFeatureEnabled() && content.companyId) {
|
|
87
|
+
entity.companyId = content.companyId;
|
|
88
|
+
}
|
|
89
|
+
return entity;
|
|
90
|
+
}
|
|
91
|
+
emitToUser(notification, realtime) {
|
|
92
|
+
if (!realtime || !this.gateway) {
|
|
113
93
|
return;
|
|
114
94
|
}
|
|
115
95
|
const ext = notification;
|
|
116
96
|
const payload = this.toPayload(notification);
|
|
117
97
|
if (this.configService.isCompanyFeatureEnabled() && ext.companyId) {
|
|
118
|
-
// Company feature enabled - emit only to sockets in matching company context
|
|
119
98
|
this.gateway.emitToUserInCompany(notification.userId, ext.companyId, 'notification:new', payload);
|
|
120
99
|
} else {
|
|
121
|
-
// No company feature or no companyId - emit to all user's sockets
|
|
122
100
|
this.gateway.emitToUser(notification.userId, 'notification:new', payload);
|
|
123
101
|
}
|
|
124
102
|
}
|
|
@@ -133,7 +111,7 @@ export class NotificationHelperService {
|
|
|
133
111
|
};
|
|
134
112
|
}
|
|
135
113
|
async clearCache() {
|
|
136
|
-
await this.utilsService.clearCache(
|
|
114
|
+
await this.utilsService.clearCache('notification', this.cacheManager);
|
|
137
115
|
}
|
|
138
116
|
constructor(cacheManager, utilsService, configService, dataSourceProvider, gateway){
|
|
139
117
|
_define_property(this, "cacheManager", void 0);
|
|
@@ -141,14 +119,12 @@ export class NotificationHelperService {
|
|
|
141
119
|
_define_property(this, "configService", void 0);
|
|
142
120
|
_define_property(this, "dataSourceProvider", void 0);
|
|
143
121
|
_define_property(this, "gateway", void 0);
|
|
144
|
-
_define_property(this, "entityName", void 0);
|
|
145
122
|
_define_property(this, "dataSource", void 0);
|
|
146
123
|
this.cacheManager = cacheManager;
|
|
147
124
|
this.utilsService = utilsService;
|
|
148
125
|
this.configService = configService;
|
|
149
126
|
this.dataSourceProvider = dataSourceProvider;
|
|
150
127
|
this.gateway = gateway;
|
|
151
|
-
this.entityName = 'notification';
|
|
152
128
|
this.dataSource = null;
|
|
153
129
|
}
|
|
154
130
|
}
|
|
@@ -159,7 +135,7 @@ _ts_decorate([
|
|
|
159
135
|
}),
|
|
160
136
|
_ts_metadata("design:type", Function),
|
|
161
137
|
_ts_metadata("design:paramtypes", [
|
|
162
|
-
typeof
|
|
138
|
+
typeof INotificationSendOptions === "undefined" ? Object : INotificationSendOptions
|
|
163
139
|
]),
|
|
164
140
|
_ts_metadata("design:returntype", Promise)
|
|
165
141
|
], NotificationHelperService.prototype, "send", null);
|
|
@@ -170,25 +146,10 @@ _ts_decorate([
|
|
|
170
146
|
}),
|
|
171
147
|
_ts_metadata("design:type", Function),
|
|
172
148
|
_ts_metadata("design:paramtypes", [
|
|
173
|
-
typeof
|
|
149
|
+
typeof INotificationBulkSendOptions === "undefined" ? Object : INotificationBulkSendOptions
|
|
174
150
|
]),
|
|
175
151
|
_ts_metadata("design:returntype", Promise)
|
|
176
152
|
], NotificationHelperService.prototype, "sendToMany", null);
|
|
177
|
-
_ts_decorate([
|
|
178
|
-
LogAction({
|
|
179
|
-
action: 'notification.sendToCompany',
|
|
180
|
-
module: 'notification'
|
|
181
|
-
}),
|
|
182
|
-
_ts_metadata("design:type", Function),
|
|
183
|
-
_ts_metadata("design:paramtypes", [
|
|
184
|
-
String,
|
|
185
|
-
String,
|
|
186
|
-
Object,
|
|
187
|
-
Object,
|
|
188
|
-
Object
|
|
189
|
-
]),
|
|
190
|
-
_ts_metadata("design:returntype", Promise)
|
|
191
|
-
], NotificationHelperService.prototype, "sendToCompany", null);
|
|
192
153
|
NotificationHelperService = _ts_decorate([
|
|
193
154
|
Injectable(),
|
|
194
155
|
_ts_param(0, Inject('CACHE_INSTANCE')),
|
|
@@ -26,7 +26,7 @@ function _ts_param(paramIndex, decorator) {
|
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
28
|
import { LogAction } from '@flusys/nestjs-shared';
|
|
29
|
-
import {
|
|
29
|
+
import { ApiService, HybridCache } from '@flusys/nestjs-shared/classes';
|
|
30
30
|
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';
|
|
@@ -36,13 +36,10 @@ import { Notification, NotificationWithCompany } from '../entities';
|
|
|
36
36
|
import { NotificationGateway } from '../gateways';
|
|
37
37
|
import { NotificationConfigService } from './notification-config.service';
|
|
38
38
|
import { NotificationDataSourceProvider } from './notification-datasource.provider';
|
|
39
|
-
export class NotificationService extends
|
|
39
|
+
export class NotificationService extends ApiService {
|
|
40
40
|
resolveEntity() {
|
|
41
41
|
return this.notificationConfig.isCompanyFeatureEnabled() ? NotificationWithCompany : Notification;
|
|
42
42
|
}
|
|
43
|
-
getDataSourceProvider() {
|
|
44
|
-
return this.dataSourceProvider;
|
|
45
|
-
}
|
|
46
43
|
async convertSingleDtoToEntity(dto, user) {
|
|
47
44
|
let entity = {};
|
|
48
45
|
if ('id' in dto && dto.id) {
|
|
@@ -74,6 +71,24 @@ export class NotificationService extends RequestScopedApiService {
|
|
|
74
71
|
}
|
|
75
72
|
return merged;
|
|
76
73
|
}
|
|
74
|
+
async getSelectQuery(query, _user, select) {
|
|
75
|
+
const fields = select && select.length ? select : [];
|
|
76
|
+
const always = [
|
|
77
|
+
'createdAt',
|
|
78
|
+
'updatedAt'
|
|
79
|
+
];
|
|
80
|
+
const merged = [
|
|
81
|
+
...new Set([
|
|
82
|
+
...fields,
|
|
83
|
+
...always
|
|
84
|
+
])
|
|
85
|
+
];
|
|
86
|
+
query.addSelect(merged.map((f)=>`${this.entityName}.${f}`));
|
|
87
|
+
return {
|
|
88
|
+
query,
|
|
89
|
+
isRaw: false
|
|
90
|
+
};
|
|
91
|
+
}
|
|
77
92
|
async getFilterQuery(query, filter, _user) {
|
|
78
93
|
for (const [key, value] of Object.entries(filter)){
|
|
79
94
|
if (key === 'isRead' && typeof value === 'boolean') {
|
|
@@ -115,11 +130,11 @@ export class NotificationService extends RequestScopedApiService {
|
|
|
115
130
|
async afterInsertOperation(entities, _user, _qr) {
|
|
116
131
|
for (const entity of entities){
|
|
117
132
|
const ext = entity;
|
|
118
|
-
this.
|
|
133
|
+
this.emitToUser(entity.userId, ext.companyId, 'notification:new', this.toPayload(entity));
|
|
119
134
|
}
|
|
120
135
|
}
|
|
121
136
|
async markAsRead(id, user) {
|
|
122
|
-
await this.
|
|
137
|
+
await this.ensureDataSourceRepository();
|
|
123
138
|
const notification = await this.repository.findOne({
|
|
124
139
|
where: {
|
|
125
140
|
id,
|
|
@@ -143,13 +158,13 @@ export class NotificationService extends RequestScopedApiService {
|
|
|
143
158
|
]);
|
|
144
159
|
// Emit to user's sockets in the same company context
|
|
145
160
|
const ext = notification;
|
|
146
|
-
this.
|
|
161
|
+
this.emitToUser(user.id, ext.companyId ?? user.companyId, 'notification:read', {
|
|
147
162
|
id
|
|
148
163
|
});
|
|
149
164
|
return notification;
|
|
150
165
|
}
|
|
151
166
|
async markAllAsRead(user) {
|
|
152
|
-
await this.
|
|
167
|
+
await this.ensureDataSourceRepository();
|
|
153
168
|
const where = {
|
|
154
169
|
userId: user.id,
|
|
155
170
|
isRead: false
|
|
@@ -163,11 +178,11 @@ export class NotificationService extends RequestScopedApiService {
|
|
|
163
178
|
});
|
|
164
179
|
await this.clearCacheForAll();
|
|
165
180
|
// Emit to user's sockets in the current company context
|
|
166
|
-
this.
|
|
181
|
+
this.emitToUser(user.id, user.companyId, 'notification:read-all', {});
|
|
167
182
|
return result.affected ?? 0;
|
|
168
183
|
}
|
|
169
184
|
async getUnreadCount(user) {
|
|
170
|
-
await this.
|
|
185
|
+
await this.ensureDataSourceRepository();
|
|
171
186
|
const where = {
|
|
172
187
|
userId: user.id,
|
|
173
188
|
isRead: false
|
|
@@ -189,24 +204,18 @@ export class NotificationService extends RequestScopedApiService {
|
|
|
189
204
|
createdAt: entity.createdAt
|
|
190
205
|
};
|
|
191
206
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
* If company feature is enabled and companyId is provided, only sockets
|
|
195
|
-
* connected with that company will receive the event.
|
|
196
|
-
*/ emitToUserInCompanyIfRealtime(userId, companyId, event, payload) {
|
|
197
|
-
if (!this.notificationConfig.isRealtimeEnabled() || !this.gateway) {
|
|
207
|
+
emitToUser(userId, companyId, event, payload) {
|
|
208
|
+
if (!this.gateway) {
|
|
198
209
|
return;
|
|
199
210
|
}
|
|
200
211
|
if (this.notificationConfig.isCompanyFeatureEnabled() && companyId) {
|
|
201
|
-
// Company feature enabled - emit only to sockets in matching company context
|
|
202
212
|
this.gateway.emitToUserInCompany(userId, companyId, event, payload);
|
|
203
213
|
} else {
|
|
204
|
-
// No company feature or no companyId - emit to all user's sockets
|
|
205
214
|
this.gateway.emitToUser(userId, event, payload);
|
|
206
215
|
}
|
|
207
216
|
}
|
|
208
217
|
constructor(cacheManager, utilsService, notificationConfig, dataSourceProvider, gateway){
|
|
209
|
-
super('notification',
|
|
218
|
+
super('notification', cacheManager, utilsService, NotificationService.name, false, 'notification', undefined, dataSourceProvider), _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;
|
|
210
219
|
}
|
|
211
220
|
}
|
|
212
221
|
_ts_decorate([
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { IBootstrapAppConfig, IDataSourceServiceOptions, IDynamicModuleConfig, IModuleOptionsFactory } from '@flusys/nestjs-core';
|
|
2
2
|
import { ModuleMetadata, Type } from '@nestjs/common';
|
|
3
3
|
export interface INotificationModuleConfig extends IDataSourceServiceOptions {
|
|
4
|
-
enableRealtime?: boolean;
|
|
5
4
|
jwtSecret?: string;
|
|
6
5
|
}
|
|
7
6
|
export interface NotificationModuleOptions extends IDynamicModuleConfig {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flusys/nestjs-notification",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.2.0",
|
|
4
4
|
"description": "Notification module with real-time Socket.IO support",
|
|
5
5
|
"main": "cjs/index.js",
|
|
6
6
|
"module": "fesm/index.js",
|
|
@@ -77,23 +77,23 @@
|
|
|
77
77
|
}
|
|
78
78
|
},
|
|
79
79
|
"peerDependencies": {
|
|
80
|
-
"@nestjs/common": "^
|
|
81
|
-
"@nestjs/config": "^
|
|
82
|
-
"@nestjs/core": "^
|
|
83
|
-
"@nestjs/platform-express": "^
|
|
84
|
-
"@nestjs/swagger": "^
|
|
85
|
-
"@nestjs/typeorm": "^
|
|
86
|
-
"@nestjs/websockets": "^
|
|
87
|
-
"@nestjs/platform-socket.io": "^
|
|
88
|
-
"class-transformer": "^0.5.
|
|
89
|
-
"class-validator": "^0.14.0",
|
|
90
|
-
"socket.io": "^4.
|
|
80
|
+
"@nestjs/common": "^11.0.0",
|
|
81
|
+
"@nestjs/config": "^4.0.0",
|
|
82
|
+
"@nestjs/core": "^11.0.0",
|
|
83
|
+
"@nestjs/platform-express": "^11.0.0",
|
|
84
|
+
"@nestjs/swagger": "^11.0.0",
|
|
85
|
+
"@nestjs/typeorm": "^11.0.0",
|
|
86
|
+
"@nestjs/websockets": "^11.0.0",
|
|
87
|
+
"@nestjs/platform-socket.io": "^11.0.0",
|
|
88
|
+
"class-transformer": "^0.5.1",
|
|
89
|
+
"class-validator": "^0.14.0 || ^0.15.0",
|
|
90
|
+
"socket.io": "^4.8.0",
|
|
91
91
|
"typeorm": "^0.3.0",
|
|
92
|
-
"express": "^4.18.0"
|
|
92
|
+
"express": "^4.18.0 || ^5.0.0"
|
|
93
93
|
},
|
|
94
94
|
"dependencies": {
|
|
95
|
-
"@flusys/nestjs-core": "5.
|
|
96
|
-
"@flusys/nestjs-shared": "5.
|
|
95
|
+
"@flusys/nestjs-core": "5.2.0",
|
|
96
|
+
"@flusys/nestjs-shared": "5.2.0",
|
|
97
97
|
"jsonwebtoken": "^9.0.0"
|
|
98
98
|
}
|
|
99
99
|
}
|
|
@@ -7,7 +7,6 @@ export declare class NotificationConfigService implements IModuleConfigService {
|
|
|
7
7
|
isCompanyFeatureEnabled(): boolean;
|
|
8
8
|
getDatabaseMode(): DatabaseMode;
|
|
9
9
|
isMultiTenant(): boolean;
|
|
10
|
-
isRealtimeEnabled(): boolean;
|
|
11
10
|
getJwtSecret(): string | undefined;
|
|
12
11
|
getOptions(): NotificationModuleOptions;
|
|
13
12
|
}
|
|
@@ -16,6 +16,4 @@ export declare class NotificationDataSourceProvider extends MultiTenantDataSourc
|
|
|
16
16
|
getEnableCompanyFeatureForTenant(tenant?: ITenantDatabaseConfig): boolean;
|
|
17
17
|
getNotificationEntities(enableCompanyFeature?: boolean): Promise<any[]>;
|
|
18
18
|
protected createDataSourceFromConfig(config: IDatabaseConfig): Promise<DataSource>;
|
|
19
|
-
protected getSingleDataSource(): Promise<DataSource>;
|
|
20
|
-
protected getOrCreateTenantConnection(tenant: ITenantDatabaseConfig): Promise<DataSource>;
|
|
21
19
|
}
|
|
@@ -1,43 +1,26 @@
|
|
|
1
|
+
import { INotificationBulkSendOptions, INotificationSendOptions } from '@flusys/nestjs-shared';
|
|
1
2
|
import { HybridCache } from '@flusys/nestjs-shared/classes';
|
|
2
3
|
import { UtilsService } from '@flusys/nestjs-shared/modules';
|
|
4
|
+
import { NotificationBase } from '../entities';
|
|
5
|
+
import { NotificationGateway } from '../gateways';
|
|
3
6
|
import { NotificationConfigService } from './notification-config.service';
|
|
4
7
|
import { NotificationDataSourceProvider } from './notification-datasource.provider';
|
|
5
|
-
import { NotificationGateway } from '../gateways';
|
|
6
|
-
import { NotificationBase } from '../entities';
|
|
7
|
-
import { NotificationType } from '@flusys/nestjs-shared/enums';
|
|
8
|
-
export interface SendNotificationOptions {
|
|
9
|
-
userId: string;
|
|
10
|
-
title: string;
|
|
11
|
-
message?: string | null;
|
|
12
|
-
type?: NotificationType | string;
|
|
13
|
-
data?: Record<string, unknown> | null;
|
|
14
|
-
companyId?: string | null;
|
|
15
|
-
}
|
|
16
|
-
export interface SendBulkNotificationOptions {
|
|
17
|
-
userIds: string[];
|
|
18
|
-
title: string;
|
|
19
|
-
message?: string | null;
|
|
20
|
-
type?: NotificationType | string;
|
|
21
|
-
data?: Record<string, unknown> | null;
|
|
22
|
-
companyId?: string | null;
|
|
23
|
-
}
|
|
24
8
|
export declare class NotificationHelperService {
|
|
25
9
|
private readonly cacheManager;
|
|
26
10
|
private readonly utilsService;
|
|
27
11
|
private readonly configService;
|
|
28
12
|
private readonly dataSourceProvider;
|
|
29
13
|
private readonly gateway?;
|
|
30
|
-
private readonly entityName;
|
|
31
14
|
private dataSource;
|
|
32
|
-
constructor(cacheManager: HybridCache, utilsService: UtilsService, configService: NotificationConfigService, dataSourceProvider: NotificationDataSourceProvider, gateway?: NotificationGateway);
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
send(options: SendNotificationOptions): Promise<NotificationBase>;
|
|
36
|
-
sendToMany(options: SendBulkNotificationOptions): Promise<NotificationBase[]>;
|
|
37
|
-
sendToCompany(companyId: string, title: string, message?: string | null, type?: NotificationType | string, data?: Record<string, unknown> | null): Promise<void>;
|
|
15
|
+
constructor(cacheManager: HybridCache, utilsService: UtilsService, configService: NotificationConfigService, dataSourceProvider: NotificationDataSourceProvider, gateway?: NotificationGateway | undefined);
|
|
16
|
+
send(options: INotificationSendOptions): Promise<NotificationBase>;
|
|
17
|
+
sendToMany(options: INotificationBulkSendOptions): Promise<NotificationBase[]>;
|
|
38
18
|
isUserOnline(userId: string): boolean;
|
|
39
19
|
getOnlineUserCount(): number;
|
|
40
|
-
private
|
|
20
|
+
private getDataSource;
|
|
21
|
+
private getRepository;
|
|
22
|
+
private buildEntity;
|
|
23
|
+
private emitToUser;
|
|
41
24
|
private toPayload;
|
|
42
25
|
private clearCache;
|
|
43
26
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ApiService, HybridCache } from '@flusys/nestjs-shared/classes';
|
|
2
2
|
import { ILoggedUserInfo } from '@flusys/nestjs-shared/interfaces';
|
|
3
3
|
import { UtilsService } from '@flusys/nestjs-shared/modules';
|
|
4
4
|
import { EntityTarget, QueryRunner, SelectQueryBuilder } from 'typeorm';
|
|
@@ -8,16 +8,18 @@ import { NotificationGateway } from '../gateways';
|
|
|
8
8
|
import { INotification } from '../interfaces';
|
|
9
9
|
import { NotificationConfigService } from './notification-config.service';
|
|
10
10
|
import { NotificationDataSourceProvider } from './notification-datasource.provider';
|
|
11
|
-
export declare class NotificationService extends
|
|
11
|
+
export declare class NotificationService extends ApiService<CreateNotificationDto, UpdateNotificationDto, INotification, NotificationBase> {
|
|
12
12
|
protected cacheManager: HybridCache;
|
|
13
13
|
protected utilsService: UtilsService;
|
|
14
14
|
private readonly notificationConfig;
|
|
15
|
-
private readonly dataSourceProvider;
|
|
16
15
|
private readonly gateway?;
|
|
17
|
-
constructor(cacheManager: HybridCache, utilsService: UtilsService, notificationConfig: NotificationConfigService, dataSourceProvider: NotificationDataSourceProvider, gateway?: NotificationGateway);
|
|
16
|
+
constructor(cacheManager: HybridCache, utilsService: UtilsService, notificationConfig: NotificationConfigService, dataSourceProvider: NotificationDataSourceProvider, gateway?: NotificationGateway | undefined);
|
|
18
17
|
protected resolveEntity(): EntityTarget<NotificationBase>;
|
|
19
|
-
protected getDataSourceProvider(): NotificationDataSourceProvider;
|
|
20
18
|
convertSingleDtoToEntity(dto: CreateNotificationDto | UpdateNotificationDto, user: ILoggedUserInfo | null): Promise<NotificationBase>;
|
|
19
|
+
protected getSelectQuery(query: SelectQueryBuilder<NotificationBase>, _user: ILoggedUserInfo | null, select?: string[]): Promise<{
|
|
20
|
+
query: SelectQueryBuilder<NotificationBase>;
|
|
21
|
+
isRaw: boolean;
|
|
22
|
+
}>;
|
|
21
23
|
protected getFilterQuery(query: SelectQueryBuilder<NotificationBase>, filter: Record<string, unknown>, _user: ILoggedUserInfo | null): Promise<{
|
|
22
24
|
query: SelectQueryBuilder<NotificationBase>;
|
|
23
25
|
isRaw: boolean;
|
|
@@ -31,5 +33,5 @@ export declare class NotificationService extends RequestScopedApiService<CreateN
|
|
|
31
33
|
markAllAsRead(user: ILoggedUserInfo): Promise<number>;
|
|
32
34
|
getUnreadCount(user: ILoggedUserInfo): Promise<number>;
|
|
33
35
|
private toPayload;
|
|
34
|
-
private
|
|
36
|
+
private emitToUser;
|
|
35
37
|
}
|