@flusys/nestjs-notification 3.0.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/README.md +998 -0
- package/adapters/index.d.ts +1 -0
- package/adapters/notification.adapter.d.ts +10 -0
- package/cjs/adapters/index.js +18 -0
- package/cjs/adapters/notification.adapter.js +79 -0
- package/cjs/config/index.js +18 -0
- package/cjs/config/notification.constants.js +11 -0
- package/cjs/controllers/index.js +18 -0
- package/cjs/controllers/notification.controller.js +278 -0
- package/cjs/docs/index.js +18 -0
- package/cjs/docs/notification-swagger.config.js +61 -0
- package/cjs/dtos/index.js +18 -0
- package/cjs/dtos/notification.dto.js +244 -0
- package/cjs/entities/index.js +48 -0
- package/cjs/entities/notification-with-company.entity.js +57 -0
- package/cjs/entities/notification.entity.js +111 -0
- package/cjs/gateways/index.js +18 -0
- package/cjs/gateways/notification.gateway.js +226 -0
- package/cjs/index.js +28 -0
- package/cjs/interfaces/index.js +19 -0
- package/cjs/interfaces/notification-module.interface.js +4 -0
- package/cjs/interfaces/notification.interface.js +4 -0
- package/cjs/modules/index.js +18 -0
- package/cjs/modules/notification.module.js +139 -0
- package/cjs/services/index.js +21 -0
- package/cjs/services/notification-config.service.js +72 -0
- package/cjs/services/notification-datasource.provider.js +174 -0
- package/cjs/services/notification-helper.service.js +186 -0
- package/cjs/services/notification.service.js +220 -0
- package/config/index.d.ts +1 -0
- package/config/notification.constants.d.ts +1 -0
- package/controllers/index.d.ts +1 -0
- package/controllers/notification.controller.d.ts +26 -0
- package/docs/index.d.ts +1 -0
- package/docs/notification-swagger.config.d.ts +2 -0
- package/dtos/index.d.ts +1 -0
- package/dtos/notification.dto.d.ts +47 -0
- package/entities/index.d.ts +8 -0
- package/entities/notification-with-company.entity.d.ts +4 -0
- package/entities/notification.entity.d.ts +10 -0
- package/fesm/adapters/index.js +1 -0
- package/fesm/adapters/notification.adapter.js +69 -0
- package/fesm/config/index.js +1 -0
- package/fesm/config/notification.constants.js +1 -0
- package/fesm/controllers/index.js +1 -0
- package/fesm/controllers/notification.controller.js +268 -0
- package/fesm/docs/index.js +1 -0
- package/fesm/docs/notification-swagger.config.js +51 -0
- package/fesm/dtos/index.js +1 -0
- package/fesm/dtos/notification.dto.js +211 -0
- package/fesm/entities/index.js +14 -0
- package/fesm/entities/notification-with-company.entity.js +47 -0
- package/fesm/entities/notification.entity.js +101 -0
- package/fesm/gateways/index.js +1 -0
- package/fesm/gateways/notification.gateway.js +175 -0
- package/fesm/index.js +20 -0
- package/fesm/interfaces/index.js +2 -0
- package/fesm/interfaces/notification-module.interface.js +1 -0
- package/fesm/interfaces/notification.interface.js +1 -0
- package/fesm/modules/index.js +1 -0
- package/fesm/modules/notification.module.js +129 -0
- package/fesm/services/index.js +4 -0
- package/fesm/services/notification-config.service.js +62 -0
- package/fesm/services/notification-datasource.provider.js +123 -0
- package/fesm/services/notification-helper.service.js +176 -0
- package/fesm/services/notification.service.js +210 -0
- package/gateways/index.d.ts +1 -0
- package/gateways/notification.gateway.d.ts +21 -0
- package/index.d.ts +10 -0
- package/interfaces/index.d.ts +2 -0
- package/interfaces/notification-module.interface.d.ts +20 -0
- package/interfaces/notification.interface.d.ts +19 -0
- package/modules/index.d.ts +1 -0
- package/modules/notification.module.d.ts +8 -0
- package/package.json +98 -0
- package/services/index.d.ts +4 -0
- package/services/notification-config.service.d.ts +13 -0
- package/services/notification-datasource.provider.d.ts +23 -0
- package/services/notification-helper.service.d.ts +44 -0
- package/services/notification.service.d.ts +35 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
function _define_property(obj, key, value) {
|
|
2
|
+
if (key in obj) {
|
|
3
|
+
Object.defineProperty(obj, key, {
|
|
4
|
+
value: value,
|
|
5
|
+
enumerable: true,
|
|
6
|
+
configurable: true,
|
|
7
|
+
writable: true
|
|
8
|
+
});
|
|
9
|
+
} else {
|
|
10
|
+
obj[key] = value;
|
|
11
|
+
}
|
|
12
|
+
return obj;
|
|
13
|
+
}
|
|
14
|
+
function _ts_decorate(decorators, target, key, desc) {
|
|
15
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
16
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
17
|
+
else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
18
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
19
|
+
}
|
|
20
|
+
function _ts_metadata(k, v) {
|
|
21
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
22
|
+
}
|
|
23
|
+
function _ts_param(paramIndex, decorator) {
|
|
24
|
+
return function(target, key) {
|
|
25
|
+
decorator(target, key, paramIndex);
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
import { Inject, Injectable, Logger, Optional } from '@nestjs/common';
|
|
29
|
+
import { HybridCache } from '@flusys/nestjs-shared/classes';
|
|
30
|
+
import { UtilsService } from '@flusys/nestjs-shared/modules';
|
|
31
|
+
import { NotificationConfigService } from './notification-config.service';
|
|
32
|
+
import { NotificationDataSourceProvider } from './notification-datasource.provider';
|
|
33
|
+
import { NotificationGateway } from '../gateways';
|
|
34
|
+
import { Notification, NotificationWithCompany } from '../entities';
|
|
35
|
+
import { NotificationType } from '@flusys/nestjs-shared/enums';
|
|
36
|
+
export class NotificationHelperService {
|
|
37
|
+
async getDataSource() {
|
|
38
|
+
if (!this.dataSource) {
|
|
39
|
+
this.dataSource = await this.dataSourceProvider.getDataSource();
|
|
40
|
+
}
|
|
41
|
+
return this.dataSource;
|
|
42
|
+
}
|
|
43
|
+
async getRepository() {
|
|
44
|
+
const ds = await this.getDataSource();
|
|
45
|
+
const EntityClass = this.configService.isCompanyFeatureEnabled() ? NotificationWithCompany : Notification;
|
|
46
|
+
return ds.getRepository(EntityClass);
|
|
47
|
+
}
|
|
48
|
+
async send(options) {
|
|
49
|
+
const { userId, title, message = null, type = NotificationType.INFO, data = null, companyId } = options;
|
|
50
|
+
const repo = await this.getRepository();
|
|
51
|
+
const entity = {
|
|
52
|
+
userId,
|
|
53
|
+
type,
|
|
54
|
+
title,
|
|
55
|
+
message,
|
|
56
|
+
data,
|
|
57
|
+
isRead: false
|
|
58
|
+
};
|
|
59
|
+
if (this.configService.isCompanyFeatureEnabled() && companyId) {
|
|
60
|
+
entity.companyId = companyId;
|
|
61
|
+
}
|
|
62
|
+
const saved = await repo.save(entity);
|
|
63
|
+
await this.clearCache();
|
|
64
|
+
this.emitRealtime(saved);
|
|
65
|
+
this.logger.debug(`Notification sent to user ${userId}: ${title}`);
|
|
66
|
+
return saved;
|
|
67
|
+
}
|
|
68
|
+
async sendToMany(options) {
|
|
69
|
+
const { userIds, title, message = null, type = NotificationType.INFO, data = null, companyId } = options;
|
|
70
|
+
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
|
+
}));
|
|
82
|
+
const saved = await repo.save(entities);
|
|
83
|
+
await this.clearCache();
|
|
84
|
+
for (const notification of saved){
|
|
85
|
+
this.emitRealtime(notification);
|
|
86
|
+
}
|
|
87
|
+
this.logger.debug(`Notification sent to ${userIds.length} users: ${title}`);
|
|
88
|
+
return saved;
|
|
89
|
+
}
|
|
90
|
+
async sendToCompany(companyId, title, message, type = NotificationType.INFO, data) {
|
|
91
|
+
if (!this.configService.isCompanyFeatureEnabled()) {
|
|
92
|
+
this.logger.warn('sendToCompany called but company feature is disabled');
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (this.configService.isRealtimeEnabled() && this.gateway) {
|
|
96
|
+
const payload = {
|
|
97
|
+
id: `broadcast-${Date.now()}`,
|
|
98
|
+
type,
|
|
99
|
+
title,
|
|
100
|
+
message: message ?? null,
|
|
101
|
+
data: data ?? null,
|
|
102
|
+
createdAt: new Date()
|
|
103
|
+
};
|
|
104
|
+
this.gateway.emitToCompany(companyId, 'notification:new', payload);
|
|
105
|
+
this.logger.debug(`Broadcast notification sent to company ${companyId}: ${title}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
isUserOnline(userId) {
|
|
109
|
+
return this.gateway?.isUserOnline(userId) ?? false;
|
|
110
|
+
}
|
|
111
|
+
getOnlineUserCount() {
|
|
112
|
+
return this.gateway?.getOnlineUserCount() ?? 0;
|
|
113
|
+
}
|
|
114
|
+
emitRealtime(notification) {
|
|
115
|
+
if (!this.configService.isRealtimeEnabled() || !this.gateway) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const ext = notification;
|
|
119
|
+
const payload = this.toPayload(notification);
|
|
120
|
+
if (this.configService.isCompanyFeatureEnabled() && ext.companyId) {
|
|
121
|
+
// Company feature enabled - emit only to sockets in matching company context
|
|
122
|
+
this.gateway.emitToUserInCompany(notification.userId, ext.companyId, 'notification:new', payload);
|
|
123
|
+
} else {
|
|
124
|
+
// No company feature or no companyId - emit to all user's sockets
|
|
125
|
+
this.gateway.emitToUser(notification.userId, 'notification:new', payload);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
toPayload(notification) {
|
|
129
|
+
return {
|
|
130
|
+
id: notification.id,
|
|
131
|
+
type: notification.type,
|
|
132
|
+
title: notification.title,
|
|
133
|
+
message: notification.message,
|
|
134
|
+
data: notification.data,
|
|
135
|
+
createdAt: notification.createdAt
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
async clearCache() {
|
|
139
|
+
await this.utilsService.clearCache(this.entityName, this.cacheManager);
|
|
140
|
+
}
|
|
141
|
+
constructor(cacheManager, utilsService, configService, dataSourceProvider, gateway){
|
|
142
|
+
_define_property(this, "cacheManager", void 0);
|
|
143
|
+
_define_property(this, "utilsService", void 0);
|
|
144
|
+
_define_property(this, "configService", void 0);
|
|
145
|
+
_define_property(this, "dataSourceProvider", void 0);
|
|
146
|
+
_define_property(this, "gateway", void 0);
|
|
147
|
+
_define_property(this, "logger", void 0);
|
|
148
|
+
_define_property(this, "entityName", void 0);
|
|
149
|
+
_define_property(this, "dataSource", void 0);
|
|
150
|
+
this.cacheManager = cacheManager;
|
|
151
|
+
this.utilsService = utilsService;
|
|
152
|
+
this.configService = configService;
|
|
153
|
+
this.dataSourceProvider = dataSourceProvider;
|
|
154
|
+
this.gateway = gateway;
|
|
155
|
+
this.logger = new Logger(NotificationHelperService.name);
|
|
156
|
+
this.entityName = 'notification';
|
|
157
|
+
this.dataSource = null;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
NotificationHelperService = _ts_decorate([
|
|
161
|
+
Injectable(),
|
|
162
|
+
_ts_param(0, Inject('CACHE_INSTANCE')),
|
|
163
|
+
_ts_param(1, Inject(UtilsService)),
|
|
164
|
+
_ts_param(2, Inject(NotificationConfigService)),
|
|
165
|
+
_ts_param(3, Inject(NotificationDataSourceProvider)),
|
|
166
|
+
_ts_param(4, Optional()),
|
|
167
|
+
_ts_param(4, Inject(NotificationGateway)),
|
|
168
|
+
_ts_metadata("design:type", Function),
|
|
169
|
+
_ts_metadata("design:paramtypes", [
|
|
170
|
+
typeof HybridCache === "undefined" ? Object : HybridCache,
|
|
171
|
+
typeof UtilsService === "undefined" ? Object : UtilsService,
|
|
172
|
+
typeof NotificationConfigService === "undefined" ? Object : NotificationConfigService,
|
|
173
|
+
typeof NotificationDataSourceProvider === "undefined" ? Object : NotificationDataSourceProvider,
|
|
174
|
+
typeof NotificationGateway === "undefined" ? Object : NotificationGateway
|
|
175
|
+
])
|
|
176
|
+
], NotificationHelperService);
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
function _define_property(obj, key, value) {
|
|
2
|
+
if (key in obj) {
|
|
3
|
+
Object.defineProperty(obj, key, {
|
|
4
|
+
value: value,
|
|
5
|
+
enumerable: true,
|
|
6
|
+
configurable: true,
|
|
7
|
+
writable: true
|
|
8
|
+
});
|
|
9
|
+
} else {
|
|
10
|
+
obj[key] = value;
|
|
11
|
+
}
|
|
12
|
+
return obj;
|
|
13
|
+
}
|
|
14
|
+
function _ts_decorate(decorators, target, key, desc) {
|
|
15
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
16
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
17
|
+
else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
18
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
19
|
+
}
|
|
20
|
+
function _ts_metadata(k, v) {
|
|
21
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
22
|
+
}
|
|
23
|
+
function _ts_param(paramIndex, decorator) {
|
|
24
|
+
return function(target, key) {
|
|
25
|
+
decorator(target, key, paramIndex);
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
import { HybridCache, RequestScopedApiService } from '@flusys/nestjs-shared/classes';
|
|
29
|
+
import { UtilsService } from '@flusys/nestjs-shared/modules';
|
|
30
|
+
import { applyCompanyFilter } from '@flusys/nestjs-shared/utils';
|
|
31
|
+
import { Inject, Injectable, NotFoundException, Optional, Scope } from '@nestjs/common';
|
|
32
|
+
import { Notification, NotificationWithCompany } from '../entities';
|
|
33
|
+
import { NotificationGateway } from '../gateways';
|
|
34
|
+
import { NotificationConfigService } from './notification-config.service';
|
|
35
|
+
import { NotificationDataSourceProvider } from './notification-datasource.provider';
|
|
36
|
+
export class NotificationService extends RequestScopedApiService {
|
|
37
|
+
resolveEntity() {
|
|
38
|
+
return this.notificationConfig.isCompanyFeatureEnabled() ? NotificationWithCompany : Notification;
|
|
39
|
+
}
|
|
40
|
+
getDataSourceProvider() {
|
|
41
|
+
return this.dataSourceProvider;
|
|
42
|
+
}
|
|
43
|
+
async convertSingleDtoToEntity(dto, user) {
|
|
44
|
+
let entity = {};
|
|
45
|
+
if ('id' in dto && dto.id) {
|
|
46
|
+
const existing = await this.repository.findOne({
|
|
47
|
+
where: {
|
|
48
|
+
id: dto.id
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
if (!existing) throw new NotFoundException('Notification not found');
|
|
52
|
+
entity = existing;
|
|
53
|
+
}
|
|
54
|
+
const merged = {
|
|
55
|
+
...entity,
|
|
56
|
+
userId: user?.id ?? entity.userId,
|
|
57
|
+
type: dto.type ?? entity.type ?? 'info',
|
|
58
|
+
title: dto.title ?? entity.title,
|
|
59
|
+
message: dto.message ?? entity.message,
|
|
60
|
+
data: dto.data ?? entity.data,
|
|
61
|
+
isRead: entity.isRead ?? false,
|
|
62
|
+
readAt: entity.readAt
|
|
63
|
+
};
|
|
64
|
+
if (this.notificationConfig.isCompanyFeatureEnabled()) {
|
|
65
|
+
merged.companyId = dto.companyId ?? user?.companyId ?? entity.companyId ?? null;
|
|
66
|
+
}
|
|
67
|
+
return merged;
|
|
68
|
+
}
|
|
69
|
+
async getFilterQuery(query, filter, _user) {
|
|
70
|
+
for (const [key, value] of Object.entries(filter)){
|
|
71
|
+
if (key === 'isRead' && typeof value === 'boolean') {
|
|
72
|
+
query.andWhere(`${this.entityName}.isRead = :isRead`, {
|
|
73
|
+
isRead: value
|
|
74
|
+
});
|
|
75
|
+
} else if (key === 'type' && value) {
|
|
76
|
+
query.andWhere(`${this.entityName}.type = :type`, {
|
|
77
|
+
type: value
|
|
78
|
+
});
|
|
79
|
+
} else if (key === 'userId' && value) {
|
|
80
|
+
query.andWhere(`${this.entityName}.userId = :userId`, {
|
|
81
|
+
userId: value
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
query,
|
|
87
|
+
isRaw: false
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
async getExtraManipulateQuery(query, filterDto, user) {
|
|
91
|
+
const result = await super.getExtraManipulateQuery(query, filterDto, user);
|
|
92
|
+
if (user?.id) {
|
|
93
|
+
query.andWhere(`${this.entityName}.userId = :currentUserId`, {
|
|
94
|
+
currentUserId: user.id
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
applyCompanyFilter(query, {
|
|
98
|
+
isCompanyFeatureEnabled: this.notificationConfig.isCompanyFeatureEnabled(),
|
|
99
|
+
entityAlias: this.entityName
|
|
100
|
+
}, user);
|
|
101
|
+
if (!filterDto?.sort || Object.keys(filterDto.sort).length === 0) {
|
|
102
|
+
query.orderBy(`${this.entityName}.createdAt`, 'DESC');
|
|
103
|
+
}
|
|
104
|
+
return result;
|
|
105
|
+
}
|
|
106
|
+
async afterInsertOperation(entities, _user, _qr) {
|
|
107
|
+
for (const entity of entities){
|
|
108
|
+
const ext = entity;
|
|
109
|
+
this.emitToUserInCompanyIfRealtime(entity.userId, ext.companyId, 'notification:new', this.toPayload(entity));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
async markAsRead(id, user) {
|
|
113
|
+
await this.ensureRepositoryInitialized();
|
|
114
|
+
const notification = await this.repository.findOne({
|
|
115
|
+
where: {
|
|
116
|
+
id,
|
|
117
|
+
userId: user.id
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
if (!notification) {
|
|
121
|
+
throw new NotFoundException('Notification not found');
|
|
122
|
+
}
|
|
123
|
+
notification.isRead = true;
|
|
124
|
+
notification.readAt = new Date();
|
|
125
|
+
await this.repository.save(notification);
|
|
126
|
+
await Promise.all([
|
|
127
|
+
this.clearCacheForAll(),
|
|
128
|
+
this.clearCacheForId([
|
|
129
|
+
notification
|
|
130
|
+
])
|
|
131
|
+
]);
|
|
132
|
+
// Emit to user's sockets in the same company context
|
|
133
|
+
const ext = notification;
|
|
134
|
+
this.emitToUserInCompanyIfRealtime(user.id, ext.companyId ?? user.companyId, 'notification:read', {
|
|
135
|
+
id
|
|
136
|
+
});
|
|
137
|
+
return notification;
|
|
138
|
+
}
|
|
139
|
+
async markAllAsRead(user) {
|
|
140
|
+
await this.ensureRepositoryInitialized();
|
|
141
|
+
const result = await this.repository.update({
|
|
142
|
+
userId: user.id,
|
|
143
|
+
isRead: false
|
|
144
|
+
}, {
|
|
145
|
+
isRead: true,
|
|
146
|
+
readAt: new Date()
|
|
147
|
+
});
|
|
148
|
+
await this.clearCacheForAll();
|
|
149
|
+
// Emit to user's sockets in the current company context
|
|
150
|
+
this.emitToUserInCompanyIfRealtime(user.id, user.companyId, 'notification:read-all', {});
|
|
151
|
+
return result.affected ?? 0;
|
|
152
|
+
}
|
|
153
|
+
async getUnreadCount(user) {
|
|
154
|
+
await this.ensureRepositoryInitialized();
|
|
155
|
+
return this.repository.count({
|
|
156
|
+
where: {
|
|
157
|
+
userId: user.id,
|
|
158
|
+
isRead: false
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
toPayload(entity) {
|
|
163
|
+
return {
|
|
164
|
+
id: entity.id,
|
|
165
|
+
type: entity.type,
|
|
166
|
+
title: entity.title,
|
|
167
|
+
message: entity.message,
|
|
168
|
+
data: entity.data,
|
|
169
|
+
createdAt: entity.createdAt
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Emit to user's sockets within a specific company context.
|
|
174
|
+
* If company feature is enabled and companyId is provided, only sockets
|
|
175
|
+
* connected with that company will receive the event.
|
|
176
|
+
*/ emitToUserInCompanyIfRealtime(userId, companyId, event, payload) {
|
|
177
|
+
if (!this.notificationConfig.isRealtimeEnabled() || !this.gateway) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
if (this.notificationConfig.isCompanyFeatureEnabled() && companyId) {
|
|
181
|
+
// Company feature enabled - emit only to sockets in matching company context
|
|
182
|
+
this.gateway.emitToUserInCompany(userId, companyId, event, payload);
|
|
183
|
+
} else {
|
|
184
|
+
// No company feature or no companyId - emit to all user's sockets
|
|
185
|
+
this.gateway.emitToUser(userId, event, payload);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
constructor(cacheManager, utilsService, notificationConfig, dataSourceProvider, gateway){
|
|
189
|
+
super('notification', null, cacheManager, utilsService, NotificationService.name, false), _define_property(this, "cacheManager", void 0), _define_property(this, "utilsService", void 0), _define_property(this, "notificationConfig", void 0), _define_property(this, "dataSourceProvider", void 0), _define_property(this, "gateway", void 0), this.cacheManager = cacheManager, this.utilsService = utilsService, this.notificationConfig = notificationConfig, this.dataSourceProvider = dataSourceProvider, this.gateway = gateway;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
NotificationService = _ts_decorate([
|
|
193
|
+
Injectable({
|
|
194
|
+
scope: Scope.REQUEST
|
|
195
|
+
}),
|
|
196
|
+
_ts_param(0, Inject('CACHE_INSTANCE')),
|
|
197
|
+
_ts_param(1, Inject(UtilsService)),
|
|
198
|
+
_ts_param(2, Inject(NotificationConfigService)),
|
|
199
|
+
_ts_param(3, Inject(NotificationDataSourceProvider)),
|
|
200
|
+
_ts_param(4, Optional()),
|
|
201
|
+
_ts_param(4, Inject(NotificationGateway)),
|
|
202
|
+
_ts_metadata("design:type", Function),
|
|
203
|
+
_ts_metadata("design:paramtypes", [
|
|
204
|
+
typeof HybridCache === "undefined" ? Object : HybridCache,
|
|
205
|
+
typeof UtilsService === "undefined" ? Object : UtilsService,
|
|
206
|
+
typeof NotificationConfigService === "undefined" ? Object : NotificationConfigService,
|
|
207
|
+
typeof NotificationDataSourceProvider === "undefined" ? Object : NotificationDataSourceProvider,
|
|
208
|
+
typeof NotificationGateway === "undefined" ? Object : NotificationGateway
|
|
209
|
+
])
|
|
210
|
+
], NotificationService);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './notification.gateway';
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { OnGatewayConnection, OnGatewayDisconnect, OnGatewayInit } from '@nestjs/websockets';
|
|
2
|
+
import { Server, Socket } from 'socket.io';
|
|
3
|
+
import { NotificationConfigService } from '../services/notification-config.service';
|
|
4
|
+
export declare class NotificationGateway implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect {
|
|
5
|
+
private readonly configService;
|
|
6
|
+
private readonly logger;
|
|
7
|
+
private readonly connectedUsers;
|
|
8
|
+
server: Server;
|
|
9
|
+
constructor(configService: NotificationConfigService);
|
|
10
|
+
afterInit(): void;
|
|
11
|
+
handleConnection(client: Socket): Promise<void>;
|
|
12
|
+
handleDisconnect(client: Socket): void;
|
|
13
|
+
emitToUser(userId: string, event: string, data: unknown): void;
|
|
14
|
+
emitToUserInCompany(userId: string, companyId: string | null | undefined, event: string, data: unknown): void;
|
|
15
|
+
emitToCompany(companyId: string, event: string, data: unknown): void;
|
|
16
|
+
broadcast(event: string, data: unknown): void;
|
|
17
|
+
isUserOnline(userId: string): boolean;
|
|
18
|
+
getOnlineUserCount(): number;
|
|
19
|
+
private extractToken;
|
|
20
|
+
private verifyToken;
|
|
21
|
+
}
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export * from './adapters';
|
|
2
|
+
export * from './config';
|
|
3
|
+
export * from './controllers';
|
|
4
|
+
export * from './docs';
|
|
5
|
+
export * from './dtos';
|
|
6
|
+
export * from './entities';
|
|
7
|
+
export * from './gateways';
|
|
8
|
+
export * from './interfaces';
|
|
9
|
+
export * from './modules';
|
|
10
|
+
export * from './services';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { IBootstrapAppConfig, IDataSourceServiceOptions, IDynamicModuleConfig, IModuleOptionsFactory } from '@flusys/nestjs-core';
|
|
2
|
+
import { ModuleMetadata, Type } from '@nestjs/common';
|
|
3
|
+
export interface INotificationModuleConfig extends IDataSourceServiceOptions {
|
|
4
|
+
enableRealtime?: boolean;
|
|
5
|
+
jwtSecret?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface NotificationModuleOptions extends IDynamicModuleConfig {
|
|
8
|
+
bootstrapAppConfig?: IBootstrapAppConfig;
|
|
9
|
+
config?: INotificationModuleConfig;
|
|
10
|
+
}
|
|
11
|
+
export interface NotificationOptionsFactory extends IModuleOptionsFactory<INotificationModuleConfig> {
|
|
12
|
+
createNotificationOptions(): Promise<INotificationModuleConfig> | INotificationModuleConfig;
|
|
13
|
+
}
|
|
14
|
+
export interface NotificationModuleAsyncOptions extends Pick<ModuleMetadata, 'imports'>, IDynamicModuleConfig {
|
|
15
|
+
bootstrapAppConfig: IBootstrapAppConfig;
|
|
16
|
+
useFactory?: (...args: any[]) => Promise<INotificationModuleConfig> | INotificationModuleConfig;
|
|
17
|
+
inject?: any[];
|
|
18
|
+
useClass?: Type<NotificationOptionsFactory>;
|
|
19
|
+
useExisting?: Type<NotificationOptionsFactory>;
|
|
20
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { IIdentity } from '@flusys/nestjs-shared/interfaces';
|
|
2
|
+
export interface INotification extends IIdentity {
|
|
3
|
+
userId: string;
|
|
4
|
+
type: string;
|
|
5
|
+
title: string;
|
|
6
|
+
message: string | null;
|
|
7
|
+
data: Record<string, unknown> | null;
|
|
8
|
+
isRead: boolean;
|
|
9
|
+
readAt: Date | null;
|
|
10
|
+
companyId?: string | null;
|
|
11
|
+
}
|
|
12
|
+
export interface INotificationPayload {
|
|
13
|
+
id: string;
|
|
14
|
+
type: string;
|
|
15
|
+
title: string;
|
|
16
|
+
message: string | null;
|
|
17
|
+
data: Record<string, unknown> | null;
|
|
18
|
+
createdAt: Date;
|
|
19
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './notification.module';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { DynamicModule } from '@nestjs/common';
|
|
2
|
+
import { NotificationModuleAsyncOptions, NotificationModuleOptions } from '../interfaces';
|
|
3
|
+
export declare class NotificationModule {
|
|
4
|
+
static forRoot(options: NotificationModuleOptions): DynamicModule;
|
|
5
|
+
static forRootAsync(options: NotificationModuleAsyncOptions): DynamicModule;
|
|
6
|
+
private static buildProviders;
|
|
7
|
+
private static createAsyncProviders;
|
|
8
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@flusys/nestjs-notification",
|
|
3
|
+
"version": "3.0.1",
|
|
4
|
+
"description": "Notification module with real-time Socket.IO support",
|
|
5
|
+
"main": "cjs/index.js",
|
|
6
|
+
"module": "fesm/index.js",
|
|
7
|
+
"types": "index.d.ts",
|
|
8
|
+
"sideEffects": false,
|
|
9
|
+
"keywords": [
|
|
10
|
+
"nestjs",
|
|
11
|
+
"notification",
|
|
12
|
+
"socket.io",
|
|
13
|
+
"real-time",
|
|
14
|
+
"@flusys"
|
|
15
|
+
],
|
|
16
|
+
"license": "UNLICENSED",
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./index.d.ts",
|
|
20
|
+
"import": "./fesm/index.js",
|
|
21
|
+
"require": "./cjs/index.js"
|
|
22
|
+
},
|
|
23
|
+
"./config": {
|
|
24
|
+
"types": "./config/index.d.ts",
|
|
25
|
+
"import": "./fesm/config/index.js",
|
|
26
|
+
"require": "./cjs/config/index.js"
|
|
27
|
+
},
|
|
28
|
+
"./controllers": {
|
|
29
|
+
"types": "./controllers/index.d.ts",
|
|
30
|
+
"import": "./fesm/controllers/index.js",
|
|
31
|
+
"require": "./cjs/controllers/index.js"
|
|
32
|
+
},
|
|
33
|
+
"./docs": {
|
|
34
|
+
"types": "./docs/index.d.ts",
|
|
35
|
+
"import": "./fesm/docs/index.js",
|
|
36
|
+
"require": "./cjs/docs/index.js"
|
|
37
|
+
},
|
|
38
|
+
"./dtos": {
|
|
39
|
+
"types": "./dtos/index.d.ts",
|
|
40
|
+
"import": "./fesm/dtos/index.js",
|
|
41
|
+
"require": "./cjs/dtos/index.js"
|
|
42
|
+
},
|
|
43
|
+
"./entities": {
|
|
44
|
+
"types": "./entities/index.d.ts",
|
|
45
|
+
"import": "./fesm/entities/index.js",
|
|
46
|
+
"require": "./cjs/entities/index.js"
|
|
47
|
+
},
|
|
48
|
+
"./enums": {
|
|
49
|
+
"types": "./enums/index.d.ts",
|
|
50
|
+
"import": "./fesm/enums/index.js",
|
|
51
|
+
"require": "./cjs/enums/index.js"
|
|
52
|
+
},
|
|
53
|
+
"./gateways": {
|
|
54
|
+
"types": "./gateways/index.d.ts",
|
|
55
|
+
"import": "./fesm/gateways/index.js",
|
|
56
|
+
"require": "./cjs/gateways/index.js"
|
|
57
|
+
},
|
|
58
|
+
"./interfaces": {
|
|
59
|
+
"types": "./interfaces/index.d.ts",
|
|
60
|
+
"import": "./fesm/interfaces/index.js",
|
|
61
|
+
"require": "./cjs/interfaces/index.js"
|
|
62
|
+
},
|
|
63
|
+
"./modules": {
|
|
64
|
+
"types": "./modules/index.d.ts",
|
|
65
|
+
"import": "./fesm/modules/index.js",
|
|
66
|
+
"require": "./cjs/modules/index.js"
|
|
67
|
+
},
|
|
68
|
+
"./services": {
|
|
69
|
+
"types": "./services/index.d.ts",
|
|
70
|
+
"import": "./fesm/services/index.js",
|
|
71
|
+
"require": "./cjs/services/index.js"
|
|
72
|
+
},
|
|
73
|
+
"./*": {
|
|
74
|
+
"types": "./*.d.ts",
|
|
75
|
+
"import": "./*.js",
|
|
76
|
+
"require": "./*.js"
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
"peerDependencies": {
|
|
80
|
+
"@nestjs/common": "^10.0.0 || ^11.0.0",
|
|
81
|
+
"@nestjs/config": "^3.0.0 || ^4.0.0",
|
|
82
|
+
"@nestjs/core": "^10.0.0 || ^11.0.0",
|
|
83
|
+
"@nestjs/platform-express": "^10.0.0 || ^11.0.0",
|
|
84
|
+
"@nestjs/swagger": "^7.0.0 || ^11.0.0",
|
|
85
|
+
"@nestjs/typeorm": "^10.0.0 || ^11.0.0",
|
|
86
|
+
"@nestjs/websockets": "^10.0.0 || ^11.0.0",
|
|
87
|
+
"@nestjs/platform-socket.io": "^10.0.0 || ^11.0.0",
|
|
88
|
+
"class-transformer": "^0.5.0",
|
|
89
|
+
"class-validator": "^0.14.0",
|
|
90
|
+
"socket.io": "^4.0.0",
|
|
91
|
+
"typeorm": "^0.3.0"
|
|
92
|
+
},
|
|
93
|
+
"dependencies": {
|
|
94
|
+
"@flusys/nestjs-core": "3.0.1",
|
|
95
|
+
"@flusys/nestjs-shared": "3.0.1",
|
|
96
|
+
"jsonwebtoken": "^9.0.0"
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { DatabaseMode } from '@flusys/nestjs-core';
|
|
2
|
+
import { IModuleConfigService } from '@flusys/nestjs-shared/interfaces';
|
|
3
|
+
import { NotificationModuleOptions } from '../interfaces';
|
|
4
|
+
export declare class NotificationConfigService implements IModuleConfigService {
|
|
5
|
+
private readonly options;
|
|
6
|
+
constructor(options: NotificationModuleOptions);
|
|
7
|
+
isCompanyFeatureEnabled(): boolean;
|
|
8
|
+
getDatabaseMode(): DatabaseMode;
|
|
9
|
+
isMultiTenant(): boolean;
|
|
10
|
+
isRealtimeEnabled(): boolean;
|
|
11
|
+
getJwtSecret(): string | undefined;
|
|
12
|
+
getOptions(): NotificationModuleOptions;
|
|
13
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { MultiTenantDataSourceService } from '@flusys/nestjs-shared/modules';
|
|
2
|
+
import { IDatabaseConfig, ITenantDatabaseConfig } from '@flusys/nestjs-core';
|
|
3
|
+
import { Logger } from '@nestjs/common';
|
|
4
|
+
import { Request } from 'express';
|
|
5
|
+
import { DataSource } from 'typeorm';
|
|
6
|
+
import { NotificationConfigService } from './notification-config.service';
|
|
7
|
+
export declare class NotificationDataSourceProvider extends MultiTenantDataSourceService {
|
|
8
|
+
private readonly configService;
|
|
9
|
+
protected readonly logger: Logger;
|
|
10
|
+
protected static readonly tenantConnections: Map<string, DataSource>;
|
|
11
|
+
protected static singleDataSource: DataSource | null;
|
|
12
|
+
protected static readonly tenantsRegistry: Map<string, ITenantDatabaseConfig>;
|
|
13
|
+
protected static initialized: boolean;
|
|
14
|
+
protected static readonly connectionLocks: Map<string, Promise<DataSource>>;
|
|
15
|
+
protected static singleConnectionLock: Promise<DataSource> | null;
|
|
16
|
+
constructor(configService: NotificationConfigService, request?: Request);
|
|
17
|
+
private static buildParentOptions;
|
|
18
|
+
getEnableCompanyFeatureForTenant(tenant?: ITenantDatabaseConfig): boolean;
|
|
19
|
+
getNotificationEntities(enableCompanyFeature?: boolean): Promise<any[]>;
|
|
20
|
+
protected createDataSourceFromConfig(config: IDatabaseConfig): Promise<DataSource>;
|
|
21
|
+
protected getSingleDataSource(): Promise<DataSource>;
|
|
22
|
+
protected getOrCreateTenantConnection(tenant: ITenantDatabaseConfig): Promise<DataSource>;
|
|
23
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { HybridCache } from '@flusys/nestjs-shared/classes';
|
|
2
|
+
import { UtilsService } from '@flusys/nestjs-shared/modules';
|
|
3
|
+
import { NotificationConfigService } from './notification-config.service';
|
|
4
|
+
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
|
+
export declare class NotificationHelperService {
|
|
25
|
+
private readonly cacheManager;
|
|
26
|
+
private readonly utilsService;
|
|
27
|
+
private readonly configService;
|
|
28
|
+
private readonly dataSourceProvider;
|
|
29
|
+
private readonly gateway?;
|
|
30
|
+
private readonly logger;
|
|
31
|
+
private readonly entityName;
|
|
32
|
+
private dataSource;
|
|
33
|
+
constructor(cacheManager: HybridCache, utilsService: UtilsService, configService: NotificationConfigService, dataSourceProvider: NotificationDataSourceProvider, gateway?: NotificationGateway);
|
|
34
|
+
private getDataSource;
|
|
35
|
+
private getRepository;
|
|
36
|
+
send(options: SendNotificationOptions): Promise<NotificationBase>;
|
|
37
|
+
sendToMany(options: SendBulkNotificationOptions): Promise<NotificationBase[]>;
|
|
38
|
+
sendToCompany(companyId: string, title: string, message?: string | null, type?: NotificationType | string, data?: Record<string, unknown> | null): Promise<void>;
|
|
39
|
+
isUserOnline(userId: string): boolean;
|
|
40
|
+
getOnlineUserCount(): number;
|
|
41
|
+
private emitRealtime;
|
|
42
|
+
private toPayload;
|
|
43
|
+
private clearCache;
|
|
44
|
+
}
|