Files
helix-engage-server/src/messaging/messaging.controller.ts
saridsa2 d819888351 feat: appointment QR code — generated and sent via WhatsApp after booking
- QrService: generates QR PNG from appointment data, cached in-memory
- GET /api/messaging/qr/:appointmentId serves the image (Gupshup needs URL)
- sendImage added to MessagingProvider + GupshupProvider
- send_appointment_qr tool registered in ToolRegistry
- Flow JSON updated: QR sent after booking confirmation
- Variable interpolation now supports dot notation ({{result.field}})
- SIDECAR_PUBLIC_URL env var for the QR image URL

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 20:23:06 +05:30

53 lines
1.9 KiB
TypeScript

import { Controller, Post, Get, Body, Param, Res, Logger } from '@nestjs/common';
import type { Response } from 'express';
import { MessagingProvider } from './providers/messaging-provider.interface';
import { MessagingService } from './messaging.service';
import { QrService } from './qr.service';
@Controller('api/messaging')
export class MessagingController {
private readonly logger = new Logger(MessagingController.name);
constructor(
private readonly provider: MessagingProvider,
private readonly messaging: MessagingService,
private readonly qr: QrService,
) {}
@Post('webhook')
async webhook(@Body() body: any) {
this.logger.log(`[WA-WEBHOOK] Received: ${JSON.stringify(body).substring(0, 500)}`);
if (!this.provider.validateWebhook(body)) {
this.logger.warn('[WA-WEBHOOK] Validation failed — ignoring');
return { status: 'ignored', reason: 'validation failed' };
}
const message = this.provider.parseInbound(body);
if (!message) {
this.logger.log('[WA-WEBHOOK] Non-message event — skipped');
return { status: 'ok', type: body?.type ?? 'unknown' };
}
// Handle async — don't block webhook response
this.messaging.handleInbound(message).catch(err => {
this.logger.error(`[WA-WEBHOOK] handleInbound failed: ${err.message}`);
});
return { status: 'ok' };
}
// Serve QR code images — Gupshup needs a public URL to send images
@Get('qr/:appointmentId')
async serveQr(@Param('appointmentId') appointmentId: string, @Res() res: Response) {
const png = this.qr.get(appointmentId);
if (!png) {
res.status(404).json({ error: 'QR code not found or expired' });
return;
}
res.set('Content-Type', 'image/png');
res.set('Cache-Control', 'public, max-age=86400');
res.send(png);
}
}