Files
beavergram/backend/src/api/routers/media.py
T

107 lines
3.3 KiB
Python

from typing import Annotated
import asyncpg
from dishka.integrations.fastapi import DishkaRoute, FromDishka
from fastapi import APIRouter, HTTPException, Query
from fastapi.responses import FileResponse
from utils.cache import DAY_HEADERS, IMMUTABLE_HEADERS
from utils.files import content_disposition, media_file_name, resolve_mime
from utils.read.media import (
get_media,
get_media_version,
get_media_versions,
get_message_media,
)
from utils.read.models import MediaVersionView, MediaView
from utils.storage import ContentAddressedStorage
router = APIRouter(prefix="/api/media", tags=["media"], route_class=DishkaRoute)
Download = Annotated[bool, Query()]
def _attachment(headers: dict[str, str], file_name: str) -> dict[str, str]:
return {
**headers,
"Content-Disposition": content_disposition(file_name, attachment=True),
}
@router.get("/{media_id}/meta")
async def media_meta(pool: FromDishka[asyncpg.Pool], media_id: int) -> MediaView:
media = await get_media(pool, media_id)
if media is None:
raise HTTPException(status_code=404, detail="media not found")
return media
@router.get("/versions/{chat_id}/{message_id}")
async def message_media_versions(
pool: FromDishka[asyncpg.Pool],
chat_id: int,
message_id: int,
account_id: Annotated[int, Query()],
) -> list[MediaVersionView]:
return await get_media_versions(pool, account_id, chat_id, message_id)
@router.get("/version/{version_id}")
async def serve_media_version(
pool: FromDishka[asyncpg.Pool],
storage: FromDishka[ContentAddressedStorage],
version_id: int,
download: Download = False,
) -> FileResponse:
version = await get_media_version(pool, version_id)
if version is None:
raise HTTPException(status_code=404, detail="media version not found")
headers = IMMUTABLE_HEADERS
if download:
headers = _attachment(
headers, media_file_name(version.kind, version.mime, version_id)
)
return FileResponse(
storage.url(version.storage_key),
media_type=resolve_mime(version.kind, version.mime),
headers=headers,
)
@router.get("/message/{chat_id}/{message_id}")
async def message_media(
pool: FromDishka[asyncpg.Pool],
chat_id: int,
message_id: int,
account_id: Annotated[int, Query()],
) -> MediaView:
media = await get_message_media(pool, account_id, chat_id, message_id)
if media is None:
raise HTTPException(status_code=404, detail="media not found")
return media
@router.get("/{media_id}")
async def serve_media(
pool: FromDishka[asyncpg.Pool],
storage: FromDishka[ContentAddressedStorage],
media_id: int,
download: Download = False,
) -> FileResponse:
media = await get_media(pool, media_id)
if media is None:
raise HTTPException(status_code=404, detail="media not found")
if not media.downloaded or media.storage_key is None:
raise HTTPException(
status_code=409,
detail="media not downloaded; enqueue fetch via POST /api/media/fetch",
)
headers = DAY_HEADERS
if download:
headers = _attachment(headers, media.file_name or f"media_{media_id}")
return FileResponse(
storage.url(media.storage_key),
media_type=resolve_mime(media.kind, media.mime, media.file_name),
headers=headers,
)