feat(sdk): API-Referenz Frontend + Backend-Konsolidierung (Shared Utilities, CRUD Factory)
All checks were successful
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-ai-compliance (push) Successful in 32s
CI / test-python-backend-compliance (push) Successful in 30s
CI / test-python-document-crawler (push) Successful in 21s
CI / test-python-dsms-gateway (push) Successful in 18s

- API-Referenz Seite (/sdk/api-docs) mit ~690 Endpoints, Suche, Filter, Modul-Index
- Shared db_utils.py (row_to_dict) + tenant_utils Integration in 6 Route-Dateien
- CRUD Factory (crud_factory.py) fuer zukuenftige Module
- Version-Route Auto-Registration in versioning_utils.py
- 1338 Tests bestanden, -232 Zeilen Duplikat-Code

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-03-07 17:07:43 +01:00
parent 7ec6b9f6c0
commit 6509e64dd9
19 changed files with 1921 additions and 390 deletions

View File

@@ -14,7 +14,6 @@ Endpoints:
import logging
from datetime import datetime
from typing import Optional, List, Any, Dict
from uuid import UUID
from fastapi import APIRouter, Depends, HTTPException, Query, Header
from pydantic import BaseModel
@@ -22,12 +21,12 @@ from sqlalchemy import text
from sqlalchemy.orm import Session
from classroom_engine.database import get_db
from .tenant_utils import get_tenant_id as _get_tenant_id
from .db_utils import row_to_dict as _row_to_dict
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/obligations", tags=["obligations"])
DEFAULT_TENANT_ID = "9282a473-5c95-4b3a-bf78-0ecc0ec71d3e"
# =============================================================================
# Pydantic Schemas
@@ -65,25 +64,6 @@ class ObligationStatusUpdate(BaseModel):
status: str
def _row_to_dict(row) -> Dict[str, Any]:
result = dict(row._mapping)
for key, val in result.items():
if isinstance(val, datetime):
result[key] = val.isoformat()
elif hasattr(val, '__str__') and not isinstance(val, (str, int, float, bool, list, dict, type(None))):
result[key] = str(val)
return result
def _get_tenant_id(x_tenant_id: Optional[str] = Header(None)) -> str:
if x_tenant_id:
try:
UUID(x_tenant_id)
return x_tenant_id
except ValueError:
pass
return DEFAULT_TENANT_ID
# =============================================================================
# Routes
@@ -98,10 +78,9 @@ async def list_obligations(
limit: int = Query(100, ge=1, le=500),
offset: int = Query(0, ge=0),
db: Session = Depends(get_db),
x_tenant_id: Optional[str] = Header(None),
tenant_id: str = Depends(_get_tenant_id),
):
"""List obligations with optional filters."""
tenant_id = _get_tenant_id(x_tenant_id)
where_clauses = ["tenant_id = :tenant_id"]
params: Dict[str, Any] = {"tenant_id": tenant_id, "limit": limit, "offset": offset}
@@ -159,10 +138,9 @@ async def list_obligations(
@router.get("/stats")
async def get_obligation_stats(
db: Session = Depends(get_db),
x_tenant_id: Optional[str] = Header(None),
tenant_id: str = Depends(_get_tenant_id),
):
"""Return obligation counts per status and priority."""
tenant_id = _get_tenant_id(x_tenant_id)
rows = db.execute(text("""
SELECT
@@ -187,11 +165,10 @@ async def get_obligation_stats(
async def create_obligation(
payload: ObligationCreate,
db: Session = Depends(get_db),
x_tenant_id: Optional[str] = Header(None),
tenant_id: str = Depends(_get_tenant_id),
x_user_id: Optional[str] = Header(None),
):
"""Create a new compliance obligation."""
tenant_id = _get_tenant_id(x_tenant_id)
logger.info("create_obligation user_id=%s tenant_id=%s title=%s", x_user_id, tenant_id, payload.title)
import json
@@ -228,9 +205,8 @@ async def create_obligation(
async def get_obligation(
obligation_id: str,
db: Session = Depends(get_db),
x_tenant_id: Optional[str] = Header(None),
tenant_id: str = Depends(_get_tenant_id),
):
tenant_id = _get_tenant_id(x_tenant_id)
row = db.execute(text("""
SELECT * FROM compliance_obligations
WHERE id = :id AND tenant_id = :tenant_id
@@ -245,11 +221,10 @@ async def update_obligation(
obligation_id: str,
payload: ObligationUpdate,
db: Session = Depends(get_db),
x_tenant_id: Optional[str] = Header(None),
tenant_id: str = Depends(_get_tenant_id),
x_user_id: Optional[str] = Header(None),
):
"""Update an obligation's fields."""
tenant_id = _get_tenant_id(x_tenant_id)
logger.info("update_obligation user_id=%s tenant_id=%s id=%s", x_user_id, tenant_id, obligation_id)
import json
@@ -285,11 +260,10 @@ async def update_obligation_status(
obligation_id: str,
payload: ObligationStatusUpdate,
db: Session = Depends(get_db),
x_tenant_id: Optional[str] = Header(None),
tenant_id: str = Depends(_get_tenant_id),
x_user_id: Optional[str] = Header(None),
):
"""Quick status update for an obligation."""
tenant_id = _get_tenant_id(x_tenant_id)
logger.info("update_obligation_status user_id=%s tenant_id=%s id=%s status=%s", x_user_id, tenant_id, obligation_id, payload.status)
valid_statuses = {"pending", "in-progress", "completed", "overdue"}
if payload.status not in valid_statuses:
@@ -312,10 +286,9 @@ async def update_obligation_status(
async def delete_obligation(
obligation_id: str,
db: Session = Depends(get_db),
x_tenant_id: Optional[str] = Header(None),
tenant_id: str = Depends(_get_tenant_id),
x_user_id: Optional[str] = Header(None),
):
tenant_id = _get_tenant_id(x_tenant_id)
logger.info("delete_obligation user_id=%s tenant_id=%s id=%s", x_user_id, tenant_id, obligation_id)
result = db.execute(text("""
DELETE FROM compliance_obligations
@@ -334,11 +307,10 @@ async def delete_obligation(
async def list_obligation_versions(
obligation_id: str,
db: Session = Depends(get_db),
x_tenant_id: Optional[str] = Header(None),
tenant_id: str = Depends(_get_tenant_id),
):
"""List all versions for an Obligation."""
from .versioning_utils import list_versions
tenant_id = _get_tenant_id(x_tenant_id)
return list_versions(db, "obligation", obligation_id, tenant_id)
@@ -347,11 +319,10 @@ async def get_obligation_version(
obligation_id: str,
version_number: int,
db: Session = Depends(get_db),
x_tenant_id: Optional[str] = Header(None),
tenant_id: str = Depends(_get_tenant_id),
):
"""Get a specific Obligation version with full snapshot."""
from .versioning_utils import get_version
tenant_id = _get_tenant_id(x_tenant_id)
v = get_version(db, "obligation", obligation_id, version_number, tenant_id)
if not v:
raise HTTPException(status_code=404, detail=f"Version {version_number} not found")