diff --git a/.gsd/milestones/M024/M024-ROADMAP.md b/.gsd/milestones/M024/M024-ROADMAP.md index 64df4e0..dcd4b90 100644 --- a/.gsd/milestones/M024/M024-ROADMAP.md +++ b/.gsd/milestones/M024/M024-ROADMAP.md @@ -6,7 +6,7 @@ Shorts pipeline goes end-to-end with captioning and templates. Player gets key m ## Slice Overview | ID | Slice | Risk | Depends | Done | After this | |----|-------|------|---------|------|------------| -| S01 | [A] Shorts Publishing Flow | medium | — | ⬜ | Creator approves a short → it renders → gets a shareable URL and embed code | +| S01 | [A] Shorts Publishing Flow | medium | — | ✅ | Creator approves a short → it renders → gets a shareable URL and embed code | | S02 | [A] Key Moment Pins on Player Timeline | low | — | ⬜ | Key technique moments appear as clickable pins on the player timeline | | S03 | [A] Embed Support (iframe Snippet) | low | — | ⬜ | Creators can copy an iframe embed snippet to put the player on their own site | | S04 | [B] Auto-Captioning + Template System | medium | — | ⬜ | Shorts have Whisper-generated animated subtitles and creator-configurable intro/outro cards | diff --git a/.gsd/milestones/M024/slices/S01/S01-SUMMARY.md b/.gsd/milestones/M024/slices/S01/S01-SUMMARY.md new file mode 100644 index 0000000..1758d8b --- /dev/null +++ b/.gsd/milestones/M024/slices/S01/S01-SUMMARY.md @@ -0,0 +1,103 @@ +--- +id: S01 +parent: M024 +milestone: M024 +provides: + - Public short URL pattern: /shorts/{token} + - share_token field on GeneratedShort model and API response + - Public shorts API endpoint: GET /api/v1/public/shorts/{share_token} +requires: + [] +affects: + - S03 + - S06 +key_files: + - backend/models.py + - alembic/versions/026_add_share_token.py + - backend/pipeline/stages.py + - backend/routers/shorts_public.py + - backend/routers/shorts.py + - backend/main.py + - frontend/src/pages/ShortPlayer.tsx + - frontend/src/pages/ShortPlayer.module.css + - frontend/src/api/shorts.ts + - frontend/src/App.tsx + - frontend/src/pages/HighlightQueue.tsx +key_decisions: + - Public endpoint returns 404 for both missing and non-complete shorts — avoids leaking short status + - fetchPublicShort uses raw fetch() to avoid injecting auth token on public endpoint + - Share/embed buttons only render when share_token is non-null — graceful for pre-migration shorts +patterns_established: + - Token-based public access pattern: model gets share_token column, pipeline generates on completion, public router resolves by token with no auth +observability_surfaces: + - none +drill_down_paths: + - .gsd/milestones/M024/slices/S01/tasks/T01-SUMMARY.md + - .gsd/milestones/M024/slices/S01/tasks/T02-SUMMARY.md +duration: "" +verification_result: passed +completed_at: 2026-04-04T10:37:15.032Z +blocker_discovered: false +--- + +# S01: [A] Shorts Publishing Flow + +**Shorts get shareable public URLs with token-based access, a standalone video player page, and copy-to-clipboard share/embed buttons on the admin queue.** + +## What Happened + +This slice adds the complete shorts publishing flow: from token generation at pipeline completion through to a public-facing player page. + +**Backend (T01):** Added `share_token` column (String(16), nullable, unique-indexed) to the `GeneratedShort` model with Alembic migration 026 that also backfills existing complete shorts. Token generation (`secrets.token_urlsafe(8)`) is wired into `stage_generate_shorts` at the completion point. A new unauthenticated endpoint `GET /api/v1/public/shorts/{share_token}` resolves the token via selectinload joins through HighlightCandidate → KeyMoment → SourceVideo → Creator to return metadata (format, dimensions, duration, creator name, highlight title) and a fresh MinIO presigned download URL. Returns 404 for both missing and non-complete shorts to avoid leaking status info. + +**Frontend (T02):** Created `ShortPlayer.tsx` — a public page at `/shorts/:token` that fetches metadata via unauthenticated API call, renders a `