55 lines
2.0 KiB
Python
55 lines
2.0 KiB
Python
|
|
"""GET /api/media/{media_hash} — stream a cached media blob (Task 2.8).
|
||
|
|
|
||
|
|
Looks up the :class:`app.db.models.Media` row keyed by ``media_hash`` and
|
||
|
|
streams the local file with the correct ``Content-Type``. HTTP Range requests
|
||
|
|
are honored (Starlette's :class:`fastapi.responses.FileResponse` handles
|
||
|
|
``Range`` / ``206 Partial Content`` natively). Returns ``404`` when the row is
|
||
|
|
absent, has no local file recorded, or the underlying file is missing on disk.
|
||
|
|
|
||
|
|
The endpoint reads the absolute ``local_path`` straight off the row (the
|
||
|
|
downloader stores it absolute), so it does not need to know the
|
||
|
|
:class:`ObjectStore` layout — only that the file exists where the row points.
|
||
|
|
"""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
from pathlib import Path
|
||
|
|
from typing import Annotated
|
||
|
|
|
||
|
|
import sqlalchemy as sa
|
||
|
|
from fastapi import APIRouter, Depends, HTTPException
|
||
|
|
from fastapi.responses import FileResponse
|
||
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||
|
|
|
||
|
|
from app.api.deps import get_session
|
||
|
|
from app.db.models import Media
|
||
|
|
from app.scraper.media import guess_mime_type
|
||
|
|
|
||
|
|
router = APIRouter(prefix="/api", tags=["media"])
|
||
|
|
|
||
|
|
SessionDep = Annotated[AsyncSession, Depends(get_session)]
|
||
|
|
|
||
|
|
|
||
|
|
@router.get("/media/{media_hash}")
|
||
|
|
async def get_media(media_hash: str, session: SessionDep) -> FileResponse:
|
||
|
|
"""Stream the cached media blob for ``media_hash``."""
|
||
|
|
row = (
|
||
|
|
await session.execute(
|
||
|
|
sa.select(Media).where(Media.media_hash == media_hash)
|
||
|
|
)
|
||
|
|
).scalar_one_or_none()
|
||
|
|
if row is None or not row.local_path:
|
||
|
|
raise HTTPException(status_code=404, detail="media not found")
|
||
|
|
|
||
|
|
path = Path(row.local_path)
|
||
|
|
if not path.is_file():
|
||
|
|
# Row exists but the blob was removed out-of-band — treat as not-found.
|
||
|
|
raise HTTPException(status_code=404, detail="media file missing")
|
||
|
|
|
||
|
|
media_type = (
|
||
|
|
row.mime_type
|
||
|
|
or guess_mime_type(path.suffix)
|
||
|
|
or "application/octet-stream"
|
||
|
|
)
|
||
|
|
return FileResponse(path, media_type=media_type, filename=path.name)
|