chrysopedia/backend/routers/shorts_public.py
jlightner 5f4b960dc1 feat: Added share_token column with migration 026, wired token generati…
- "backend/models.py"
- "alembic/versions/026_add_share_token.py"
- "backend/pipeline/stages.py"
- "backend/routers/shorts.py"
- "backend/routers/shorts_public.py"
- "backend/main.py"

GSD-Task: S01/T01
2026-04-04 10:33:00 +00:00

95 lines
2.9 KiB
Python

"""Public (unauthenticated) endpoint for sharing generated shorts.
Resolves a share_token to video metadata and a presigned download URL.
No auth dependency — anyone with the token can access the short.
"""
from __future__ import annotations
import logging
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload
from database import get_session
from models import GeneratedShort, HighlightCandidate, ShortStatus, SourceVideo
logger = logging.getLogger("chrysopedia.shorts_public")
router = APIRouter(prefix="/public/shorts", tags=["shorts-public"])
class PublicShortResponse(BaseModel):
"""Public metadata for a shared short — no internal IDs exposed."""
format_preset: str
width: int
height: int
duration_secs: float | None
creator_name: str
highlight_title: str
download_url: str
@router.get("/{share_token}", response_model=PublicShortResponse)
async def get_public_short(
share_token: str,
db: AsyncSession = Depends(get_session),
):
"""Resolve a share token to short metadata and a presigned download URL."""
stmt = (
select(GeneratedShort)
.where(GeneratedShort.share_token == share_token)
.options(
selectinload(GeneratedShort.highlight_candidate)
.selectinload(HighlightCandidate.key_moment),
selectinload(GeneratedShort.highlight_candidate)
.selectinload(HighlightCandidate.source_video)
.selectinload(SourceVideo.creator),
)
)
result = await db.execute(stmt)
short = result.scalar_one_or_none()
if short is None:
raise HTTPException(status_code=404, detail="Short not found")
if short.status != ShortStatus.complete:
raise HTTPException(status_code=404, detail="Short not found")
if not short.minio_object_key:
raise HTTPException(
status_code=500,
detail="Short is complete but has no storage key",
)
highlight = short.highlight_candidate
key_moment = highlight.key_moment
source_video = highlight.source_video
creator = source_video.creator
from minio_client import generate_download_url
try:
url = generate_download_url(short.minio_object_key)
except Exception as exc:
logger.error(
"Failed to generate download URL for share_token=%s: %s",
share_token, exc,
)
raise HTTPException(
status_code=503,
detail="Failed to generate download URL",
) from exc
return PublicShortResponse(
format_preset=short.format_preset.value,
width=short.width,
height=short.height,
duration_secs=short.duration_secs,
creator_name=creator.name,
highlight_title=key_moment.title,
download_url=url,
)