import uuid from fastapi import APIRouter, File, Form, Response, UploadFile, status from app.api.deps import CurrentUser, ObjectStoreDep, SessionDep from app.schemas.media import MediaRead from app.services import media_service, tree_service def _content_url(media) -> str: return f"/api/v1/trees/{media.tree_id}/media/{media.id}/content" def _read(media) -> MediaRead: out = MediaRead.model_validate(media) # Stream through the backend (privacy-checked, browser-reachable) rather # than expose the internal object store directly. out.url = _content_url(media) return out router = APIRouter(prefix="/trees", tags=["media"]) @router.post("/{tree_id}/media", response_model=MediaRead, status_code=status.HTTP_201_CREATED) async def upload_media( tree_id: uuid.UUID, session: SessionDep, current: CurrentUser, store: ObjectStoreDep, file: UploadFile = File(...), title: str | None = Form(None), person_id: uuid.UUID | None = Form(None), event_id: uuid.UUID | None = Form(None), source_id: uuid.UUID | None = Form(None), ) -> MediaRead: tree = await tree_service.get_tree(session, viewer_id=current.id, tree_id=tree_id) data = await file.read() media = await media_service.upload_media( session, store, actor=current, tree=tree, data=data, filename=file.filename or "upload", content_type=file.content_type or "application/octet-stream", title=title, person_id=person_id, event_id=event_id, source_id=source_id, ) return _read(media) @router.get("/{tree_id}/media", response_model=list[MediaRead]) async def list_media( tree_id: uuid.UUID, session: SessionDep, current: CurrentUser ) -> list[MediaRead]: tree = await tree_service.get_tree(session, viewer_id=current.id, tree_id=tree_id) items = await media_service.list_media(session, viewer_id=current.id, tree=tree) return [_read(m) for m in items] @router.get("/{tree_id}/media/{media_id}/content") async def media_content( tree_id: uuid.UUID, media_id: uuid.UUID, session: SessionDep, current: CurrentUser, store: ObjectStoreDep, ) -> Response: tree = await tree_service.get_tree(session, viewer_id=current.id, tree_id=tree_id) media = await media_service.get_media( session, viewer_id=current.id, tree=tree, media_id=media_id ) data = await store.get_object(key=media.storage_key) return Response( content=data, media_type=media.content_type, headers={"Content-Disposition": f'inline; filename="{media.original_filename}"'}, ) @router.delete("/{tree_id}/media/{media_id}", status_code=status.HTTP_204_NO_CONTENT) async def delete_media( tree_id: uuid.UUID, media_id: uuid.UUID, session: SessionDep, current: CurrentUser ) -> None: tree = await tree_service.get_tree(session, viewer_id=current.id, tree_id=tree_id) await media_service.delete_media(session, actor=current, tree=tree, media_id=media_id)