""" Classroom API - Homework Routes Homework tracking endpoints (Feature f20). """ from uuid import uuid4 from typing import Dict, Optional from datetime import datetime import logging from fastapi import APIRouter, HTTPException, Query from classroom_engine import ( Homework, HomeworkStatus, ) from ..models import ( CreateHomeworkRequest, UpdateHomeworkRequest, HomeworkResponse, HomeworkListResponse, ) from ..services.persistence import ( init_db_if_needed, DB_ENABLED, SessionLocal, ) logger = logging.getLogger(__name__) router = APIRouter(tags=["Homework"]) # In-Memory Storage fuer Hausaufgaben (Fallback) _homework: Dict[str, Homework] = {} def _build_homework_response(hw: Homework) -> HomeworkResponse: """Baut eine HomeworkResponse aus einem Homework-Objekt.""" is_overdue = False if hw.due_date and hw.status != HomeworkStatus.COMPLETED: is_overdue = hw.due_date < datetime.utcnow() return HomeworkResponse( homework_id=hw.homework_id, teacher_id=hw.teacher_id, class_id=hw.class_id, subject=hw.subject, title=hw.title, description=hw.description, session_id=hw.session_id, due_date=hw.due_date.isoformat() if hw.due_date else None, status=hw.status.value, is_overdue=is_overdue, created_at=hw.created_at.isoformat() if hw.created_at else None, updated_at=hw.updated_at.isoformat() if hw.updated_at else None, ) @router.post("/homework", response_model=HomeworkResponse, status_code=201) async def create_homework(request: CreateHomeworkRequest) -> HomeworkResponse: """ Erstellt eine neue Hausaufgabe (Feature f20). Kann mit einer Session verknuepft werden. """ init_db_if_needed() due_date = None if request.due_date: try: due_date = datetime.fromisoformat(request.due_date.replace('Z', '+00:00')) except ValueError: raise HTTPException(status_code=400, detail="Ungueltiges Datumsformat") homework = Homework( homework_id=str(uuid4()), teacher_id=request.teacher_id, class_id=request.class_id, subject=request.subject, title=request.title, description=request.description, session_id=request.session_id, due_date=due_date, status=HomeworkStatus.ASSIGNED, created_at=datetime.utcnow(), ) # Persistieren wenn DB verfuegbar if DB_ENABLED: try: from classroom_engine.repository import HomeworkRepository db = SessionLocal() repo = HomeworkRepository(db) repo.create(homework) db.close() except Exception as e: logger.warning(f"DB persist failed for homework: {e}") _homework[homework.homework_id] = homework return _build_homework_response(homework) @router.get("/homework", response_model=HomeworkListResponse) async def list_homework( teacher_id: str = Query(None, description="Filter nach Lehrer"), class_id: str = Query(None, description="Filter nach Klasse"), status: Optional[str] = Query(None, description="Filter nach Status: assigned, in_progress, completed"), limit: int = Query(50, ge=1, le=100) ) -> HomeworkListResponse: """ Listet Hausaufgaben mit optionalen Filtern (Feature f20). """ init_db_if_needed() homework_list = [] # Aus DB laden wenn verfuegbar if DB_ENABLED: try: from classroom_engine.repository import HomeworkRepository db = SessionLocal() repo = HomeworkRepository(db) db_homework = repo.get_by_filters( teacher_id=teacher_id, class_id=class_id, status=status, limit=limit ) for db_hw in db_homework: hw = repo.to_dataclass(db_hw) _homework[hw.homework_id] = hw homework_list.append(_build_homework_response(hw)) db.close() return HomeworkListResponse(homework=homework_list, total=len(homework_list)) except Exception as e: logger.warning(f"DB read failed for homework: {e}") # Fallback auf Memory for hw in _homework.values(): if teacher_id and hw.teacher_id != teacher_id: continue if class_id and hw.class_id != class_id: continue if status: try: filter_status = HomeworkStatus(status) if hw.status != filter_status: continue except ValueError: pass homework_list.append(_build_homework_response(hw)) return HomeworkListResponse(homework=homework_list[:limit], total=len(homework_list)) @router.get("/homework/{homework_id}", response_model=HomeworkResponse) async def get_homework(homework_id: str) -> HomeworkResponse: """ Ruft eine einzelne Hausaufgabe ab (Feature f20). """ init_db_if_needed() # Aus Memory if homework_id in _homework: return _build_homework_response(_homework[homework_id]) # Aus DB laden if DB_ENABLED: try: from classroom_engine.repository import HomeworkRepository db = SessionLocal() repo = HomeworkRepository(db) db_hw = repo.get_by_id(homework_id) db.close() if db_hw: hw = repo.to_dataclass(db_hw) _homework[hw.homework_id] = hw return _build_homework_response(hw) except Exception as e: logger.warning(f"DB read failed: {e}") raise HTTPException(status_code=404, detail="Hausaufgabe nicht gefunden") @router.put("/homework/{homework_id}", response_model=HomeworkResponse) async def update_homework( homework_id: str, request: UpdateHomeworkRequest ) -> HomeworkResponse: """ Aktualisiert eine Hausaufgabe (Feature f20). """ init_db_if_needed() # Aus Memory holen homework = _homework.get(homework_id) # Aus DB laden wenn nicht in Memory if not homework and DB_ENABLED: try: from classroom_engine.repository import HomeworkRepository db = SessionLocal() repo = HomeworkRepository(db) db_hw = repo.get_by_id(homework_id) db.close() if db_hw: homework = repo.to_dataclass(db_hw) _homework[homework.homework_id] = homework except Exception as e: logger.warning(f"DB read failed: {e}") if not homework: raise HTTPException(status_code=404, detail="Hausaufgabe nicht gefunden") # Aktualisieren if request.title is not None: homework.title = request.title if request.description is not None: homework.description = request.description if request.due_date is not None: try: homework.due_date = datetime.fromisoformat(request.due_date.replace('Z', '+00:00')) except ValueError: raise HTTPException(status_code=400, detail="Ungueltiges Datumsformat") if request.status is not None: try: homework.status = HomeworkStatus(request.status) except ValueError: raise HTTPException(status_code=400, detail="Ungueltiger Status") homework.updated_at = datetime.utcnow() # In DB aktualisieren if DB_ENABLED: try: from classroom_engine.repository import HomeworkRepository db = SessionLocal() repo = HomeworkRepository(db) repo.update(homework) db.close() except Exception as e: logger.warning(f"DB update failed: {e}") _homework[homework_id] = homework return _build_homework_response(homework) @router.patch("/homework/{homework_id}/status") async def update_homework_status( homework_id: str, status: str = Query(..., description="Neuer Status: assigned, in_progress, completed") ) -> HomeworkResponse: """ Aktualisiert nur den Status einer Hausaufgabe (Feature f20). """ return await update_homework(homework_id, UpdateHomeworkRequest(status=status)) @router.delete("/homework/{homework_id}") async def delete_homework(homework_id: str) -> Dict[str, str]: """ Loescht eine Hausaufgabe (Feature f20). """ init_db_if_needed() if homework_id in _homework: del _homework[homework_id] if DB_ENABLED: try: from classroom_engine.repository import HomeworkRepository db = SessionLocal() repo = HomeworkRepository(db) repo.delete(homework_id) db.close() except Exception as e: logger.warning(f"DB delete failed: {e}") return {"status": "deleted", "homework_id": homework_id}