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.
## 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
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
Stand up the Django 6 shortener as its own service (not folded into
monica_site). Trusted callers mint links over Bearer auth; phones hitGET /<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)SHORTENER_API_TOKENS→ 503; missing/wrong token → 401GET /<code>— no auth, 302 (not 301), click count + hashed IP (CLICK_IP_PEPPER)/api/or/admin/;/admin/localhost onlySHORT_ALLOWED_HOSTS): https only; suffix /*.hostsyntaxtarget_url+external_refmatch an active linkshort_urlbuilt fromPUBLIC_SHORT_URL, not the API HostSurfaces
GET /landing (“URL shortening service”, aimloperations.com)GET /debug/mint form —DEBUGonly, never on the short hostOps
monica_site: tests on PR; beta on merge tomaster; prod via Actions buttonAPI.mdTest plan
cd site && uv run python manage.py testdocker compose up --build→ http://127.0.0.1:8005/POST /api/links/→ 201 +short_urlGET /<code>→ 302 to allowlisted https targethttps://evil.com→ 400/debug/works when DEBUG; 404 when not