Files
gt-ai-os-community/apps/tenant-backend/app/api/v1/games.py
HackWeasel b9dfb86260 GT AI OS Community Edition v2.0.33
Security hardening release addressing CodeQL and Dependabot alerts:

- Fix stack trace exposure in error responses
- Add SSRF protection with DNS resolution checking
- Implement proper URL hostname validation (replaces substring matching)
- Add centralized path sanitization to prevent path traversal
- Fix ReDoS vulnerability in email validation regex
- Improve HTML sanitization in validation utilities
- Fix capability wildcard matching in auth utilities
- Update glob dependency to address CVE
- Add CodeQL suppression comments for verified false positives

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-12 17:04:45 -05:00

520 lines
18 KiB
Python

from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.ext.asyncio import AsyncSession
from typing import List, Optional, Dict, Any
from pydantic import BaseModel, Field
from datetime import datetime
from app.core.database import get_db
from app.api.auth import get_current_user
from app.services.game_service import GameService, PuzzleService, PhilosophicalDialogueService
from app.models.game import GameSession, PuzzleSession, PhilosophicalDialogue, LearningAnalytics
router = APIRouter(prefix="/games", tags=["AI Literacy & Games"])
# Request/Response Models
class GameConfigRequest(BaseModel):
game_type: str = Field(..., description="Type of game: chess, go")
difficulty: str = Field(default="intermediate", description="Difficulty level")
name: Optional[str] = Field(None, description="Custom game name")
ai_personality: Optional[str] = Field(default="teaching", description="AI opponent personality")
time_control: Optional[str] = Field(None, description="Time control settings")
class GameMoveRequest(BaseModel):
move_data: Dict[str, Any] = Field(..., description="Move data specific to game type")
request_analysis: Optional[bool] = Field(default=False, description="Request move analysis")
class PuzzleConfigRequest(BaseModel):
puzzle_type: str = Field(..., description="Type of puzzle: lateral_thinking, logical_deduction, etc.")
difficulty: Optional[int] = Field(None, description="Difficulty level 1-10", ge=1, le=10)
category: Optional[str] = Field(None, description="Puzzle category")
class PuzzleSolutionRequest(BaseModel):
solution: Dict[str, Any] = Field(..., description="Puzzle solution attempt")
reasoning: Optional[str] = Field(None, description="User's reasoning explanation")
class HintRequest(BaseModel):
hint_level: int = Field(default=1, description="Hint level 1-3", ge=1, le=3)
class DilemmaConfigRequest(BaseModel):
dilemma_type: str = Field(..., description="Type of dilemma: ethical_frameworks, game_theory, ai_consciousness")
topic: str = Field(..., description="Specific topic within the dilemma type")
complexity: Optional[str] = Field(default="intermediate", description="Complexity level")
class DilemmaResponseRequest(BaseModel):
response: str = Field(..., description="User's response to the dilemma")
framework: Optional[str] = Field(None, description="Ethical framework being applied")
class DilemmaFinalPositionRequest(BaseModel):
final_position: Dict[str, Any] = Field(..., description="User's final position on the dilemma")
key_insights: Optional[List[str]] = Field(default=[], description="Key insights gained")
# Strategic Games Endpoints
@router.get("/available")
async def get_available_games(
db: AsyncSession = Depends(get_db),
current_user = Depends(get_current_user)
):
"""Get available games and user progress"""
service = GameService(db)
return await service.get_available_games(current_user["user_id"])
@router.post("/start")
async def start_game_session(
config: GameConfigRequest,
db: AsyncSession = Depends(get_db),
current_user = Depends(get_current_user)
):
"""Start a new game session"""
service = GameService(db)
try:
session = await service.start_game_session(
user_id=current_user["user_id"],
game_type=config.game_type,
config=config.dict()
)
return {
"session_id": session.id,
"game_type": session.game_type,
"difficulty": session.difficulty_level,
"initial_state": session.current_state,
"ai_config": session.ai_opponent_config,
"started_at": session.started_at.isoformat()
}
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
@router.get("/session/{session_id}")
async def get_game_session(
session_id: str,
db: AsyncSession = Depends(get_db),
current_user = Depends(get_current_user)
):
"""Get current game session details"""
service = GameService(db)
try:
analysis = await service.get_game_analysis(session_id, current_user["user_id"])
return analysis
except ValueError as e:
raise HTTPException(status_code=404, detail=str(e))
@router.post("/session/{session_id}/move")
async def make_move(
session_id: str,
move: GameMoveRequest,
db: AsyncSession = Depends(get_db),
current_user = Depends(get_current_user)
):
"""Make a move in the game"""
service = GameService(db)
try:
result = await service.make_move(
session_id=session_id,
user_id=current_user["user_id"],
move_data=move.move_data
)
return result
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
@router.get("/session/{session_id}/analysis")
async def get_game_analysis(
session_id: str,
db: AsyncSession = Depends(get_db),
current_user = Depends(get_current_user)
):
"""Get detailed game analysis"""
service = GameService(db)
try:
analysis = await service.get_game_analysis(session_id, current_user["user_id"])
return analysis
except ValueError as e:
raise HTTPException(status_code=404, detail=str(e))
@router.get("/history")
async def get_game_history(
game_type: Optional[str] = Query(None, description="Filter by game type"),
limit: int = Query(20, description="Number of games to return", ge=1, le=100),
db: AsyncSession = Depends(get_db),
current_user = Depends(get_current_user)
):
"""Get user's game history"""
service = GameService(db)
return await service.get_user_game_history(
user_id=current_user["user_id"],
game_type=game_type,
limit=limit
)
# Logic Puzzles Endpoints
@router.get("/puzzles/available")
async def get_available_puzzles(
category: Optional[str] = Query(None, description="Filter by puzzle category"),
db: AsyncSession = Depends(get_db),
current_user = Depends(get_current_user)
):
"""Get available puzzle categories and difficulty recommendations"""
service = PuzzleService(db)
return await service.get_available_puzzles(current_user["user_id"], category)
@router.post("/puzzles/start")
async def start_puzzle_session(
config: PuzzleConfigRequest,
db: AsyncSession = Depends(get_db),
current_user = Depends(get_current_user)
):
"""Start a new puzzle session"""
service = PuzzleService(db)
try:
session = await service.start_puzzle_session(
user_id=current_user["user_id"],
puzzle_type=config.puzzle_type,
difficulty=config.difficulty
)
return {
"session_id": session.id,
"puzzle_type": session.puzzle_type,
"difficulty": session.difficulty_rating,
"puzzle": session.puzzle_definition,
"estimated_time": session.estimated_time_minutes,
"started_at": session.started_at.isoformat()
}
except Exception as e:
raise HTTPException(status_code=400, detail=str(e))
@router.post("/puzzles/{session_id}/solve")
async def submit_puzzle_solution(
session_id: str,
solution: PuzzleSolutionRequest,
db: AsyncSession = Depends(get_db),
current_user = Depends(get_current_user)
):
"""Submit a solution for the puzzle"""
service = PuzzleService(db)
try:
result = await service.submit_solution(
session_id=session_id,
user_id=current_user["user_id"],
solution=solution.solution,
reasoning=solution.reasoning
)
return result
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
@router.post("/puzzles/{session_id}/hint")
async def get_puzzle_hint(
session_id: str,
hint_request: HintRequest,
db: AsyncSession = Depends(get_db),
current_user = Depends(get_current_user)
):
"""Get a hint for the current puzzle"""
service = PuzzleService(db)
try:
hint = await service.get_hint(
session_id=session_id,
user_id=current_user["user_id"],
hint_level=hint_request.hint_level
)
return hint
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
# Philosophical Dilemmas Endpoints
@router.get("/dilemmas/available")
async def get_available_dilemmas(
db: AsyncSession = Depends(get_db),
current_user = Depends(get_current_user)
):
"""Get available philosophical dilemmas"""
service = PhilosophicalDialogueService(db)
return await service.get_available_dilemmas(current_user["user_id"])
@router.post("/dilemmas/start")
async def start_philosophical_dialogue(
config: DilemmaConfigRequest,
db: AsyncSession = Depends(get_db),
current_user = Depends(get_current_user)
):
"""Start a new philosophical dialogue session"""
service = PhilosophicalDialogueService(db)
try:
dialogue = await service.start_dialogue_session(
user_id=current_user["user_id"],
dilemma_type=config.dilemma_type,
topic=config.topic
)
return {
"dialogue_id": dialogue.id,
"dilemma_title": dialogue.dilemma_title,
"scenario": dialogue.scenario_description,
"framework_options": dialogue.framework_options,
"complexity": dialogue.complexity_level,
"estimated_time": dialogue.estimated_discussion_time,
"started_at": dialogue.started_at.isoformat()
}
except Exception as e:
raise HTTPException(status_code=400, detail=str(e))
@router.post("/dilemmas/{dialogue_id}/respond")
async def submit_dilemma_response(
dialogue_id: str,
response: DilemmaResponseRequest,
db: AsyncSession = Depends(get_db),
current_user = Depends(get_current_user)
):
"""Submit a response to the philosophical dilemma"""
service = PhilosophicalDialogueService(db)
try:
result = await service.submit_response(
dialogue_id=dialogue_id,
user_id=current_user["user_id"],
response=response.response,
framework=response.framework
)
return result
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
@router.post("/dilemmas/{dialogue_id}/conclude")
async def conclude_philosophical_dialogue(
dialogue_id: str,
final_position: DilemmaFinalPositionRequest,
db: AsyncSession = Depends(get_db),
current_user = Depends(get_current_user)
):
"""Conclude the philosophical dialogue with final assessment"""
service = PhilosophicalDialogueService(db)
try:
result = await service.conclude_dialogue(
dialogue_id=dialogue_id,
user_id=current_user["user_id"],
final_position=final_position.final_position
)
return result
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
@router.get("/dilemmas/{dialogue_id}")
async def get_dialogue_session(
dialogue_id: str,
db: AsyncSession = Depends(get_db),
current_user = Depends(get_current_user)
):
"""Get philosophical dialogue session details"""
query = """
SELECT * FROM philosophical_dialogues
WHERE id = :dialogue_id AND user_id = :user_id
"""
result = await db.execute(query, {
"dialogue_id": dialogue_id,
"user_id": current_user["user_id"]
})
dialogue = result.fetchone()
if not dialogue:
raise HTTPException(status_code=404, detail="Dialogue session not found")
return {
"dialogue_id": dialogue["id"],
"dilemma_title": dialogue["dilemma_title"],
"scenario": dialogue["scenario_description"],
"dialogue_history": dialogue["dialogue_history"],
"frameworks_explored": dialogue["frameworks_explored"],
"status": dialogue["dialogue_status"],
"exchange_count": dialogue["exchange_count"],
"started_at": dialogue["started_at"],
"last_exchange_at": dialogue["last_exchange_at"]
}
# Learning Analytics Endpoints
@router.get("/analytics/progress")
async def get_learning_progress(
db: AsyncSession = Depends(get_db),
current_user = Depends(get_current_user)
):
"""Get comprehensive learning progress and analytics"""
service = GameService(db)
analytics = await service.get_or_create_analytics(current_user["user_id"])
return {
"overall_progress": {
"total_sessions": analytics.total_sessions,
"total_time_minutes": analytics.total_time_minutes,
"current_streak": analytics.current_streak_days,
"longest_streak": analytics.longest_streak_days
},
"game_ratings": {
"chess": analytics.chess_rating,
"go": analytics.go_rating,
"puzzle_level": analytics.puzzle_solving_level,
"philosophical_depth": analytics.philosophical_depth_level
},
"cognitive_skills": {
"strategic_thinking": analytics.strategic_thinking_score,
"logical_reasoning": analytics.logical_reasoning_score,
"creative_problem_solving": analytics.creative_problem_solving_score,
"ethical_reasoning": analytics.ethical_reasoning_score,
"pattern_recognition": analytics.pattern_recognition_score,
"metacognitive_awareness": analytics.metacognitive_awareness_score
},
"thinking_style": {
"system1_reliance": analytics.system1_reliance_average,
"system2_engagement": analytics.system2_engagement_average,
"intuition_accuracy": analytics.intuition_accuracy_score,
"reflection_frequency": analytics.reflection_frequency_score
},
"ai_collaboration": {
"dependency_index": analytics.ai_dependency_index,
"prompt_engineering": analytics.prompt_engineering_skill,
"output_evaluation": analytics.ai_output_evaluation_skill,
"collaborative_solving": analytics.collaborative_problem_solving
},
"achievements": analytics.achievement_badges,
"recommendations": analytics.recommended_activities,
"last_activity": analytics.last_activity_date.isoformat() if analytics.last_activity_date else None
}
@router.get("/analytics/trends")
async def get_learning_trends(
timeframe: str = Query("30d", description="Timeframe: 7d, 30d, 90d, 1y"),
skill_area: Optional[str] = Query(None, description="Specific skill area to analyze"),
db: AsyncSession = Depends(get_db),
current_user = Depends(get_current_user)
):
"""Get learning trends and performance over time"""
service = GameService(db)
analytics = await service.get_or_create_analytics(current_user["user_id"])
# This would typically involve more complex analytics queries
# For now, return structured trend data
return {
"timeframe": timeframe,
"skill_progression": analytics.skill_progression_data,
"performance_trends": {
"chess_rating_history": [{"date": "2024-01-01", "rating": 1200}], # Mock data
"puzzle_completion_rate": [{"week": 1, "rate": 0.75}],
"session_frequency": [{"week": 1, "sessions": 3}]
},
"comparative_metrics": {
"peer_comparison": "Above average",
"improvement_rate": "15% this month",
"consistency_score": 0.85
},
"insights": [
"Strong improvement in strategic thinking",
"Puzzle-solving speed has increased 20%",
"Consider more challenging philosophical dilemmas"
]
}
@router.get("/analytics/recommendations")
async def get_learning_recommendations(
db: AsyncSession = Depends(get_db),
current_user = Depends(get_current_user)
):
"""Get personalized learning recommendations"""
service = GameService(db)
analytics = await service.get_or_create_analytics(current_user["user_id"])
return {
"next_activities": [
{
"type": "chess",
"difficulty": "advanced",
"reason": "Your tactical skills have improved significantly",
"estimated_time": 30
},
{
"type": "logical_deduction",
"difficulty": 6,
"reason": "Ready for more complex reasoning challenges",
"estimated_time": 20
},
{
"type": "ai_consciousness",
"topic": "chinese_room",
"reason": "Explore deeper philosophical concepts",
"estimated_time": 25
}
],
"skill_focus_areas": [
"Synthesis of multiple ethical frameworks",
"Advanced strategic planning in Go",
"Metacognitive awareness development"
],
"adaptive_settings": {
"chess_difficulty": "advanced",
"puzzle_difficulty": min(analytics.puzzle_solving_level + 1, 10),
"philosophical_complexity": "advanced" if analytics.philosophical_depth_level > 6 else "intermediate"
},
"learning_goals": analytics.learning_goals or [
"Improve system 2 thinking engagement",
"Develop better AI collaboration skills",
"Master ethical framework application"
]
}
@router.post("/analytics/reflection")
async def submit_learning_reflection(
reflection_data: Dict[str, Any],
db: AsyncSession = Depends(get_db),
current_user = Depends(get_current_user)
):
"""Submit learning reflection and self-assessment"""
service = GameService(db)
analytics = await service.get_or_create_analytics(current_user["user_id"])
# Process reflection data and update analytics
# This would involve sophisticated analysis of user self-reflection
return {
"reflection_recorded": True,
"metacognitive_feedback": "Your self-awareness of thinking patterns is improving",
"updated_recommendations": [
"Continue exploring areas where intuition conflicts with analysis",
"Practice explaining your reasoning process more explicitly"
],
"insights_gained": reflection_data.get("insights", [])
}