import { Controller, Post, Get, Body, Logger, HttpCode, HttpStatus, UseGuards, BadRequestException, } from '@nestjs/common'; import { ApiKeyGuard } from '../auth/api-key.guard'; import { WebhookQueueService } from './webhook-queue.service'; import { AllowApiKey, RequirePermissions } from '../auth/permissions.decorator'; import { Permission } from '../auth/permissions.enum'; export interface PaperlessWebhookPayload { doc_url?: string; document_id?: number; // optional, für manuelles Testen action?: string; [key: string]: any; } @Controller('api/webhook') export class WebhookController { private readonly logger = new Logger(WebhookController.name); constructor(private readonly webhookQueue: WebhookQueueService) {} @UseGuards(ApiKeyGuard) @Post('paperless') @AllowApiKey() @HttpCode(HttpStatus.OK) async handlePaperlessWebhook(@Body() payload: PaperlessWebhookPayload) { const documentId = this.extractDocumentId(payload); if (!documentId) { this.logger.warn( `Webhook ohne ermittelbare Dokument-ID: ${JSON.stringify(payload)}`, ); await this.webhookQueue.recordStatus({ documentId: null, action: payload.action, status: 'bad-request', }); throw new BadRequestException( 'Keine Dokument-ID aus doc_url/document_id ermittelbar', ); } this.logger.log( `Webhook: action=${payload.action}, document=${documentId} → eingereiht`, ); // Sofort einreihen und antworten; die eigentliche Verarbeitung übernimmt // der separate Queue-Prozess (sequenziell, dedupliziert). await this.webhookQueue.enqueue(documentId, payload.action); return { status: 'queued', documentId }; } /** * Liefert Informationen zum letzten Webhook-Vorgang (Zeitpunkt, Dokument, * Ergebnis) und die aktuelle Warteschlangen-Größe. Über die globalen Guards * per JWT oder API-Key zugänglich. */ @Get('status') @RequirePermissions(Permission.MANAGE_SETTINGS) async getWebhookStatus(): Promise<{ lastCall: unknown; queueSize: number }> { return this.webhookQueue.getStatus(); } /** * Ermittelt die Dokument-ID aus dem Webhook-Payload. Bevorzugt wird das * optionale Feld `document_id` (für manuelles Testen), andernfalls wird die * ID aus dem Paperless-Platzhalter `{{doc_url}}` extrahiert * (z.B. ".../documents/123/"). */ private extractDocumentId(payload: PaperlessWebhookPayload): number | null { if ( payload?.document_id != null && !Number.isNaN(Number(payload.document_id)) ) { return Number(payload.document_id); } if (typeof payload?.doc_url === 'string') { const m = payload.doc_url.match(/\/documents\/(\d+)/); if (m) return Number(m[1]); } return null; } }