node-storage

Storage di file e BLOB multi-backend (MinIO, filesystem) con link temporanei.
storage
/storageauth: nginx

node-storage

In sintesi

Servizio di persistenza file e BLOB del tenant. Astrae il backend di storage dietro un'interfaccia StorageProvider con due implementazioni intercambiabili — MinIO (S3-compatible) per produzione e filesystem locale per sviluppo, test e deployment edge — e arricchisce ogni file con un livello di metadati transazionali in PostgreSQL (tabella allegati) collegabili a qualsiasi entità di dominio tramite tabelle di abbinamento many-to-many. Aggiunge sopra questi due livelli un sistema di link temporanei basato su Redis con TTL automatico, pensato per consentire download autenticati senza esporre credenziali del backend a chi consuma. È il deposito unico dei documenti generati dagli altri microservizi (PDF di stampa, render di template, export Excel, allegati di ticket e board).

Funzionalità principali

  • Storage multi-backend con strategia selezionabile via STORAGE_PROVIDER (minio | local), interfaccia unica StorageProvider, factory che istanzia entrambi all'avvio e li registra nel container DI
  • Upload sicuro: multipart (fino a 50 MB / 50 file per richiesta di default) e variante base64-over-JSON per pipeline orchestrate; validazione MIME su whitelist (STORAGE_ALLOWED_MIME_TYPES), controllo magic bytes e scansione ClamAV opzionale
  • Allegati tracciati: ogni file ha un record in allegati (idallegato uuid, nomefile, dimensione, mime_type, datacaricamento) e zero/uno/più reference verso entità parent (es. boardelementiallegato.idboardelementi) tramite tabelle di abbinamento — schema convenzionale PZeta in italiano minuscolo
  • Soft-delete + cleanup orfani: rilevazione di file presenti in storage ma senza record DB (o viceversa) e job di pulizia idempotente via endpoint admin
  • Link temporanei con token random 32-byte hex, TTL configurabile in giorni e numero massimo di download; consumo atomico via Lua script Redis che previene race condition TOCTOU sui download concorrenti
  • Path confinement: validazione anti path-traversal (rifiuto di .., path assoluti e segmenti vietati come .git, node_modules) applicata sia al provider locale sia al servizio applicativo
  • Rollback transazionale dell'upload: se la INSERT su PostgreSQL fallisce dopo la scrittura del BLOB, il file viene rimosso dallo storage per evitare orfani
  • Modulo orchestrator: si registra come modulo @pzeta/orchestrator-node esponendo 4 task riusabili dai workflow (upload-file-base64, download-file, list-files, create-temporary-link) — pattern thin module su `node-orchestrator*

Architettura

Stack: Fastify v5 · @pzeta/fastify-utils (security, error handler, OpenAPI, validation, health, metrics, tracing, multipart wiring) · Inversify (DI con @injectable/@inject) · PostgreSQL via driver pg diretto · Redis (redis client v5) · MinIO SDK ufficiale minio v8 · @pzeta/orchestrator-node per integrazione hub · @pzeta/log per logging strutturato · Zod v4 per validazione I/O.

Layout DDD (src/):

LayerContenuto
domain/Aggregate Attachment (con reference verso entità parent) e TemporaryLink; value object AttachmentId, FileName, FileSize, MimeType, FilePath, Token, ExpiryDate, DownloadCounter; servizi FileValidationService, ImageProcessingService, IAntivirusService; errori tipizzati (StorageError, AttachmentError, TemporaryLinkError)
application/Use case (UploadAttachmentUseCase, DownloadAttachmentUseCase, DeleteAttachmentUseCase, DeleteMultipleAttachmentsUseCase, ListAttachmentsByReferenceUseCase, FindOrphanedFilesUseCase, CleanOrphanedFilesUseCase, GetAttachmentUseCase); StorageService di basso livello; DTO e mapper; porte (IStorageService, ITemporaryLinksService)
infrastructure/MinioStorageProvider, LocalStorageProvider, StorageFactory; PostgresAttachmentRepository; DatabaseService (transazioni + sanitizeIdentifier per identificatori dinamici); RedisTemporaryLinksService con Lua script atomico; ClamAVService opzionale; OrchestratorAdapter con manifest e 4 handler
presentation/StorageController, AttachmentsController, TemporaryLinksController con plugin di route Fastify e schema Zod per request/response

Pattern adottati: Strategy/Provider per i backend, Factory per la selezione runtime, Repository per la persistenza degli allegati, Use Case con rollback compensativo, Adapter verso orchestrator-node, Singleton per il client Redis.

Casi d'uso

  • Archivio PDF da node-print: il servizio di stampa produce un PDF (etichetta, documento di trasporto, ricevuta) e lo deposita in node-storage con una reference alla riga business che lo ha generato; il frontend lo recupera via GET /attachments/{id}/download o tramite link temporaneo per condivisione esterna
  • Asset template per node-renderer: template di stampa, font custom, loghi cliente e immagini di sfondo sono caricati come allegati con referenceTable=template_assets; al render time node-renderer chiama GET /attachments filtrando per reference e scarica il binario via download-file task dell'orchestrator
  • Allegati di entità di dominio: ticket di assistenza, schede board (boardelementi), anagrafiche e ordini espongono allegati tramite tabelle di abbinamento (boardelementiallegato, anagraficaallegato, ecc.); l'integrità referenziale è garantita da ON DELETE CASCADE dalla tabella allegati
  • Export ricorrente da node-excel-export: un workflow schedulato genera un report .xlsx, lo carica via task orchestrator upload-file-base64 e produce un link temporaneo (TTL 7 giorni, 1 download) inviato per email all'utente destinatario senza che l'utente abbia accesso al portale autenticato
  • Condivisione anonima controllata: l'operatore genera da UI un link temporaneo per condividere un documento con un fornitore esterno; il fornitore scarica il file via GET /download/{token} senza credenziali, il contatore di download viene decrementato atomicamente e il link viene eliminato al raggiungimento del limite o alla scadenza Redis (TTL nativo)

Identità & esposizione

CampoValore
Categoriastorage
Versione cluster1.0.11
Imagegitea.pzetatouch.it/pzeta_touch/node-storage:1.0.8
URL pubblicohttps://ditta.pzeta.it/storage
Path regex ingress`/storage(/
Rewrite a backend/$2
DNS internonode-storage-ditta.ditta.svc.cluster.local:3000
Auth nginxauth_requestnode-user-auth
Repositorynode-storage
Endpoint REST19 (vedi sezione "API reference")

Endpoint operazionali

Endpoint convenzionali esposti da tutti i microservizi PZeta basati su @pzeta/fastify-utils:

Path pubblicoScopo
https://ditta.pzeta.it/storage/healthliveness probe
https://ditta.pzeta.it/storage/readyreadiness probe
https://ditta.pzeta.it/storage/metricsmetriche Prometheus
https://ditta.pzeta.it/storage/api-docs.jsonspec OpenAPI runtime (richiede OPENAPI_EXPOSE_IN_PRODUCTION=true)
https://ditta.pzeta.it/storage/api-docsSwagger UI (solo in NODE_ENV !== production)

Configurazione

Variabili rilevanti per un integratore (la lista esaustiva resta nel .env.example del repo):

VariabileRuolo
STORAGE_PROVIDERminio (default in cluster) o local; pilota la StorageFactory
STORAGE_MINIO_ENDPOINT / _PORT / _USE_SSL / _ACCESS_KEY / _SECRET_KEY / _BUCKETConnessione al cluster MinIO; il bucket viene creato on-demand al primo upload se assente
STORAGE_LOCAL_PATHBase path del filesystem provider; soggetto a path confinement
STORAGE_MAX_FILE_SIZE_BYTESLimite per upload multipart (default 50 MB); applicato sia da @fastify/multipart sia dal FileValidationService
STORAGE_ALLOWED_MIME_TYPESWhitelist CSV di MIME accettati in upload; in assenza di valore esplicito viene applicata la whitelist sicura di default
STORAGE_DISALLOWED_PATHSSegmenti di path vietati (default .git,node_modules,.env,...)
DB_HOST / _PORT / _NAME / _USER / _PASSWORD / _POOL_MAXConnessione PostgreSQL per la tabella allegati e le tabelle di abbinamento
REDIS_HOST / _PORT / _PASSWORDBackend dei link temporanei; TTL gestito nativamente da Redis
BASE_URLURL pubblico usato per costruire l'url restituito alla creazione di un link temporaneo
ORCHESTRATOR_ENABLED / ORCHESTRATOR_URL / ORCHESTRATOR_MODULE_ID / ORCHESTRATOR_API_KEYRegistrazione opzionale come modulo presso node-orchestrator

Isolamento per tenant: il servizio non è multi-tenant a livello applicativo. L'isolamento è ottenuto in modo dichiarativo a livello di deployment: un'istanza per tenant (node-storage-<tenant> nel namespace omonimo), un bucket MinIO dedicato (STORAGE_MINIO_BUCKET=storage-<tenant> per convenzione), uno schema PostgreSQL separato per la tabella allegati. L'autenticazione nginx davanti all'ingress garantisce che le richieste non possano traversare istanze di tenant diversi.

Note eventing NATS

Il servizio non è connesso direttamente a NATS in questa release: nats.json è vuoto, non ci sono publishsubscribe statici verso il broker. La comunicazione asincrona con il resto della piattaforma avviene esclusivamente tramite il pattern thin module dell'orchestrator: OrchestratorAdapter istanzia un ModuleSDK di @pzeta/orchestrator-node che effettua il registration al boot (manifest con i 4 task pubblici), riceve i dispatch da node-orchestrator via HTTP request/reply, e ne ritorna l'esito. È l'orchestrator, non node-storage, a produrre gli eventi NATS di completamento task (workflow.events.task.*) consumati dalla timeline di esecuzione.

L'apertura del servizio a eventi di dominio nativi (es. storage.object.created / storage.object.deleted da consumare da node-notification o da workflow reattivi) è un'evoluzione naturale ma non ancora implementata: il pubblicatore tipico sarebbe UploadAttachmentUseCase dopo il commit transazionale e DeleteAttachmentUseCase dopo la conferma dello storage. Fino a quel momento, chi vuole reagire a un nuovo allegato deve costruire un workflow esplicito che invochi upload-file-base64 e poi un task downstream.

Dipendenze e dipendenti

Dipende da (servizi che questo servizio chiama):

Consumato da (chi chiama questo servizio):

Infrastruttura (PostgreSQL, NATS, Redis, MinIO) non è elencata qui — vedi sezione Architettura del singolo servizio.

Loading OpenAPI…
Loading NATS contracts…