Files
PecHub/backend/app/schemas/tenant_settings.py
T
2026-03-27 13:54:07 +01:00

122 lines
3.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Schema Pydantic per TenantSettings lettura e aggiornamento impostazioni tenant.
Include schemi per il modulo di indicizzazione full-text.
"""
import uuid
from datetime import datetime
from typing import Literal, Optional
from pydantic import BaseModel, Field, field_validator
ArchivalMode = Literal["mock", "production"]
class TenantSettingsResponse(BaseModel):
"""
Risposta GET /settings.
Le credenziali del conservatore non vengono mai esposte in chiaro:
vengono restituite come flag booleani (is_configured).
"""
id: uuid.UUID
tenant_id: uuid.UUID
# Archiviazione
archival_mode: ArchivalMode
conservatore_id: str
conservatore_endpoint: str | None
conservatore_username_configured: bool # TRUE se la username è già salvata
conservatore_password_configured: bool # TRUE se la password è già salvata
archival_notes: str | None
created_at: datetime
updated_at: datetime
model_config = {"from_attributes": True}
class TenantSettingsUpdate(BaseModel):
"""
Body PUT /settings.
Tutti i campi sono opzionali (PATCH semantics).
Le credenziali vengono aggiornate solo se fornite.
Un valore esplicitamente None può essere usato per cancellare le credenziali.
"""
archival_mode: ArchivalMode | None = None
conservatore_id: str | None = Field(
default=None, min_length=1, max_length=100
)
# URL endpoint del conservatore (obbligatorio in produzione, ignorato in mock)
conservatore_endpoint: str | None = None
# Credenziali in chiaro: vengono cifrate prima del salvataggio.
# Valore stringa vuota ("") = cancella la credenziale.
conservatore_username: str | None = None
conservatore_password: str | None = None
archival_notes: str | None = None
@field_validator("archival_mode")
@classmethod
def validate_archival_mode(cls, v: str | None) -> str | None:
if v is not None and v not in ("mock", "production"):
raise ValueError("archival_mode deve essere 'mock' o 'production'")
return v
# ─── Schemi indicizzazione full-text ──────────────────────────────────────────
class IndexingStats(BaseModel):
"""Statistiche di copertura dell'indicizzazione per un tenant."""
total_messages: int
indexed_messages: int
unindexed_messages: int
coverage_pct: float # percentuale messaggi con search_vector != NULL
attachments_total: int # allegati PDF/DOCX totali
attachments_extracted: int # allegati con testo estratto
attachments_pct: float # percentuale allegati con testo estratto
class IndexingJobStatus(BaseModel):
"""Stato di un job di reindex in corso o completato."""
status: str # idle | running | completed | failed | cancelled
mode: Optional[str] = None # full | differential
total: int = 0
processed: int = 0
progress_pct: float = 0.0
started_at: Optional[str] = None # ISO datetime
finished_at: Optional[str] = None # ISO datetime
started_by: Optional[str] = None # email utente che ha avviato il job
elapsed_seconds: Optional[int] = None
is_stale: bool = False # True se running da piu' di STALE_THRESHOLD_HOURS
error: Optional[str] = None
class StartReindexRequest(BaseModel):
"""Body per POST /settings/indexing/reindex."""
mode: Literal["full", "differential"] = "differential"
class StartRescanRequest(BaseModel):
"""Body per POST /settings/indexing/rescan."""
force: bool = Field(
default=False,
description=(
"False (default): estrae solo allegati con extracted_text NULL. "
"True: ri-estrae tutti gli allegati, sovrascrivendo i testi gia' presenti."
),
)