- Updated all component headers and documentation
- Changed navbar and footer branding
- Updated homepage hero badge
- Modified page title in index.html
- Simplified footer text to 'Built with ❤️'
- Consistent V2 capitalization across all references
260 lines
10 KiB
JavaScript
260 lines
10 KiB
JavaScript
"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
|