UNPKG

@openade/pel

Version:

Punto di Elaborazione (Elaboration Point) - Server library for managing PEMs and communicating with ADE

401 lines 17.9 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.AuditServer = exports.AuditRequestStatus = void 0; const express_1 = __importDefault(require("express")); const node_crypto_1 = require("node:crypto"); var AuditRequestStatus; (function (AuditRequestStatus) { AuditRequestStatus["PRONTA"] = "PRONTA"; AuditRequestStatus["IN_ELABORAZIONE"] = "IN_ELABORAZIONE"; AuditRequestStatus["NON_DISPONIBILE"] = "NON_DISPONIBILE"; })(AuditRequestStatus || (exports.AuditRequestStatus = AuditRequestStatus = {})); class AuditServer { constructor(config) { this.jobs = new Map(); this.server = null; this.config = { jobRetentionMs: 24 * 60 * 60 * 1000, ...config, }; this.app = (0, express_1.default)(); this.setupMiddleware(); this.setupRoutes(); this.startJobCleanup(); } setupMiddleware() { this.app.use(express_1.default.json()); this.app.use(express_1.default.raw({ type: 'application/octet-stream', limit: '50mb' })); if (this.config.enableCORS) { this.app.use((req, res, next) => { res.header('Access-Control-Allow-Origin', '*'); res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization'); if (req.method === 'OPTIONS') { res.sendStatus(200); } else { next(); } }); } if (this.config.authMiddleware) { this.app.use(this.config.authMiddleware); } this.app.use((err, req, res, _next) => { console.error('Error:', err); res.status(500).json({ anomalie: [ { codice: 'ERR_INTERNAL', descrizione: 'Errore non previsto', }, ], }); }); } setupRoutes() { this.app.post('/audit/journal', async (req, res) => { try { const idPresaInCarico = (0, node_crypto_1.randomUUID)(); const job = { id: idPresaInCarico, type: 'journal', status: AuditRequestStatus.IN_ELABORAZIONE, requestData: req.body, files: [], createdAt: new Date(), }; this.jobs.set(idPresaInCarico, job); this.processJournalAudit(job).catch((err) => { console.error('Error processing journal audit:', err); job.status = AuditRequestStatus.NON_DISPONIBILE; }); res.status(200).json({ idPresaInCarico }); } catch { res.status(500).json({ anomalie: [{ codice: 'ERR_INTERNAL', descrizione: 'Errore non previsto' }], }); } }); this.app.get('/audit/journal/:idPresaInCarico/stato', (req, res) => { const job = this.jobs.get(req.params.idPresaInCarico); if (!job || job.type !== 'journal') { return res.status(404).json({ anomalie: [{ codice: 'ERR_NOT_FOUND', descrizione: 'Richiesta di Audit non trovata' }], }); } res.status(200).json({ stato: job.status }); }); this.app.get('/audit/journal/:idPresaInCarico', (req, res) => { const job = this.jobs.get(req.params.idPresaInCarico); if (!job || job.type !== 'journal') { return res.status(404).json({ anomalie: [{ codice: 'ERR_NOT_FOUND', descrizione: 'IdPresoinCarico non trovato' }], }); } if (job.status !== AuditRequestStatus.PRONTA) { return res.status(409).json({ anomalie: [ { codice: 'ERR_NOT_READY', descrizione: 'Richiesta correttamente elaborata ma requisiti non soddisfatti', }, ], }); } res.status(200).json({ archivi: job.files }); }); this.app.get('/audit/journal/:idPresaInCarico/zip/:nomeFile', async (req, res) => { const job = this.jobs.get(req.params.idPresaInCarico); if (!job || job.type !== 'journal') { return res.status(404).json({ anomalie: [{ codice: 'ERR_NOT_FOUND', descrizione: 'IdPresoInCarico non trovato' }], }); } try { const filePath = `audit/journal/${job.id}/${req.params.nomeFile}`; const fileData = await this.config.storage.retrieve(filePath); if (!fileData) { return res.status(404).json({ anomalie: [{ codice: 'ERR_FILE_NOT_FOUND', descrizione: 'File non trovato' }], }); } res.setHeader('Content-Type', 'application/octet-stream'); res.setHeader('Content-Disposition', `attachment; filename="${req.params.nomeFile}"`); res.send(fileData); } catch { res.status(500).json({ anomalie: [{ codice: 'ERR_INTERNAL', descrizione: 'Errore non previsto' }], }); } }); this.app.post('/audit/dc', async (req, res) => { try { const { hash } = req.body; if (!hash || !Array.isArray(hash)) { return res.status(406).json({ anomalie: [ { codice: 'ERR_INVALID_PARAMS', descrizione: 'Parametri di input non validi' }, ], }); } const idPresaInCarico = (0, node_crypto_1.randomUUID)(); const job = { id: idPresaInCarico, type: 'dc', status: AuditRequestStatus.IN_ELABORAZIONE, requestData: { hash }, files: [], createdAt: new Date(), }; this.jobs.set(idPresaInCarico, job); this.processDocumentAudit(job).catch((err) => { console.error('Error processing document audit:', err); job.status = AuditRequestStatus.NON_DISPONIBILE; }); res.status(200).json({ idPresaInCarico }); } catch { res.status(500).json({ anomalie: [{ codice: 'ERR_INTERNAL', descrizione: 'Errore non previsto' }], }); } }); this.app.get('/audit/dc/:idPresaInCarico/stato', (req, res) => { const job = this.jobs.get(req.params.idPresaInCarico); if (!job || job.type !== 'dc') { return res.status(404).json({ anomalie: [{ codice: 'ERR_NOT_FOUND', descrizione: 'Richiesta non trovata' }], }); } res.status(200).json({ stato: job.status }); }); this.app.get('/audit/dc/:idPresaInCarico', (req, res) => { const job = this.jobs.get(req.params.idPresaInCarico); if (!job || job.type !== 'dc') { return res.status(404).json({ anomalie: [{ codice: 'ERR_NOT_FOUND', descrizione: 'IdPresoinCarico non trovato' }], }); } if (job.status !== AuditRequestStatus.PRONTA) { return res.status(409).json({ anomalie: [ { codice: 'ERR_NOT_READY', descrizione: 'Richiesta correttamente elaborata ma requisiti non soddisfatti', }, ], }); } res.status(200).json({ dc: job.files }); }); this.app.get('/audit/dc/:idPresaInCarico/files/:nomeFile', async (req, res) => { const job = this.jobs.get(req.params.idPresaInCarico); if (!job || job.type !== 'dc') { return res.status(404).json({ anomalie: [{ codice: 'ERR_NOT_FOUND', descrizione: 'IdPresoInCarico non trovato' }], }); } try { const filePath = `audit/dc/${job.id}/${req.params.nomeFile}`; const fileData = await this.config.storage.retrieve(filePath); if (!fileData) { return res.status(404).json({ anomalie: [{ codice: 'ERR_FILE_NOT_FOUND', descrizione: 'File non trovato' }], }); } res.setHeader('Content-Type', 'application/octet-stream'); res.setHeader('Content-Disposition', `attachment; filename="${req.params.nomeFile}"`); res.send(fileData); } catch { res.status(500).json({ anomalie: [{ codice: 'ERR_INTERNAL', descrizione: 'Errore non previsto' }], }); } }); } async processJournalAudit(job) { try { console.log(`Processing journal audit request ${job.id}`); const { emissionPointId, dateFrom, dateTo } = job.requestData; const result = await this.config.database.listJournals({ emissionPointId: emissionPointId, dateFrom: dateFrom, dateTo: dateTo, }); const journals = result.data || []; if (journals.length === 0) { console.warn('No journals found for audit request'); job.status = AuditRequestStatus.NON_DISPONIBILE; return; } console.log(`Found ${journals.length} journals for audit`); const encoder = new TextEncoder(); const files = []; for (const journal of journals) { const journalXML = this.generateJournalXML(journal); const xmlData = encoder.encode(journalXML); const dateStr = journal.dataRiferimento.replace(/[:-]/g, ''); const fileName = `J_${journal.identificativoPEM}_${dateStr}.xml`; await this.config.storage.store(`audit/journal/${job.id}/${fileName}`, xmlData); files.push({ nome: fileName, size: xmlData.length.toString(), }); } job.files = files; job.status = AuditRequestStatus.PRONTA; job.completedAt = new Date(); console.log(`✓ Journal audit completed: ${files.length} files ready`); } catch (error) { console.error('Failed to process journal audit:', error); job.status = AuditRequestStatus.NON_DISPONIBILE; } } generateJournalXML(journal) { return `<?xml version="1.0" encoding="UTF-8"?> <Journal versione="${journal.versione}"> <IdentificativoPEM>${journal.identificativoPEM}</IdentificativoPEM> <DataRiferimento>${journal.dataRiferimento}</DataRiferimento> <DataOraGenerazione>${journal.dataOraGenerazione}</DataOraGenerazione> <NumeroVoci>${journal.numeroVoci || (journal.voci ? journal.voci.length : 0)}</NumeroVoci> <ImportoTotaleGiornata>${journal.importoTotaleGiornata || 0}</ImportoTotaleGiornata> </Journal>`; } async processDocumentAudit(job) { try { console.log(`Processing document audit request ${job.id}`); const { hash, emissionPointId, dateFrom, dateTo } = job.requestData; const encoder = new TextEncoder(); const files = []; if (hash && Array.isArray(hash)) { for (const documentHash of hash) { try { let document = null; if (this.config.database && this.config.database.getDocumentByHash) { document = await this.config.database.getDocumentByHash(documentHash); } if (!document) { console.warn(`Document with hash ${documentHash} not found or method not implemented`); continue; } const docXML = this.generateDocumentXML(document); const xmlData = encoder.encode(docXML); const dateStr = document.datiGenerali.dataOra.substring(0, 10).replace(/[:-]/g, ''); const fileName = `DC_${document.datiGenerali.numero}_${document.identificativoPEM}_${dateStr}.xml`; await this.config.storage.store(`audit/dc/${job.id}/${fileName}`, xmlData); files.push({ nome: fileName, size: xmlData.length.toString(), }); } catch (err) { console.error(`Error processing document hash ${documentHash}:`, err); } } } else if (emissionPointId || (dateFrom && dateTo)) { const result = await this.config.database.listDocuments({ emissionPointId: emissionPointId, dateFrom: dateFrom, dateTo: dateTo, }); const documents = result.data || []; console.log(`Found ${documents.length} documents for audit`); for (const document of documents) { const docXML = this.generateDocumentXML(document); const xmlData = encoder.encode(docXML); const dateStr = document.datiGenerali.dataOra.substring(0, 10).replace(/[:-]/g, ''); const fileName = `DC_${document.datiGenerali.numero}_${document.identificativoPEM}_${dateStr}.xml`; await this.config.storage.store(`audit/dc/${job.id}/${fileName}`, xmlData); files.push({ nome: fileName, size: xmlData.length.toString(), }); } } if (files.length === 0) { console.warn('No documents found for audit request'); job.status = AuditRequestStatus.NON_DISPONIBILE; return; } job.files = files; job.status = AuditRequestStatus.PRONTA; job.completedAt = new Date(); console.log(`✓ Document audit completed: ${files.length} files ready`); } catch (error) { console.error('Failed to process document audit:', error); job.status = AuditRequestStatus.NON_DISPONIBILE; } } generateDocumentXML(document) { return `<?xml version="1.0" encoding="UTF-8"?> <DocumentoCommerciale> <IdentificativoPEM>${document.identificativoPEM}</IdentificativoPEM> <DatiGenerali> <Numero>${document.datiGenerali.numero}</Numero> <DataOra>${document.datiGenerali.dataOra}</DataOra> <TipoDocumento>${document.datiGenerali.tipoDocumento}</TipoDocumento> </DatiGenerali> <ImportoTotale>${document.importoTotale}</ImportoTotale> </DocumentoCommerciale>`; } startJobCleanup() { setInterval(() => { const now = Date.now(); const retentionMs = this.config.jobRetentionMs; for (const [id, job] of this.jobs.entries()) { const jobAge = now - job.createdAt.getTime(); if (jobAge > retentionMs) { this.jobs.delete(id); console.log(`Cleaned up expired job: ${id}`); } } }, 60 * 60 * 1000); } async start() { return new Promise((resolve) => { this.server = this.app.listen(this.config.port, () => { console.log(`Audit server listening on port ${this.config.port}`); console.log(`Endpoints:`); console.log(` POST /audit/journal - Request journal audit`); console.log(` GET /audit/journal/{id}/stato - Check journal status`); console.log(` GET /audit/journal/{id} - List journal files`); console.log(` GET /audit/journal/{id}/zip/{file} - Download journal file`); console.log(` POST /audit/dc - Request document audit`); console.log(` GET /audit/dc/{id}/stato - Check document status`); console.log(` GET /audit/dc/{id} - List document files`); console.log(` GET /audit/dc/{id}/files/{file} - Download document file`); resolve(); }); }); } async stop() { return new Promise((resolve, reject) => { if (this.server) { this.server.close((err) => { if (err) reject(err); else resolve(); }); } else { resolve(); } }); } getApp() { return this.app; } } exports.AuditServer = AuditServer; //# sourceMappingURL=audit.server.js.map