246 lines
7.8 KiB
TypeScript
246 lines
7.8 KiB
TypeScript
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 { Pager } from './entities/pager.entity';
|
|
import { WsAdminAuthGuard, type AuthenticatedSocket } from './guards/ws-admin-auth.guard';
|
|
import { WsRestId } from './decorators/ws-rest-id.decorator';
|
|
import { PagerService } from './providers/pager.service';
|
|
import { WsAdminId } from './decorators/ws-admin-id.decorator';
|
|
import { PagerStatus } from './interface/pager';
|
|
|
|
@WebSocketGateway({
|
|
cors: {
|
|
origin: true,
|
|
credentials: true,
|
|
},
|
|
namespace: '/pager',
|
|
})
|
|
export class PagerGateway implements OnGatewayConnection, OnGatewayDisconnect {
|
|
@WebSocketServer()
|
|
server!: Server;
|
|
|
|
private readonly logger = new Logger(PagerGateway.name);
|
|
|
|
constructor(
|
|
@Inject(forwardRef(() => PagerService))
|
|
private readonly pagerService: PagerService,
|
|
) {}
|
|
|
|
handleConnection(client: Socket) {
|
|
this.logger.log(`Client connected: ${client.id}`);
|
|
}
|
|
|
|
handleDisconnect(client: Socket) {
|
|
this.logger.log(`Client disconnected: ${client.id}`);
|
|
}
|
|
|
|
/**
|
|
* 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}`);
|
|
}
|
|
}
|