initial skeleton: FastAPI + SQLAlchemy + schema rendicontazione + template RE-START

This commit is contained in:
BFLOWS
2026-04-18 07:50:06 +02:00
commit 63fd2f66e6
14 changed files with 602 additions and 0 deletions

0
app/routers/__init__.py Normal file
View File

24
app/routers/health.py Normal file
View File

@@ -0,0 +1,24 @@
from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
from sqlalchemy import text
from ..db import get_db
router = APIRouter()
@router.get("/health")
def health(db: Session = Depends(get_db)):
# Verifica connessione DB e schema
try:
result = db.execute(text("SELECT current_schema(), current_user, now();")).first()
return {
"status": "ok",
"service": "rendicontazione-api",
"db": {
"schema": result[0],
"user": result[1],
"now": str(result[2]),
},
}
except Exception as e:
return {"status": "degraded", "error": str(e)}

162
app/routers/schemas.py Normal file
View File

@@ -0,0 +1,162 @@
"""
Endpoint gestione schema rendicontazione per bando.
"""
import copy
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.orm import Session
from sqlalchemy.exc import IntegrityError
from ..db import get_db
from ..auth import AuthUser, get_current_user, require_superadmin
from ..models import CallRemissionSchema
from ..schemas import RemissionSchemaOut, RemissionSchemaCreate, RemissionSchemaUpdate, ApiResponse
from ..templates import RESTART_TEMPLATE
router = APIRouter(prefix="/api/rendicontazione-schemas", tags=["rendicontazione-schemas"])
@router.get("/{call_id}", response_model=ApiResponse)
def get_schema(call_id: int, db: Session = Depends(get_db), user: AuthUser = Depends(get_current_user)):
"""Legge lo schema di rendicontazione per un bando. 404 se non esiste."""
schema = db.query(CallRemissionSchema).filter(CallRemissionSchema.call_id == call_id).first()
if not schema:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Nessuno schema di rendicontazione per call_id={call_id}. Usa POST per crearlo.",
)
return ApiResponse(data=RemissionSchemaOut.model_validate(schema).model_dump(mode="json"))
@router.post("/{call_id}", response_model=ApiResponse)
def create_schema(
call_id: int,
body: RemissionSchemaCreate,
db: Session = Depends(get_db),
user: AuthUser = Depends(require_superadmin),
):
"""Crea uno schema di rendicontazione per un bando. Fallisce se esiste già."""
existing = db.query(CallRemissionSchema).filter(CallRemissionSchema.call_id == call_id).first()
if existing:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail=f"Schema già esistente per call_id={call_id}. Usa PUT per aggiornarlo.",
)
schema = CallRemissionSchema(
call_id=call_id,
schema_json=body.schema_json,
status="DRAFT",
created_by=user.user_id,
)
db.add(schema)
db.commit()
db.refresh(schema)
return ApiResponse(
message=f"Schema creato per call_id={call_id}",
data=RemissionSchemaOut.model_validate(schema).model_dump(mode="json"),
)
@router.put("/{call_id}", response_model=ApiResponse)
def update_schema(
call_id: int,
body: RemissionSchemaUpdate,
db: Session = Depends(get_db),
user: AuthUser = Depends(require_superadmin),
):
"""Aggiorna schema esistente. Blocca modifiche se già PUBLISHED."""
schema = db.query(CallRemissionSchema).filter(CallRemissionSchema.call_id == call_id).first()
if not schema:
raise HTTPException(status_code=404, detail=f"Schema non trovato per call_id={call_id}")
if schema.status == "PUBLISHED":
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail="Schema PUBLISHED non modificabile. Crea una nuova versione.",
)
if body.schema_json is not None:
schema.schema_json = body.schema_json
db.commit()
db.refresh(schema)
return ApiResponse(
message=f"Schema aggiornato per call_id={call_id}",
data=RemissionSchemaOut.model_validate(schema).model_dump(mode="json"),
)
@router.post("/{call_id}/initialize-restart", response_model=ApiResponse)
def initialize_restart(
call_id: int,
db: Session = Depends(get_db),
user: AuthUser = Depends(require_superadmin),
):
"""Inizializza schema per un bando usando il template RE-START. Fallisce se esiste già."""
existing = db.query(CallRemissionSchema).filter(CallRemissionSchema.call_id == call_id).first()
if existing:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail=f"Schema già esistente per call_id={call_id}. Usa PUT per modificarlo.",
)
schema = CallRemissionSchema(
call_id=call_id,
schema_json=copy.deepcopy(RESTART_TEMPLATE),
status="DRAFT",
created_by=user.user_id,
)
db.add(schema)
db.commit()
db.refresh(schema)
return ApiResponse(
message=f"Schema RE-START inizializzato per call_id={call_id}",
data=RemissionSchemaOut.model_validate(schema).model_dump(mode="json"),
)
@router.post("/{call_id}/publish", response_model=ApiResponse)
def publish_schema(
call_id: int,
db: Session = Depends(get_db),
user: AuthUser = Depends(require_superadmin),
):
"""Pubblica lo schema (status DRAFT -> PUBLISHED). Una volta pubblicato, non è più editabile."""
from datetime import datetime, timezone
schema = db.query(CallRemissionSchema).filter(CallRemissionSchema.call_id == call_id).first()
if not schema:
raise HTTPException(status_code=404, detail=f"Schema non trovato per call_id={call_id}")
if schema.status == "PUBLISHED":
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail="Schema già pubblicato.",
)
schema.status = "PUBLISHED"
schema.published_at = datetime.now(timezone.utc)
schema.published_by = user.user_id
db.commit()
db.refresh(schema)
return ApiResponse(
message=f"Schema pubblicato per call_id={call_id}",
data=RemissionSchemaOut.model_validate(schema).model_dump(mode="json"),
)
@router.delete("/{call_id}", response_model=ApiResponse)
def delete_schema(
call_id: int,
db: Session = Depends(get_db),
user: AuthUser = Depends(require_superadmin),
):
"""Cancella schema. Consentito solo in DRAFT."""
schema = db.query(CallRemissionSchema).filter(CallRemissionSchema.call_id == call_id).first()
if not schema:
raise HTTPException(status_code=404, detail=f"Schema non trovato per call_id={call_id}")
if schema.status == "PUBLISHED":
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail="Schema PUBLISHED non cancellabile.",
)
db.delete(schema)
db.commit()
return ApiResponse(message=f"Schema cancellato per call_id={call_id}")
@router.get("/templates/restart", response_model=ApiResponse)
def get_restart_template(user: AuthUser = Depends(require_superadmin)):
"""Restituisce il template RE-START senza persisterlo. Utile per preview."""
return ApiResponse(data=RESTART_TEMPLATE)