Add user-defined permalinks for recordings #1

Merged
qe-ci-bot merged 1 commit from feat/custom-permalinks into main 2026-07-18 20:10:29 -04:00
Member

Recordings can now have an optional custom permalink, so a share link reads /v/product-demo instead of /v/aB3xK9pq.

What you get

  • Owners/admins get a Permalink editor on the watch page: type a custom link, see a live preview, Save / Clear.
  • /v/{slug} and the original /v/{id} both resolve (a slug match wins), so old links never break.
  • The Copy link button, the address bar, and the library / thread / reply links all prefer the slug once set.

How it works

  • videos.slug column + a unique index, created in db.init_db() (SQLite cannot add a UNIQUE column via ALTER TABLE; NULL = "use the id", and a unique index still allows many NULLs).
  • PATCH /api/videos/{id} accepts slug: server-side normalize (lowercase, non-alphanumeric runs to -, capped 80), collision + id-shadow guards to 409, invalid to 400, empty clears to NULL.
  • /v/{key} resolves WHERE slug=? OR id=? ORDER BY (slug=?) DESC.

Verified

TestClient smoke test, 18 checks: set/normalize, resolve by slug and by id, duplicate 409, id-shadow 409, invalid 400, clear + 404-after-clear + id-still-resolves, reuse-after-clear, and the editor renders on the watch page.

Migration is additive and idempotent; existing videos get a NULL slug so their URLs are unchanged.

Recordings can now have an optional **custom permalink**, so a share link reads `/v/product-demo` instead of `/v/aB3xK9pq`. ### What you get - Owners/admins get a **Permalink** editor on the watch page: type a custom link, see a live preview, **Save** / **Clear**. - `/v/{slug}` and the original `/v/{id}` both resolve (a slug match wins), so **old links never break**. - The **Copy link** button, the address bar, and the library / thread / reply links all prefer the slug once set. ### How it works - `videos.slug` column + a unique index, created in `db.init_db()` (SQLite cannot add a `UNIQUE` column via `ALTER TABLE`; `NULL` = "use the id", and a unique index still allows many `NULL`s). - `PATCH /api/videos/{id}` accepts `slug`: server-side normalize (lowercase, non-alphanumeric runs to `-`, capped 80), collision + id-shadow guards to `409`, invalid to `400`, empty clears to `NULL`. - `/v/{key}` resolves `WHERE slug=? OR id=? ORDER BY (slug=?) DESC`. ### Verified TestClient smoke test, 18 checks: set/normalize, resolve by slug and by id, duplicate 409, id-shadow 409, invalid 400, clear + 404-after-clear + id-still-resolves, reuse-after-clear, and the editor renders on the watch page. Migration is additive and idempotent; existing videos get a `NULL` slug so their URLs are unchanged.
Recordings can now have an optional custom slug, so a share link reads
/v/product-demo instead of /v/aB3xK9pq. Owners/admins set or clear the
permalink from a new editor on the watch page; the original id link keeps
working, and the share button + library/thread/reply links prefer the slug.

- schema: videos.slug + a unique index, created in db.init_db() (ALTER TABLE
  can't add a UNIQUE column inline; NULL slug = "use the id", and a unique
  index still allows many NULLs)
- /v/{key} resolves by slug or id (a slug match wins); PATCH /api/videos
  accepts "slug" with server-side normalize, collision + id-shadow guards
  (409), invalid (400) and clear-to-NULL
- watch page: permalink editor panel (owner/admin) with live preview; the
  address bar + Copy-link stay canonical via history.replaceState

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
qe-ci-bot deleted branch feat/custom-permalinks 2026-07-18 20:10:29 -04:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
QuaderEnterprises/lumo!1
No description provided.