- models: colonne file inline (storage_path, mime, size_bytes, sha256, uploaded_by, uploaded_at) su remission_invoice, remission_ula_employee, remission_document - migrations: ALTER idempotente al lifespan per evolvere schema in sandbox - storage: FS adapter /var/uploads con validazione MIME/size, dedup sha256, sanitize - routers/files: POST upload / GET download (con ?inline=1) / DELETE matrix autorizzazioni: beneficiary su DRAFT|AWAITING_AMENDMENT, istruttore read-only, superadmin full - main: include router files, version bump 0.2.0 Testato E2E con admin JWT: upload 549B PDF -> DB coerente, storage 1/invoice/<uuid>/<sha12>-file.pdf, download con magic bytes PDF corretti, delete chirurgico con cleanup FS e metadata.
57 lines
2.1 KiB
Python
57 lines
2.1 KiB
Python
"""
|
|
Migration idempotente per sandbox.
|
|
Base.metadata.create_all() crea solo tabelle mancanti, non colonne aggiuntive.
|
|
Qui gestiamo ALTER TABLE ADD COLUMN IF NOT EXISTS per l'evoluzione dello schema.
|
|
|
|
Ogni migration è una stringa SQL che puo essere eseguita piu volte senza errori.
|
|
"""
|
|
from sqlalchemy import text
|
|
import logging
|
|
|
|
log = logging.getLogger("rendicontazione-api.migrations")
|
|
|
|
|
|
MIGRATIONS = [
|
|
# 2026-04-18: colonne file upload su remission_invoice
|
|
"""
|
|
ALTER TABLE gepafin_rendic.remission_invoice
|
|
ADD COLUMN IF NOT EXISTS storage_path varchar(1024),
|
|
ADD COLUMN IF NOT EXISTS mime varchar(128),
|
|
ADD COLUMN IF NOT EXISTS size_bytes bigint,
|
|
ADD COLUMN IF NOT EXISTS sha256 varchar(64),
|
|
ADD COLUMN IF NOT EXISTS uploaded_by integer,
|
|
ADD COLUMN IF NOT EXISTS uploaded_at timestamptz;
|
|
""",
|
|
# 2026-04-18: colonne file upload su remission_ula_employee
|
|
"""
|
|
ALTER TABLE gepafin_rendic.remission_ula_employee
|
|
ADD COLUMN IF NOT EXISTS storage_path varchar(1024),
|
|
ADD COLUMN IF NOT EXISTS mime varchar(128),
|
|
ADD COLUMN IF NOT EXISTS size_bytes bigint,
|
|
ADD COLUMN IF NOT EXISTS sha256 varchar(64),
|
|
ADD COLUMN IF NOT EXISTS uploaded_by integer,
|
|
ADD COLUMN IF NOT EXISTS uploaded_at timestamptz;
|
|
""",
|
|
# 2026-04-18: colonne file upload su remission_document
|
|
"""
|
|
ALTER TABLE gepafin_rendic.remission_document
|
|
ADD COLUMN IF NOT EXISTS storage_path varchar(1024),
|
|
ADD COLUMN IF NOT EXISTS mime varchar(128),
|
|
ADD COLUMN IF NOT EXISTS size_bytes bigint,
|
|
ADD COLUMN IF NOT EXISTS sha256 varchar(64),
|
|
ADD COLUMN IF NOT EXISTS uploaded_by integer;
|
|
""",
|
|
]
|
|
|
|
|
|
def run_migrations(engine) -> None:
|
|
"""Esegue tutte le migration in transazione. Log su ciascuna."""
|
|
with engine.begin() as conn:
|
|
for i, sql in enumerate(MIGRATIONS, 1):
|
|
try:
|
|
conn.execute(text(sql))
|
|
log.info(f"migration {i}/{len(MIGRATIONS)} OK")
|
|
except Exception as e:
|
|
log.error(f"migration {i} FAILED: {e}")
|
|
raise
|