81 lines
2.8 KiB
TypeScript
81 lines
2.8 KiB
TypeScript
/**
|
|
* ReportWorker — handles 'report:generate' jobs.
|
|
* Generates HTML, JSON, or PDF reports in the background.
|
|
*/
|
|
import { JobHandler } from '../JobQueue';
|
|
import { Logger } from '../../shared/infrastructure/Logger';
|
|
import { IReportRepository } from '../../modules/reporting/domain/ports/IReportRepository';
|
|
import { IFindingRepository } from '../../modules/findings/domain/ports/IFindingRepository';
|
|
import { HTMLReportGenerator } from '../../modules/reporting/infrastructure/generators/HTMLReportGenerator';
|
|
import { JSONReportGenerator } from '../../modules/reporting/infrastructure/generators/JSONReportGenerator';
|
|
import { PDFReportGenerator } from '../../modules/reporting/infrastructure/generators/PDFReportGenerator';
|
|
|
|
export const REPORT_JOB_TYPE = 'report:generate';
|
|
|
|
export interface ReportJobPayload {
|
|
reportId: string;
|
|
format: 'html' | 'pdf' | 'json';
|
|
filters?: {
|
|
sessionId?: string;
|
|
severity?: string;
|
|
fromDate?: string;
|
|
toDate?: string;
|
|
};
|
|
}
|
|
|
|
export interface ReportJobResult {
|
|
reportId: string;
|
|
filePath: string;
|
|
}
|
|
|
|
export function createReportJobHandler(deps: {
|
|
logger: Logger;
|
|
reportRepository: IReportRepository;
|
|
findingRepository: IFindingRepository;
|
|
}): JobHandler<ReportJobPayload, ReportJobResult> {
|
|
const htmlGen = new HTMLReportGenerator();
|
|
const jsonGen = new JSONReportGenerator();
|
|
const pdfGen = new PDFReportGenerator();
|
|
|
|
return async (payload: ReportJobPayload): Promise<ReportJobResult> => {
|
|
const log = deps.logger.child({ jobType: REPORT_JOB_TYPE, reportId: payload.reportId });
|
|
log.info({ format: payload.format }, 'Report generation job executing');
|
|
|
|
const report = await deps.reportRepository.findById(payload.reportId);
|
|
if (!report) {
|
|
throw new Error(`Report not found: ${payload.reportId}`);
|
|
}
|
|
|
|
report.markGenerating();
|
|
await deps.reportRepository.update(report);
|
|
|
|
// Load findings with filters from report
|
|
const findings = await deps.findingRepository.findAll({
|
|
sessionId: report.filters.sessionId,
|
|
severity: report.filters.severity,
|
|
});
|
|
|
|
let filePath: string;
|
|
try {
|
|
if (payload.format === 'pdf') {
|
|
filePath = await pdfGen.generate(report, findings);
|
|
} else if (payload.format === 'json') {
|
|
filePath = await jsonGen.generate(report, findings);
|
|
} else {
|
|
filePath = await htmlGen.generate(report, findings);
|
|
}
|
|
} catch (err) {
|
|
const msg = err instanceof Error ? err.message : String(err);
|
|
report.markFailed(msg);
|
|
await deps.reportRepository.update(report);
|
|
throw err;
|
|
}
|
|
|
|
report.markReady(filePath, findings.length);
|
|
await deps.reportRepository.update(report);
|
|
|
|
log.info({ filePath, totalFindings: findings.length }, 'Report job complete');
|
|
return { reportId: payload.reportId, filePath };
|
|
};
|
|
}
|