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.jobs import enqueue from utils.read.avatars import avatar_by_unique_id, avatar_history, current_avatar from utils.read.models import AvatarHistoryView from utils.storage import ContentAddressedStorage router = APIRouter(prefix="/api/avatars", tags=["avatars"], route_class=DishkaRoute) @router.get("/{owner_kind}/{owner_id}/history") async def serve_avatar_history( pool: FromDishka[asyncpg.Pool], owner_kind: str, # noqa: ARG001 owner_id: int, account_id: Annotated[int, Query()], ) -> list[AvatarHistoryView]: return await avatar_history(pool, account_id, owner_id) @router.get("/{owner_kind}/{owner_id}") async def serve_avatar( pool: FromDishka[asyncpg.Pool], storage: FromDishka[ContentAddressedStorage], owner_kind: str, owner_id: int, account_id: Annotated[int, Query()], unique_id: Annotated[str | None, Query()] = None, ) -> FileResponse: avatar = ( await avatar_by_unique_id(pool, account_id, owner_id, unique_id) if unique_id is not None else await current_avatar(pool, account_id, owner_kind, owner_id) ) if avatar is None: raise HTTPException(status_code=404, detail="avatar not found") if not avatar.downloaded or avatar.storage_key is None: await enqueue( pool, account_id, "fetch_avatar", { "owner_kind": owner_kind, "owner_id": owner_id, "unique_id": avatar.unique_id, }, ) raise HTTPException(status_code=409, detail="avatar not downloaded; fetching") return FileResponse( storage.url(avatar.storage_key), media_type=avatar.mime or "image/jpeg" )