from datetime import datetime from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select, delete from ..dependencies import get_db, get_current_user from ..models.user import User from ..models.progress import MediaProgress, Bookmark from ..models.media_item import LibraryItem from ..schemas.user import UserOut, UserSettings from ..routers.auth import _build_user_out router = APIRouter(prefix="/api/me", tags=["me"]) def _progress_to_out(p: MediaProgress) -> dict: return { "id": p.id, "libraryItemId": p.library_item_id, "episodeId": p.episode_id, "duration": p.duration, "progress": round(p.current_time / p.duration, 4) if p.duration > 0 else 0.0, "currentTime": p.current_time, "isFinished": p.is_finished, "hideFromContinueListening": p.hide_from_continue_listening, "lastUpdate": int(p.last_update.timestamp() * 1000) if p.last_update else 0, "startedAt": int(p.started_at.timestamp() * 1000) if p.started_at else 0, "finishedAt": int(p.finished_at.timestamp() * 1000) if p.finished_at else None, } def _bookmark_to_out(b: Bookmark) -> dict: return { "libraryItemId": b.library_item_id, "title": b.title, "time": b.time_seconds, "createdAt": int(b.created_at.timestamp() * 1000) if b.created_at else 0, } @router.get("") async def get_me( current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): progress_result = await db.execute( select(MediaProgress).where(MediaProgress.user_id == current_user.id) ) all_progress = progress_result.scalars().all() bookmarks_result = await db.execute( select(Bookmark).where(Bookmark.user_id == current_user.id) ) all_bookmarks = bookmarks_result.scalars().all() user_out = _build_user_out(current_user) user_dict = user_out.model_dump(by_alias=True) user_dict["mediaProgress"] = [_progress_to_out(p) for p in all_progress] user_dict["bookmarks"] = [_bookmark_to_out(b) for b in all_bookmarks] return user_dict @router.get("/progress") async def get_all_progress( current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): result = await db.execute( select(MediaProgress).where(MediaProgress.user_id == current_user.id) ) return [_progress_to_out(p) for p in result.scalars().all()] @router.patch("/progress/{library_item_id}") async def update_progress( library_item_id: str, body: dict, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): episode_id = body.get("episodeId") query = select(MediaProgress).where( MediaProgress.user_id == current_user.id, MediaProgress.library_item_id == library_item_id, ) if episode_id: query = query.where(MediaProgress.episode_id == episode_id) result = await db.execute(query) progress = result.scalar_one_or_none() if not progress: progress = MediaProgress( user_id=current_user.id, library_item_id=library_item_id, episode_id=episode_id, ) db.add(progress) if "currentTime" in body: progress.current_time = float(body["currentTime"]) if "duration" in body: progress.duration = float(body["duration"]) if "isFinished" in body: progress.is_finished = bool(body["isFinished"]) if progress.is_finished and not progress.finished_at: progress.finished_at = datetime.utcnow() if "hideFromContinueListening" in body: progress.hide_from_continue_listening = bool(body["hideFromContinueListening"]) if not progress.started_at: progress.started_at = datetime.utcnow() progress.last_update = datetime.utcnow() await db.commit() await db.refresh(progress) return _progress_to_out(progress) @router.post("/sync-local-progress") async def sync_local_progress( body: dict, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): local_progress_list = body.get("localMediaProgress", []) updated = [] for lp in local_progress_list: lib_item_id = lp.get("libraryItemId") episode_id = lp.get("episodeId") if not lib_item_id: continue query = select(MediaProgress).where( MediaProgress.user_id == current_user.id, MediaProgress.library_item_id == lib_item_id, ) if episode_id: query = query.where(MediaProgress.episode_id == episode_id) result = await db.execute(query) progress = result.scalar_one_or_none() local_last_update = lp.get("lastUpdate", 0) server_last_update = int(progress.last_update.timestamp() * 1000) if progress and progress.last_update else 0 # Nur updaten wenn lokaler Stand neuer ist if not progress or local_last_update > server_last_update: if not progress: progress = MediaProgress( user_id=current_user.id, library_item_id=lib_item_id, episode_id=episode_id, ) db.add(progress) progress.current_time = float(lp.get("currentTime", 0)) progress.duration = float(lp.get("duration", 0)) progress.is_finished = bool(lp.get("isFinished", False)) progress.last_update = datetime.utcnow() updated.append(lib_item_id) await db.commit() return {"updated": updated} @router.delete("/progress/{progress_id}") async def delete_progress( progress_id: str, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): result = await db.execute( select(MediaProgress).where( MediaProgress.id == progress_id, MediaProgress.user_id == current_user.id, ) ) progress = result.scalar_one_or_none() if not progress: raise HTTPException(status_code=404, detail="Progress not found") await db.delete(progress) await db.commit() return {"success": True} @router.get("/bookmarks") async def get_bookmarks( current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): result = await db.execute( select(Bookmark).where(Bookmark.user_id == current_user.id) ) return [_bookmark_to_out(b) for b in result.scalars().all()] @router.post("/bookmark/{library_item_id}") async def create_bookmark( library_item_id: str, body: dict, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): bookmark = Bookmark( user_id=current_user.id, library_item_id=library_item_id, time_seconds=float(body.get("time", 0)), title=body.get("title", "Lesezeichen"), ) db.add(bookmark) await db.commit() await db.refresh(bookmark) return _bookmark_to_out(bookmark) @router.delete("/bookmark/{library_item_id}/{time}") async def delete_bookmark( library_item_id: str, time: float, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): result = await db.execute( select(Bookmark).where( Bookmark.user_id == current_user.id, Bookmark.library_item_id == library_item_id, Bookmark.time_seconds == time, ) ) bookmark = result.scalar_one_or_none() if not bookmark: raise HTTPException(status_code=404, detail="Bookmark not found") await db.delete(bookmark) await db.commit() return {"success": True}