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
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:
@@ -13,7 +13,7 @@ Endpoints:
|
||||
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from typing import Optional, List, Any, Dict
|
||||
from typing import Optional, Any, Dict
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, Header
|
||||
from pydantic import BaseModel
|
||||
@@ -21,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="/escalations", tags=["escalations"])
|
||||
|
||||
DEFAULT_TENANT_ID = '9282a473-5c95-4b3a-bf78-0ecc0ec71d3e'
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Pydantic Schemas
|
||||
@@ -59,17 +59,6 @@ class EscalationStatusUpdate(BaseModel):
|
||||
resolved_at: Optional[datetime] = None
|
||||
|
||||
|
||||
def _row_to_dict(row) -> Dict[str, Any]:
|
||||
"""Convert a SQLAlchemy row to a serialisable dict."""
|
||||
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, type(None))):
|
||||
result[key] = str(val)
|
||||
return result
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Routes
|
||||
# =============================================================================
|
||||
@@ -80,14 +69,12 @@ async def list_escalations(
|
||||
priority: Optional[str] = Query(None),
|
||||
limit: int = Query(50, ge=1, le=500),
|
||||
offset: int = Query(0, ge=0),
|
||||
tenant_id: Optional[str] = Header(None, alias="x-tenant-id"),
|
||||
tenant_id: str = Depends(_get_tenant_id),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""List escalations with optional filters."""
|
||||
tid = tenant_id or DEFAULT_TENANT_ID
|
||||
|
||||
where_clauses = ["tenant_id = :tenant_id"]
|
||||
params: Dict[str, Any] = {"tenant_id": tid, "limit": limit, "offset": offset}
|
||||
params: Dict[str, Any] = {"tenant_id": tenant_id, "limit": limit, "offset": offset}
|
||||
|
||||
if status:
|
||||
where_clauses.append("status = :status")
|
||||
@@ -122,13 +109,11 @@ async def list_escalations(
|
||||
@router.post("", status_code=201)
|
||||
async def create_escalation(
|
||||
request: EscalationCreate,
|
||||
tenant_id: Optional[str] = Header(None, alias="x-tenant-id"),
|
||||
tenant_id: str = Depends(_get_tenant_id),
|
||||
user_id: Optional[str] = Header(None, alias="x-user-id"),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""Create a new escalation."""
|
||||
tid = tenant_id or DEFAULT_TENANT_ID
|
||||
|
||||
row = db.execute(
|
||||
text(
|
||||
"""
|
||||
@@ -142,7 +127,7 @@ async def create_escalation(
|
||||
"""
|
||||
),
|
||||
{
|
||||
"tenant_id": tid,
|
||||
"tenant_id": tenant_id,
|
||||
"title": request.title,
|
||||
"description": request.description,
|
||||
"priority": request.priority,
|
||||
@@ -161,18 +146,16 @@ async def create_escalation(
|
||||
|
||||
@router.get("/stats")
|
||||
async def get_stats(
|
||||
tenant_id: Optional[str] = Header(None, alias="x-tenant-id"),
|
||||
tenant_id: str = Depends(_get_tenant_id),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""Return counts per status and priority."""
|
||||
tid = tenant_id or DEFAULT_TENANT_ID
|
||||
|
||||
status_rows = db.execute(
|
||||
text(
|
||||
"SELECT status, COUNT(*) as cnt FROM compliance_escalations "
|
||||
"WHERE tenant_id = :tenant_id GROUP BY status"
|
||||
),
|
||||
{"tenant_id": tid},
|
||||
{"tenant_id": tenant_id},
|
||||
).fetchall()
|
||||
|
||||
priority_rows = db.execute(
|
||||
@@ -180,12 +163,12 @@ async def get_stats(
|
||||
"SELECT priority, COUNT(*) as cnt FROM compliance_escalations "
|
||||
"WHERE tenant_id = :tenant_id GROUP BY priority"
|
||||
),
|
||||
{"tenant_id": tid},
|
||||
{"tenant_id": tenant_id},
|
||||
).fetchall()
|
||||
|
||||
total_row = db.execute(
|
||||
text("SELECT COUNT(*) FROM compliance_escalations WHERE tenant_id = :tenant_id"),
|
||||
{"tenant_id": tid},
|
||||
{"tenant_id": tenant_id},
|
||||
).fetchone()
|
||||
|
||||
active_row = db.execute(
|
||||
@@ -193,7 +176,7 @@ async def get_stats(
|
||||
"SELECT COUNT(*) FROM compliance_escalations "
|
||||
"WHERE tenant_id = :tenant_id AND status NOT IN ('resolved', 'closed')"
|
||||
),
|
||||
{"tenant_id": tid},
|
||||
{"tenant_id": tenant_id},
|
||||
).fetchone()
|
||||
|
||||
by_status = {"open": 0, "in_progress": 0, "escalated": 0, "resolved": 0, "closed": 0}
|
||||
@@ -217,17 +200,16 @@ async def get_stats(
|
||||
@router.get("/{escalation_id}")
|
||||
async def get_escalation(
|
||||
escalation_id: str,
|
||||
tenant_id: Optional[str] = Header(None, alias="x-tenant-id"),
|
||||
tenant_id: str = Depends(_get_tenant_id),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""Get a single escalation by ID."""
|
||||
tid = tenant_id or DEFAULT_TENANT_ID
|
||||
row = db.execute(
|
||||
text(
|
||||
"SELECT * FROM compliance_escalations "
|
||||
"WHERE id = :id AND tenant_id = :tenant_id"
|
||||
),
|
||||
{"id": escalation_id, "tenant_id": tid},
|
||||
{"id": escalation_id, "tenant_id": tenant_id},
|
||||
).fetchone()
|
||||
if not row:
|
||||
raise HTTPException(status_code=404, detail=f"Escalation {escalation_id} not found")
|
||||
@@ -238,18 +220,16 @@ async def get_escalation(
|
||||
async def update_escalation(
|
||||
escalation_id: str,
|
||||
request: EscalationUpdate,
|
||||
tenant_id: Optional[str] = Header(None, alias="x-tenant-id"),
|
||||
tenant_id: str = Depends(_get_tenant_id),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""Update an escalation's fields."""
|
||||
tid = tenant_id or DEFAULT_TENANT_ID
|
||||
|
||||
existing = db.execute(
|
||||
text(
|
||||
"SELECT id FROM compliance_escalations "
|
||||
"WHERE id = :id AND tenant_id = :tenant_id"
|
||||
),
|
||||
{"id": escalation_id, "tenant_id": tid},
|
||||
{"id": escalation_id, "tenant_id": tenant_id},
|
||||
).fetchone()
|
||||
if not existing:
|
||||
raise HTTPException(status_code=404, detail=f"Escalation {escalation_id} not found")
|
||||
@@ -281,18 +261,16 @@ async def update_escalation(
|
||||
async def update_status(
|
||||
escalation_id: str,
|
||||
request: EscalationStatusUpdate,
|
||||
tenant_id: Optional[str] = Header(None, alias="x-tenant-id"),
|
||||
tenant_id: str = Depends(_get_tenant_id),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""Update only the status of an escalation."""
|
||||
tid = tenant_id or DEFAULT_TENANT_ID
|
||||
|
||||
existing = db.execute(
|
||||
text(
|
||||
"SELECT id FROM compliance_escalations "
|
||||
"WHERE id = :id AND tenant_id = :tenant_id"
|
||||
),
|
||||
{"id": escalation_id, "tenant_id": tid},
|
||||
{"id": escalation_id, "tenant_id": tenant_id},
|
||||
).fetchone()
|
||||
if not existing:
|
||||
raise HTTPException(status_code=404, detail=f"Escalation {escalation_id} not found")
|
||||
@@ -321,18 +299,16 @@ async def update_status(
|
||||
@router.delete("/{escalation_id}")
|
||||
async def delete_escalation(
|
||||
escalation_id: str,
|
||||
tenant_id: Optional[str] = Header(None, alias="x-tenant-id"),
|
||||
tenant_id: str = Depends(_get_tenant_id),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""Delete an escalation."""
|
||||
tid = tenant_id or DEFAULT_TENANT_ID
|
||||
|
||||
existing = db.execute(
|
||||
text(
|
||||
"SELECT id FROM compliance_escalations "
|
||||
"WHERE id = :id AND tenant_id = :tenant_id"
|
||||
),
|
||||
{"id": escalation_id, "tenant_id": tid},
|
||||
{"id": escalation_id, "tenant_id": tenant_id},
|
||||
).fetchone()
|
||||
if not existing:
|
||||
raise HTTPException(status_code=404, detail=f"Escalation {escalation_id} not found")
|
||||
|
||||
Reference in New Issue
Block a user