from fastapi import FastAPI, Depends, HTTPException, status from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials from fastapi.middleware.cors import CORSMiddleware from sqlalchemy.orm import Session from pydantic import BaseModel from typing import List, Optional, Dict, Any import sys import os from datetime import datetime, timedelta import json sys.path.append(os.path.join(os.path.dirname(__file__), "../../../shared")) from database.connection import get_db from database.models import Question, Theme, Player, GameSession, GameAnswer from auth.oauth_handler import oauth_handler app = FastAPI(title="Admin Service", version="1.0.0") # Add CORS middleware app.add_middleware( CORSMiddleware, allow_origins=["http://localhost:3000", "http://localhost:5173"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) security = HTTPBearer() def verify_admin(credentials: HTTPAuthorizationCredentials = Depends(security)): """Verify admin authentication and permissions""" token = credentials.credentials payload = oauth_handler.verify_admin_token(token) if not payload: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid or expired authentication token", headers={"WWW-Authenticate": "Bearer"} ) return payload def require_permission(permission: str): """Decorator to require specific permission""" def permission_checker(credentials: HTTPAuthorizationCredentials = Depends(security)): payload = verify_admin(credentials) if not oauth_handler.has_permission(credentials.credentials, permission): raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail=f"Missing required permission: {permission}" ) return payload return permission_checker class QuestionAdmin(BaseModel): id: Optional[int] = None theme_id: int question_text: str correct_answer: str hint: Optional[str] = None difficulty_level: int = 1 class ThemeAdmin(BaseModel): id: Optional[int] = None name: str description: Optional[str] = None class GameStats(BaseModel): total_players: int total_games: int total_questions: int average_score: float most_played_theme: str total_answers: int correct_answers: int accuracy_rate: float class AuthRequest(BaseModel): auth0_token: str class BulkImportRequest(BaseModel): questions: List[Dict[str, Any]] theme_id: int class AnalyticsResponse(BaseModel): question_stats: List[Dict[str, Any]] theme_stats: List[Dict[str, Any]] player_stats: List[Dict[str, Any]] daily_stats: List[Dict[str, Any]] @app.get("/health") def health_check(): return {"status": "healthy", "service": "admin-service"} @app.post("/auth") def authenticate_admin(request: AuthRequest): """Authenticate admin user with Auth0 token""" try: admin_token = oauth_handler.authenticate_admin(request.auth0_token) if not admin_token: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Authentication failed" ) # Get user info from token user_info = oauth_handler.verify_admin_token(admin_token) return { "access_token": admin_token, "token_type": "bearer", "expires_in": 28800, # 8 hours "user": { "email": user_info.get("email"), "name": user_info.get("name"), "permissions": user_info.get("permissions", []) } } except PermissionError as e: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail=str(e) ) except Exception as e: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Authentication service error" ) @app.get("/profile") def get_admin_profile(admin_user=Depends(verify_admin)): """Get current admin user profile""" return { "email": admin_user.get("email"), "name": admin_user.get("name"), "role": admin_user.get("role"), "permissions": admin_user.get("permissions", []), "login_time": admin_user.get("iat") } @app.get("/stats", response_model=GameStats) def get_game_statistics( admin_user=Depends(require_permission("read:analytics")), db: Session = Depends(get_db) ): # Basic stats total_players = db.query(Player).count() total_games = db.query(GameSession).filter(GameSession.ended_at.isnot(None)).count() total_questions = db.query(Question).count() # Answer statistics total_answers = db.query(GameAnswer).count() correct_answers = db.query(GameAnswer).filter(GameAnswer.is_correct == True).count() accuracy_rate = (correct_answers / max(total_answers, 1)) * 100 # Score statistics completed_sessions = db.query(GameSession).filter(GameSession.ended_at.isnot(None)).all() average_score = sum(session.total_score for session in completed_sessions) / max(len(completed_sessions), 1) # Most played theme (by question answers) theme_query = db.query(Theme.name, db.func.count(GameAnswer.id).label('answer_count'))\ .join(Question, Theme.id == Question.theme_id)\ .join(GameAnswer, Question.id == GameAnswer.question_id)\ .group_by(Theme.id, Theme.name)\ .order_by(db.func.count(GameAnswer.id).desc())\ .first() most_played_theme = theme_query[0] if theme_query else "None" return GameStats( total_players=total_players, total_games=total_games, total_questions=total_questions, average_score=round(average_score, 2), most_played_theme=most_played_theme, total_answers=total_answers, correct_answers=correct_answers, accuracy_rate=round(accuracy_rate, 2) ) @app.get("/questions", response_model=List[dict]) def list_questions( admin_user = Depends(verify_admin), db: Session = Depends(get_db) ): questions = db.query(Question).join(Theme).all() return [ { "id": q.id, "theme_id": q.theme_id, "theme_name": q.theme.name, "question_text": q.question_text, "correct_answer": q.correct_answer, "hint": q.hint, "difficulty_level": q.difficulty_level } for q in questions ] @app.post("/questions") def create_question( question_data: QuestionAdmin, admin_user = Depends(verify_admin), db: Session = Depends(get_db) ): theme = db.query(Theme).filter(Theme.id == question_data.theme_id).first() if not theme: raise HTTPException(status_code=404, detail="Theme not found") question = Question( theme_id=question_data.theme_id, question_text=question_data.question_text, correct_answer=question_data.correct_answer, hint=question_data.hint, difficulty_level=question_data.difficulty_level ) db.add(question) db.commit() db.refresh(question) return {"id": question.id, "message": "Question created successfully"} @app.put("/questions/{question_id}") def update_question( question_id: int, question_data: QuestionAdmin, admin_user = Depends(verify_admin), db: Session = Depends(get_db) ): question = db.query(Question).filter(Question.id == question_id).first() if not question: raise HTTPException(status_code=404, detail="Question not found") question.theme_id = question_data.theme_id question.question_text = question_data.question_text question.correct_answer = question_data.correct_answer question.hint = question_data.hint question.difficulty_level = question_data.difficulty_level db.commit() return {"message": "Question updated successfully"} @app.delete("/questions/{question_id}") def delete_question( question_id: int, admin_user = Depends(verify_admin), db: Session = Depends(get_db) ): question = db.query(Question).filter(Question.id == question_id).first() if not question: raise HTTPException(status_code=404, detail="Question not found") db.delete(question) db.commit() return {"message": "Question deleted successfully"} @app.get("/themes", response_model=List[dict]) def list_themes( admin_user = Depends(verify_admin), db: Session = Depends(get_db) ): themes = db.query(Theme).all() return [{"id": theme.id, "name": theme.name, "description": theme.description} for theme in themes] @app.post("/themes") def create_theme( theme_data: ThemeAdmin, admin_user = Depends(verify_admin), db: Session = Depends(get_db) ): existing_theme = db.query(Theme).filter(Theme.name == theme_data.name).first() if existing_theme: raise HTTPException(status_code=400, detail="Theme already exists") theme = Theme(name=theme_data.name, description=theme_data.description) db.add(theme) db.commit() db.refresh(theme) return {"id": theme.id, "message": "Theme created successfully"} if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=8004)