update notif
This commit is contained in:
@@ -1,18 +1,9 @@
|
|||||||
import { IsNotEmpty, IsEnum } from 'class-validator';
|
import { IsNotEmpty, IsEnum, IsArray } from 'class-validator';
|
||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
import { NotifTitleEnum } from '../interfaces/notification.interface';
|
import { NotifTitleEnum } from '../interfaces/notification.interface';
|
||||||
import { NotificationType } from '../interfaces/notification.interface';
|
import { NotifChannelEnum } from '../interfaces/notification.interface';
|
||||||
|
|
||||||
export class CreatePreferenceDto {
|
export class CreatePreferenceDto {
|
||||||
@ApiProperty({
|
|
||||||
description: 'Notification type (SMS, PUSH, BOTH, or NONE)',
|
|
||||||
enum: NotificationType,
|
|
||||||
example: NotificationType.SMS,
|
|
||||||
})
|
|
||||||
@IsEnum(NotificationType)
|
|
||||||
@IsNotEmpty()
|
|
||||||
notificationType!: NotificationType;
|
|
||||||
|
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
description: 'Notification title/type (e.g., order.created, review.created)',
|
description: 'Notification title/type (e.g., order.created, review.created)',
|
||||||
enum: NotifTitleEnum,
|
enum: NotifTitleEnum,
|
||||||
@@ -21,4 +12,13 @@ export class CreatePreferenceDto {
|
|||||||
@IsEnum(NotifTitleEnum)
|
@IsEnum(NotifTitleEnum)
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
title!: NotifTitleEnum;
|
title!: NotifTitleEnum;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'Notification type (SMS, PUSH, BOTH, or NONE)',
|
||||||
|
enum: NotifChannelEnum,
|
||||||
|
example: NotifChannelEnum.SMS,
|
||||||
|
})
|
||||||
|
@IsArray()
|
||||||
|
@IsEnum(NotifChannelEnum, { each: true })
|
||||||
|
channels!: NotifChannelEnum[];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
import { NotificationType } from '../interfaces/notification.interface';
|
import { NotifChannelEnum } from '../interfaces/notification.interface';
|
||||||
import { IsNotEmpty } from 'class-validator';
|
import { IsArray, IsEnum } from 'class-validator';
|
||||||
import { IsEnum } from 'class-validator';
|
|
||||||
|
|
||||||
export class UpdatePreferenceDto {
|
export class UpdatePreferenceDto {
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
description: 'Notification type (SMS, PUSH, BOTH, or NONE)',
|
description: 'Notification type (SMS, PUSH, BOTH, or NONE)',
|
||||||
enum: NotificationType,
|
enum: NotifChannelEnum,
|
||||||
example: NotificationType.SMS,
|
example: NotifChannelEnum.SMS,
|
||||||
})
|
})
|
||||||
@IsEnum(NotificationType)
|
@IsEnum(NotifChannelEnum)
|
||||||
@IsNotEmpty()
|
@IsArray()
|
||||||
notificationType!: NotificationType;
|
@IsEnum(NotifChannelEnum, { each: true })
|
||||||
|
channels!: NotifChannelEnum[];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { Entity, Property, ManyToOne, Unique } from '@mikro-orm/core';
|
|||||||
import { BaseEntity } from '../../../common/entities/base.entity';
|
import { BaseEntity } from '../../../common/entities/base.entity';
|
||||||
import { Restaurant } from '../../restaurants/entities/restaurant.entity';
|
import { Restaurant } from '../../restaurants/entities/restaurant.entity';
|
||||||
import { NotifTitleEnum } from '../interfaces/notification.interface';
|
import { NotifTitleEnum } from '../interfaces/notification.interface';
|
||||||
import { Admin } from 'src/modules/admin/entities/admin.entity';
|
|
||||||
import { NotifChannelEnum } from '../interfaces/notification.interface';
|
import { NotifChannelEnum } from '../interfaces/notification.interface';
|
||||||
|
|
||||||
@Entity({ tableName: 'notification_preferences' })
|
@Entity({ tableName: 'notification_preferences' })
|
||||||
@@ -11,9 +10,6 @@ export class NotificationPreference extends BaseEntity {
|
|||||||
@ManyToOne(() => Restaurant)
|
@ManyToOne(() => Restaurant)
|
||||||
restaurant!: Restaurant;
|
restaurant!: Restaurant;
|
||||||
|
|
||||||
@ManyToOne(() => Admin, { nullable: true })
|
|
||||||
admin?: Admin;
|
|
||||||
|
|
||||||
@Property()
|
@Property()
|
||||||
title!: NotifTitleEnum;
|
title!: NotifTitleEnum;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { NotificationType } from './notification.interface';
|
import type { NotifChannelEnum } from './notification.interface';
|
||||||
import type { NotifTitleEnum } from './notification.interface';
|
import type { NotifTitleEnum } from './notification.interface';
|
||||||
|
|
||||||
export interface NotificationQueueJob {
|
export interface NotificationQueueJob {
|
||||||
@@ -8,7 +8,7 @@ export interface NotificationQueueJob {
|
|||||||
content: string;
|
content: string;
|
||||||
idempotencyKey?: string;
|
idempotencyKey?: string;
|
||||||
notificationId?: string; // For retries
|
notificationId?: string; // For retries
|
||||||
notificationType: NotificationType;
|
channels: NotifChannelEnum[];
|
||||||
pushToken?: string; // FCM token for push notifications
|
pushToken?: string; // FCM token for push notifications
|
||||||
pushTokens?: string[]; // Multiple FCM tokens for bulk push notifications
|
pushTokens?: string[]; // Multiple FCM tokens for bulk push notifications
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,27 @@
|
|||||||
import {
|
import {
|
||||||
WebSocketGateway,
|
WebSocketGateway,
|
||||||
WebSocketServer,
|
WebSocketServer,
|
||||||
SubscribeMessage,
|
// SubscribeMessage,
|
||||||
OnGatewayConnection,
|
OnGatewayConnection,
|
||||||
OnGatewayDisconnect,
|
OnGatewayDisconnect,
|
||||||
MessageBody,
|
// MessageBody,
|
||||||
ConnectedSocket,
|
// ConnectedSocket,
|
||||||
} from '@nestjs/websockets';
|
} from '@nestjs/websockets';
|
||||||
import { Server, Socket } from 'socket.io';
|
import { Server, Socket } from 'socket.io';
|
||||||
import { Logger, UseGuards } from '@nestjs/common';
|
import { Logger, UseGuards, Inject, forwardRef } from '@nestjs/common';
|
||||||
import { WsAdminAuthGuard, type AuthenticatedSocket } from './guards/ws-admin-auth.guard';
|
// 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';
|
||||||
|
|
||||||
|
@UseGuards(WsAdminAuthGuard)
|
||||||
@WebSocketGateway({
|
@WebSocketGateway({
|
||||||
cors: {
|
cors: {
|
||||||
origin: true,
|
origin: true,
|
||||||
credentials: true,
|
credentials: true,
|
||||||
|
connectionGuard: WsAdminAuthGuard,
|
||||||
},
|
},
|
||||||
namespace: '/notifications',
|
namespace: '/notifications',
|
||||||
|
transports: ['websocket', 'polling'],
|
||||||
})
|
})
|
||||||
export class NotificationsGateway implements OnGatewayConnection, OnGatewayDisconnect {
|
export class NotificationsGateway implements OnGatewayConnection, OnGatewayDisconnect {
|
||||||
@WebSocketServer()
|
@WebSocketServer()
|
||||||
@@ -26,9 +29,13 @@ export class NotificationsGateway implements OnGatewayConnection, OnGatewayDisco
|
|||||||
|
|
||||||
private readonly logger = new Logger(NotificationsGateway.name);
|
private readonly logger = new Logger(NotificationsGateway.name);
|
||||||
|
|
||||||
constructor(private readonly notificationService: NotificationService) {}
|
constructor(
|
||||||
|
@Inject(forwardRef(() => NotificationService))
|
||||||
|
private readonly notificationService: NotificationService,
|
||||||
|
) {}
|
||||||
|
|
||||||
handleConnection(client: Socket) {
|
handleConnection(client: AuthenticatedSocket) {
|
||||||
|
console.log(client);
|
||||||
this.logger.log(`Client connected: ${client.id}`);
|
this.logger.log(`Client connected: ${client.id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,34 +43,212 @@ export class NotificationsGateway implements OnGatewayConnection, OnGatewayDisco
|
|||||||
this.logger.log(`Client disconnected: ${client.id}`);
|
this.logger.log(`Client disconnected: ${client.id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sendAdminNotification(client: AuthenticatedSocket, event: string, data: any) {
|
||||||
|
const room = this.getRoom(client);
|
||||||
|
this.server.to(room).emit(event, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private getRoom(client: AuthenticatedSocket) {
|
||||||
|
return `restaurant:${client.restId}-admin:${client.adminId}`;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Get admin restaurant notifications
|
* Join a room for a specific restaurant (admin/staff)
|
||||||
* Requires admin authentication via JWT token
|
* Requires admin authentication via JWT token
|
||||||
* Restaurant ID is extracted from the authenticated admin's token
|
* Restaurant ID is extracted from the authenticated admin's token
|
||||||
* This is a WebSocket substitution for GET /admin/notifications
|
* Room format: `restaurant:${restId}`
|
||||||
*/
|
*/
|
||||||
@UseGuards(WsAdminAuthGuard)
|
// @UseGuards(WsAdminAuthGuard)
|
||||||
@SubscribeMessage('get:notifications')
|
// @SubscribeMessage('join:restaurant')
|
||||||
async handleGetNotifications(
|
// handleJoinRestaurant(@ConnectedSocket() client: AuthenticatedSocket, @WsRestId() restId: string) {
|
||||||
@ConnectedSocket() client: AuthenticatedSocket,
|
// const room = `restaurant:${restId}`;
|
||||||
@WsRestId() restId: string,
|
// void client.join(room);
|
||||||
@MessageBody() data?: { limit?: number },
|
// this.logger.log(`Admin ${client.adminId} (Client ${client.id}) joined room: ${room}`);
|
||||||
) {
|
// void client.emit('joined', { room, message: 'Successfully joined restaurant room' });
|
||||||
try {
|
// }
|
||||||
const limit = data?.limit ? parseInt(data.limit.toString(), 10) : 50;
|
|
||||||
const notifications = await this.notificationService.findByRestaurant(restId, limit);
|
|
||||||
|
|
||||||
void client.emit('notifications:list', { notifications });
|
/**
|
||||||
this.logger.log(`Admin ${client.adminId} requested notifications list for restaurant ${restId}`);
|
* Join a room for a specific cookie/session (public user)
|
||||||
} catch (error) {
|
* Room format: `cookie:${cookieId}`
|
||||||
this.logger.error(
|
*/
|
||||||
`Error getting notifications list: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
// @SubscribeMessage('join:session')
|
||||||
);
|
// handleJoinSession(@ConnectedSocket() client: Socket, @MessageBody() data: { cookieId: string }) {
|
||||||
void client.emit('error', {
|
// if (!data.cookieId) {
|
||||||
message: 'Failed to get notifications list',
|
// void client.emit('error', { message: 'Cookie ID is required' });
|
||||||
code: 'GET_NOTIFICATIONS_ERROR',
|
// return;
|
||||||
details: error instanceof Error ? error.message : 'Unknown error',
|
// }
|
||||||
});
|
|
||||||
}
|
// const room = `cookie:${data.cookieId}`;
|
||||||
}
|
// void client.join(room);
|
||||||
|
// this.logger.log(`Client ${client.id} joined room: ${room}`);
|
||||||
|
// void client.emit('joined', { room, message: 'Successfully joined session room' });
|
||||||
|
// }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Leave a room
|
||||||
|
*/
|
||||||
|
// @SubscribeMessage('leave:room')
|
||||||
|
// handleLeaveRoom(@ConnectedSocket() client: Socket, @MessageBody() data: { room: string }) {
|
||||||
|
// if (data.room) {
|
||||||
|
// void client.leave(data.room);
|
||||||
|
// this.logger.log(`Client ${client.id} left room: ${data.room}`);
|
||||||
|
// void client.emit('left', { room: data.room, message: 'Successfully left room' });
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get list of pagers for the restaurant (admin only)
|
||||||
|
* Requires admin authentication via JWT token
|
||||||
|
* Restaurant ID is extracted from the authenticated admin's token
|
||||||
|
*/
|
||||||
|
// @UseGuards(WsAdminAuthGuard)
|
||||||
|
// @SubscribeMessage('get:pagers')
|
||||||
|
// async handleGetPagers(@ConnectedSocket() client: AuthenticatedSocket, @WsRestId() restId: string) {
|
||||||
|
// try {
|
||||||
|
// const pagers = await this.pagerService.findAllByRestaurantId(restId);
|
||||||
|
|
||||||
|
// const pagersData = pagers.map(pager => ({
|
||||||
|
// id: pager.id,
|
||||||
|
// tableNumber: pager.tableNumber,
|
||||||
|
// message: pager.message,
|
||||||
|
// status: pager.status,
|
||||||
|
// cookieId: pager.cookieId,
|
||||||
|
// createdAt: pager.createdAt,
|
||||||
|
// updatedAt: pager.updatedAt,
|
||||||
|
// user: pager.user
|
||||||
|
// ? {
|
||||||
|
// id: pager.user.id,
|
||||||
|
// firstName: pager.user.firstName,
|
||||||
|
// lastName: pager.user.lastName,
|
||||||
|
// }
|
||||||
|
// : null,
|
||||||
|
// staff: pager.staff
|
||||||
|
// ? {
|
||||||
|
// id: pager.staff.id,
|
||||||
|
// firstName: pager.staff.firstName,
|
||||||
|
// lastName: pager.staff.lastName,
|
||||||
|
// }
|
||||||
|
// : null,
|
||||||
|
// }));
|
||||||
|
|
||||||
|
// void client.emit('pagers:list', { pagers: pagersData });
|
||||||
|
// this.logger.log(`Admin ${client.adminId} requested pagers list for restaurant ${restId}`);
|
||||||
|
// } catch (error) {
|
||||||
|
// this.logger.error(`Error getting pagers list: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
||||||
|
// void client.emit('error', {
|
||||||
|
// message: 'Failed to get pagers list',
|
||||||
|
// code: 'GET_PAGERS_ERROR',
|
||||||
|
// details: error instanceof Error ? error.message : 'Unknown error',
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update pager status (admin only)
|
||||||
|
* Requires admin authentication via JWT token
|
||||||
|
* Restaurant ID and Admin ID are extracted from the authenticated admin's token
|
||||||
|
*/
|
||||||
|
// @UseGuards(WsAdminAuthGuard)
|
||||||
|
// @SubscribeMessage('update:pager:status')
|
||||||
|
// async handleUpdatePagerStatus(
|
||||||
|
// @ConnectedSocket() client: AuthenticatedSocket,
|
||||||
|
// @WsRestId() restId: string,
|
||||||
|
// @WsAdminId() adminId: string,
|
||||||
|
// @MessageBody() data: { pagerId: string; status: PagerStatus },
|
||||||
|
// ) {
|
||||||
|
// try {
|
||||||
|
// if (!data.pagerId || !data.status) {
|
||||||
|
// void client.emit('error', {
|
||||||
|
// message: 'Pager ID and status are required',
|
||||||
|
// code: 'INVALID_PAYLOAD',
|
||||||
|
// });
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const pager = await this.pagerService.updateStatus(data.pagerId, restId, adminId, data.status);
|
||||||
|
|
||||||
|
// const pagerData = {
|
||||||
|
// id: pager.id,
|
||||||
|
// tableNumber: pager.tableNumber,
|
||||||
|
// message: pager.message,
|
||||||
|
// status: pager.status,
|
||||||
|
// updatedAt: pager.updatedAt,
|
||||||
|
// staff: pager.staff
|
||||||
|
// ? {
|
||||||
|
// id: pager.staff.id,
|
||||||
|
// firstName: pager.staff.firstName,
|
||||||
|
// lastName: pager.staff.lastName,
|
||||||
|
// }
|
||||||
|
// : null,
|
||||||
|
// };
|
||||||
|
|
||||||
|
// void client.emit('pager:status:updated:response', { pager: pagerData });
|
||||||
|
// this.logger.log(`Admin ${adminId} updated pager ${data.pagerId} status to ${data.status}`);
|
||||||
|
// } catch (error) {
|
||||||
|
// this.logger.error(`Error updating pager status: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
||||||
|
// void client.emit('error', {
|
||||||
|
// message: error instanceof Error ? error.message : 'Failed to update pager status',
|
||||||
|
// code: 'UPDATE_PAGER_STATUS_ERROR',
|
||||||
|
// details: error instanceof Error ? error.message : 'Unknown error',
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * Emit pager created event to restaurant room
|
||||||
|
// */
|
||||||
|
// emitPagerCreated(pager: Pager) {
|
||||||
|
// const room = `restaurant:${pager.restaurant.id}`;
|
||||||
|
// this.server.to(room).emit('pager:created', {
|
||||||
|
// pager: {
|
||||||
|
// id: pager.id,
|
||||||
|
// tableNumber: pager.tableNumber,
|
||||||
|
// message: pager.message,
|
||||||
|
// status: pager.status,
|
||||||
|
// createdAt: pager.createdAt,
|
||||||
|
// user: pager.user
|
||||||
|
// ? {
|
||||||
|
// id: pager.user.id,
|
||||||
|
// firstName: pager.user.firstName,
|
||||||
|
// lastName: pager.user.lastName,
|
||||||
|
// }
|
||||||
|
// : null,
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
// this.logger.log(`Emitted pager:created to room: ${room}`);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * Emit pager status updated event to both restaurant and cookie rooms
|
||||||
|
// */
|
||||||
|
// emitPagerStatusUpdated(pager: Pager) {
|
||||||
|
// const restaurantRoom = `restaurant:${pager.restaurant.id}`;
|
||||||
|
// const cookieRoom = `cookie:${pager.cookieId}`;
|
||||||
|
|
||||||
|
// const pagerData = {
|
||||||
|
// id: pager.id,
|
||||||
|
// tableNumber: pager.tableNumber,
|
||||||
|
// message: pager.message,
|
||||||
|
// status: pager.status,
|
||||||
|
// updatedAt: pager.updatedAt,
|
||||||
|
// staff: pager.staff
|
||||||
|
// ? {
|
||||||
|
// id: pager.staff.id,
|
||||||
|
// firstName: pager.staff.firstName,
|
||||||
|
// lastName: pager.staff.lastName,
|
||||||
|
// }
|
||||||
|
// : null,
|
||||||
|
// };
|
||||||
|
|
||||||
|
// // Emit to restaurant room (admin/staff)
|
||||||
|
// this.server.to(restaurantRoom).emit('pager:status:updated', {
|
||||||
|
// pager: pagerData,
|
||||||
|
// });
|
||||||
|
// this.logger.log(`Emitted pager:status:updated to room: ${restaurantRoom}`);
|
||||||
|
|
||||||
|
// // Emit to cookie room (public user)
|
||||||
|
// this.server.to(cookieRoom).emit('pager:status:updated', {
|
||||||
|
// pager: pagerData,
|
||||||
|
// });
|
||||||
|
// this.logger.log(`Emitted pager:status:updated to room: ${cookieRoom}`);
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,254 +0,0 @@
|
|||||||
import {
|
|
||||||
WebSocketGateway,
|
|
||||||
WebSocketServer,
|
|
||||||
// SubscribeMessage,
|
|
||||||
OnGatewayConnection,
|
|
||||||
OnGatewayDisconnect,
|
|
||||||
// MessageBody,
|
|
||||||
// ConnectedSocket,
|
|
||||||
} from '@nestjs/websockets';
|
|
||||||
import { Server, Socket } from 'socket.io';
|
|
||||||
import { Logger, UseGuards, Inject, forwardRef } from '@nestjs/common';
|
|
||||||
// import { WsRestId } from './decorators/ws-rest-id.decorator';
|
|
||||||
import { NotificationService } from './services/notification.service';
|
|
||||||
import { WsAdminAuthGuard, type AuthenticatedSocket } from './guards/ws-admin-auth.guard';
|
|
||||||
|
|
||||||
@UseGuards(WsAdminAuthGuard)
|
|
||||||
@WebSocketGateway({
|
|
||||||
cors: {
|
|
||||||
origin: true,
|
|
||||||
credentials: true,
|
|
||||||
connectionGuard: WsAdminAuthGuard,
|
|
||||||
},
|
|
||||||
namespace: '/notifications',
|
|
||||||
transports: ['websocket', 'polling'],
|
|
||||||
})
|
|
||||||
export class NotificationGateway implements OnGatewayConnection, OnGatewayDisconnect {
|
|
||||||
@WebSocketServer()
|
|
||||||
server!: Server;
|
|
||||||
|
|
||||||
private readonly logger = new Logger(NotificationGateway.name);
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
@Inject(forwardRef(() => NotificationService))
|
|
||||||
private readonly notificationService: NotificationService,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
handleConnection(client: AuthenticatedSocket) {
|
|
||||||
console.log(client);
|
|
||||||
this.logger.log(`Client connected: ${client.id}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleDisconnect(client: Socket) {
|
|
||||||
this.logger.log(`Client disconnected: ${client.id}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
sendAdminNotification(client: AuthenticatedSocket, event: string, data: any) {
|
|
||||||
const room = this.getRoom(client);
|
|
||||||
this.server.to(room).emit(event, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
private getRoom(client: AuthenticatedSocket) {
|
|
||||||
return `restaurant:${client.restId}-admin:${client.adminId}`;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Join a room for a specific restaurant (admin/staff)
|
|
||||||
* Requires admin authentication via JWT token
|
|
||||||
* Restaurant ID is extracted from the authenticated admin's token
|
|
||||||
* Room format: `restaurant:${restId}`
|
|
||||||
*/
|
|
||||||
// @UseGuards(WsAdminAuthGuard)
|
|
||||||
// @SubscribeMessage('join:restaurant')
|
|
||||||
// handleJoinRestaurant(@ConnectedSocket() client: AuthenticatedSocket, @WsRestId() restId: string) {
|
|
||||||
// const room = `restaurant:${restId}`;
|
|
||||||
// void client.join(room);
|
|
||||||
// this.logger.log(`Admin ${client.adminId} (Client ${client.id}) joined room: ${room}`);
|
|
||||||
// void client.emit('joined', { room, message: 'Successfully joined restaurant room' });
|
|
||||||
// }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Join a room for a specific cookie/session (public user)
|
|
||||||
* Room format: `cookie:${cookieId}`
|
|
||||||
*/
|
|
||||||
// @SubscribeMessage('join:session')
|
|
||||||
// handleJoinSession(@ConnectedSocket() client: Socket, @MessageBody() data: { cookieId: string }) {
|
|
||||||
// if (!data.cookieId) {
|
|
||||||
// void client.emit('error', { message: 'Cookie ID is required' });
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const room = `cookie:${data.cookieId}`;
|
|
||||||
// void client.join(room);
|
|
||||||
// this.logger.log(`Client ${client.id} joined room: ${room}`);
|
|
||||||
// void client.emit('joined', { room, message: 'Successfully joined session room' });
|
|
||||||
// }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Leave a room
|
|
||||||
*/
|
|
||||||
// @SubscribeMessage('leave:room')
|
|
||||||
// handleLeaveRoom(@ConnectedSocket() client: Socket, @MessageBody() data: { room: string }) {
|
|
||||||
// if (data.room) {
|
|
||||||
// void client.leave(data.room);
|
|
||||||
// this.logger.log(`Client ${client.id} left room: ${data.room}`);
|
|
||||||
// void client.emit('left', { room: data.room, message: 'Successfully left room' });
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get list of pagers for the restaurant (admin only)
|
|
||||||
* Requires admin authentication via JWT token
|
|
||||||
* Restaurant ID is extracted from the authenticated admin's token
|
|
||||||
*/
|
|
||||||
// @UseGuards(WsAdminAuthGuard)
|
|
||||||
// @SubscribeMessage('get:pagers')
|
|
||||||
// async handleGetPagers(@ConnectedSocket() client: AuthenticatedSocket, @WsRestId() restId: string) {
|
|
||||||
// try {
|
|
||||||
// const pagers = await this.pagerService.findAllByRestaurantId(restId);
|
|
||||||
|
|
||||||
// const pagersData = pagers.map(pager => ({
|
|
||||||
// id: pager.id,
|
|
||||||
// tableNumber: pager.tableNumber,
|
|
||||||
// message: pager.message,
|
|
||||||
// status: pager.status,
|
|
||||||
// cookieId: pager.cookieId,
|
|
||||||
// createdAt: pager.createdAt,
|
|
||||||
// updatedAt: pager.updatedAt,
|
|
||||||
// user: pager.user
|
|
||||||
// ? {
|
|
||||||
// id: pager.user.id,
|
|
||||||
// firstName: pager.user.firstName,
|
|
||||||
// lastName: pager.user.lastName,
|
|
||||||
// }
|
|
||||||
// : null,
|
|
||||||
// staff: pager.staff
|
|
||||||
// ? {
|
|
||||||
// id: pager.staff.id,
|
|
||||||
// firstName: pager.staff.firstName,
|
|
||||||
// lastName: pager.staff.lastName,
|
|
||||||
// }
|
|
||||||
// : null,
|
|
||||||
// }));
|
|
||||||
|
|
||||||
// void client.emit('pagers:list', { pagers: pagersData });
|
|
||||||
// this.logger.log(`Admin ${client.adminId} requested pagers list for restaurant ${restId}`);
|
|
||||||
// } catch (error) {
|
|
||||||
// this.logger.error(`Error getting pagers list: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
||||||
// void client.emit('error', {
|
|
||||||
// message: 'Failed to get pagers list',
|
|
||||||
// code: 'GET_PAGERS_ERROR',
|
|
||||||
// details: error instanceof Error ? error.message : 'Unknown error',
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update pager status (admin only)
|
|
||||||
* Requires admin authentication via JWT token
|
|
||||||
* Restaurant ID and Admin ID are extracted from the authenticated admin's token
|
|
||||||
*/
|
|
||||||
// @UseGuards(WsAdminAuthGuard)
|
|
||||||
// @SubscribeMessage('update:pager:status')
|
|
||||||
// async handleUpdatePagerStatus(
|
|
||||||
// @ConnectedSocket() client: AuthenticatedSocket,
|
|
||||||
// @WsRestId() restId: string,
|
|
||||||
// @WsAdminId() adminId: string,
|
|
||||||
// @MessageBody() data: { pagerId: string; status: PagerStatus },
|
|
||||||
// ) {
|
|
||||||
// try {
|
|
||||||
// if (!data.pagerId || !data.status) {
|
|
||||||
// void client.emit('error', {
|
|
||||||
// message: 'Pager ID and status are required',
|
|
||||||
// code: 'INVALID_PAYLOAD',
|
|
||||||
// });
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const pager = await this.pagerService.updateStatus(data.pagerId, restId, adminId, data.status);
|
|
||||||
|
|
||||||
// const pagerData = {
|
|
||||||
// id: pager.id,
|
|
||||||
// tableNumber: pager.tableNumber,
|
|
||||||
// message: pager.message,
|
|
||||||
// status: pager.status,
|
|
||||||
// updatedAt: pager.updatedAt,
|
|
||||||
// staff: pager.staff
|
|
||||||
// ? {
|
|
||||||
// id: pager.staff.id,
|
|
||||||
// firstName: pager.staff.firstName,
|
|
||||||
// lastName: pager.staff.lastName,
|
|
||||||
// }
|
|
||||||
// : null,
|
|
||||||
// };
|
|
||||||
|
|
||||||
// void client.emit('pager:status:updated:response', { pager: pagerData });
|
|
||||||
// this.logger.log(`Admin ${adminId} updated pager ${data.pagerId} status to ${data.status}`);
|
|
||||||
// } catch (error) {
|
|
||||||
// this.logger.error(`Error updating pager status: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
||||||
// void client.emit('error', {
|
|
||||||
// message: error instanceof Error ? error.message : 'Failed to update pager status',
|
|
||||||
// code: 'UPDATE_PAGER_STATUS_ERROR',
|
|
||||||
// details: error instanceof Error ? error.message : 'Unknown error',
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * Emit pager created event to restaurant room
|
|
||||||
// */
|
|
||||||
// emitPagerCreated(pager: Pager) {
|
|
||||||
// const room = `restaurant:${pager.restaurant.id}`;
|
|
||||||
// this.server.to(room).emit('pager:created', {
|
|
||||||
// pager: {
|
|
||||||
// id: pager.id,
|
|
||||||
// tableNumber: pager.tableNumber,
|
|
||||||
// message: pager.message,
|
|
||||||
// status: pager.status,
|
|
||||||
// createdAt: pager.createdAt,
|
|
||||||
// user: pager.user
|
|
||||||
// ? {
|
|
||||||
// id: pager.user.id,
|
|
||||||
// firstName: pager.user.firstName,
|
|
||||||
// lastName: pager.user.lastName,
|
|
||||||
// }
|
|
||||||
// : null,
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
// this.logger.log(`Emitted pager:created to room: ${room}`);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * Emit pager status updated event to both restaurant and cookie rooms
|
|
||||||
// */
|
|
||||||
// emitPagerStatusUpdated(pager: Pager) {
|
|
||||||
// const restaurantRoom = `restaurant:${pager.restaurant.id}`;
|
|
||||||
// const cookieRoom = `cookie:${pager.cookieId}`;
|
|
||||||
|
|
||||||
// const pagerData = {
|
|
||||||
// id: pager.id,
|
|
||||||
// tableNumber: pager.tableNumber,
|
|
||||||
// message: pager.message,
|
|
||||||
// status: pager.status,
|
|
||||||
// updatedAt: pager.updatedAt,
|
|
||||||
// staff: pager.staff
|
|
||||||
// ? {
|
|
||||||
// id: pager.staff.id,
|
|
||||||
// firstName: pager.staff.firstName,
|
|
||||||
// lastName: pager.staff.lastName,
|
|
||||||
// }
|
|
||||||
// : null,
|
|
||||||
// };
|
|
||||||
|
|
||||||
// // Emit to restaurant room (admin/staff)
|
|
||||||
// this.server.to(restaurantRoom).emit('pager:status:updated', {
|
|
||||||
// pager: pagerData,
|
|
||||||
// });
|
|
||||||
// this.logger.log(`Emitted pager:status:updated to room: ${restaurantRoom}`);
|
|
||||||
|
|
||||||
// // Emit to cookie room (public user)
|
|
||||||
// this.server.to(cookieRoom).emit('pager:status:updated', {
|
|
||||||
// pager: pagerData,
|
|
||||||
// });
|
|
||||||
// this.logger.log(`Emitted pager:status:updated to room: ${cookieRoom}`);
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
@@ -18,7 +18,7 @@ export class NotificationPreferenceService {
|
|||||||
|
|
||||||
const preference = this.em.create(NotificationPreference, {
|
const preference = this.em.create(NotificationPreference, {
|
||||||
restaurant,
|
restaurant,
|
||||||
notificationType: dto.notificationType,
|
channels: dto.channels,
|
||||||
title: dto.title,
|
title: dto.title,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,7 @@ import { NotificationQueueService } from './notification-queue.service';
|
|||||||
import { NotificationQueueJob } from '../interfaces/notification-queue.interface';
|
import { NotificationQueueJob } from '../interfaces/notification-queue.interface';
|
||||||
import { Restaurant } from '../../restaurants/entities/restaurant.entity';
|
import { Restaurant } from '../../restaurants/entities/restaurant.entity';
|
||||||
import { User } from '../../users/entities/user.entity';
|
import { User } from '../../users/entities/user.entity';
|
||||||
import { NotificationType } from '../interfaces/notification.interface';
|
import { NotifTitleEnum } from '../interfaces/notification.interface';
|
||||||
import { NotifTitleEnum } from '../interfaces/notification.interface';
|
|
||||||
|
|
||||||
export interface SendNotificationParams {
|
export interface SendNotificationParams {
|
||||||
restaurantId: string;
|
restaurantId: string;
|
||||||
@@ -28,7 +27,7 @@ export class NotificationService {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
async sendNotification(params: SendNotificationParams): Promise<Notification> {
|
async sendNotification(params: SendNotificationParams): Promise<Notification> {
|
||||||
const { restaurantId, userId, title, content, idempotencyKey } = params;
|
const { restaurantId, userId, title, content } = params;
|
||||||
|
|
||||||
// Verify restaurant exists
|
// Verify restaurant exists
|
||||||
const restaurant = await this.em.findOne(Restaurant, { id: restaurantId });
|
const restaurant = await this.em.findOne(Restaurant, { id: restaurantId });
|
||||||
@@ -59,7 +58,7 @@ export class NotificationService {
|
|||||||
|
|
||||||
await this.em.persistAndFlush(notification);
|
await this.em.persistAndFlush(notification);
|
||||||
|
|
||||||
if (preference.notificationType === NotificationType.NONE) {
|
if (preference.channels.length === 0) {
|
||||||
this.logger.warn(`Notification type is NONE for restaurant ${restaurantId}, title ${title}`);
|
this.logger.warn(`Notification type is NONE for restaurant ${restaurantId}, title ${title}`);
|
||||||
return notification;
|
return notification;
|
||||||
}
|
}
|
||||||
@@ -71,7 +70,7 @@ export class NotificationService {
|
|||||||
content,
|
content,
|
||||||
// idempotencyKey: finalIdempotencyKey,
|
// idempotencyKey: finalIdempotencyKey,
|
||||||
notificationId: notification.id,
|
notificationId: notification.id,
|
||||||
notificationType: preference.notificationType,
|
channels: preference.channels,
|
||||||
};
|
};
|
||||||
// if (preference.notificationType === NotificationType.SMS) {
|
// if (preference.notificationType === NotificationType.SMS) {
|
||||||
// await this.queueService.addSmsNotification(job);
|
// await this.queueService.addSmsNotification(job);
|
||||||
|
|||||||
@@ -1,26 +1,25 @@
|
|||||||
import { NotifTitleEnum } from '../../modules/notifications/interfaces/notification.interface';
|
import { NotifChannelEnum, NotifTitleEnum } from '../../modules/notifications/interfaces/notification.interface';
|
||||||
import { NotificationType } from '../../modules/notifications/interfaces/notification.interface';
|
|
||||||
|
|
||||||
export interface NotificationPreferenceData {
|
export interface NotificationPreferenceData {
|
||||||
title: NotifTitleEnum;
|
title: NotifTitleEnum;
|
||||||
notificationType: NotificationType;
|
channels: NotifChannelEnum[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const notificationPreferencesData: NotificationPreferenceData[] = [
|
export const notificationPreferencesData: NotificationPreferenceData[] = [
|
||||||
{
|
{
|
||||||
title: NotifTitleEnum.ORDER_CREATED,
|
title: NotifTitleEnum.ORDER_CREATED,
|
||||||
notificationType: NotificationType.SMS,
|
channels: [NotifChannelEnum.SMS, NotifChannelEnum.PUSH, NotifChannelEnum.IN_APP],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: NotifTitleEnum.PAYMENT_SUCCESS,
|
title: NotifTitleEnum.PAYMENT_SUCCESS,
|
||||||
notificationType: NotificationType.SMS,
|
channels: [NotifChannelEnum.SMS, NotifChannelEnum.PUSH, NotifChannelEnum.IN_APP],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: NotifTitleEnum.REVIEW_CREATED,
|
title: NotifTitleEnum.REVIEW_CREATED,
|
||||||
notificationType: NotificationType.PUSH,
|
channels: [NotifChannelEnum.SMS, NotifChannelEnum.PUSH, NotifChannelEnum.IN_APP],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: NotifTitleEnum.ORDER_STATUS_CHANGED,
|
title: NotifTitleEnum.ORDER_STATUS_CHANGED,
|
||||||
notificationType: NotificationType.Both,
|
channels: [NotifChannelEnum.SMS, NotifChannelEnum.PUSH, NotifChannelEnum.IN_APP],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { EntityManager } from '@mikro-orm/core';
|
import type { EntityManager } from '@mikro-orm/core';
|
||||||
import { NotificationPreference } from '../modules/notifications/entities/notification-preference.entity';
|
import { NotificationPreference } from '../modules/notifications/entities/notification-preference.entity';
|
||||||
import { Restaurant } from '../modules/restaurants/entities/restaurant.entity';
|
import type { Restaurant } from '../modules/restaurants/entities/restaurant.entity';
|
||||||
import { notificationPreferencesData } from './data/notification-preferences.data';
|
import { notificationPreferencesData } from './data/notification-preferences.data';
|
||||||
|
|
||||||
export class NotificationPreferencesSeeder {
|
export class NotificationPreferencesSeeder {
|
||||||
@@ -18,13 +18,13 @@ export class NotificationPreferencesSeeder {
|
|||||||
const preference = em.create(NotificationPreference, {
|
const preference = em.create(NotificationPreference, {
|
||||||
restaurant,
|
restaurant,
|
||||||
title: preferenceData.title,
|
title: preferenceData.title,
|
||||||
notificationType: preferenceData.notificationType,
|
channels: preferenceData.channels,
|
||||||
});
|
});
|
||||||
em.persist(preference);
|
em.persist(preference);
|
||||||
} else {
|
} else {
|
||||||
// Update existing preference if notification type changed
|
// Update existing preference if notification type changed
|
||||||
if (existing.notificationType !== preferenceData.notificationType) {
|
if (existing.channels.length !== preferenceData.channels.length) {
|
||||||
existing.notificationType = preferenceData.notificationType;
|
existing.channels = preferenceData.channels;
|
||||||
em.persist(existing);
|
em.persist(existing);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user