259 lines
7.2 KiB
TypeScript
259 lines
7.2 KiB
TypeScript
/**
|
|
* 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<string, string>;
|
|
}
|
|
|
|
class UpdateWebhookDto {
|
|
url?: string;
|
|
events?: WebhookEvent[];
|
|
description?: string;
|
|
headers?: Record<string, string>;
|
|
}
|
|
|
|
class WebhookResponseDto {
|
|
id: string;
|
|
url: string;
|
|
events: WebhookEvent[];
|
|
status: string;
|
|
description?: string;
|
|
headers?: Record<string, string>;
|
|
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<WebhookResponseDto> {
|
|
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<WebhookResponseDto[]> {
|
|
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<WebhookResponseDto> {
|
|
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<WebhookResponseDto> {
|
|
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(),
|
|
};
|
|
}
|
|
}
|