from fastapi import APIRouter, HTTPException, status from fastapi.responses import JSONResponse from src.utils.logger import logger from pydantic import BaseModel from typing import List, Dict, Any, Optional import json import os router = APIRouter(prefix="/pronunciation", tags=["Pronunciation"]) class Level(BaseModel): id: str name: str description: str color: str class Vocabulary(BaseModel): word: str ipa: str vietnamese: str audioUrl: str class Sentence(BaseModel): text: str ipa: str vietnamese: str audioUrl: str class PronunciationLesson(BaseModel): id: str title: str description: str level: str vocabulary: List[Vocabulary] sentence: Sentence class LevelsResponse(BaseModel): levels: List[Level] total: int class LessonsResponse(BaseModel): lessons: List[PronunciationLesson] total: int level: str class LessonDetailResponse(BaseModel): lesson: PronunciationLesson def load_pronunciation_data() -> Dict[str, Any]: """Load pronunciation lessons data from JSON file""" try: data_file_path = os.path.join( os.path.dirname(__file__), "..", "..", "data", "pronunciation_lessons.json" ) if not os.path.exists(data_file_path): logger.warning(f"Pronunciation lessons file not found at {data_file_path}") return {"levels": [], "lessons": {}} with open(data_file_path, "r", encoding="utf-8") as file: data = json.load(file) return data except Exception as e: logger.error(f"Error loading pronunciation lessons data: {str(e)}") return {"levels": [], "lessons": {}} @router.get("/levels", response_model=LevelsResponse) async def get_levels(): """ Get all available levels for pronunciation practice Returns: LevelsResponse: Contains list of all levels and total count """ try: data = load_pronunciation_data() levels_data = data.get("levels", []) levels = [Level(**level_data) for level_data in levels_data] return LevelsResponse(levels=levels, total=len(levels)) except Exception as e: logger.error(f"Error retrieving levels: {str(e)}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to retrieve levels", ) @router.get("/lessons/{level_id}", response_model=LessonsResponse) async def get_lessons_by_level(level_id: str): """ Get all lessons for a specific level Args: level_id (str): The level ID (beginner, elementary, etc.) Returns: LessonsResponse: Contains list of lessons for the specified level """ try: data = load_pronunciation_data() lessons_data = data.get("lessons", {}) if level_id not in lessons_data: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Level '{level_id}' not found", ) level_lessons = lessons_data[level_id] lessons = [PronunciationLesson(**lesson_data) for lesson_data in level_lessons] return LessonsResponse(lessons=lessons, total=len(lessons), level=level_id) except HTTPException: raise except Exception as e: logger.error(f"Error retrieving lessons for level {level_id}: {str(e)}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to retrieve lessons", ) @router.get("/lesson/{lesson_id}", response_model=LessonDetailResponse) async def get_lesson_detail(lesson_id: str): """ Get detailed information about a specific lesson Args: lesson_id (str): The unique identifier of the lesson Returns: LessonDetailResponse: Contains the lesson details """ try: data = load_pronunciation_data() lessons_data = data.get("lessons", {}) # Search for the lesson across all levels found_lesson = None for level_id, level_lessons in lessons_data.items(): for lesson_data in level_lessons: if lesson_data.get("id") == lesson_id: found_lesson = lesson_data break if found_lesson: break if not found_lesson: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Lesson with ID '{lesson_id}' not found", ) lesson = PronunciationLesson(**found_lesson) return LessonDetailResponse(lesson=lesson) except HTTPException: raise except Exception as e: logger.error(f"Error retrieving lesson {lesson_id}: {str(e)}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to retrieve lesson", ) @router.get("/search/level/{level_id}/title/{title}") async def search_lessons_by_title(level_id: str, title: str): """ Search lessons by title within a specific level Args: level_id (str): The level ID to search within title (str): Part of the lesson title to search for Returns: LessonsResponse: Contains list of matching lessons """ try: data = load_pronunciation_data() lessons_data = data.get("lessons", {}) if level_id not in lessons_data: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Level '{level_id}' not found", ) level_lessons = lessons_data[level_id] matching_lessons = [ lesson_data for lesson_data in level_lessons if title.lower() in lesson_data.get("title", "").lower() ] lessons = [PronunciationLesson(**lesson_data) for lesson_data in matching_lessons] return LessonsResponse(lessons=lessons, total=len(lessons), level=level_id) except HTTPException: raise except Exception as e: logger.error(f"Error searching lessons by title '{title}' in level {level_id}: {str(e)}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to search lessons", )