diff --git a/.gsd/completed-units-M023.json b/.gsd/completed-units-M023.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/.gsd/completed-units-M023.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/.gsd/milestones/M024/slices/S01/S01-PLAN.md b/.gsd/milestones/M024/slices/S01/S01-PLAN.md index 8befe8f..a6d2044 100644 --- a/.gsd/milestones/M024/slices/S01/S01-PLAN.md +++ b/.gsd/milestones/M024/slices/S01/S01-PLAN.md @@ -1,6 +1,42 @@ # S01: [A] Shorts Publishing Flow -**Goal:** Build the shorts publishing flow from approval to shareable output +**Goal:** Creator approves a short → it renders → gets a shareable URL and embed code **Demo:** After this: Creator approves a short → it renders → gets a shareable URL and embed code ## Tasks +- [x] **T01: Added share_token column with migration 026, wired token generation into pipeline completion, and created public unauthenticated endpoint GET /api/v1/public/shorts/{share_token}** — Add a `share_token` column (String, nullable, unique indexed) to the `GeneratedShort` model. Create Alembic migration 026. Wire token generation into `stage_generate_shorts` at the point where status is set to `complete`. Add `share_token` to the existing `GeneratedShortResponse` so the admin UI can see it. Backfill existing complete shorts with tokens via a one-time migration data step. Create a new public (unauthenticated) API endpoint `GET /api/v1/shorts/public/{share_token}` that resolves the token to video metadata (format_preset, dimensions, duration, creator name, highlight title) plus a fresh MinIO presigned download URL. Register the new public router in `main.py`. + +## Steps + +1. Add `share_token: Mapped[str | None]` column to `GeneratedShort` in `backend/models.py`. Use `String(16)`, nullable, with a unique index. +2. Create `alembic/versions/026_add_share_token.py` migration: add column, create unique index, backfill existing `complete` rows with `secrets.token_urlsafe(8)` tokens. +3. In `backend/pipeline/stages.py` `stage_generate_shorts`, after setting `short.status = ShortStatus.complete`, generate and set `short.share_token = secrets.token_urlsafe(8)`. +4. Add `share_token: str | None = None` to `GeneratedShortResponse` in `backend/routers/shorts.py` and include it in the response construction. +5. Create `backend/routers/shorts_public.py` with a single endpoint: `GET /public/shorts/{share_token}`. Query `GeneratedShort` by share_token, join to `HighlightCandidate` → `KeyMoment` → `SourceVideo` to get metadata. Return presigned URL via `minio_client.generate_download_url`. No auth dependency. +6. Register the new router in `backend/main.py`: `app.include_router(shorts_public.router, prefix="/api/v1")`. +7. Verify: `alembic upgrade head` succeeds. Python can import the new router without errors. + - Estimate: 1.5h + - Files: 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 + - Verify: cd backend && python -c "from routers.shorts_public import router; print('import ok')" && cd .. && echo 'alembic migration file exists:' && test -f alembic/versions/026_add_share_token.py && echo 'OK' +- [ ] **T02: Public shorts page and share/embed buttons on HighlightQueue** — Create the `/shorts/:token` public frontend page with a video player and metadata display. Add share URL copy and embed snippet copy buttons to the HighlightQueue page for completed shorts. Create the frontend API client for the public endpoint. + +## Steps + +1. Add `fetchPublicShort` function to `frontend/src/api/shorts.ts` — `GET /api/v1/public/shorts/{token}`, no auth header. Define `PublicShortResponse` type with fields: `video_url`, `format_preset`, `width`, `height`, `duration_secs`, `creator_name`, `highlight_title`, `share_token`. +2. Create `frontend/src/pages/ShortPlayer.tsx` — a lightweight public page. Uses `useParams<{ token: string }>()`. Fetches public short data on mount. Renders a `