Files
breakpilot-compliance/backend-compliance/compliance/api/notfallplan_routes.py
Sharang Parnerkar 1a2ae896fb refactor(backend/api): extract Notfallplan schemas + services (Step 4)
Split notfallplan_routes.py (1018 LOC) into clean architecture layers:
- compliance/schemas/notfallplan.py (146 LOC): all Pydantic models
- compliance/services/notfallplan_service.py (500 LOC): contacts, scenarios, checklists, exercises, stats
- compliance/services/notfallplan_workflow_service.py (309 LOC): incidents, templates
- compliance/api/notfallplan_routes.py (361 LOC): thin handlers with domain error translation

All 250 tests pass. Schemas re-exported via __all__ for legacy test imports.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 20:10:43 +02:00

362 lines
11 KiB
Python

"""
FastAPI routes for Notfallplan (Emergency Plan) -- Art. 33/34 DSGVO.
Endpoints:
GET /notfallplan/contacts -- List emergency contacts
POST /notfallplan/contacts -- Create contact
PUT /notfallplan/contacts/{id} -- Update contact
DELETE /notfallplan/contacts/{id} -- Delete contact
GET /notfallplan/scenarios -- List scenarios
POST /notfallplan/scenarios -- Create scenario
PUT /notfallplan/scenarios/{id} -- Update scenario
DELETE /notfallplan/scenarios/{id} -- Delete scenario
GET /notfallplan/checklists -- List checklists
POST /notfallplan/checklists -- Create checklist item
PUT /notfallplan/checklists/{id} -- Update checklist item
DELETE /notfallplan/checklists/{id} -- Delete checklist item
GET /notfallplan/exercises -- List exercises
POST /notfallplan/exercises -- Create exercise
GET /notfallplan/stats -- Statistics overview
GET /notfallplan/incidents -- List incidents
POST /notfallplan/incidents -- Create incident
PUT /notfallplan/incidents/{id} -- Update incident
DELETE /notfallplan/incidents/{id} -- Delete incident
GET /notfallplan/templates -- List templates
POST /notfallplan/templates -- Create template
PUT /notfallplan/templates/{id} -- Update template
DELETE /notfallplan/templates/{id} -- Delete template
Phase 1 Step 4 refactor: handlers delegate to NotfallplanService and
NotfallplanWorkflowService. Schemas re-exported for legacy test imports.
"""
import logging
from typing import Optional
from fastapi import APIRouter, Depends, Header, Query
from sqlalchemy.orm import Session
from classroom_engine.database import get_db
from compliance.api._http_errors import translate_domain_errors
from compliance.schemas.notfallplan import ( # noqa: F401 -- re-export
ChecklistCreate,
ChecklistUpdate,
ContactCreate,
ContactUpdate,
ExerciseCreate,
IncidentCreate,
IncidentUpdate,
ScenarioCreate,
ScenarioUpdate,
TemplateCreate,
TemplateUpdate,
)
from compliance.services.notfallplan_service import NotfallplanService
from compliance.services.notfallplan_workflow_service import (
NotfallplanWorkflowService,
)
__all__ = [
"ContactCreate",
"ContactUpdate",
"ScenarioCreate",
"ScenarioUpdate",
"ChecklistCreate",
"ChecklistUpdate",
"ExerciseCreate",
"IncidentCreate",
"IncidentUpdate",
"TemplateCreate",
"TemplateUpdate",
"router",
]
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/notfallplan", tags=["notfallplan"])
# ============================================================================
# Dependencies
# ============================================================================
def _get_tenant(
x_tenant_id: Optional[str] = Header(None, alias="X-Tenant-ID"),
) -> str:
return x_tenant_id or "default"
def _get_service(db: Session = Depends(get_db)) -> NotfallplanService:
return NotfallplanService(db)
def _get_workflow(db: Session = Depends(get_db)) -> NotfallplanWorkflowService:
return NotfallplanWorkflowService(db)
# ============================================================================
# Contacts
# ============================================================================
@router.get("/contacts")
async def list_contacts(
svc: NotfallplanService = Depends(_get_service),
tenant_id: str = Depends(_get_tenant),
):
with translate_domain_errors():
return svc.list_contacts(tenant_id)
@router.post("/contacts", status_code=201)
async def create_contact(
request: ContactCreate,
svc: NotfallplanService = Depends(_get_service),
tenant_id: str = Depends(_get_tenant),
):
with translate_domain_errors():
return svc.create_contact(tenant_id, request)
@router.put("/contacts/{contact_id}")
async def update_contact(
contact_id: str,
request: ContactUpdate,
svc: NotfallplanService = Depends(_get_service),
tenant_id: str = Depends(_get_tenant),
):
with translate_domain_errors():
return svc.update_contact(tenant_id, contact_id, request)
@router.delete("/contacts/{contact_id}")
async def delete_contact(
contact_id: str,
svc: NotfallplanService = Depends(_get_service),
tenant_id: str = Depends(_get_tenant),
):
with translate_domain_errors():
return svc.delete_contact(tenant_id, contact_id)
# ============================================================================
# Scenarios
# ============================================================================
@router.get("/scenarios")
async def list_scenarios(
svc: NotfallplanService = Depends(_get_service),
tenant_id: str = Depends(_get_tenant),
):
with translate_domain_errors():
return svc.list_scenarios(tenant_id)
@router.post("/scenarios", status_code=201)
async def create_scenario(
request: ScenarioCreate,
svc: NotfallplanService = Depends(_get_service),
tenant_id: str = Depends(_get_tenant),
):
with translate_domain_errors():
return svc.create_scenario(tenant_id, request)
@router.put("/scenarios/{scenario_id}")
async def update_scenario(
scenario_id: str,
request: ScenarioUpdate,
svc: NotfallplanService = Depends(_get_service),
tenant_id: str = Depends(_get_tenant),
):
with translate_domain_errors():
return svc.update_scenario(tenant_id, scenario_id, request)
@router.delete("/scenarios/{scenario_id}")
async def delete_scenario(
scenario_id: str,
svc: NotfallplanService = Depends(_get_service),
tenant_id: str = Depends(_get_tenant),
):
with translate_domain_errors():
return svc.delete_scenario(tenant_id, scenario_id)
# ============================================================================
# Checklists
# ============================================================================
@router.get("/checklists")
async def list_checklists(
scenario_id: Optional[str] = Query(None),
svc: NotfallplanService = Depends(_get_service),
tenant_id: str = Depends(_get_tenant),
):
with translate_domain_errors():
return svc.list_checklists(tenant_id, scenario_id)
@router.post("/checklists", status_code=201)
async def create_checklist(
request: ChecklistCreate,
svc: NotfallplanService = Depends(_get_service),
tenant_id: str = Depends(_get_tenant),
):
with translate_domain_errors():
return svc.create_checklist(tenant_id, request)
@router.put("/checklists/{checklist_id}")
async def update_checklist(
checklist_id: str,
request: ChecklistUpdate,
svc: NotfallplanService = Depends(_get_service),
tenant_id: str = Depends(_get_tenant),
):
with translate_domain_errors():
return svc.update_checklist(tenant_id, checklist_id, request)
@router.delete("/checklists/{checklist_id}")
async def delete_checklist(
checklist_id: str,
svc: NotfallplanService = Depends(_get_service),
tenant_id: str = Depends(_get_tenant),
):
with translate_domain_errors():
return svc.delete_checklist(tenant_id, checklist_id)
# ============================================================================
# Exercises
# ============================================================================
@router.get("/exercises")
async def list_exercises(
svc: NotfallplanService = Depends(_get_service),
tenant_id: str = Depends(_get_tenant),
):
with translate_domain_errors():
return svc.list_exercises(tenant_id)
@router.post("/exercises", status_code=201)
async def create_exercise(
request: ExerciseCreate,
svc: NotfallplanService = Depends(_get_service),
tenant_id: str = Depends(_get_tenant),
):
with translate_domain_errors():
return svc.create_exercise(tenant_id, request)
# ============================================================================
# Stats
# ============================================================================
@router.get("/stats")
async def get_stats(
svc: NotfallplanService = Depends(_get_service),
tenant_id: str = Depends(_get_tenant),
):
with translate_domain_errors():
return svc.get_stats(tenant_id)
# ============================================================================
# Incidents
# ============================================================================
@router.get("/incidents")
async def list_incidents(
status: Optional[str] = None,
severity: Optional[str] = None,
wf: NotfallplanWorkflowService = Depends(_get_workflow),
tenant_id: str = Depends(_get_tenant),
):
with translate_domain_errors():
return wf.list_incidents(tenant_id, status, severity)
@router.post("/incidents", status_code=201)
async def create_incident(
request: IncidentCreate,
wf: NotfallplanWorkflowService = Depends(_get_workflow),
tenant_id: str = Depends(_get_tenant),
):
with translate_domain_errors():
return wf.create_incident(tenant_id, request)
@router.put("/incidents/{incident_id}")
async def update_incident(
incident_id: str,
request: IncidentUpdate,
wf: NotfallplanWorkflowService = Depends(_get_workflow),
tenant_id: str = Depends(_get_tenant),
):
with translate_domain_errors():
return wf.update_incident(tenant_id, incident_id, request)
@router.delete("/incidents/{incident_id}", status_code=204)
async def delete_incident(
incident_id: str,
wf: NotfallplanWorkflowService = Depends(_get_workflow),
tenant_id: str = Depends(_get_tenant),
):
with translate_domain_errors():
wf.delete_incident(tenant_id, incident_id)
# ============================================================================
# Templates
# ============================================================================
@router.get("/templates")
async def list_templates(
type: Optional[str] = None,
wf: NotfallplanWorkflowService = Depends(_get_workflow),
tenant_id: str = Depends(_get_tenant),
):
with translate_domain_errors():
return wf.list_templates(tenant_id, type)
@router.post("/templates", status_code=201)
async def create_template(
request: TemplateCreate,
wf: NotfallplanWorkflowService = Depends(_get_workflow),
tenant_id: str = Depends(_get_tenant),
):
with translate_domain_errors():
return wf.create_template(tenant_id, request)
@router.put("/templates/{template_id}")
async def update_template(
template_id: str,
request: TemplateUpdate,
wf: NotfallplanWorkflowService = Depends(_get_workflow),
tenant_id: str = Depends(_get_tenant),
):
with translate_domain_errors():
return wf.update_template(tenant_id, template_id, request)
@router.delete("/templates/{template_id}", status_code=204)
async def delete_template(
template_id: str,
wf: NotfallplanWorkflowService = Depends(_get_workflow),
tenant_id: str = Depends(_get_tenant),
):
with translate_domain_errors():
wf.delete_template(tenant_id, template_id)