""" Classroom API - Feedback Routes Teacher feedback endpoints (Phase 7). """ from uuid import uuid4 from typing import Dict, List, Any, Optional from datetime import datetime import logging from fastapi import APIRouter, HTTPException, Query, Request from ..models import ( FeedbackCreate, FeedbackResponse, FeedbackListResponse, FeedbackStatsResponse, ) from ..services.persistence import ( init_db_if_needed, DB_ENABLED, SessionLocal, ) logger = logging.getLogger(__name__) router = APIRouter(tags=["Feedback"]) # In-Memory Fallback wenn DB nicht verfuegbar _feedback_store: List[Dict[str, Any]] = [] async def get_optional_current_user(request: Request) -> Dict[str, Any]: """Gets current user from auth token if available.""" # Simplified - in production this would check JWT token return { "user_id": "anonymous", "name": "", "email": "", } @router.post("/feedback", response_model=FeedbackResponse, status_code=201) async def create_feedback( data: FeedbackCreate, request: Request, teacher_id: Optional[str] = Query(None, description="Lehrer-ID (optional, wird aus Auth Token gelesen)") ) -> FeedbackResponse: """ Erstellt neues Lehrer-Feedback. Ermoeglicht Lehrern, Bug-Reports, Feature-Requests und Verbesserungsvorschlaege direkt aus dem Lehrer-Frontend zu senden. Authentifizierung optional - wenn eingeloggt, wird User-ID automatisch verwendet. """ init_db_if_needed() # Auth: User aus Token holen oder Demo-User verwenden user = await get_optional_current_user(request) effective_teacher_id = teacher_id or user.get("user_id", "anonymous") feedback_id = str(uuid4()) created_at = datetime.utcnow() if DB_ENABLED: try: from classroom_engine.repository import TeacherFeedbackRepository with SessionLocal() as db: repo = TeacherFeedbackRepository(db) db_feedback = repo.create( teacher_id=effective_teacher_id, title=data.title, description=data.description, feedback_type=data.feedback_type, priority=data.priority, teacher_name=data.teacher_name or user.get("name", ""), teacher_email=data.teacher_email or user.get("email", ""), context_url=data.context_url, context_phase=data.context_phase, context_session_id=data.context_session_id, related_feature=data.related_feature, ) return FeedbackResponse( id=db_feedback.id, teacher_id=db_feedback.teacher_id, teacher_name=db_feedback.teacher_name, title=db_feedback.title, description=db_feedback.description, feedback_type=db_feedback.feedback_type.value, priority=db_feedback.priority.value, status=db_feedback.status.value, created_at=db_feedback.created_at.isoformat(), ) except Exception as e: logger.error(f"Failed to create feedback in DB: {e}") # Fallback: In-Memory Storage feedback = { "id": feedback_id, "teacher_id": effective_teacher_id, "teacher_name": data.teacher_name, "teacher_email": data.teacher_email, "title": data.title, "description": data.description, "feedback_type": data.feedback_type, "priority": data.priority, "status": "new", "related_feature": data.related_feature, "context_url": data.context_url, "context_phase": data.context_phase, "context_session_id": data.context_session_id, "response": None, "created_at": created_at.isoformat(), "updated_at": created_at.isoformat(), } _feedback_store.append(feedback) return FeedbackResponse( id=feedback_id, teacher_id=effective_teacher_id, teacher_name=data.teacher_name or user.get("name", ""), title=data.title, description=data.description, feedback_type=data.feedback_type, priority=data.priority, status="new", created_at=created_at.isoformat(), ) @router.get("/feedback", response_model=FeedbackListResponse) async def list_feedback( status: Optional[str] = Query(None, description="Filter nach Status"), feedback_type: Optional[str] = Query(None, description="Filter nach Typ"), limit: int = Query(100, ge=1, le=500), offset: int = Query(0, ge=0) ) -> FeedbackListResponse: """ Listet alle Feedbacks (fuer Developer Dashboard). """ init_db_if_needed() if DB_ENABLED: try: from classroom_engine.repository import TeacherFeedbackRepository with SessionLocal() as db: repo = TeacherFeedbackRepository(db) feedbacks = repo.get_all( status=status, feedback_type=feedback_type, limit=limit, offset=offset ) return FeedbackListResponse( feedbacks=[repo.to_dict(fb) for fb in feedbacks], total=len(feedbacks) ) except Exception as e: logger.error(f"Failed to list feedback from DB: {e}") # Fallback: In-Memory result = _feedback_store if status: result = [fb for fb in result if fb.get("status") == status] if feedback_type: result = [fb for fb in result if fb.get("feedback_type") == feedback_type] return FeedbackListResponse( feedbacks=result[offset:offset + limit], total=len(result) ) @router.get("/feedback/stats", response_model=FeedbackStatsResponse) async def get_feedback_stats() -> FeedbackStatsResponse: """ Gibt Feedback-Statistiken zurueck. """ init_db_if_needed() if DB_ENABLED: try: from classroom_engine.repository import TeacherFeedbackRepository with SessionLocal() as db: repo = TeacherFeedbackRepository(db) stats = repo.get_stats() return FeedbackStatsResponse(**stats) except Exception as e: logger.error(f"Failed to get feedback stats: {e}") # Fallback: In-Memory stats = { "total": len(_feedback_store), "by_status": {}, "by_type": {}, "by_priority": {}, } for fb in _feedback_store: stats["by_status"][fb["status"]] = stats["by_status"].get(fb["status"], 0) + 1 stats["by_type"][fb["feedback_type"]] = stats["by_type"].get(fb["feedback_type"], 0) + 1 stats["by_priority"][fb["priority"]] = stats["by_priority"].get(fb["priority"], 0) + 1 return FeedbackStatsResponse(**stats) @router.get("/feedback/{feedback_id}") async def get_feedback(feedback_id: str) -> Dict[str, Any]: """ Ruft ein einzelnes Feedback ab. """ init_db_if_needed() if DB_ENABLED: try: from classroom_engine.repository import TeacherFeedbackRepository with SessionLocal() as db: repo = TeacherFeedbackRepository(db) db_feedback = repo.get_by_id(feedback_id) if db_feedback: return repo.to_dict(db_feedback) except Exception as e: logger.error(f"Failed to get feedback: {e}") # Fallback: In-Memory for fb in _feedback_store: if fb["id"] == feedback_id: return fb raise HTTPException(status_code=404, detail="Feedback nicht gefunden") @router.put("/feedback/{feedback_id}/status") async def update_feedback_status( feedback_id: str, status: str = Query(..., description="Neuer Status"), response: Optional[str] = Query(None, description="Antwort"), responded_by: Optional[str] = Query(None, description="Wer antwortet") ) -> Dict[str, Any]: """ Aktualisiert den Status eines Feedbacks (fuer Entwickler). """ init_db_if_needed() valid_statuses = ["new", "acknowledged", "planned", "implemented", "declined"] if status not in valid_statuses: raise HTTPException( status_code=400, detail=f"Ungueltiger Status. Erlaubt: {valid_statuses}" ) if DB_ENABLED: try: from classroom_engine.repository import TeacherFeedbackRepository with SessionLocal() as db: repo = TeacherFeedbackRepository(db) db_feedback = repo.update_status( feedback_id=feedback_id, status=status, response=response, responded_by=responded_by ) if db_feedback: return repo.to_dict(db_feedback) except Exception as e: logger.error(f"Failed to update feedback status: {e}") # Fallback: In-Memory for fb in _feedback_store: if fb["id"] == feedback_id: fb["status"] = status if response: fb["response"] = response fb["responded_by"] = responded_by fb["responded_at"] = datetime.utcnow().isoformat() fb["updated_at"] = datetime.utcnow().isoformat() return fb raise HTTPException(status_code=404, detail="Feedback nicht gefunden")