- Add WebSocket gateway with workspace-scoped rooms - Define event types: task.created, task.updated, task.deleted - Define event types: event.created, event.updated, event.deleted - Define event types: project.created, project.updated, project.deleted - Add shared WebSocket types for type safety - WebSocketModule already integrated in AppModule
172 lines
4.6 KiB
TypeScript
172 lines
4.6 KiB
TypeScript
import {
|
|
WebSocketGateway as WSGateway,
|
|
WebSocketServer,
|
|
OnGatewayConnection,
|
|
OnGatewayDisconnect,
|
|
} from '@nestjs/websockets';
|
|
import { Logger } from '@nestjs/common';
|
|
import { Server, Socket } from 'socket.io';
|
|
|
|
interface AuthenticatedSocket extends Socket {
|
|
data: {
|
|
userId?: string;
|
|
workspaceId?: string;
|
|
};
|
|
}
|
|
|
|
interface Task {
|
|
id: string;
|
|
workspaceId: string;
|
|
[key: string]: unknown;
|
|
}
|
|
|
|
interface Event {
|
|
id: string;
|
|
workspaceId: string;
|
|
[key: string]: unknown;
|
|
}
|
|
|
|
interface Project {
|
|
id: string;
|
|
workspaceId: string;
|
|
[key: string]: unknown;
|
|
}
|
|
|
|
/**
|
|
* WebSocket Gateway for real-time updates
|
|
* Handles workspace-scoped rooms for broadcasting events
|
|
*/
|
|
@WSGateway({
|
|
cors: {
|
|
origin: process.env.WEB_URL || 'http://localhost:3000',
|
|
credentials: true,
|
|
},
|
|
})
|
|
export class WebSocketGateway implements OnGatewayConnection, OnGatewayDisconnect {
|
|
@WebSocketServer()
|
|
server!: Server;
|
|
|
|
private readonly logger = new Logger(WebSocketGateway.name);
|
|
|
|
/**
|
|
* Handle client connection
|
|
* Joins client to workspace-specific room
|
|
*/
|
|
async handleConnection(client: AuthenticatedSocket): Promise<void> {
|
|
const { userId, workspaceId } = client.data;
|
|
|
|
if (!userId || !workspaceId) {
|
|
this.logger.warn(`Client ${client.id} connected without authentication`);
|
|
client.disconnect();
|
|
return;
|
|
}
|
|
|
|
const room = this.getWorkspaceRoom(workspaceId);
|
|
await client.join(room);
|
|
|
|
this.logger.log(`Client ${client.id} joined room ${room}`);
|
|
}
|
|
|
|
/**
|
|
* Handle client disconnect
|
|
* Leaves workspace room
|
|
*/
|
|
handleDisconnect(client: AuthenticatedSocket): void {
|
|
const { workspaceId } = client.data;
|
|
|
|
if (workspaceId) {
|
|
const room = this.getWorkspaceRoom(workspaceId);
|
|
client.leave(room);
|
|
this.logger.log(`Client ${client.id} left room ${room}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Emit task:created event to workspace room
|
|
*/
|
|
emitTaskCreated(workspaceId: string, task: Task): void {
|
|
const room = this.getWorkspaceRoom(workspaceId);
|
|
this.server.to(room).emit('task:created', task);
|
|
this.logger.debug(`Emitted task:created to ${room}`);
|
|
}
|
|
|
|
/**
|
|
* Emit task:updated event to workspace room
|
|
*/
|
|
emitTaskUpdated(workspaceId: string, task: Task): void {
|
|
const room = this.getWorkspaceRoom(workspaceId);
|
|
this.server.to(room).emit('task:updated', task);
|
|
this.logger.debug(`Emitted task:updated to ${room}`);
|
|
}
|
|
|
|
/**
|
|
* Emit task:deleted event to workspace room
|
|
*/
|
|
emitTaskDeleted(workspaceId: string, taskId: string): void {
|
|
const room = this.getWorkspaceRoom(workspaceId);
|
|
this.server.to(room).emit('task:deleted', { id: taskId });
|
|
this.logger.debug(`Emitted task:deleted to ${room}`);
|
|
}
|
|
|
|
/**
|
|
* Emit event:created event to workspace room
|
|
*/
|
|
emitEventCreated(workspaceId: string, event: Event): void {
|
|
const room = this.getWorkspaceRoom(workspaceId);
|
|
this.server.to(room).emit('event:created', event);
|
|
this.logger.debug(`Emitted event:created to ${room}`);
|
|
}
|
|
|
|
/**
|
|
* Emit event:updated event to workspace room
|
|
*/
|
|
emitEventUpdated(workspaceId: string, event: Event): void {
|
|
const room = this.getWorkspaceRoom(workspaceId);
|
|
this.server.to(room).emit('event:updated', event);
|
|
this.logger.debug(`Emitted event:updated to ${room}`);
|
|
}
|
|
|
|
/**
|
|
* Emit event:deleted event to workspace room
|
|
*/
|
|
emitEventDeleted(workspaceId: string, eventId: string): void {
|
|
const room = this.getWorkspaceRoom(workspaceId);
|
|
this.server.to(room).emit('event:deleted', { id: eventId });
|
|
this.logger.debug(`Emitted event:deleted to ${room}`);
|
|
}
|
|
|
|
/**
|
|
* Emit project:created event to workspace room
|
|
*/
|
|
emitProjectCreated(workspaceId: string, project: Project): void {
|
|
const room = this.getWorkspaceRoom(workspaceId);
|
|
this.server.to(room).emit('project:created', project);
|
|
this.logger.debug(`Emitted project:created to ${room}`);
|
|
}
|
|
|
|
/**
|
|
* Emit project:updated event to workspace room
|
|
*/
|
|
emitProjectUpdated(workspaceId: string, project: Project): void {
|
|
const room = this.getWorkspaceRoom(workspaceId);
|
|
this.server.to(room).emit('project:updated', project);
|
|
this.logger.debug(`Emitted project:updated to ${room}`);
|
|
}
|
|
|
|
/**
|
|
* Emit project:deleted event to workspace room
|
|
*/
|
|
emitProjectDeleted(workspaceId: string, projectId: string): void {
|
|
const room = this.getWorkspaceRoom(workspaceId);
|
|
this.server.to(room).emit('project:deleted', { id: projectId });
|
|
this.logger.debug(`Emitted project:deleted to ${room}`);
|
|
}
|
|
|
|
/**
|
|
* Get workspace room name
|
|
*/
|
|
private getWorkspaceRoom(workspaceId: string): string {
|
|
return `workspace:${workspaceId}`;
|
|
}
|
|
}
|