node-excel-export
node-excel-export
In sintesi
Servizio di esportazione tabellare che trasforma payload JSON o query su PostgreSQL in file Excel (XLSX) e CSV pronti al download. Espone due workflow distinti: uno legacy a payload diretto (il chiamante passa righe e intestazioni) e uno dinamico basato su modelli persistiti su DB, dove il client indica risorsa, campi, filtri e ordinamento, e il servizio costruisce la query, esegue l'estrazione paginata e formatta il file. È il punto di uscita standard per le funzioni "Esporta" del frontend Vue del tenant ditta.
Oltre al download tradizionale, il servizio supporta l'anteprima inline tramite il parametro query disposition=inline sugli endpoint di export, abilitando l'embedding del file in iframe/object lato frontend. Si registra inoltre come modulo orchestrator (@pzeta/orchestrator-node) esponendo il task sincrono export-dataset per essere richiamato da workflow inter-servizio.
Funzionalità principali
- Export sincrono da payload JSON (
POST /export) per fogli singoli o multi-worksheet con intestazioni, righe e merge cells dichiarate dal client - Export dinamico da template DB (
POST /api/export/dynamic) che esegue query sicure su risorse registrate (anagrafica,articoli, ecc.) applicando campi, filtri, ordinamento e limite - Content-Disposition selezionabile via query param
?disposition=inline|attachment(defaultattachment) sugli endpoint di export legacy e dinamico:inlineabilita la preview embedded del file iniframe/object/viewer del browser senza forzare il download - Registrazione come modulo orchestrator (
@pzeta/orchestrator-node) con task sincronoexport-datasetche replica il workflow dinamico via NATS request/reply e restituisce il file in base64; attiva solo seORCHESTRATOR_ENABLED=true(in caso contrario il servizio opera in modalità degraded solo HTTP) - CRUD modelli di export persistiti su PostgreSQL (
modelliexport): creazione, aggiornamento, soft delete e contatorevolteusatoper le statistiche - Discovery metadati campi per risorsa (
GET /api/export/metadata/:risorsa/fields) consumata dalla libreria@pzeta/vue-importexportper costruire l'UI di selezione - Strategy multi-formato (XLSX via
exceljs, CSV streaming) selezionata dinamicamente dalformatorichiesto - Rate limiting per IP configurabile (default 10 richieste / 60 s) sulla rotta di export, applicato dal
securityPlugindi@pzeta/fastify-utils - Validazione strutturale Zod su request, schema OpenAPI auto-generato e formattazione uniforme degli errori HTTP 400/422
- Logging applicativo delle esportazioni su tabella
logexportazioniper audit e diagnostica, con request ID di tracing propagato - Migrazioni automatiche dello schema
importexportall'avvio (idempotenti) seRUN_MIGRATIONS=true
Architettura
Stack: Fastify v5 · TypeScript strict · @pzeta/fastify-utils (security/error-handler/healthcheck/openapi/metrics/tracing/logging) · @pzeta/log per logging strutturato · @pzeta/orchestrator-node per la registrazione come modulo orchestrator e l'esposizione di task via NATS · PostgreSQL via driver pg diretto · exceljs per la generazione XLSX · Zod per la validazione · DI container interno (infrastructure/container/DIContainer.ts) con risoluzione per token string.
Layout DDD (src/):
| Layer | Contenuto |
|---|---|
domain/ | Entità (ExportJob, ExportTemplate, ExportLog, Workbook, Worksheet), repository interface (IExportTemplateRepository, IExportLogRepository, IFileManager, IDatabaseQueryProvider), servizi di dominio per formattazione ed export |
application/ | DTO request/response, ExportApplicationService, DynamicExportService, DynamicQueryService, RequestValidationService, adapter verso strategie infrastrutturali |
infrastructure/ | PostgresConnection (pool pg), MigrationRunner, repository concreti, ExportStrategyFactory con XlsxExportStrategy e CsvExportStrategy, EnvironmentConfig singleton, orchestrator/ (OrchestratorAdapter, OrchestratorConfigurationService con strategia soft-fail/degraded mode, handler ExportDatasetHandler e schemi Zod dei task) |
presentation/ | Plugin di route (export.routes, dynamicExport.routes, exportLimits.routes, exportExamples.routes, buildInfo.routes), controller dedicati per ciascuna famiglia di endpoint, schemi Fastify+JSON-Schema generati da Zod (incluso ExportQuerySchema per il parametro disposition) |
Pattern adottati: Strategy (un'implementazione per ogni formato di output), Factory per la selezione della strategia, Repository per la persistenza dei template e dei log, DI container leggero con resolve per token. La generazione del file avviene in memoria (stream) e ritorna Content-Disposition: attachment direttamente nella response, senza scrittura su disco né caricamento su storage esterno.
Sicurezza: dietro nginx con auth_request verso node-user-auth (strategia nginx); nessun token applicativo verificato dal servizio. Il rate limiting è applicato per IP a livello di plugin.
Casi d'uso
- Export "Esporta tabella" dal frontend: una griglia Vue del Laundry ERP (anagrafiche, articoli, ordini) invoca
POST /api/export/execute(alias compatibile con@pzeta/vue-importexport) con risorsa, campi selezionati, filtri della ricerca e ordinamento corrente; riceve il file XLSX da scaricare - Anteprima inline del file generato: il frontend richiede
POST /api/export/dynamic?disposition=inlineper ottenere un XLSX/CSV renderizzato direttamente in un viewer embedded (iframe/object) senza forzare il dialog di download — utile per preview di report e pannelli "guarda prima di scaricare" - Riuso di un modello salvato: l'operatore seleziona un template precedente (
GET /api/export/templates/:risorsa), il servizio lo applica con i filtri runtime e incrementavolteusatoper le statistiche d'uso - Export ad-hoc da modulo legacy: un microservizio chiama
POST /exportpassando direttamente intestazioni e righe già pronte (ad esempio un report aggregato calcolato altrove), senza coinvolgere la pipeline dinamica - Workflow orchestrator con task
export-dataset: un workflow definito sunode-orchestratorinvoca il taskexport-datasetdi questo modulo (via NATS request/reply) per generare un export inline e passarlo come step successivo — ad esempio come allegato di una notifica inviata danode-notificationo come input per una pipeline di archiviazione - Step di un workflow orchestrato:
node-orchestratoresegue un export programmato come task di un workflow notturno e inoltra l'output anode-storageo a un destinatario vianode-notification - Discovery UI: la libreria
@pzeta/vue-importexportchiamaGET /api/export/metadata/:risorsa/fieldsper popolare il selettore campi con i metadati ufficiali della risorsa (label, tipo, gruppo)
Identità & esposizione
| Campo | Valore |
|---|---|
| Categoria | data-io |
| Versione cluster | 1.0.3 |
| Image | gitea.pzetatouch.it/pzeta_touch/node-excel-export:1.0.3 |
| URL pubblico | https://ditta.pzeta.it/excel-export |
| Path regex ingress | `/excel-export(/ |
| Rewrite a backend | /$2 |
| DNS interno | node-excel-export-ditta.ditta.svc.cluster.local:3000 |
| Auth nginx | auth_request → node-user-auth |
| Repository | node-excel-export |
| Endpoint REST | 13 (vedi sezione "API reference") |
Endpoint operazionali
Endpoint convenzionali esposti da tutti i microservizi PZeta basati su @pzeta/fastify-utils:
| Path pubblico | Scopo |
|---|---|
https://ditta.pzeta.it/excel-export/health | liveness probe |
https://ditta.pzeta.it/excel-export/ready | readiness probe |
https://ditta.pzeta.it/excel-export/metrics | metriche Prometheus |
https://ditta.pzeta.it/excel-export/api-docs.json | spec OpenAPI runtime (richiede OPENAPI_EXPOSE_IN_PRODUCTION=true) |
https://ditta.pzeta.it/excel-export/api-docs | Swagger UI (solo in NODE_ENV !== production) |
Configurazione
Variabili d'ambiente che un integratore deve conoscere (per la lista completa vedi .env.example del repo):
| Variabile | Ruolo |
|---|---|
DATABASE_HOST / _PORT / _USER / _PASSWORD / _NAME | Connessione PostgreSQL; lo schema applicativo è importexport (template, log) ma il servizio interroga anche le tabelle business per l'export dinamico |
DATABASE_POOL_MAX / _IDLE_TIMEOUT_MS / _CONNECTION_TIMEOUT_MS | Tuning del pool pg; default 20 / 30000 / 2000 ms |
RUN_MIGRATIONS | Esegue le migrazioni dello schema importexport all'avvio; true di default |
TEMP_DIRECTORY | Cartella per file temporanei di lavoro (./temp); il servizio non persiste l'output, ma può usarla per buffering |
MAX_FILE_SIZE_MB | Limite hard sulla dimensione del file generato (default 50 MB); oltre il limite la richiesta fallisce con 413 |
EXPORT_RATE_LIMIT_REQUESTS / EXPORT_RATE_LIMIT_WINDOW_MS | Rate limit per IP sulle rotte di export (default 10 richieste / 60 s) |
CORS_ALLOWED_ORIGINS | Origini consentite in produzione, separate da virgola; in NODE_ENV !== production la policy è aperta |
REVERSE_PROXY | Abilita trustProxy di Fastify quando il servizio è dietro nginx |
ORCHESTRATOR_ENABLED | Abilita la registrazione come modulo orchestrator e l'esposizione del task export-dataset via NATS; default false. Se la connessione fallisce il servizio prosegue in modalità degraded HTTP-only |
ORCHESTRATOR_URL | URL dell'orchestrator a cui registrarsi (obbligatorio quando ORCHESTRATOR_ENABLED=true, validato come URL) |
ORCHESTRATOR_MODULE_ID | ID univoco del modulo nel registry orchestrator; default node-excel-export |
ORCHESTRATOR_API_KEY | API key opzionale per l'autenticazione del modulo verso l'orchestrator |
MODULE_VERSION / MODULE_NAME / MODULE_DESCRIPTION | Override opzionali del manifest del modulo (default letti da package.json) |
Swagger UI è esposto solo se NODE_ENV !== production; le metriche Prometheus sono pubblicate con prefisso excel_export_.
Note eventing NATS
Il servizio partecipa al bus NATS solo in modalità request/reply come modulo orchestrator, non come publisher di eventi di dominio. Quando ORCHESTRATOR_ENABLED=true l'OrchestratorAdapter istanzia un ModuleSDK (@pzeta/orchestrator-node), costruisce il manifest del modulo a partire da package.json (override possibile via MODULE_VERSION/MODULE_NAME/MODULE_DESCRIPTION) e registra l'unico task attualmente supportato:
export-dataset— task sincrono che riceve gli stessi parametri diPOST /api/export/dynamic(risorsa, fileName, formato, templateId o campi inline, filtri, opzioni), esegue l'export tramiteDynamicExportService, legge il file generato e lo restituisce nella response NATS serializzato in base64 insieme afileName,format,byteSize,rowCount,processingTimeMs. Il file temporaneo viene rimosso dopo la lettura (best-effort).
I subject NATS effettivi (tasks.<moduleId>.export-dataset e analoghi) non sono hard-coded nel codice: vengono derivati a runtime dal manifest del modulo dall'SDK orchestrator. Per questo motivo lo scanner statico continua a rilevare zero publish e zero subscribe in nats.json — l'informazione è disponibile solo dopo la registrazione live presso l'orchestrator (visibile su getRegisteredTasks() e tramite gli endpoint del registry orchestrator).
Cosa NON è ancora implementato: il task asincrono export-dataset-async (con persistenza del file su node-storage e ack immediato) e la pubblicazione di eventi di completamento sul subject services.excel-export.job.completed (e relativi job.started/job.failed) sono dichiarati esplicitamente fuori scope in questa iterazione (vedi commento di estensione in ExportDatasetHandler.ts e OrchestratorTaskSchemas.ts, issue PZeta_Touch/node-excel-export#12 risolta solo parzialmente). Eventuali audit cross-servizio sulle esportazioni continuano quindi a basarsi sulla tabella logexportazioni interna, non su eventi di dominio NATS.
In modalità degraded (orchestrator disabilitato o irraggiungibile) il servizio continua a funzionare esclusivamente come microservizio HTTP REST: i client (frontend Vue, altri microservizi) effettuano una POST e ricevono lo stream del file, con disposition attachment o inline a seconda del query param.
Dipendenze e dipendenti
Dipende da (servizi che questo servizio chiama):
Consumato da (chi chiama questo servizio):
frontend Vue
Infrastruttura (PostgreSQL, NATS, Redis, MinIO) non è elencata qui — vedi sezione Architettura del singolo servizio.