107 lines
3.3 KiB
Python
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,
|
|
)
|