/** * Webhooks Controller * * REST API endpoints for managing webhooks */ import { Controller, Get, Post, Patch, Delete, Body, Param, UseGuards, NotFoundException, ForbiddenException, } from '@nestjs/common'; import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth, } from '@nestjs/swagger'; import { WebhookService, CreateWebhookInput, UpdateWebhookInput } from '../services/webhook.service'; import { JwtAuthGuard } from '../guards/jwt-auth.guard'; import { RolesGuard } from '../guards/roles.guard'; import { Roles } from '../decorators/roles.decorator'; import { CurrentUser, UserPayload } from '../decorators/current-user.decorator'; import { Webhook, WebhookEvent } from '../../domain/entities/webhook.entity'; class CreateWebhookDto { url: string; events: WebhookEvent[]; description?: string; headers?: Record; } class UpdateWebhookDto { url?: string; events?: WebhookEvent[]; description?: string; headers?: Record; } class WebhookResponseDto { id: string; url: string; events: WebhookEvent[]; status: string; description?: string; headers?: Record; retryCount: number; lastTriggeredAt?: string; failureCount: number; createdAt: string; updatedAt: string; } @ApiTags('Webhooks') @ApiBearerAuth() @Controller('api/v1/webhooks') @UseGuards(JwtAuthGuard, RolesGuard) export class WebhooksController { constructor(private readonly webhookService: WebhookService) {} /** * Create a new webhook * Only admins and managers can create webhooks */ @Post() @Roles('admin', 'manager') @ApiOperation({ summary: 'Create a new webhook' }) @ApiResponse({ status: 201, description: 'Webhook created successfully' }) async createWebhook( @Body() dto: CreateWebhookDto, @CurrentUser() user: UserPayload, ): Promise { const input: CreateWebhookInput = { organizationId: user.organizationId, url: dto.url, events: dto.events, description: dto.description, headers: dto.headers, }; const webhook = await this.webhookService.createWebhook(input); return this.mapToDto(webhook); } /** * Get all webhooks for organization */ @Get() @Roles('admin', 'manager') @ApiOperation({ summary: 'Get all webhooks for organization' }) @ApiResponse({ status: 200, description: 'Webhooks retrieved successfully' }) async getWebhooks(@CurrentUser() user: UserPayload): Promise { const webhooks = await this.webhookService.getWebhooksByOrganization( user.organizationId, ); return webhooks.map((w) => this.mapToDto(w)); } /** * Get webhook by ID */ @Get(':id') @Roles('admin', 'manager') @ApiOperation({ summary: 'Get webhook by ID' }) @ApiResponse({ status: 200, description: 'Webhook retrieved successfully' }) @ApiResponse({ status: 404, description: 'Webhook not found' }) async getWebhookById( @Param('id') id: string, @CurrentUser() user: UserPayload, ): Promise { const webhook = await this.webhookService.getWebhookById(id); if (!webhook) { throw new NotFoundException('Webhook not found'); } // Verify webhook belongs to user's organization if (webhook.organizationId !== user.organizationId) { throw new ForbiddenException('Access denied'); } return this.mapToDto(webhook); } /** * Update webhook */ @Patch(':id') @Roles('admin', 'manager') @ApiOperation({ summary: 'Update webhook' }) @ApiResponse({ status: 200, description: 'Webhook updated successfully' }) @ApiResponse({ status: 404, description: 'Webhook not found' }) async updateWebhook( @Param('id') id: string, @Body() dto: UpdateWebhookDto, @CurrentUser() user: UserPayload, ): Promise { const webhook = await this.webhookService.getWebhookById(id); if (!webhook) { throw new NotFoundException('Webhook not found'); } // Verify webhook belongs to user's organization if (webhook.organizationId !== user.organizationId) { throw new ForbiddenException('Access denied'); } const updatedWebhook = await this.webhookService.updateWebhook(id, dto); return this.mapToDto(updatedWebhook); } /** * Activate webhook */ @Post(':id/activate') @Roles('admin', 'manager') @ApiOperation({ summary: 'Activate webhook' }) @ApiResponse({ status: 200, description: 'Webhook activated successfully' }) @ApiResponse({ status: 404, description: 'Webhook not found' }) async activateWebhook( @Param('id') id: string, @CurrentUser() user: UserPayload, ): Promise<{ success: boolean }> { const webhook = await this.webhookService.getWebhookById(id); if (!webhook) { throw new NotFoundException('Webhook not found'); } // Verify webhook belongs to user's organization if (webhook.organizationId !== user.organizationId) { throw new ForbiddenException('Access denied'); } await this.webhookService.activateWebhook(id); return { success: true }; } /** * Deactivate webhook */ @Post(':id/deactivate') @Roles('admin', 'manager') @ApiOperation({ summary: 'Deactivate webhook' }) @ApiResponse({ status: 200, description: 'Webhook deactivated successfully' }) @ApiResponse({ status: 404, description: 'Webhook not found' }) async deactivateWebhook( @Param('id') id: string, @CurrentUser() user: UserPayload, ): Promise<{ success: boolean }> { const webhook = await this.webhookService.getWebhookById(id); if (!webhook) { throw new NotFoundException('Webhook not found'); } // Verify webhook belongs to user's organization if (webhook.organizationId !== user.organizationId) { throw new ForbiddenException('Access denied'); } await this.webhookService.deactivateWebhook(id); return { success: true }; } /** * Delete webhook */ @Delete(':id') @Roles('admin', 'manager') @ApiOperation({ summary: 'Delete webhook' }) @ApiResponse({ status: 200, description: 'Webhook deleted successfully' }) @ApiResponse({ status: 404, description: 'Webhook not found' }) async deleteWebhook( @Param('id') id: string, @CurrentUser() user: UserPayload, ): Promise<{ success: boolean }> { const webhook = await this.webhookService.getWebhookById(id); if (!webhook) { throw new NotFoundException('Webhook not found'); } // Verify webhook belongs to user's organization if (webhook.organizationId !== user.organizationId) { throw new ForbiddenException('Access denied'); } await this.webhookService.deleteWebhook(id); return { success: true }; } /** * Map webhook entity to DTO (without exposing secret) */ private mapToDto(webhook: Webhook): WebhookResponseDto { return { id: webhook.id, url: webhook.url, events: webhook.events, status: webhook.status, description: webhook.description, headers: webhook.headers, retryCount: webhook.retryCount, lastTriggeredAt: webhook.lastTriggeredAt?.toISOString(), failureCount: webhook.failureCount, createdAt: webhook.createdAt.toISOString(), updatedAt: webhook.updatedAt.toISOString(), }; } }