A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.
This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).
Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
136 lines
4.7 KiB
Python
136 lines
4.7 KiB
Python
"""
|
|
Klausur-Service API Proxy
|
|
Routes API requests from /api/klausur/* to the klausur-service microservice
|
|
"""
|
|
|
|
import os
|
|
import jwt
|
|
import datetime
|
|
import httpx
|
|
from fastapi import APIRouter, Request, HTTPException, Response
|
|
|
|
# Klausur Service URL
|
|
KLAUSUR_SERVICE_URL = os.getenv("KLAUSUR_SERVICE_URL", "http://klausur-service:8086")
|
|
JWT_SECRET = os.getenv("JWT_SECRET", "your-super-secret-jwt-key-change-in-production")
|
|
ENVIRONMENT = os.getenv("ENVIRONMENT", "development")
|
|
|
|
# Demo teacher UUID for development mode
|
|
DEMO_TEACHER_ID = "e9484ad9-32ee-4f2b-a4e1-d182e02ccf20"
|
|
|
|
router = APIRouter(prefix="/klausur", tags=["klausur"])
|
|
|
|
|
|
def get_demo_token() -> str:
|
|
"""Generate a demo JWT token for development mode"""
|
|
payload = {
|
|
"user_id": DEMO_TEACHER_ID,
|
|
"email": "demo@breakpilot.app",
|
|
"role": "admin",
|
|
"exp": datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(hours=24),
|
|
"iat": datetime.datetime.now(datetime.timezone.utc)
|
|
}
|
|
return jwt.encode(payload, JWT_SECRET, algorithm="HS256")
|
|
|
|
|
|
async def proxy_request(request: Request, path: str) -> Response:
|
|
"""Forward a request to the klausur service"""
|
|
url = f"{KLAUSUR_SERVICE_URL}/api/v1{path}"
|
|
|
|
# Forward headers, especially Authorization
|
|
headers = {}
|
|
if "authorization" in request.headers:
|
|
headers["Authorization"] = request.headers["authorization"]
|
|
elif ENVIRONMENT == "development":
|
|
# In development mode, use demo token if no auth provided
|
|
demo_token = get_demo_token()
|
|
headers["Authorization"] = f"Bearer {demo_token}"
|
|
if "content-type" in request.headers:
|
|
headers["Content-Type"] = request.headers["content-type"]
|
|
|
|
# Get request body for POST/PUT/PATCH/DELETE
|
|
body = None
|
|
if request.method in ("POST", "PUT", "PATCH", "DELETE"):
|
|
body = await request.body()
|
|
|
|
async with httpx.AsyncClient(timeout=60.0) as client:
|
|
try:
|
|
response = await client.request(
|
|
method=request.method,
|
|
url=url,
|
|
headers=headers,
|
|
content=body,
|
|
params=request.query_params
|
|
)
|
|
return Response(
|
|
content=response.content,
|
|
status_code=response.status_code,
|
|
headers=dict(response.headers),
|
|
media_type=response.headers.get("content-type", "application/json")
|
|
)
|
|
except httpx.ConnectError:
|
|
raise HTTPException(
|
|
status_code=503,
|
|
detail="Klausur service unavailable"
|
|
)
|
|
except httpx.TimeoutException:
|
|
raise HTTPException(
|
|
status_code=504,
|
|
detail="Klausur service timeout"
|
|
)
|
|
|
|
|
|
# Health check
|
|
@router.get("/health")
|
|
async def health():
|
|
"""Health check for klausur service connection"""
|
|
async with httpx.AsyncClient(timeout=5.0) as client:
|
|
try:
|
|
response = await client.get(f"{KLAUSUR_SERVICE_URL}/health")
|
|
return {"klausur_service": "healthy", "connected": response.status_code == 200}
|
|
except Exception:
|
|
return {"klausur_service": "unhealthy", "connected": False}
|
|
|
|
|
|
# Klausuren
|
|
@router.api_route("/klausuren", methods=["GET", "POST"])
|
|
async def klausuren(request: Request):
|
|
return await proxy_request(request, "/klausuren")
|
|
|
|
|
|
@router.api_route("/klausuren/{klausur_id}", methods=["GET", "PUT", "DELETE"])
|
|
async def klausur_by_id(klausur_id: str, request: Request):
|
|
return await proxy_request(request, f"/klausuren/{klausur_id}")
|
|
|
|
|
|
# Students
|
|
@router.api_route("/klausuren/{klausur_id}/students", methods=["GET", "POST"])
|
|
async def klausur_students(klausur_id: str, request: Request):
|
|
return await proxy_request(request, f"/klausuren/{klausur_id}/students")
|
|
|
|
|
|
@router.api_route("/students/{student_id}", methods=["GET", "DELETE"])
|
|
async def student_by_id(student_id: str, request: Request):
|
|
return await proxy_request(request, f"/students/{student_id}")
|
|
|
|
|
|
# Grading
|
|
@router.api_route("/students/{student_id}/criteria", methods=["PUT"])
|
|
async def update_criteria(student_id: str, request: Request):
|
|
return await proxy_request(request, f"/students/{student_id}/criteria")
|
|
|
|
|
|
@router.api_route("/students/{student_id}/gutachten", methods=["PUT"])
|
|
async def update_gutachten(student_id: str, request: Request):
|
|
return await proxy_request(request, f"/students/{student_id}/gutachten")
|
|
|
|
|
|
@router.api_route("/students/{student_id}/finalize", methods=["POST"])
|
|
async def finalize_student(student_id: str, request: Request):
|
|
return await proxy_request(request, f"/students/{student_id}/finalize")
|
|
|
|
|
|
# Grade info
|
|
@router.get("/grade-info")
|
|
async def grade_info(request: Request):
|
|
return await proxy_request(request, "/grade-info")
|