Implement v1 URL shortener: Bearer API, public 302, landing, admin, CI #1

Closed
opened 2026-08-30 04:54:09 -07:00 by westfarn · 0 comments
Owner

Summary

Stand up the Django 6 shortener as its own service (not folded into monica_site). Trusted callers mint links over Bearer auth; phones hit GET /<code> on the short domain and get a 302.

Companion infra: server-infra#22.

What landed

Core

  • POST/GET /api/links/ + detail + disable — Bearer required (name:secret)
  • Empty SHORTENER_API_TOKENS → 503; missing/wrong token → 401
  • GET /<code> — no auth, 302 (not 301), click count + hashed IP (CLICK_IP_PEPPER)
  • Host split: short hostname never serves /api/ or /admin/; /admin/ localhost only
  • Target allowlist (SHORT_ALLOWED_HOSTS): https only; suffix / *.host syntax
  • Idempotent create when target_url + external_ref match an active link
  • short_url built from PUBLIC_SHORT_URL, not the API Host

Surfaces

  • GET / landing (“URL shortening service”, aimloperations.com)
  • GET /debug/ mint form — DEBUG only, never on the short host
  • Django admin: list/search/disable, mint on add, read-only clicks

Ops

  • uv + Docker + compose (dev host port 8005) + prod compose (8005/8015)
  • Gitea CI like monica_site: tests on PR; beta on merge to master; prod via Actions button
  • Caller guide: API.md

Test plan

  • cd site && uv run python manage.py test
  • docker compose up --buildhttp://127.0.0.1:8005/
  • Bearer POST /api/links/ → 201 + short_url
  • GET /<code> → 302 to allowlisted https target
  • No Bearer → 401; https://evil.com → 400
  • /debug/ works when DEBUG; 404 when not
## Summary Stand up the Django 6 shortener as its own service (not folded into `monica_site`). Trusted callers mint links over Bearer auth; phones hit `GET /<code>` on the short domain and get a 302. Companion infra: [server-infra#22](https://git.aimloperations.com/ai_ml_operations/server-infra/issues/22). ## What landed ### Core - `POST/GET /api/links/` + detail + disable — Bearer required (`name:secret`) - Empty `SHORTENER_API_TOKENS` → 503; missing/wrong token → 401 - `GET /<code>` — no auth, **302** (not 301), click count + hashed IP (`CLICK_IP_PEPPER`) - Host split: short hostname never serves `/api/` or `/admin/`; `/admin/` localhost only - Target allowlist (`SHORT_ALLOWED_HOSTS`): https only; suffix / `*.host` syntax - Idempotent create when `target_url` + `external_ref` match an active link - `short_url` built from `PUBLIC_SHORT_URL`, not the API Host ### Surfaces - `GET /` landing (“URL shortening service”, aimloperations.com) - `GET /debug/` mint form — `DEBUG` only, never on the short host - Django admin: list/search/disable, mint on add, read-only clicks ### Ops - uv + Docker + compose (dev host port **8005**) + prod compose (8005/8015) - Gitea CI like `monica_site`: tests on PR; beta on merge to `master`; prod via Actions button - Caller guide: `API.md` ## Test plan - [ ] `cd site && uv run python manage.py test` - [ ] `docker compose up --build` → http://127.0.0.1:8005/ - [ ] Bearer `POST /api/links/` → 201 + `short_url` - [ ] `GET /<code>` → 302 to allowlisted https target - [ ] No Bearer → 401; `https://evil.com` → 400 - [ ] `/debug/` works when DEBUG; 404 when not
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: ai_ml_operations/url_shortening_service#1