Versamento su API AgID

This commit is contained in:
2026-03-19 14:43:36 +01:00
parent 06dfbfcbc4
commit 4e19090f0f
12 changed files with 1319 additions and 3 deletions
@@ -0,0 +1,152 @@
"""
Servizio TenantSettings lettura e aggiornamento configurazione per-tenant.
Responsabilità:
- Upsert della riga settings (crea se non esiste, aggiorna altrimenti)
- Cifratura/decifratura AES-256-GCM delle credenziali conservatore
- Validazione di consistenza (produzione richiede endpoint + credenziali)
"""
import uuid
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.exceptions import BadRequestError
from app.core.security import decrypt_credential, encrypt_credential
from app.models.tenant_settings import TenantSettings
from app.schemas.tenant_settings import TenantSettingsResponse, TenantSettingsUpdate
class TenantSettingsService:
def __init__(self, db: AsyncSession) -> None:
self.db = db
# ─── Lettura (o creazione defaults) ──────────────────────────────────────
async def get_or_create(self, tenant_id: uuid.UUID) -> TenantSettings:
"""
Restituisce le impostazioni del tenant.
Se non esistono, crea una riga con i valori di default (mock).
"""
result = await self.db.execute(
select(TenantSettings).where(TenantSettings.tenant_id == tenant_id)
)
settings = result.scalar_one_or_none()
if settings is None:
settings = TenantSettings(
tenant_id=tenant_id,
archival_mode="mock",
conservatore_id="mock",
)
self.db.add(settings)
await self.db.flush()
return settings
# ─── Aggiornamento ────────────────────────────────────────────────────────
async def update(
self,
tenant_id: uuid.UUID,
data: TenantSettingsUpdate,
) -> TenantSettings:
"""
Aggiornamento parziale delle impostazioni.
Regole:
- Il passaggio a 'production' è consentito solo se conservatore_endpoint
è già configurato oppure viene fornito nel body corrente.
- Le credenziali vengono cifrate prima del salvataggio.
- Una stringa vuota "" su username/password cancella la credenziale.
"""
settings = await self.get_or_create(tenant_id)
# Applica aggiornamenti campi non-credenziali
if data.archival_mode is not None:
settings.archival_mode = data.archival_mode
if data.conservatore_id is not None:
settings.conservatore_id = data.conservatore_id
if data.conservatore_endpoint is not None:
settings.conservatore_endpoint = data.conservatore_endpoint or None
if data.archival_notes is not None:
settings.archival_notes = data.archival_notes or None
# Aggiorna credenziali (cifra se fornite, cancella se stringa vuota)
if data.conservatore_username is not None:
if data.conservatore_username == "":
settings.conservatore_username_enc = None
else:
settings.conservatore_username_enc = encrypt_credential(
data.conservatore_username
)
if data.conservatore_password is not None:
if data.conservatore_password == "":
settings.conservatore_password_enc = None
else:
settings.conservatore_password_enc = encrypt_credential(
data.conservatore_password
)
# Validazione: produzione richiede endpoint configurato
if settings.archival_mode == "production":
if not settings.conservatore_endpoint:
raise BadRequestError(
"La modalità produzione richiede un endpoint conservatore valido. "
"Inserisci l'URL prima di attivare la modalità produzione."
)
await self.db.flush()
return settings
# ─── Costruzione response ─────────────────────────────────────────────────
@staticmethod
def to_response(settings: TenantSettings) -> TenantSettingsResponse:
"""
Converte il modello in DTO di risposta.
Le credenziali NON vengono mai esposte in chiaro.
"""
return TenantSettingsResponse(
id=settings.id,
tenant_id=settings.tenant_id,
archival_mode=settings.archival_mode, # type: ignore[arg-type]
conservatore_id=settings.conservatore_id,
conservatore_endpoint=settings.conservatore_endpoint,
conservatore_username_configured=settings.conservatore_username_enc is not None,
conservatore_password_configured=settings.conservatore_password_enc is not None,
archival_notes=settings.archival_notes,
created_at=settings.created_at,
updated_at=settings.updated_at,
)
# ─── Helper per il worker (recupera credenziali decifrate) ───────────────
async def get_conservatore_credentials(
self, tenant_id: uuid.UUID
) -> dict:
"""
Restituisce le credenziali del conservatore decifrate.
Usato dal worker per effettuare versamenti.
"""
settings = await self.get_or_create(tenant_id)
return {
"mode": settings.archival_mode,
"conservatore_id": settings.conservatore_id,
"endpoint": settings.conservatore_endpoint,
"username": (
decrypt_credential(settings.conservatore_username_enc)
if settings.conservatore_username_enc
else None
),
"password": (
decrypt_credential(settings.conservatore_password_enc)
if settings.conservatore_password_enc
else None
),
}