import express from 'express'; import Webhook from '../models/Webhook.js'; import WebhookLog from '../models/WebhookLog.js'; import { validateRequest } from '../middleware/validateRequest.js'; import { authenticate } from '../middleware/auth.js'; import Joi from 'joi'; const router = express.Router(); // Validation schemas const createWebhookSchema = Joi.object({ name: Joi.string().required().min(1).max(100), description: Joi.string().optional().max(500), url: Joi.string().required().uri({ scheme: ['http', 'https'] }), events: Joi.array().items( Joi.string().valid( 'message.sent', 'message.delivered', 'message.failed', 'message.read', 'contact.created', 'contact.updated', 'contact.deleted', 'campaign.started', 'campaign.completed', 'campaign.failed', 'conversion.tracked', 'workflow.triggered', 'workflow.completed', 'account.updated' ) ).required().min(1), headers: Joi.object().pattern(Joi.string(), Joi.string()).optional(), retryConfig: Joi.object({ maxRetries: Joi.number().min(0).max(10).optional(), retryDelay: Joi.number().min(1000).max(60000).optional() }).optional(), timeout: Joi.number().min(1000).max(60000).optional(), metadata: Joi.object().optional() }); const updateWebhookSchema = Joi.object({ name: Joi.string().min(1).max(100).optional(), description: Joi.string().max(500).optional(), url: Joi.string().uri({ scheme: ['http', 'https'] }).optional(), events: Joi.array().items( Joi.string().valid( 'message.sent', 'message.delivered', 'message.failed', 'message.read', 'contact.created', 'contact.updated', 'contact.deleted', 'campaign.started', 'campaign.completed', 'campaign.failed', 'conversion.tracked', 'workflow.triggered', 'workflow.completed', 'account.updated' ) ).min(1).optional(), headers: Joi.object().pattern(Joi.string(), Joi.string()).optional(), active: Joi.boolean().optional(), retryConfig: Joi.object({ maxRetries: Joi.number().min(0).max(10).optional(), retryDelay: Joi.number().min(1000).max(60000).optional() }).optional(), timeout: Joi.number().min(1000).max(60000).optional(), metadata: Joi.object().optional() }); const testWebhookSchema = Joi.object({ event: Joi.string().required(), payload: Joi.object().required() }); // Create webhook router.post('/', authenticate, validateRequest(createWebhookSchema), async (req, res, next) => { try { const webhook = new Webhook({ accountId: req.user.accountId, ...req.body, headers: req.body.headers ? new Map(Object.entries(req.body.headers)) : new Map() }); await webhook.save(); res.status(201).json({ webhook: { id: webhook._id, name: webhook.name, description: webhook.description, url: webhook.url, events: webhook.events, active: webhook.active, secret: webhook.secret, headers: Object.fromEntries(webhook.headers), retryConfig: webhook.retryConfig, timeout: webhook.timeout, metadata: webhook.metadata ? Object.fromEntries(webhook.metadata) : {}, createdAt: webhook.createdAt } }); } catch (error) { next(error); } }); // List webhooks router.get('/', authenticate, async (req, res, next) => { try { const { page = 1, limit = 20, active } = req.query; const skip = (page - 1) * limit; const query = { accountId: req.user.accountId }; if (active !== undefined) { query.active = active === 'true'; } const [webhooks, total] = await Promise.all([ Webhook.find(query) .sort({ createdAt: -1 }) .skip(skip) .limit(parseInt(limit)), Webhook.countDocuments(query) ]); res.json({ webhooks: webhooks.map(webhook => ({ id: webhook._id, name: webhook.name, description: webhook.description, url: webhook.url, events: webhook.events, active: webhook.active, stats: webhook.stats, lastError: webhook.lastError, createdAt: webhook.createdAt, updatedAt: webhook.updatedAt })), pagination: { page: parseInt(page), limit: parseInt(limit), total, pages: Math.ceil(total / limit) } }); } catch (error) { next(error); } }); // Get webhook details router.get('/:id', authenticate, async (req, res, next) => { try { const webhook = await Webhook.findOne({ _id: req.params.id, accountId: req.user.accountId }); if (!webhook) { return res.status(404).json({ error: 'Webhook not found' }); } res.json({ webhook: { id: webhook._id, name: webhook.name, description: webhook.description, url: webhook.url, events: webhook.events, active: webhook.active, secret: webhook.secret, headers: Object.fromEntries(webhook.headers), retryConfig: webhook.retryConfig, timeout: webhook.timeout, metadata: webhook.metadata ? Object.fromEntries(webhook.metadata) : {}, stats: webhook.stats, lastError: webhook.lastError, createdAt: webhook.createdAt, updatedAt: webhook.updatedAt } }); } catch (error) { next(error); } }); // Update webhook router.put('/:id', authenticate, validateRequest(updateWebhookSchema), async (req, res, next) => { try { const webhook = await Webhook.findOne({ _id: req.params.id, accountId: req.user.accountId }); if (!webhook) { return res.status(404).json({ error: 'Webhook not found' }); } // Update fields Object.keys(req.body).forEach(key => { if (key === 'headers') { webhook.headers = new Map(Object.entries(req.body.headers)); } else if (key === 'metadata') { webhook.metadata = new Map(Object.entries(req.body.metadata)); } else { webhook[key] = req.body[key]; } }); await webhook.save(); res.json({ webhook: { id: webhook._id, name: webhook.name, description: webhook.description, url: webhook.url, events: webhook.events, active: webhook.active, headers: Object.fromEntries(webhook.headers), retryConfig: webhook.retryConfig, timeout: webhook.timeout, metadata: webhook.metadata ? Object.fromEntries(webhook.metadata) : {}, updatedAt: webhook.updatedAt } }); } catch (error) { next(error); } }); // Delete webhook router.delete('/:id', authenticate, async (req, res, next) => { try { const webhook = await Webhook.findOneAndDelete({ _id: req.params.id, accountId: req.user.accountId }); if (!webhook) { return res.status(404).json({ error: 'Webhook not found' }); } // Also delete associated logs await WebhookLog.deleteMany({ webhookId: webhook._id }); res.json({ message: 'Webhook deleted successfully' }); } catch (error) { next(error); } }); // Test webhook router.post('/:id/test', authenticate, validateRequest(testWebhookSchema), async (req, res, next) => { try { const webhook = await Webhook.findOne({ _id: req.params.id, accountId: req.user.accountId }); if (!webhook) { return res.status(404).json({ error: 'Webhook not found' }); } // Get webhook processor from app const webhookProcessor = req.app.get('webhookProcessor'); const log = await webhookProcessor.callWebhook( webhook, req.body.event, req.body.payload ); res.json({ success: log.status === 'success', log: { id: log._id, status: log.status, request: { url: log.request.url, headers: Object.fromEntries(log.request.headers), body: log.request.body }, response: log.response ? { status: log.response.status, statusText: log.response.statusText, headers: Object.fromEntries(log.response.headers), body: log.response.body, responseTime: log.response.responseTime } : null, error: log.error } }); } catch (error) { next(error); } }); // Get webhook logs router.get('/:id/logs', authenticate, async (req, res, next) => { try { const { page = 1, limit = 20, status } = req.query; const skip = (page - 1) * limit; const webhook = await Webhook.findOne({ _id: req.params.id, accountId: req.user.accountId }); if (!webhook) { return res.status(404).json({ error: 'Webhook not found' }); } const query = { webhookId: webhook._id }; if (status) { query.status = status; } const [logs, total] = await Promise.all([ WebhookLog.find(query) .sort({ createdAt: -1 }) .skip(skip) .limit(parseInt(limit)), WebhookLog.countDocuments(query) ]); res.json({ logs: logs.map(log => ({ id: log._id, event: log.event, status: log.status, attempts: log.attempts, response: log.response ? { status: log.response.status, responseTime: log.response.responseTime } : null, error: log.error, createdAt: log.createdAt, completedAt: log.completedAt })), pagination: { page: parseInt(page), limit: parseInt(limit), total, pages: Math.ceil(total / limit) } }); } catch (error) { next(error); } }); // Regenerate webhook secret router.post('/:id/regenerate-secret', authenticate, async (req, res, next) => { try { const webhook = await Webhook.findOne({ _id: req.params.id, accountId: req.user.accountId }); if (!webhook) { return res.status(404).json({ error: 'Webhook not found' }); } // Generate new secret webhook.secret = CryptoJS.lib.WordArray.random(32).toString(); await webhook.save(); res.json({ secret: webhook.secret }); } catch (error) { next(error); } }); export default router;