socket
This commit is contained in:
@@ -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<string, never>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<string, never>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
@@ -12,6 +12,9 @@ import { Logger, UseGuards } from '@nestjs/common';
|
|||||||
import { Pager } from './entities/pager.entity';
|
import { Pager } from './entities/pager.entity';
|
||||||
import { WsAdminAuthGuard, type AuthenticatedSocket } from './guards/ws-admin-auth.guard';
|
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 { PagerService } from './pager.service';
|
||||||
|
import { WsAdminId } from './decorators/ws-admin-id.decorator';
|
||||||
|
import { PagerStatus } from './interface/pager';
|
||||||
|
|
||||||
@WebSocketGateway({
|
@WebSocketGateway({
|
||||||
cors: {
|
cors: {
|
||||||
@@ -26,6 +29,8 @@ export class PagerGateway implements OnGatewayConnection, OnGatewayDisconnect {
|
|||||||
|
|
||||||
private readonly logger = new Logger(PagerGateway.name);
|
private readonly logger = new Logger(PagerGateway.name);
|
||||||
|
|
||||||
|
constructor(private readonly pagerService: PagerService) {}
|
||||||
|
|
||||||
handleConnection(client: Socket) {
|
handleConnection(client: Socket) {
|
||||||
this.logger.log(`Client connected: ${client.id}`);
|
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
|
* Emit pager created event to restaurant room
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user