This commit is contained in:
2025-12-12 23:14:48 +03:30
parent 0434329db6
commit 6094878e0e
6 changed files with 70 additions and 49 deletions
@@ -53,18 +53,11 @@ interface INotifySms {
phone: string; phone: string;
subject: NotifTitleEnum; subject: NotifTitleEnum;
} }
// Sub intefaces export interface IInAppNotificationPayload {
// 1. Order Created Sms Notify Payload subject: NotifTitleEnum;
interface IOrderCreatedSmsNotifyPayload extends INotifySmsPayload { body: string;
orderNumber: string;
orderAmount: number;
orderDate: Date;
} }
interface IOrderCreatedSmsNotify extends INotifySms {
subject: NotifTitleEnum.ORDER_CREATED;
payload: IOrderCreatedSmsNotifyPayload;
}
// 2. Payment Success // 2. Payment Success
interface IPaymentSuccessSmsNotifyPayload extends INotifySmsPayload { interface IPaymentSuccessSmsNotifyPayload extends INotifySmsPayload {
amount: number; amount: number;
@@ -76,4 +69,4 @@ interface IPaymentSuccessSmsNotify extends INotifySms {
payload: IPaymentSuccessSmsNotifyPayload; payload: IPaymentSuccessSmsNotifyPayload;
} }
export type ISmsNotifyPayload = IOrderCreatedSmsNotify | IPaymentSuccessSmsNotify; export type ISmsNotifyPayload = IPaymentSuccessSmsNotify;
@@ -12,14 +12,15 @@ import { Logger, UseGuards, Inject, forwardRef } from '@nestjs/common';
// import { WsRestId } from './decorators/ws-rest-id.decorator'; // import { WsRestId } from './decorators/ws-rest-id.decorator';
import { NotificationService } from './services/notification.service'; import { NotificationService } from './services/notification.service';
import { WsAdminAuthGuard, type AuthenticatedSocket } from './guards/ws-admin-auth.guard'; import { WsAdminAuthGuard, type AuthenticatedSocket } from './guards/ws-admin-auth.guard';
import { ModuleRef } from '@nestjs/core';
import { ExecutionContext } from '@nestjs/common';
import { IInAppNotificationPayload } from './interfaces/notification.interface';
@UseGuards(WsAdminAuthGuard)
@WebSocketGateway({ @WebSocketGateway({
cors: { cors: {
origin: true, origin: true,
credentials: true, credentials: true,
}, },
// connectionGuard: WsAdminAuthGuard,
namespace: '/notifications', namespace: '/notifications',
transports: ['websocket', 'polling'], transports: ['websocket', 'polling'],
}) })
@@ -32,21 +33,31 @@ export class NotificationsGateway implements OnGatewayConnection, OnGatewayDisco
constructor( constructor(
@Inject(forwardRef(() => NotificationService)) @Inject(forwardRef(() => NotificationService))
private readonly notificationService: NotificationService, private readonly notificationService: NotificationService,
private readonly moduleRef: ModuleRef,
) {} ) {}
handleConnection(client: AuthenticatedSocket) { async handleConnection(client: Socket) {
this.logger.log(`Admin connected: ${client.adminId}`); try {
this.handleJoinRoom(client); await this.authenticateConnection(client);
const authenticatedClient = client as AuthenticatedSocket;
this.logger.log(`Admin connected: ${authenticatedClient.adminId}`);
this.handleJoinRoom(authenticatedClient);
} catch (error) {
this.logger.error(
`Connection authentication failed: ${error instanceof Error ? error.message : 'Unknown error'}`,
);
client.disconnect();
}
} }
handleDisconnect(client: Socket) { handleDisconnect(client: Socket) {
this.logger.log(`Client disconnected: ${client.id}`); this.logger.log(`Client disconnected: ${client.id}`);
} }
sendInAppNotification(repipient: { adminId: string; restaurantId: string }, event: string, data: any) { sendInAppNotification(repipient: { adminId: string; restaurantId: string }, payload: IInAppNotificationPayload) {
this.logger.log(`Sending in app notification to admin: ${repipient.adminId}`); this.logger.log(`Sending in app notification to admin: ${repipient.adminId}`);
const room = this.getRoom(repipient.adminId, repipient.restaurantId); const room = this.getRoom(repipient.adminId, repipient.restaurantId);
this.server.to(room).emit(event, data); this.server.to(room).emit('notifications', payload);
this.logger.log(`In app notification sent to admin: ${repipient.adminId}`); this.logger.log(`In app notification sent to admin: ${repipient.adminId}`);
} }
@@ -251,4 +262,18 @@ export class NotificationsGateway implements OnGatewayConnection, OnGatewayDisco
// }); // });
// this.logger.log(`Emitted pager:status:updated to room: ${cookieRoom}`); // this.logger.log(`Emitted pager:status:updated to room: ${cookieRoom}`);
// } // }
private async authenticateConnection(client: Socket): Promise<void> {
const guard = this.moduleRef.get(WsAdminAuthGuard);
const context = {
switchToWs: () => ({ getClient: () => client }),
getClass: () => NotificationsGateway,
getHandler: () => this.handleConnection,
} as unknown as ExecutionContext;
const canActivate = await guard.canActivate(context);
if (!canActivate) {
throw new Error('Authentication failed');
}
}
} }
@@ -44,30 +44,30 @@ export class SmsProcessor extends WorkerHost {
// Send SMS notification // Send SMS notification
let smsResult: ISmsResponse; let smsResult: ISmsResponse;
if (title === NotifTitleEnum.ORDER_CREATED) { // if (title === NotifTitleEnum.ORDER_CREATED) {
smsResult = await this.smsAdaptorService.sendNotifySms({ // smsResult = await this.smsAdaptorService.sendNotifySms({
phone: user.phone, // phone: user.phone,
subject: NotifTitleEnum.ORDER_CREATED, // subject: NotifTitleEnum.ORDER_CREATED,
payload: { // payload: {
orderNumber: '1234567890', // orderNumber: '1234567890',
orderAmount: 100000, // orderAmount: 100000,
orderDate: new Date(), // orderDate: new Date(),
}, // },
}); // });
} else { // } else {
smsResult = await this.smsAdaptorService.sendNotifySms({ // smsResult = await this.smsAdaptorService.sendNotifySms({
phone: user.phone, // phone: user.phone,
subject: NotifTitleEnum.PAYMENT_SUCCESS, // subject: NotifTitleEnum.PAYMENT_SUCCESS,
payload: { // payload: {
amount: 100000, // amount: 100000,
date: new Date(), // date: new Date(),
}, // },
}); // });
} // }
if (smsResult.status !== 1) { // if (smsResult.status !== 1) {
throw new Error(smsResult.message ?? 'Failed to send SMS notification'); // throw new Error(smsResult.message ?? 'Failed to send SMS notification');
} // }
// Mark notification as sent // Mark notification as sent
@@ -46,10 +46,13 @@ export class NotificationService {
if (preference?.channels?.includes(NotifChannelEnum.IN_APP)) { if (preference?.channels?.includes(NotifChannelEnum.IN_APP)) {
recipients.forEach(recipient => { recipients.forEach(recipient => {
if ('adminId' in recipient) { if ('adminId' in recipient) {
this.notificationGateway.sendInAppNotification({ adminId: recipient.adminId, restaurantId }, 'notification', { this.notificationGateway.sendInAppNotification(
title: message.subject, { adminId: recipient.adminId, restaurantId },
content: message.body, {
}); subject: message.subject,
body: message.body,
},
);
} }
}); });
} }
@@ -20,9 +20,9 @@ export class SmsAdaptorService {
const { phone, subject, payload } = params; const { phone, subject, payload } = params;
let templateId!: string; let templateId!: string;
switch (subject) { switch (subject) {
case NotifTitleEnum.ORDER_CREATED: // case NotifTitleEnum.ORDER_CREATED:
templateId = this.smsPatternOrderCreated; // templateId = this.smsPatternOrderCreated;
break; // break;
case NotifTitleEnum.PAYMENT_SUCCESS: case NotifTitleEnum.PAYMENT_SUCCESS:
templateId = this.smsPatternPaymentSuccess; templateId = this.smsPatternPaymentSuccess;
break; break;
+1 -1
View File
@@ -370,7 +370,7 @@
addEvent('ERROR', `Connection error: ${error.message}`); addEvent('ERROR', `Connection error: ${error.message}`);
}); });
socket.on('notifications:list', data => { socket.on('notifications', data => {
addEvent('SUCCESS', `Received ${data.notifications?.length || 0} notifications`, data); addEvent('SUCCESS', `Received ${data.notifications?.length || 0} notifications`, data);
displayNotifications(data.notifications || []); displayNotifications(data.notifications || []);
}); });