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
313 lines
8.6 KiB
TypeScript
313 lines
8.6 KiB
TypeScript
/**
|
|
* Data Export API
|
|
* GET /api/transparency/export - Export transparency data in various formats
|
|
*
|
|
* Provides data export capabilities for users and compliance.
|
|
*/
|
|
|
|
import type { NextApiRequest, NextApiResponse } from 'next';
|
|
import { getTransparencyDashboard, getAuditLog, getEventStream, getSignatureManager } from '../../../lib/transparency';
|
|
import * as fs from 'fs';
|
|
import * as path from 'path';
|
|
|
|
type ExportType = 'dashboard' | 'audit' | 'events' | 'plants' | 'transport' | 'signatures' | 'full';
|
|
type ExportFormat = 'json' | 'csv' | 'summary';
|
|
|
|
export default async function handler(
|
|
req: NextApiRequest,
|
|
res: NextApiResponse
|
|
) {
|
|
if (req.method !== 'GET') {
|
|
return res.status(405).json({ error: 'Method not allowed' });
|
|
}
|
|
|
|
const {
|
|
type = 'dashboard',
|
|
format = 'json',
|
|
startDate,
|
|
endDate,
|
|
plantId,
|
|
userId
|
|
} = req.query;
|
|
|
|
try {
|
|
const exportType = type as ExportType;
|
|
const exportFormat = format as ExportFormat;
|
|
|
|
let data: any;
|
|
let filename: string;
|
|
|
|
switch (exportType) {
|
|
case 'dashboard':
|
|
data = await exportDashboard(exportFormat);
|
|
filename = 'transparency-dashboard';
|
|
break;
|
|
|
|
case 'audit':
|
|
data = await exportAudit(exportFormat, startDate as string, endDate as string);
|
|
filename = 'audit-log';
|
|
break;
|
|
|
|
case 'events':
|
|
data = await exportEvents(exportFormat, startDate as string, endDate as string);
|
|
filename = 'event-stream';
|
|
break;
|
|
|
|
case 'plants':
|
|
data = await exportPlants(exportFormat, plantId as string);
|
|
filename = plantId ? `plant-${plantId}` : 'plants';
|
|
break;
|
|
|
|
case 'transport':
|
|
data = await exportTransport(exportFormat, plantId as string);
|
|
filename = 'transport-history';
|
|
break;
|
|
|
|
case 'signatures':
|
|
data = await exportSignatures(exportFormat);
|
|
filename = 'signatures';
|
|
break;
|
|
|
|
case 'full':
|
|
data = await exportFull(exportFormat);
|
|
filename = 'full-transparency-export';
|
|
break;
|
|
|
|
default:
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: `Invalid export type: ${type}. Supported: dashboard, audit, events, plants, transport, signatures, full`
|
|
});
|
|
}
|
|
|
|
// Set appropriate headers based on format
|
|
const timestamp = new Date().toISOString().split('T')[0];
|
|
|
|
if (exportFormat === 'csv') {
|
|
res.setHeader('Content-Type', 'text/csv');
|
|
res.setHeader('Content-Disposition', `attachment; filename=${filename}-${timestamp}.csv`);
|
|
return res.status(200).send(data);
|
|
}
|
|
|
|
if (exportFormat === 'summary') {
|
|
res.setHeader('Content-Type', 'text/plain');
|
|
res.setHeader('Content-Disposition', `attachment; filename=${filename}-${timestamp}.txt`);
|
|
return res.status(200).send(data);
|
|
}
|
|
|
|
// JSON format
|
|
res.setHeader('Content-Type', 'application/json');
|
|
res.setHeader('Content-Disposition', `attachment; filename=${filename}-${timestamp}.json`);
|
|
return res.status(200).json({
|
|
success: true,
|
|
exportedAt: new Date().toISOString(),
|
|
type: exportType,
|
|
data
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('[API] Export error:', error);
|
|
return res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to export data'
|
|
});
|
|
}
|
|
}
|
|
|
|
async function exportDashboard(format: ExportFormat): Promise<any> {
|
|
const dashboard = getTransparencyDashboard();
|
|
return await dashboard.exportData(format);
|
|
}
|
|
|
|
async function exportAudit(format: ExportFormat, startDate?: string, endDate?: string): Promise<any> {
|
|
const auditLog = getAuditLog();
|
|
return auditLog.export(format, { startDate, endDate, limit: 10000 });
|
|
}
|
|
|
|
async function exportEvents(format: ExportFormat, startDate?: string, endDate?: string): Promise<any> {
|
|
const eventStream = getEventStream();
|
|
|
|
const events = startDate && endDate
|
|
? eventStream.getByTimeRange(startDate, endDate, { limit: 10000 })
|
|
: eventStream.getRecent(10000);
|
|
|
|
if (format === 'csv') {
|
|
const headers = ['ID', 'Type', 'Priority', 'Timestamp', 'Source', 'Data'];
|
|
const rows = events.map(e => [
|
|
e.id,
|
|
e.type,
|
|
e.priority,
|
|
e.timestamp,
|
|
e.source,
|
|
JSON.stringify(e.data).replace(/,/g, ';')
|
|
]);
|
|
return [headers.join(','), ...rows.map(r => r.join(','))].join('\n');
|
|
}
|
|
|
|
if (format === 'summary') {
|
|
const stats = eventStream.getStats();
|
|
return `
|
|
EVENT STREAM EXPORT
|
|
===================
|
|
Exported: ${new Date().toISOString()}
|
|
|
|
Total Events: ${stats.totalEvents}
|
|
Events Last 24h: ${stats.eventsLast24h}
|
|
Active Subscriptions: ${stats.activeSubscriptions}
|
|
Active Webhooks: ${stats.activeWebhooks}
|
|
|
|
Events by Type:
|
|
${Object.entries(stats.eventsByType).map(([t, c]) => ` ${t}: ${c}`).join('\n')}
|
|
|
|
Events by Priority:
|
|
${Object.entries(stats.eventsByPriority).map(([p, c]) => ` ${p}: ${c}`).join('\n')}
|
|
`.trim();
|
|
}
|
|
|
|
return events;
|
|
}
|
|
|
|
async function exportPlants(format: ExportFormat, plantId?: string): Promise<any> {
|
|
const dataDir = path.join(process.cwd(), 'data');
|
|
const chainFile = path.join(dataDir, 'plantchain.json');
|
|
|
|
if (!fs.existsSync(chainFile)) {
|
|
return format === 'json' ? [] : 'No plant data available';
|
|
}
|
|
|
|
const chainData = JSON.parse(fs.readFileSync(chainFile, 'utf-8'));
|
|
let plants = chainData.chain
|
|
?.filter((b: any) => b.plant)
|
|
.map((b: any) => ({
|
|
...b.plant,
|
|
blockIndex: b.index,
|
|
blockHash: b.hash,
|
|
timestamp: b.timestamp
|
|
})) || [];
|
|
|
|
if (plantId) {
|
|
plants = plants.filter((p: any) => p.id === plantId);
|
|
}
|
|
|
|
if (format === 'csv') {
|
|
if (plants.length === 0) return 'ID,Name,Variety,Owner,Location,Generation,Timestamp\n';
|
|
const headers = ['ID', 'Name', 'Variety', 'Owner', 'Location', 'Generation', 'Timestamp'];
|
|
const rows = plants.map((p: any) => [
|
|
p.id,
|
|
p.name || '',
|
|
p.variety || '',
|
|
p.ownerId || '',
|
|
p.location?.region || '',
|
|
p.generation || 1,
|
|
p.timestamp
|
|
]);
|
|
return [headers.join(','), ...rows.map((r: any) => r.join(','))].join('\n');
|
|
}
|
|
|
|
if (format === 'summary') {
|
|
return `
|
|
PLANT EXPORT
|
|
============
|
|
Total Plants: ${plants.length}
|
|
${plantId ? `Filtered by Plant ID: ${plantId}` : ''}
|
|
|
|
Sample Data:
|
|
${plants.slice(0, 5).map((p: any) =>
|
|
` - ${p.name || 'Unknown'} (${p.variety || 'Unknown'}) - Gen ${p.generation || 1}`
|
|
).join('\n')}
|
|
`.trim();
|
|
}
|
|
|
|
return plants;
|
|
}
|
|
|
|
async function exportTransport(format: ExportFormat, plantId?: string): Promise<any> {
|
|
// Transport data would come from transport blockchain
|
|
// For now, return placeholder
|
|
if (format === 'csv') {
|
|
return 'PlantID,EventType,From,To,Distance,Carbon,Timestamp\n';
|
|
}
|
|
|
|
if (format === 'summary') {
|
|
return 'TRANSPORT EXPORT\n================\nNo transport data available';
|
|
}
|
|
|
|
return [];
|
|
}
|
|
|
|
async function exportSignatures(format: ExportFormat): Promise<any> {
|
|
const signatureManager = getSignatureManager();
|
|
const stats = signatureManager.getStats();
|
|
const identities = signatureManager.getAllIdentities();
|
|
|
|
if (format === 'csv') {
|
|
const headers = ['ID', 'Name', 'Type', 'Verified', 'Created'];
|
|
const rows = identities.map(i => [
|
|
i.id,
|
|
i.name,
|
|
i.type,
|
|
i.verified ? 'Yes' : 'No',
|
|
i.createdAt
|
|
]);
|
|
return [headers.join(','), ...rows.map(r => r.join(','))].join('\n');
|
|
}
|
|
|
|
if (format === 'summary') {
|
|
return `
|
|
SIGNATURES EXPORT
|
|
=================
|
|
Total Identities: ${stats.totalIdentities}
|
|
Verified Identities: ${stats.verifiedIdentities}
|
|
Total Signatures: ${stats.totalSignatures}
|
|
Pending Multi-Sigs: ${stats.pendingMultiSigs}
|
|
Completed Multi-Sigs: ${stats.completedMultiSigs}
|
|
|
|
Signatures by Type:
|
|
${Object.entries(stats.signaturesByType).map(([t, c]) => ` ${t}: ${c}`).join('\n') || ' None'}
|
|
`.trim();
|
|
}
|
|
|
|
return { stats, identities };
|
|
}
|
|
|
|
async function exportFull(format: ExportFormat): Promise<any> {
|
|
const dashboard = await exportDashboard('json');
|
|
const audit = await exportAudit('json');
|
|
const events = await exportEvents('json');
|
|
const plants = await exportPlants('json');
|
|
const transport = await exportTransport('json');
|
|
const signatures = await exportSignatures('json');
|
|
|
|
if (format === 'summary') {
|
|
return `
|
|
FULL TRANSPARENCY EXPORT
|
|
========================
|
|
Exported: ${new Date().toISOString()}
|
|
|
|
This export contains all transparency data from LocalGreenChain.
|
|
|
|
Sections Included:
|
|
- Dashboard metrics
|
|
- Audit log entries
|
|
- Event stream data
|
|
- Plant registry
|
|
- Transport history
|
|
- Digital signatures
|
|
|
|
Use JSON format for complete data export.
|
|
`.trim();
|
|
}
|
|
|
|
if (format === 'csv') {
|
|
return 'Full export not available in CSV format. Use JSON instead.';
|
|
}
|
|
|
|
return {
|
|
dashboard,
|
|
audit,
|
|
events,
|
|
plants,
|
|
transport,
|
|
signatures
|
|
};
|
|
}
|