import { Controller, Post, Body, Logger } from '@nestjs/common'; import { StrapiService } from './strapi.service'; import { Public } from './auth/public.decorator'; interface WebhookBody { event: | 'entry.create' | 'entry.update' | 'entry.delete' | 'entry.publish' | 'entry.unpublish'; model: string; entry: { documentId: string; }; } @Controller('webhooks/strapi') export class StrapiController { private readonly logger = new Logger(StrapiController.name); constructor(private readonly strapiService: StrapiService) {} @Post('article') @Public() async handleArticleWebhook(@Body() body: WebhookBody) { this.logger.log(`Received article webhook: ${JSON.stringify(body)}`); const { event, model, entry } = body; if (model !== 'article') { this.logger.warn(`Ignored: not an article, model: ${model}`); return { message: 'Ignored: not an article' }; } await this.strapiService.handleWebhook(event, { documentId: entry.documentId, model, }); return { message: 'Webhook processed successfully' }; } @Post('live-blog') @Public() async handleLiveBlogWebhook(@Body() body: WebhookBody) { this.logger.log(`Received live-blog webhook: ${JSON.stringify(body)}`); const { event, model, entry } = body; if (model !== 'live-blog') { this.logger.warn(`Ignored: not a live blog, model: ${model}`); return { message: 'Ignored: not a live blog' }; } await this.strapiService.handleWebhook(event, { documentId: entry.documentId, model, }); return { message: 'Live blog webhook processed successfully' }; } @Post() @Public() async handleGenericWebhook(@Body() body: unknown) { this.logger.log(`Received generic webhook: ${JSON.stringify(body)}`); // Type guard to check if body is an object if (typeof body !== 'object' || body === null) { this.logger.warn(`Invalid webhook payload: ${JSON.stringify(body)}`); return { message: 'Invalid payload' }; } const bodyObj = body as Record; // Try to extract event and model from different possible payload structures const event = (bodyObj.event || bodyObj.type) as string; const model = (bodyObj.model || bodyObj.contentType) as string; const entry = bodyObj.entry || bodyObj.data || bodyObj; if (!event || !model) { this.logger.warn( `Cannot process webhook: missing event or model. Payload: ${JSON.stringify(body)}`, ); return { message: 'Cannot process: missing event or model' }; } const entryObj = entry as Record; const documentId = (entryObj.documentId || entryObj.id || (entryObj.document as Record)?.id) as string; if (!documentId) { this.logger.warn( `Cannot process webhook: missing documentId. Payload: ${JSON.stringify(body)}`, ); return { message: 'Cannot process: missing documentId' }; } // Validate event type const validEvents = [ 'entry.create', 'entry.update', 'entry.delete', 'entry.publish', 'entry.unpublish', ]; if (!validEvents.includes(event)) { this.logger.warn(`Invalid event type: ${event}`); return { message: 'Invalid event type' }; } await this.strapiService.handleWebhook( event as | 'entry.create' | 'entry.update' | 'entry.delete' | 'entry.publish' | 'entry.unpublish', { documentId, model }, ); return { message: 'Webhook processed successfully' }; } @Post('sync/all') async syncAllArticles() { await this.strapiService.syncArticles(); return { message: 'Articles sync completed' }; } @Post('sync/live-blogs') async syncAllLiveBlogs() { await this.strapiService.syncLiveBlogs(); return { message: 'Live blogs sync completed' }; } @Post('sync/everything') async syncEverything() { await Promise.all([ this.strapiService.syncArticles(), this.strapiService.syncLiveBlogs(), ]); return { message: 'Full sync completed' }; } }