""" 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")