You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

312 lines
9.2 KiB
Python

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)