import { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify'; import { reportService } from '@shieldai/report'; import { prisma } from '@shieldai/db'; import { ReportType, ReportStatus, ReportDataPayload } from '@shieldai/types'; interface AuthRequest extends FastifyRequest { user?: { id: string; email?: string; role?: string; }; } export async function reportRoutes(fastify: FastifyInstance) { // Generate a new report fastify.post('/generate', async (request: FastifyRequest, reply: FastifyReply) => { const authReq = request as AuthRequest; const userId = authReq.user?.id; if (!userId) { return reply.code(401).send({ error: 'User ID required' }); } const body = request.body as { reportType?: ReportType; periodStart?: string; periodEnd?: string; }; const subscription = await prisma.subscription.findFirst({ where: { userId, status: 'active' }, select: { id: true, tier: true }, }); if (!subscription) { return reply.code(404).send({ error: 'Active subscription not found' }); } const reportType = body.reportType || (subscription.tier === 'premium' ? 'ANNUAL_PREMIUM' : 'MONTHLY_PLUS'); const periodStart = body.periodStart ? new Date(body.periodStart) : undefined; const periodEnd = body.periodEnd ? new Date(body.periodEnd) : undefined; const report = await reportService.generateReport({ userId, subscriptionId: subscription.id, reportType, periodStart, periodEnd, }); return reply.code(201).send(report); }); // Get report history fastify.get('/', async (request: FastifyRequest, reply: FastifyReply) => { const authReq = request as AuthRequest; const userId = authReq.user?.id; if (!userId) { return reply.code(401).send({ error: 'User ID required' }); } const query = request.query as Record; const limit = parseInt(query.limit || '20', 10); const offset = parseInt(query.offset || '0', 10); const reports = await reportService.getReportHistory(userId, limit, offset); return reply.code(200).send({ reports, count: reports.length }); }); // Get specific report fastify.get('/:reportId', async (request: FastifyRequest, reply: FastifyReply) => { const authReq = request as AuthRequest; const userId = authReq.user?.id; if (!userId) { return reply.code(401).send({ error: 'User ID required' }); } const reportId = (request.params as { reportId: string }).reportId; try { const report = await reportService.getReportById(userId, reportId); return reply.code(200).send(report); } catch (error) { return reply.code(404).send({ error: error instanceof Error ? error.message : 'Report not found' }); } }); // Get report HTML content fastify.get('/:reportId/html', async (request: FastifyRequest, reply: FastifyReply) => { const authReq = request as AuthRequest; const userId = authReq.user?.id; if (!userId) { return reply.code(401).send({ error: 'User ID required' }); } const reportId = (request.params as { reportId: string }).reportId; const report = await prisma.securityReport.findFirst({ where: { id: reportId, userId }, select: { htmlContent: true, status: true }, }); if (!report) { return reply.code(404).send({ error: 'Report not found' }); } if (report.status !== 'COMPLETED') { return reply.code(404).send({ error: 'Report not yet completed' }); } reply.header('Content-Type', 'text/html'); return reply.code(200).send(report.htmlContent || ''); }); // Get report PDF fastify.get('/:reportId/pdf', async (request: FastifyRequest, reply: FastifyReply) => { const authReq = request as AuthRequest; const userId = authReq.user?.id; if (!userId) { return reply.code(401).send({ error: 'User ID required' }); } const reportId = (request.params as { reportId: string }).reportId; const report = await prisma.securityReport.findFirst({ where: { id: reportId, userId }, select: { dataPayload: true, title: true, status: true, htmlContent: true }, }); if (!report) { return reply.code(404).send({ error: 'Report not found' }); } if (report.status !== 'COMPLETED') { return reply.code(404).send({ error: 'Report not yet completed' }); } const { pdfGenerator } = await import('@shieldai/report'); const pdfData = report.dataPayload ? (typeof report.dataPayload === 'string' ? JSON.parse(report.dataPayload) : report.dataPayload as unknown as ReportDataPayload) : { exposureSummary: { totalExposures: 0, newExposures: 0, resolvedExposures: 0, criticalExposures: 0, warningExposures: 0, infoExposures: 0, exposuresBySource: {} }, spamStats: { callsBlocked: 0, textsBlocked: 0, callsFlagged: 0, textsFlagged: 0, falsePositives: 0, totalSpamEvents: 0 }, voiceStats: { analysesRun: 0, threatsDetected: 0, enrollmentsActive: 0, syntheticDetections: 0, voiceMismatchEvents: 0 }, recommendations: [], protectionScore: 0, }; const pdfBuffer = await pdfGenerator.generate({ reportTitle: report.title, periodStart: '', periodEnd: '', generatedAt: new Date().toISOString(), data: pdfData, reportId, }); reply.header('Content-Type', 'application/pdf'); reply.header('Content-Disposition', `inline; filename="${report.title}.pdf"`); return reply.code(200).send(pdfBuffer); }); // Schedule pending reports (admin/scheduler endpoint) fastify.post('/schedule/monthly', async (request: FastifyRequest, reply: FastifyReply) => { const createdIds = await reportService.scheduleMonthlyReports(); return reply.code(200).send({ scheduled: createdIds.length, reportIds: createdIds }); }); fastify.post('/schedule/annual', async (request: FastifyRequest, reply: FastifyReply) => { const createdIds = await reportService.scheduleAnnualReports(); return reply.code(200).send({ scheduled: createdIds.length, reportIds: createdIds }); }); }