This commit introduces a complete transparency infrastructure including: Core Transparency Modules: - AuditLog: Immutable, cryptographically-linked audit trail for all actions - EventStream: Real-time SSE streaming and webhook support - TransparencyDashboard: Aggregated metrics and system health monitoring - DigitalSignatures: Cryptographic verification for handoffs and certificates API Endpoints: - /api/transparency/dashboard - Full platform metrics - /api/transparency/audit - Query and log audit entries - /api/transparency/events - SSE stream and event history - /api/transparency/webhooks - Webhook management - /api/transparency/signatures - Digital signature operations - /api/transparency/certificate/[plantId] - Plant authenticity certificates - /api/transparency/export - Multi-format data export - /api/transparency/report - Compliance reporting - /api/transparency/health - System health checks Features: - Immutable audit logging with chain integrity verification - Real-time event streaming via Server-Sent Events - Webhook support with HMAC signature verification - Digital signatures for transport handoffs and ownership transfers - Certificate of Authenticity generation for plants - Multi-format data export (JSON, CSV, summary) - Public transparency portal at /transparency - System health monitoring for all components Documentation: - Comprehensive TRANSPARENCY.md guide with API examples
167 lines
4.2 KiB
TypeScript
167 lines
4.2 KiB
TypeScript
/**
|
|
* Webhooks API
|
|
* GET /api/transparency/webhooks - List all webhooks
|
|
* POST /api/transparency/webhooks - Register a new webhook
|
|
* DELETE /api/transparency/webhooks - Remove a webhook
|
|
*
|
|
* Manage webhook subscriptions for real-time notifications.
|
|
*/
|
|
|
|
import type { NextApiRequest, NextApiResponse } from 'next';
|
|
import { getEventStream, TransparencyEventType } from '../../../lib/transparency';
|
|
|
|
export default async function handler(
|
|
req: NextApiRequest,
|
|
res: NextApiResponse
|
|
) {
|
|
const eventStream = getEventStream();
|
|
|
|
if (req.method === 'GET') {
|
|
try {
|
|
const webhooks = eventStream.getWebhooks();
|
|
|
|
// Mask secrets for security
|
|
const safeWebhooks = webhooks.map(wh => ({
|
|
...wh,
|
|
secret: wh.secret.substring(0, 8) + '...'
|
|
}));
|
|
|
|
return res.status(200).json({
|
|
success: true,
|
|
data: {
|
|
webhooks: safeWebhooks,
|
|
count: safeWebhooks.length
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('[API] Webhooks list error:', error);
|
|
return res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to list webhooks'
|
|
});
|
|
}
|
|
}
|
|
|
|
if (req.method === 'POST') {
|
|
try {
|
|
const { url, types, secret } = req.body;
|
|
|
|
if (!url || !types || !Array.isArray(types)) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'Missing required fields: url, types (array)'
|
|
});
|
|
}
|
|
|
|
// Validate URL
|
|
try {
|
|
new URL(url);
|
|
} catch {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'Invalid webhook URL'
|
|
});
|
|
}
|
|
|
|
// Validate event types
|
|
const availableTypes = eventStream.getAvailableEventTypes();
|
|
const invalidTypes = types.filter((t: string) => !availableTypes.includes(t as TransparencyEventType));
|
|
if (invalidTypes.length > 0) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: `Invalid event types: ${invalidTypes.join(', ')}`
|
|
});
|
|
}
|
|
|
|
const webhook = eventStream.registerWebhook(url, types as TransparencyEventType[], secret);
|
|
|
|
return res.status(201).json({
|
|
success: true,
|
|
data: {
|
|
id: webhook.id,
|
|
url: webhook.url,
|
|
types: webhook.types,
|
|
secret: webhook.secret, // Return full secret only on creation
|
|
active: webhook.active
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('[API] Webhook create error:', error);
|
|
return res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to create webhook'
|
|
});
|
|
}
|
|
}
|
|
|
|
if (req.method === 'DELETE') {
|
|
try {
|
|
const { id } = req.body;
|
|
|
|
if (!id) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'Missing required field: id'
|
|
});
|
|
}
|
|
|
|
const removed = eventStream.removeWebhook(id);
|
|
|
|
if (!removed) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
error: 'Webhook not found'
|
|
});
|
|
}
|
|
|
|
return res.status(200).json({
|
|
success: true,
|
|
message: 'Webhook removed successfully'
|
|
});
|
|
} catch (error) {
|
|
console.error('[API] Webhook delete error:', error);
|
|
return res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to delete webhook'
|
|
});
|
|
}
|
|
}
|
|
|
|
if (req.method === 'PATCH') {
|
|
try {
|
|
const { id, active } = req.body;
|
|
|
|
if (!id) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'Missing required field: id'
|
|
});
|
|
}
|
|
|
|
const webhook = eventStream.updateWebhook(id, { active });
|
|
|
|
if (!webhook) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
error: 'Webhook not found'
|
|
});
|
|
}
|
|
|
|
return res.status(200).json({
|
|
success: true,
|
|
data: {
|
|
...webhook,
|
|
secret: webhook.secret.substring(0, 8) + '...'
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('[API] Webhook update error:', error);
|
|
return res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to update webhook'
|
|
});
|
|
}
|
|
}
|
|
|
|
return res.status(405).json({ error: 'Method not allowed' });
|
|
}
|