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
130 lines
3.4 KiB
TypeScript
130 lines
3.4 KiB
TypeScript
/**
|
|
* Event Stream API
|
|
* GET /api/transparency/events - Get recent events or SSE stream
|
|
* POST /api/transparency/events - Emit a custom event
|
|
*
|
|
* Provides real-time event streaming for transparency.
|
|
*/
|
|
|
|
import type { NextApiRequest, NextApiResponse } from 'next';
|
|
import { getEventStream, TransparencyEventType, EventPriority } from '../../../lib/transparency';
|
|
|
|
export default async function handler(
|
|
req: NextApiRequest,
|
|
res: NextApiResponse
|
|
) {
|
|
const eventStream = getEventStream();
|
|
|
|
if (req.method === 'GET') {
|
|
const { stream, types, limit = '50', start, end } = req.query;
|
|
|
|
// Server-Sent Events mode
|
|
if (stream === 'true') {
|
|
res.setHeader('Content-Type', 'text/event-stream');
|
|
res.setHeader('Cache-Control', 'no-cache');
|
|
res.setHeader('Connection', 'keep-alive');
|
|
|
|
const connectionId = `sse_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
|
|
// Send initial connection event
|
|
res.write(`event: connected\ndata: ${JSON.stringify({ connectionId })}\n\n`);
|
|
|
|
// Register SSE callback
|
|
eventStream.registerSSE(connectionId, (event) => {
|
|
try {
|
|
res.write(eventStream.formatSSE(event));
|
|
} catch {
|
|
// Connection closed
|
|
eventStream.unregisterSSE(connectionId);
|
|
}
|
|
});
|
|
|
|
// Handle client disconnect
|
|
req.on('close', () => {
|
|
eventStream.unregisterSSE(connectionId);
|
|
});
|
|
|
|
// Keep connection alive
|
|
const keepAlive = setInterval(() => {
|
|
try {
|
|
res.write(': keepalive\n\n');
|
|
} catch {
|
|
clearInterval(keepAlive);
|
|
eventStream.unregisterSSE(connectionId);
|
|
}
|
|
}, 30000);
|
|
|
|
return;
|
|
}
|
|
|
|
// Regular GET - return recent events
|
|
try {
|
|
const typeList = types ? (types as string).split(',') as TransparencyEventType[] : undefined;
|
|
|
|
let events;
|
|
if (start && end) {
|
|
events = eventStream.getByTimeRange(
|
|
start as string,
|
|
end as string,
|
|
{
|
|
types: typeList,
|
|
limit: parseInt(limit as string, 10)
|
|
}
|
|
);
|
|
} else {
|
|
events = eventStream.getRecent(parseInt(limit as string, 10), typeList);
|
|
}
|
|
|
|
return res.status(200).json({
|
|
success: true,
|
|
data: {
|
|
events,
|
|
count: events.length,
|
|
availableTypes: eventStream.getAvailableEventTypes()
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('[API] Events error:', error);
|
|
return res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to fetch events'
|
|
});
|
|
}
|
|
}
|
|
|
|
if (req.method === 'POST') {
|
|
try {
|
|
const { type, source, data, priority, metadata } = req.body;
|
|
|
|
if (!type || !source || !data) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'Missing required fields: type, source, data'
|
|
});
|
|
}
|
|
|
|
const event = eventStream.emit(
|
|
type as TransparencyEventType,
|
|
source,
|
|
data,
|
|
{
|
|
priority: priority as EventPriority,
|
|
metadata
|
|
}
|
|
);
|
|
|
|
return res.status(201).json({
|
|
success: true,
|
|
data: event
|
|
});
|
|
} catch (error) {
|
|
console.error('[API] Event emit error:', error);
|
|
return res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to emit event'
|
|
});
|
|
}
|
|
}
|
|
|
|
return res.status(405).json({ error: 'Method not allowed' });
|
|
}
|