rrr4 / app /api /endpoints /courses.py
minaewrw's picture
Initial backend deployment for Hugging Face Spaces
11757af
from fastapi import APIRouter, HTTPException, Query, Depends
from typing import List, Optional
from ...models.schemas import CourseProgressUpdate
from ...core.cache import api_cache
from ...core.database import db_manager
from scraper.courses import courses_scraper
import logging
router = APIRouter(prefix="/courses", tags=["courses"])
logger = logging.getLogger("api.courses")
@router.get("/latest")
async def get_latest_courses(page: int = 1):
cache_key = f"courses_latest_{page}"
cached = await api_cache.get(cache_key)
if cached: return cached
try:
items = await courses_scraper.fetch_latest_courses(page=page)
if items: await api_cache.set(cache_key, items)
return items
except Exception as e:
logger.error(f"Error fetching latest courses: {e}")
raise HTTPException(status_code=500, detail="Failed to fetch latest courses")
@router.get("/category/{cat_id}")
async def get_category_courses(cat_id: str, page: int = 1):
cache_key = f"courses_cat_{cat_id}_{page}"
cached = await api_cache.get(cache_key)
if cached: return cached
try:
items = await courses_scraper.fetch_category_courses(cat_id, page=page)
if items: await api_cache.set(cache_key, items)
return items
except Exception as e:
logger.error(f"Error fetching course category {cat_id}: {e}")
raise HTTPException(status_code=500, detail=f"Failed to fetch course category {cat_id}")
@router.get("/details/{safe_id}")
async def get_course_details(safe_id: str, user_id: Optional[str] = None):
cache_key = f"course_details_{safe_id}"
details = await api_cache.get(cache_key)
if not details:
try:
details = await courses_scraper.fetch_course_details(safe_id)
if not details:
raise HTTPException(status_code=404, detail="Course not found")
await api_cache.set(cache_key, details, ttl_seconds=86400)
except HTTPException: raise
except Exception as e:
logger.error(f"Error fetching course details for {safe_id}: {e}")
raise HTTPException(status_code=500, detail="Failed to fetch course details")
if user_id and details:
async with db_manager.get_connection() as db:
cursor = await db.execute(
"SELECT lesson_id, completed FROM course_progress WHERE user_id = ? AND course_id = ?",
(user_id, safe_id)
)
rows = await cursor.fetchall()
completed_map = {row['lesson_id']: row['completed'] for row in rows}
total_completed = 0
for lesson in details.get('lessons', []):
lesson['completed'] = completed_map.get(lesson['id'], 0) == 1
if lesson['completed']: total_completed += 1
total_lessons = len(details.get('lessons', []))
details['progress_percentage'] = (total_completed / total_lessons * 100) if total_lessons > 0 else 0
details['completed_count'] = total_completed
return details
@router.post("/progress")
async def update_course_progress(data: CourseProgressUpdate):
async with db_manager.get_connection() as db:
await db.execute("""
INSERT INTO course_progress (user_id, course_id, lesson_id, completed)
VALUES (?, ?, ?, ?)
ON CONFLICT(user_id, course_id, lesson_id)
DO UPDATE SET completed = excluded.completed, updated_at = CURRENT_TIMESTAMP
""", (data.user_id, data.course_id, data.lesson_id, data.completed))
if data.completed == 1:
await db.execute("UPDATE users SET points = points + 5 WHERE id = ?", (data.user_id,))
await db.commit()
return {"status": "success"}
@router.get("/lesson/{lesson_id}")
async def get_lesson_video(lesson_id: str):
cache_key = f"lesson_video_{lesson_id}"
cached = await api_cache.get(cache_key)
if cached: return {"video_url": cached}
try:
video_url = await courses_scraper.fetch_lesson_video(lesson_id)
if video_url:
await api_cache.set(cache_key, video_url)
return {"video_url": video_url}
raise HTTPException(status_code=404, detail="Video not found")
except Exception as e:
logger.error(f"Error fetching lesson video {lesson_id}: {e}")
raise HTTPException(status_code=500, detail="Failed to fetch lesson video")