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.
282 lines
8.7 KiB
Python
282 lines
8.7 KiB
Python
"""
|
|
Classroom API - Homework Endpoints.
|
|
|
|
Endpoints fuer Hausaufgaben-Tracking (Feature f20).
|
|
"""
|
|
|
|
from uuid import uuid4
|
|
from typing import Dict, List, Optional
|
|
from datetime import datetime
|
|
import logging
|
|
|
|
from fastapi import APIRouter, HTTPException, Query
|
|
from pydantic import BaseModel, Field
|
|
|
|
from classroom_engine import Homework, HomeworkStatus
|
|
|
|
from .shared import init_db_if_needed, DB_ENABLED, logger
|
|
|
|
try:
|
|
from classroom_engine.database import SessionLocal
|
|
from classroom_engine.repository import HomeworkRepository
|
|
except ImportError:
|
|
pass
|
|
|
|
router = APIRouter(tags=["Homework"])
|
|
|
|
# In-Memory Storage (Fallback)
|
|
_homework: Dict[str, Homework] = {}
|
|
|
|
|
|
# === Pydantic Models ===
|
|
|
|
class CreateHomeworkRequest(BaseModel):
|
|
"""Request zum Erstellen einer Hausaufgabe."""
|
|
teacher_id: str
|
|
class_id: str
|
|
subject: str
|
|
title: str = Field(..., max_length=300)
|
|
description: str = ""
|
|
session_id: Optional[str] = None
|
|
due_date: Optional[str] = Field(None, description="ISO-Format Datum")
|
|
|
|
|
|
class UpdateHomeworkRequest(BaseModel):
|
|
"""Request zum Aktualisieren einer Hausaufgabe."""
|
|
title: Optional[str] = Field(None, max_length=300)
|
|
description: Optional[str] = None
|
|
due_date: Optional[str] = None
|
|
status: Optional[str] = None
|
|
|
|
|
|
class HomeworkResponse(BaseModel):
|
|
"""Response fuer eine Hausaufgabe."""
|
|
homework_id: str
|
|
teacher_id: str
|
|
class_id: str
|
|
subject: str
|
|
title: str
|
|
description: str
|
|
session_id: Optional[str]
|
|
due_date: Optional[str]
|
|
status: str
|
|
is_overdue: bool
|
|
created_at: Optional[str]
|
|
updated_at: Optional[str]
|
|
|
|
|
|
class HomeworkListResponse(BaseModel):
|
|
"""Response fuer Liste von Hausaufgaben."""
|
|
homework: List[HomeworkResponse]
|
|
total: int
|
|
|
|
|
|
# === Helper Functions ===
|
|
|
|
def build_homework_response(hw: Homework) -> HomeworkResponse:
|
|
"""Baut eine HomeworkResponse aus einem Homework-Objekt."""
|
|
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=hw.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,
|
|
)
|
|
|
|
|
|
# === Endpoints ===
|
|
|
|
@router.post("/homework", response_model=HomeworkResponse, status_code=201)
|
|
async def create_homework(request: CreateHomeworkRequest) -> HomeworkResponse:
|
|
"""Erstellt eine neue Hausaufgabe (Feature f20)."""
|
|
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(),
|
|
)
|
|
|
|
if DB_ENABLED:
|
|
try:
|
|
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(...),
|
|
class_id: Optional[str] = Query(None),
|
|
status: Optional[str] = Query(None),
|
|
include_completed: bool = Query(False),
|
|
limit: int = Query(50, ge=1, le=100)
|
|
) -> HomeworkListResponse:
|
|
"""Listet Hausaufgaben eines Lehrers (Feature f20)."""
|
|
init_db_if_needed()
|
|
|
|
homework_list = []
|
|
|
|
if DB_ENABLED:
|
|
try:
|
|
db = SessionLocal()
|
|
repo = HomeworkRepository(db)
|
|
if class_id:
|
|
db_homework = repo.get_by_class(class_id, teacher_id, include_completed, limit)
|
|
else:
|
|
db_homework = repo.get_by_teacher(teacher_id, status, 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}")
|
|
|
|
for hw in _homework.values():
|
|
if hw.teacher_id != teacher_id:
|
|
continue
|
|
if class_id and hw.class_id != class_id:
|
|
continue
|
|
if status and hw.status.value != status:
|
|
continue
|
|
if not include_completed and hw.status == HomeworkStatus.COMPLETED:
|
|
continue
|
|
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()
|
|
|
|
if homework_id in _homework:
|
|
return build_homework_response(_homework[homework_id])
|
|
|
|
if DB_ENABLED:
|
|
try:
|
|
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()
|
|
|
|
homework = _homework.get(homework_id)
|
|
|
|
if not homework and DB_ENABLED:
|
|
try:
|
|
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")
|
|
|
|
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()
|
|
|
|
if DB_ENABLED:
|
|
try:
|
|
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(...)
|
|
) -> 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:
|
|
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}
|