/** * Event Controller * * API endpoints for event subscriptions and publishing. */ import { Controller, Get, Post, UseGuards, Logger, Req, Body, Param, Query } from "@nestjs/common"; import { EventService } from "./event.service"; import { AuthGuard } from "../auth/guards/auth.guard"; import { FederationMessageStatus } from "@prisma/client"; import type { AuthenticatedRequest } from "../common/types/user.types"; import type { EventMessage, EventAck, EventMessageDetails, SubscriptionDetails, } from "./types/message.types"; import { SubscribeToEventDto, UnsubscribeFromEventDto, PublishEventDto, IncomingEventDto, IncomingEventAckDto, } from "./dto/event.dto"; @Controller("v1/federation") export class EventController { private readonly logger = new Logger(EventController.name); constructor(private readonly eventService: EventService) {} /** * Subscribe to an event type from a remote instance * Requires authentication */ @Post("events/subscribe") @UseGuards(AuthGuard) async subscribeToEvent( @Req() req: AuthenticatedRequest, @Body() dto: SubscribeToEventDto ): Promise { if (!req.user?.workspaceId) { throw new Error("Workspace ID not found in request"); } this.logger.log( `User ${req.user.id} subscribing to event type ${dto.eventType} on connection ${dto.connectionId}` ); return this.eventService.subscribeToEventType( req.user.workspaceId, dto.connectionId, dto.eventType, dto.metadata ); } /** * Unsubscribe from an event type * Requires authentication */ @Post("events/unsubscribe") @UseGuards(AuthGuard) async unsubscribeFromEvent( @Req() req: AuthenticatedRequest, @Body() dto: UnsubscribeFromEventDto ): Promise<{ status: string }> { if (!req.user?.workspaceId) { throw new Error("Workspace ID not found in request"); } this.logger.log( `User ${req.user.id} unsubscribing from event type ${dto.eventType} on connection ${dto.connectionId}` ); await this.eventService.unsubscribeFromEventType( req.user.workspaceId, dto.connectionId, dto.eventType ); return { status: "unsubscribed" }; } /** * Publish an event to subscribed instances * Requires authentication */ @Post("events/publish") @UseGuards(AuthGuard) async publishEvent( @Req() req: AuthenticatedRequest, @Body() dto: PublishEventDto ): Promise { if (!req.user?.workspaceId) { throw new Error("Workspace ID not found in request"); } this.logger.log(`User ${req.user.id} publishing event type ${dto.eventType}`); return this.eventService.publishEvent(req.user.workspaceId, dto.eventType, dto.payload); } /** * Get all event subscriptions for the workspace * Requires authentication */ @Get("events/subscriptions") @UseGuards(AuthGuard) async getSubscriptions( @Req() req: AuthenticatedRequest, @Query("connectionId") connectionId?: string ): Promise { if (!req.user?.workspaceId) { throw new Error("Workspace ID not found in request"); } return this.eventService.getEventSubscriptions(req.user.workspaceId, connectionId); } /** * Get all event messages for the workspace * Requires authentication */ @Get("events/messages") @UseGuards(AuthGuard) async getEventMessages( @Req() req: AuthenticatedRequest, @Query("status") status?: FederationMessageStatus ): Promise { if (!req.user?.workspaceId) { throw new Error("Workspace ID not found in request"); } return this.eventService.getEventMessages(req.user.workspaceId, status); } /** * Get a single event message * Requires authentication */ @Get("events/messages/:id") @UseGuards(AuthGuard) async getEventMessage( @Req() req: AuthenticatedRequest, @Param("id") messageId: string ): Promise { if (!req.user?.workspaceId) { throw new Error("Workspace ID not found in request"); } return this.eventService.getEventMessage(req.user.workspaceId, messageId); } /** * Handle incoming event from remote instance * Public endpoint - no authentication required (signature-based verification) */ @Post("incoming/event") async handleIncomingEvent(@Body() dto: IncomingEventDto): Promise { this.logger.log(`Received event from ${dto.instanceId}: ${dto.messageId}`); const eventMessage: EventMessage = { messageId: dto.messageId, instanceId: dto.instanceId, eventType: dto.eventType, payload: dto.payload, timestamp: dto.timestamp, signature: dto.signature, }; return this.eventService.handleIncomingEvent(eventMessage); } /** * Handle incoming event acknowledgment from remote instance * Public endpoint - no authentication required (signature-based verification) */ @Post("incoming/event/ack") async handleIncomingEventAck(@Body() dto: IncomingEventAckDto): Promise<{ status: string }> { this.logger.log(`Received acknowledgment for event: ${dto.correlationId}`); const ack: EventAck = { messageId: dto.messageId, correlationId: dto.correlationId, instanceId: dto.instanceId, received: dto.received, ...(dto.error !== undefined ? { error: dto.error } : {}), timestamp: dto.timestamp, signature: dto.signature, }; await this.eventService.processEventAck(ack); return { status: "acknowledged" }; } }