Files
MediaLore-Web-App/backend/app/routers/media.py
Garret Patti 2b0b19eb91 add auth
2026-06-29 10:41:29 -04:00

104 lines
3.4 KiB
Python

from pathlib import Path
from fastapi import APIRouter, Depends, HTTPException
from fastapi.responses import FileResponse
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from app.database import get_db
from app.models import Library, MediaItem, Tag, User
from app.schemas import MediaItemOut, TagIdList
from app.services.thumbnails import get_or_create_thumbnail
from app.auth import get_current_user, get_current_user_from_query_or_header
router = APIRouter(prefix="/media", tags=["media"])
async def _get_item_and_lib(media_id: int, db: AsyncSession) -> tuple[MediaItem, Library]:
result = await db.execute(
select(MediaItem).where(MediaItem.id == media_id)
)
item = result.scalars().first()
if not item:
raise HTTPException(404, "Media item not found")
lib_result = await db.execute(select(Library).where(Library.id == item.library_id))
lib = lib_result.scalars().first()
return item, lib
def _resolve_safe(lib: Library, item: MediaItem) -> Path:
root = Path(lib.path)
abs_path = (root / item.rel_path).resolve()
if not str(abs_path).startswith(str(root.resolve())):
raise HTTPException(400, "Invalid path")
return abs_path
@router.get("/{media_id}", response_model=MediaItemOut)
async def get_media_item(
media_id: int,
db: AsyncSession = Depends(get_db),
_user: User = Depends(get_current_user),
):
result = await db.execute(
select(MediaItem).where(MediaItem.id == media_id)
)
item = result.scalars().first()
if not item:
raise HTTPException(404, "Media item not found")
# Eagerly load tags
await db.refresh(item, ["tags"])
return item
@router.get("/{media_id}/file")
async def serve_file(
media_id: int,
db: AsyncSession = Depends(get_db),
_user: User = Depends(get_current_user_from_query_or_header),
):
item, lib = await _get_item_and_lib(media_id, db)
if item.missing:
raise HTTPException(404, "File is missing from disk")
abs_path = _resolve_safe(lib, item)
if not abs_path.exists():
raise HTTPException(404, "File not found on disk")
return FileResponse(str(abs_path))
@router.get("/{media_id}/thumbnail")
async def serve_thumbnail(
media_id: int,
db: AsyncSession = Depends(get_db),
_user: User = Depends(get_current_user_from_query_or_header),
):
item, lib = await _get_item_and_lib(media_id, db)
abs_path = _resolve_safe(lib, item)
thumb = get_or_create_thumbnail(media_id, str(abs_path), item.media_type)
if not thumb:
raise HTTPException(404, "Thumbnail could not be generated")
return FileResponse(str(thumb), media_type="image/jpeg")
@router.put("/{media_id}/tags", response_model=MediaItemOut)
async def set_tags(
media_id: int,
body: TagIdList,
db: AsyncSession = Depends(get_db),
_user: User = Depends(get_current_user),
):
result = await db.execute(select(MediaItem).where(MediaItem.id == media_id))
item = result.scalars().first()
if not item:
raise HTTPException(404, "Media item not found")
tags_result = await db.execute(select(Tag).where(Tag.id.in_(body.tag_ids)))
tags = tags_result.scalars().all()
if len(tags) != len(body.tag_ids):
raise HTTPException(400, "One or more tag IDs not found")
await db.refresh(item, ["tags"])
item.tags = list(tags)
await db.commit()
await db.refresh(item, ["tags"])
return item