Some checks failed
Tests / Go Tests (push) Has been cancelled
Tests / Python Tests (push) Has been cancelled
Tests / Integration Tests (push) Has been cancelled
Tests / Go Lint (push) Has been cancelled
Tests / Python Lint (push) Has been cancelled
Tests / Security Scan (push) Has been cancelled
Tests / All Checks Passed (push) Has been cancelled
Security Scanning / Secret Scanning (push) Has been cancelled
Security Scanning / Dependency Vulnerability Scan (push) Has been cancelled
Security Scanning / Go Security Scan (push) Has been cancelled
Security Scanning / Python Security Scan (push) Has been cancelled
Security Scanning / Node.js Security Scan (push) Has been cancelled
Security Scanning / Docker Image Security (push) Has been cancelled
Security Scanning / Security Summary (push) Has been cancelled
CI/CD Pipeline / Go Tests (push) Has been cancelled
CI/CD Pipeline / Python Tests (push) Has been cancelled
CI/CD Pipeline / Website Tests (push) Has been cancelled
CI/CD Pipeline / Linting (push) Has been cancelled
CI/CD Pipeline / Security Scan (push) Has been cancelled
CI/CD Pipeline / Docker Build & Push (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Deploy to Staging (push) Has been cancelled
CI/CD Pipeline / Deploy to Production (push) Has been cancelled
CI/CD Pipeline / CI Summary (push) Has been cancelled
ci/woodpecker/manual/build-ci-image Pipeline was successful
ci/woodpecker/manual/main Pipeline failed
All services: admin-v2, studio-v2, website, ai-compliance-sdk, consent-service, klausur-service, voice-service, and infrastructure. Large PDFs and compiled binaries excluded via .gitignore.
251 lines
8.9 KiB
Python
251 lines
8.9 KiB
Python
"""
|
|
School Service API Proxy
|
|
Routes API requests from /api/school/* to the Go school-service
|
|
"""
|
|
|
|
import os
|
|
import jwt
|
|
import datetime
|
|
import httpx
|
|
from fastapi import APIRouter, Request, HTTPException, Response
|
|
|
|
# School Service URL
|
|
SCHOOL_SERVICE_URL = os.getenv("SCHOOL_SERVICE_URL", "http://school-service:8084")
|
|
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="/school", tags=["school"])
|
|
|
|
|
|
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 school service"""
|
|
url = f"{SCHOOL_SERVICE_URL}/api/v1/school{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
|
|
body = None
|
|
if request.method in ("POST", "PUT", "PATCH"):
|
|
body = await request.body()
|
|
|
|
async with httpx.AsyncClient(timeout=30.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="School service unavailable"
|
|
)
|
|
except httpx.TimeoutException:
|
|
raise HTTPException(
|
|
status_code=504,
|
|
detail="School service timeout"
|
|
)
|
|
|
|
|
|
# Health check
|
|
@router.get("/health")
|
|
async def health():
|
|
"""Health check for school service connection"""
|
|
async with httpx.AsyncClient(timeout=5.0) as client:
|
|
try:
|
|
response = await client.get(f"{SCHOOL_SERVICE_URL}/health")
|
|
return {"school_service": "healthy", "connected": response.status_code == 200}
|
|
except Exception:
|
|
return {"school_service": "unhealthy", "connected": False}
|
|
|
|
|
|
# School Years
|
|
@router.api_route("/years", methods=["GET", "POST"])
|
|
async def years(request: Request):
|
|
return await proxy_request(request, "/years")
|
|
|
|
|
|
@router.api_route("/years/{year_id}", methods=["GET", "PUT", "DELETE"])
|
|
async def year_by_id(request: Request, year_id: str):
|
|
return await proxy_request(request, f"/years/{year_id}")
|
|
|
|
|
|
# Classes
|
|
@router.api_route("/classes", methods=["GET", "POST"])
|
|
async def classes(request: Request):
|
|
return await proxy_request(request, "/classes")
|
|
|
|
|
|
@router.api_route("/classes/{class_id}", methods=["GET", "PUT", "DELETE"])
|
|
async def class_by_id(request: Request, class_id: str):
|
|
return await proxy_request(request, f"/classes/{class_id}")
|
|
|
|
|
|
# Students
|
|
@router.api_route("/classes/{class_id}/students", methods=["GET", "POST"])
|
|
async def students(request: Request, class_id: str):
|
|
return await proxy_request(request, f"/classes/{class_id}/students")
|
|
|
|
|
|
@router.api_route("/classes/{class_id}/students/import", methods=["POST"])
|
|
async def import_students(request: Request, class_id: str):
|
|
return await proxy_request(request, f"/classes/{class_id}/students/import")
|
|
|
|
|
|
@router.api_route("/classes/{class_id}/students/{student_id}", methods=["GET", "PUT", "DELETE"])
|
|
async def student_by_id(request: Request, class_id: str, student_id: str):
|
|
return await proxy_request(request, f"/classes/{class_id}/students/{student_id}")
|
|
|
|
|
|
# Subjects
|
|
@router.api_route("/subjects", methods=["GET", "POST"])
|
|
async def subjects(request: Request):
|
|
return await proxy_request(request, "/subjects")
|
|
|
|
|
|
@router.api_route("/subjects/{subject_id}", methods=["GET", "PUT", "DELETE"])
|
|
async def subject_by_id(request: Request, subject_id: str):
|
|
return await proxy_request(request, f"/subjects/{subject_id}")
|
|
|
|
|
|
# Exams
|
|
@router.api_route("/exams", methods=["GET", "POST"])
|
|
async def exams(request: Request):
|
|
return await proxy_request(request, "/exams")
|
|
|
|
|
|
@router.api_route("/exams/{exam_id}", methods=["GET", "PUT", "DELETE"])
|
|
async def exam_by_id(request: Request, exam_id: str):
|
|
return await proxy_request(request, f"/exams/{exam_id}")
|
|
|
|
|
|
@router.api_route("/exams/{exam_id}/results", methods=["GET", "POST"])
|
|
async def exam_results(request: Request, exam_id: str):
|
|
return await proxy_request(request, f"/exams/{exam_id}/results")
|
|
|
|
|
|
@router.api_route("/exams/{exam_id}/results/{student_id}", methods=["PUT"])
|
|
async def exam_result_update(request: Request, exam_id: str, student_id: str):
|
|
return await proxy_request(request, f"/exams/{exam_id}/results/{student_id}")
|
|
|
|
|
|
@router.api_route("/exams/{exam_id}/results/{student_id}/approve", methods=["PUT"])
|
|
async def approve_result(request: Request, exam_id: str, student_id: str):
|
|
return await proxy_request(request, f"/exams/{exam_id}/results/{student_id}/approve")
|
|
|
|
|
|
@router.api_route("/exams/{exam_id}/generate-variant", methods=["POST"])
|
|
async def generate_variant(request: Request, exam_id: str):
|
|
return await proxy_request(request, f"/exams/{exam_id}/generate-variant")
|
|
|
|
|
|
# Grades
|
|
@router.api_route("/grades/{class_id}", methods=["GET"])
|
|
async def grades_by_class(request: Request, class_id: str):
|
|
return await proxy_request(request, f"/grades/{class_id}")
|
|
|
|
|
|
@router.api_route("/grades/student/{student_id}", methods=["GET"])
|
|
async def grades_by_student(request: Request, student_id: str):
|
|
return await proxy_request(request, f"/grades/student/{student_id}")
|
|
|
|
|
|
@router.api_route("/grades/{student_id}/{subject_id}/oral", methods=["PUT"])
|
|
async def update_oral_grade(request: Request, student_id: str, subject_id: str):
|
|
return await proxy_request(request, f"/grades/{student_id}/{subject_id}/oral")
|
|
|
|
|
|
@router.api_route("/grades/calculate", methods=["POST"])
|
|
async def calculate_grades(request: Request):
|
|
return await proxy_request(request, "/grades/calculate")
|
|
|
|
|
|
# Attendance
|
|
@router.api_route("/attendance/{class_id}", methods=["GET"])
|
|
async def attendance_by_class(request: Request, class_id: str):
|
|
return await proxy_request(request, f"/attendance/{class_id}")
|
|
|
|
|
|
@router.api_route("/attendance", methods=["POST"])
|
|
async def create_attendance(request: Request):
|
|
return await proxy_request(request, "/attendance")
|
|
|
|
|
|
@router.api_route("/attendance/bulk", methods=["POST"])
|
|
async def bulk_attendance(request: Request):
|
|
return await proxy_request(request, "/attendance/bulk")
|
|
|
|
|
|
# Gradebook
|
|
@router.api_route("/gradebook/{class_id}", methods=["GET"])
|
|
async def gradebook_by_class(request: Request, class_id: str):
|
|
return await proxy_request(request, f"/gradebook/{class_id}")
|
|
|
|
|
|
@router.api_route("/gradebook", methods=["POST"])
|
|
async def create_gradebook_entry(request: Request):
|
|
return await proxy_request(request, "/gradebook")
|
|
|
|
|
|
@router.api_route("/gradebook/{entry_id}", methods=["DELETE"])
|
|
async def delete_gradebook_entry(request: Request, entry_id: str):
|
|
return await proxy_request(request, f"/gradebook/{entry_id}")
|
|
|
|
|
|
# Certificates
|
|
@router.api_route("/certificates/templates", methods=["GET"])
|
|
async def certificate_templates(request: Request):
|
|
return await proxy_request(request, "/certificates/templates")
|
|
|
|
|
|
@router.api_route("/certificates/generate", methods=["POST"])
|
|
async def generate_certificate(request: Request):
|
|
return await proxy_request(request, "/certificates/generate")
|
|
|
|
|
|
@router.api_route("/certificates/{certificate_id}", methods=["GET"])
|
|
async def certificate_by_id(request: Request, certificate_id: str):
|
|
return await proxy_request(request, f"/certificates/{certificate_id}")
|
|
|
|
|
|
@router.api_route("/certificates/{certificate_id}/pdf", methods=["GET"])
|
|
async def certificate_pdf(request: Request, certificate_id: str):
|
|
return await proxy_request(request, f"/certificates/{certificate_id}/pdf")
|
|
|
|
|
|
@router.api_route("/certificates/{certificate_id}/finalize", methods=["PUT"])
|
|
async def finalize_certificate(request: Request, certificate_id: str):
|
|
return await proxy_request(request, f"/certificates/{certificate_id}/finalize")
|