"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const express_1 = __importDefault(require("express")); const zod_1 = require("zod"); const express_rate_limit_1 = __importDefault(require("express-rate-limit")); const export_service_1 = require("../services/export.service"); const auth_middleware_1 = require("../middleware/auth.middleware"); const logger_1 = require("../lib/logger"); const router = express_1.default.Router(); const exportService = new export_service_1.ExportService(); const exportLimiter = (0, express_rate_limit_1.default)({ windowMs: 60 * 60 * 1000, max: 20, message: { success: false, error: 'Export rate limit exceeded', message: 'Too many export requests. Please try again later.' }, standardHeaders: true, legacyHeaders: false, keyGenerator: (req) => { return req.user ? `user:${req.user.id}` : `ip:${req.ip}`; }, }); const exportParamsSchema = zod_1.z.object({ checkId: zod_1.z.string().min(1, 'Check ID is required'), }); const exportQuerySchema = zod_1.z.object({ download: zod_1.z.string().optional(), filename: zod_1.z.string().optional(), }); router.get('/:checkId/markdown', auth_middleware_1.optionalAuth, exportLimiter, async (req, res) => { try { const { checkId } = exportParamsSchema.parse(req.params); const { download, filename } = exportQuerySchema.parse(req.query); logger_1.logger.info(`Generating Markdown export for check: ${checkId}`, { userId: req.user?.id, download: !!download, }); const markdown = await exportService.generateMarkdownReport(checkId, req.user?.id); const fileName = filename || `redirect-report-${checkId}.md`; if (download) { res.setHeader('Content-Disposition', `attachment; filename="${fileName}"`); } res.setHeader('Content-Type', 'text/markdown; charset=utf-8'); res.setHeader('Content-Length', Buffer.byteLength(markdown, 'utf8')); res.send(markdown); logger_1.logger.info(`Markdown export completed for check: ${checkId}`, { userId: req.user?.id, size: Buffer.byteLength(markdown, 'utf8'), }); } catch (error) { logger_1.logger.error('Markdown export failed:', error); if (error instanceof zod_1.z.ZodError) { return res.status(400).json({ success: false, error: 'Validation error', message: error.errors[0]?.message || 'Invalid input', details: error.errors }); } const statusCode = error instanceof Error && error.message.includes('not found') ? 404 : 500; res.status(statusCode).json({ success: false, error: 'Export failed', message: error instanceof Error ? error.message : 'Failed to generate markdown report' }); } }); router.get('/:checkId/pdf', auth_middleware_1.optionalAuth, exportLimiter, async (req, res) => { try { const { checkId } = exportParamsSchema.parse(req.params); const { download, filename } = exportQuerySchema.parse(req.query); logger_1.logger.info(`Generating PDF export for check: ${checkId}`, { userId: req.user?.id, download: !!download, }); const pdfBuffer = await exportService.generatePdfReport(checkId, req.user?.id); const fileName = filename || `redirect-report-${checkId}.pdf`; res.setHeader('Content-Type', 'application/pdf'); res.setHeader('Content-Length', pdfBuffer.length); if (download) { res.setHeader('Content-Disposition', `attachment; filename="${fileName}"`); } else { res.setHeader('Content-Disposition', `inline; filename="${fileName}"`); } res.setHeader('X-Content-Type-Options', 'nosniff'); res.setHeader('X-Frame-Options', 'SAMEORIGIN'); res.send(pdfBuffer); logger_1.logger.info(`PDF export completed for check: ${checkId}`, { userId: req.user?.id, size: pdfBuffer.length, }); } catch (error) { logger_1.logger.error('PDF export failed:', error); if (error instanceof zod_1.z.ZodError) { return res.status(400).json({ success: false, error: 'Validation error', message: error.errors[0]?.message || 'Invalid input', details: error.errors }); } const statusCode = error instanceof Error && error.message.includes('not found') ? 404 : 500; res.status(statusCode).json({ success: false, error: 'Export failed', message: error instanceof Error ? error.message : 'Failed to generate PDF report' }); } }); router.post('/:checkId/save', auth_middleware_1.requireAuth, exportLimiter, async (req, res) => { try { const { checkId } = exportParamsSchema.parse(req.params); const { format = 'both' } = zod_1.z.object({ format: zod_1.z.enum(['markdown', 'pdf', 'both']).default('both'), }).parse(req.body); logger_1.logger.info(`Saving reports for check: ${checkId}`, { userId: req.user.id, format, }); const results = []; if (format === 'markdown' || format === 'both') { const markdown = await exportService.generateMarkdownReport(checkId, req.user.id); const mdPath = await exportService.saveReport(markdown, checkId, 'md'); results.push({ format: 'markdown', path: mdPath }); } if (format === 'pdf' || format === 'both') { const pdfBuffer = await exportService.generatePdfReport(checkId, req.user.id); const pdfPath = await exportService.saveReport(pdfBuffer, checkId, 'pdf'); results.push({ format: 'pdf', path: pdfPath }); } logger_1.logger.info(`Reports saved for check: ${checkId}`, { userId: req.user.id, results: results.length, }); res.json({ success: true, status: 200, data: { checkId, reports: results, savedAt: new Date().toISOString(), }, meta: { version: 'v2', feature: 'export-save', } }); } catch (error) { logger_1.logger.error('Report save failed:', error); if (error instanceof zod_1.z.ZodError) { return res.status(400).json({ success: false, error: 'Validation error', message: error.errors[0]?.message || 'Invalid input', details: error.errors }); } res.status(500).json({ success: false, error: 'Save failed', message: error instanceof Error ? error.message : 'Failed to save reports' }); } }); router.get('/formats', (req, res) => { res.json({ success: true, status: 200, data: { formats: [ { name: 'markdown', extension: 'md', mimeType: 'text/markdown', description: 'Human-readable Markdown format with embedded Mermaid diagrams', features: ['mermaid_diagrams', 'tables', 'links', 'formatting'], maxSize: '1MB', }, { name: 'pdf', extension: 'pdf', mimeType: 'application/pdf', description: 'Professional PDF report with rendered charts and formatting', features: ['rendered_diagrams', 'professional_layout', 'print_ready', 'embedded_fonts'], maxSize: '10MB', } ], limits: { rateLimit: '20 requests per hour', maxReportAge: '90 days', authentication: 'Optional (higher limits for authenticated users)', }, endpoints: { markdown: 'GET /api/v2/export/:checkId/markdown', pdf: 'GET /api/v2/export/:checkId/pdf', save: 'POST /api/v2/export/:checkId/save', } }, meta: { version: 'v2', feature: 'export-formats', } }); }); router.delete('/cleanup', auth_middleware_1.requireAuth, async (req, res) => { try { const { maxAgeHours = 24 } = zod_1.z.object({ maxAgeHours: zod_1.z.number().min(1).max(168).default(24), }).parse(req.body); logger_1.logger.info('Starting report cleanup', { userId: req.user.id, maxAgeHours, }); await exportService.cleanupOldReports(maxAgeHours); logger_1.logger.info('Report cleanup completed', { userId: req.user.id, maxAgeHours, }); res.json({ success: true, status: 200, data: { message: 'Cleanup completed successfully', maxAgeHours, cleanupTime: new Date().toISOString(), }, meta: { version: 'v2', feature: 'export-cleanup', } }); } catch (error) { logger_1.logger.error('Report cleanup failed:', error); if (error instanceof zod_1.z.ZodError) { return res.status(400).json({ success: false, error: 'Validation error', message: error.errors[0]?.message || 'Invalid input', details: error.errors }); } res.status(500).json({ success: false, error: 'Cleanup failed', message: error instanceof Error ? error.message : 'Failed to cleanup reports' }); } }); exports.default = router; //# sourceMappingURL=export.routes.js.map