Files
telegram-management-system/marketing-agent/services/webhook-service/src/routes/webhooks.js
你的用户名 237c7802e5
Some checks failed
Deploy / deploy (push) Has been cancelled
Initial commit: Telegram Management System
Full-stack web application for Telegram management
- Frontend: Vue 3 + Vben Admin
- Backend: NestJS
- Features: User management, group broadcast, statistics

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-04 15:37:50 +08:00

380 lines
12 KiB
JavaScript

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;