diff --git a/src/modules/pager/dto/pager-socket-connection.dto.ts b/src/modules/pager/dto/pager-socket-connection.dto.ts new file mode 100644 index 0000000..49ed5c9 --- /dev/null +++ b/src/modules/pager/dto/pager-socket-connection.dto.ts @@ -0,0 +1,208 @@ +import type { PagerStatus } from '../interface/pager'; + +/** + * Socket connection configuration for admin pager module + * Used by frontend to establish WebSocket connection + */ +export interface PagerSocketConnectionConfig { + /** + * WebSocket server URL + * Example: 'http://localhost:3000' or 'https://api.example.com' + */ + serverUrl: string; + + /** + * Socket namespace (default: '/pager') + * The gateway is configured to use '/pager' namespace + */ + namespace?: string; + + /** + * JWT authentication token for admin + * Token should contain adminId and restId in payload + * Can be passed via auth.token, auth.authorization, query.token, or headers.authorization + * Example: { auth: { token: 'your-jwt-token' } } + */ + token: string; + + /** + * Optional: Additional connection options + */ + options?: { + /** + * Enable auto-reconnection + * @default true + */ + autoConnect?: boolean; + + /** + * Connection timeout in milliseconds + * @default 5000 + */ + timeout?: number; + + /** + * Enable transports (websocket, polling) + * @default ['websocket', 'polling'] + */ + transports?: ('websocket' | 'polling')[]; + }; +} + +/** + * Socket event names for pager module + */ +export enum PagerSocketEvents { + // Client to Server Events + JOIN_RESTAURANT = 'join:restaurant', + LEAVE_ROOM = 'leave:room', + GET_PAGERS = 'get:pagers', + UPDATE_PAGER_STATUS = 'update:pager:status', + + // Server to Client Events + JOINED = 'joined', + LEFT = 'left', + PAGER_CREATED = 'pager:created', + PAGER_STATUS_UPDATED = 'pager:status:updated', + PAGERS_LIST = 'pagers:list', + PAGER_STATUS_UPDATED_RESPONSE = 'pager:status:updated:response', + ERROR = 'error', + CONNECT = 'connect', + DISCONNECT = 'disconnect', +} + +/** + * Payload for joining restaurant room + * Note: restId is automatically extracted from JWT token, no need to send it + */ +export type JoinRestaurantPayload = Record; + +/** + * Payload for leaving a room + */ +export interface LeaveRoomPayload { + room: string; +} + +/** + * Payload for getting pagers list + * Note: restId is automatically extracted from JWT token, no need to send it + */ +export type GetPagersPayload = Record; + +/** + * Payload for updating pager status + * Note: restId and adminId are automatically extracted from JWT token + */ +export interface UpdatePagerStatusPayload { + pagerId: string; + status: PagerStatus; +} + +/** + * Response when successfully joined a room + */ +export interface JoinedResponse { + room: string; + message: string; +} + +/** + * Response when successfully left a room + */ +export interface LeftResponse { + room: string; + message: string; +} + +/** + * User information in pager response + */ +export interface PagerUserInfo { + id: string; + firstName: string; + lastName?: string; +} + +/** + * Staff information in pager response + */ +export interface PagerStaffInfo { + id: string; + firstName: string; + lastName?: string; +} + +/** + * Pager data structure emitted by socket events + */ +export interface PagerSocketData { + id: string; + tableNumber: string; + message?: string; + status: PagerStatus; + cookieId?: string; + createdAt?: Date | string; + updatedAt?: Date | string; + user?: PagerUserInfo | null; + staff?: PagerStaffInfo | null; +} + +/** + * Payload for pager:created event + */ +export interface PagerCreatedPayload { + pager: PagerSocketData; +} + +/** + * Payload for pager:status:updated event + */ +export interface PagerStatusUpdatedPayload { + pager: PagerSocketData; +} + +/** + * Payload for pagers:list event + */ +export interface PagersListPayload { + pagers: PagerSocketData[]; +} + +/** + * Payload for pager:status:updated:response event (response to update request) + */ +export interface PagerStatusUpdatedResponsePayload { + pager: PagerSocketData; +} + +/** + * Error response from socket + */ +export interface SocketErrorResponse { + message: string; + code?: string; + details?: unknown; +} + +/** + * Complete socket event map for type safety + */ +export interface PagerSocketEventMap { + // Client emits + [PagerSocketEvents.JOIN_RESTAURANT]: (payload?: JoinRestaurantPayload) => void; + [PagerSocketEvents.LEAVE_ROOM]: (payload: LeaveRoomPayload) => void; + [PagerSocketEvents.GET_PAGERS]: (payload?: GetPagersPayload) => void; + [PagerSocketEvents.UPDATE_PAGER_STATUS]: (payload: UpdatePagerStatusPayload) => void; + + // Server emits + [PagerSocketEvents.JOINED]: (response: JoinedResponse) => void; + [PagerSocketEvents.LEFT]: (response: LeftResponse) => void; + [PagerSocketEvents.PAGER_CREATED]: (payload: PagerCreatedPayload) => void; + [PagerSocketEvents.PAGER_STATUS_UPDATED]: (payload: PagerStatusUpdatedPayload) => void; + [PagerSocketEvents.PAGERS_LIST]: (payload: PagersListPayload) => void; + [PagerSocketEvents.PAGER_STATUS_UPDATED_RESPONSE]: (payload: PagerStatusUpdatedResponsePayload) => void; + [PagerSocketEvents.ERROR]: (error: SocketErrorResponse) => void; + [PagerSocketEvents.CONNECT]: () => void; + [PagerSocketEvents.DISCONNECT]: (reason: string) => void; +} diff --git a/src/modules/pager/pager.gateway.ts b/src/modules/pager/pager.gateway.ts index 6d5351f..60839a5 100644 --- a/src/modules/pager/pager.gateway.ts +++ b/src/modules/pager/pager.gateway.ts @@ -12,6 +12,9 @@ import { Logger, UseGuards } 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 './pager.service'; +import { WsAdminId } from './decorators/ws-admin-id.decorator'; +import { PagerStatus } from './interface/pager'; @WebSocketGateway({ cors: { @@ -26,6 +29,8 @@ export class PagerGateway implements OnGatewayConnection, OnGatewayDisconnect { private readonly logger = new Logger(PagerGateway.name); + constructor(private readonly pagerService: PagerService) {} + handleConnection(client: Socket) { this.logger.log(`Client connected: ${client.id}`); } @@ -78,6 +83,104 @@ export class PagerGateway implements OnGatewayConnection, OnGatewayDisconnect { } } + /** + * 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 */