GET / on the short host → landing page ("URL Shortening Service", aimloperations.com)
GET /<code> → 302 after minting via Bearer on the API host
POST /api/links/ without Bearer → 401
GET /admin/ on the public API host → 404
Notes
Caller (monica_site) uses SHORTENER_BASE_URL=https://<api-host> and Authorization: Bearer monica:<secret>. Do not mint via the short hostname.
/debug/ mint UI is DEBUG only and never on the short host. Prod/beta stay DJANGO_DEBUG=false.
No dj-queue / worker for this app.
## Summary
Wire `url_shortening_service` into the deploy pipeline for **prod** and **beta**.
App repo: [`ai_ml_operations/url_shortening_service`](https://git.aimloperations.com/ai_ml_operations/url_shortening_service) (default branch `master`).
Django 6 + uv + `docker-compose.prod.yml` (no bundled Postgres, no worker). Same shape as `monica_site` / `scha`.
**Two public NPM hosts, same container / port:**
| Host | Purpose |
|------|--------|
| Short domain (`aiml.pw` and/or `cidinn.li` — pick when DNS is ready; beta can be `short-beta.aimloperations.com`) | `GET /` landing page; `GET /<code>` 302 to the long URL. **Do not** proxy `/api/`, `/admin/`, `/debug/`. |
| API hostname (suggest `shortener.aimloperations.com`; beta `shortener-beta.aimloperations.com`) | `POST/GET /api/links/` — **Bearer required**. Not an open shortener. Drop `/admin/`. |
Ansible does not manage NPM (Ryan). This ticket is catalog + ports + DB + secrets + docs.
## Checklist
### 1. `app_catalog` (`inventory/group_vars/all.yml`)
```yaml
url_shortening_service:
type: django
repo: "{{ git_base_url }}/ai_ml_operations/url_shortening_service.git"
default_branch: master
compose_file: docker-compose.prod.yml
web_service: web
migrate_cmd: "uv run python manage.py migrate --noinput"
```
No compose `worker` profile.
### 2. Host ports (`host_vars` for adama, roslin, starbuck, apollo, ai-server-4080)
Next free Django ports after `monica_site` (8004 / 8014):
```yaml
- { name: url_shortening_service, env: prod, port: 8005 }
- { name: url_shortening_service, env: beta, port: 8015 }
```
App compose default is `${WEB_PORT:-8005}`.
### 3. Postgres on `10.0.0.230`
Create DBs + grant `westfarn`:
- `url_shortener` (prod)
- `url_shortener_beta` (beta)
Do **not** share a DB across envs. Active/active replicas of the same env share one DB.
### 4. Secrets (control node)
```text
~/Documents/secrets/url_shortening_service/url_shortening_service_prod.env
~/Documents/secrets/url_shortening_service/url_shortening_service_beta.env
```
Templates: app repo `.env.prod.example`.
| | prod | beta |
|--|--|--|
| `DJANGO_ENV` | `prod` | `beta` |
| `DJANGO_DEBUG` | `false` | `false` |
| `DJANGO_ALLOWED_HOSTS` | short domain(s) + API hostname | beta short + API hosts |
| `DATABASE_URL` | `.../url_shortener` | `.../url_shortener_beta` |
| `WEB_PORT` | `8005` | `8015` |
| `SHORT_DOMAIN` / `PUBLIC_SHORT_URL` / `SHORT_PUBLIC_HOSTS` | `aiml.pw` or `cidinn.li` (https) | beta short host |
| `SHORT_API_HOSTS` | `shortener.aimloperations.com` (+ docker names if used) | `shortener-beta.aimloperations.com` |
| `SHORT_ADMIN_HOSTS` | `localhost,127.0.0.1` | same — do not put public hosts here |
| `SHORTENER_API_TOKENS` | `monica:<token_urlsafe(32)>` | different token |
| `SHORT_ALLOWED_HOSTS` | `mkdrealtor.com` (add more later) | same |
| `CLICK_IP_PEPPER` | distinct from `DJANGO_SECRET_KEY` | different pepper |
Generate tokens/peppers with `python -c "import secrets; print(secrets.token_urlsafe(32))"`. Never reuse `DJANGO_SECRET_KEY`.
### 5. Docs
- `IMPLEMENTATION.md`: apps / ports / NPM / DB tables
- `scripts/deploy.sh` help `--app` list
### 6. NPM (manual, out of Ansible)
Same upstreams on every app host (`:8005` prod / `:8015` beta):
1. **Short host** — `/` + `/[a-z0-9]{4,8}`. 404 `/api/`, `/admin/`, `/debug/`.
2. **API host** — `/api/` only. 404 `/admin/`.
Django also 404s `/api/` on the short `Host` header even if NPM is wrong. Bearer is the lock on `/api/` (empty tokens → 503; missing/wrong → 401).
### 7. Deploy smoke
```bash
~/Documents/repos/server-infra/scripts/deploy.sh --app url_shortening_service --env beta --ref master
~/Documents/repos/server-infra/scripts/deploy.sh --app url_shortening_service --env prod --ref master
```
Confirm:
- `GET /healthz/` → `{"status":"ok"}` on both ports
- `GET /` on the short host → landing page ("URL Shortening Service", aimloperations.com)
- `GET /<code>` → 302 after minting via Bearer on the API host
- `POST /api/links/` without Bearer → 401
- `GET /admin/` on the public API host → 404
## Notes
- Caller (`monica_site`) uses `SHORTENER_BASE_URL=https://<api-host>` and `Authorization: Bearer monica:<secret>`. Do not mint via the short hostname.
- `/debug/` mint UI is **DEBUG only** and never on the short host. Prod/beta stay `DJANGO_DEBUG=false`.
- No dj-queue / worker for this app.
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
Wire
url_shortening_serviceinto the deploy pipeline for prod and beta.App repo:
ai_ml_operations/url_shortening_service(default branchmaster).Django 6 + uv +
docker-compose.prod.yml(no bundled Postgres, no worker). Same shape asmonica_site/scha.Two public NPM hosts, same container / port:
aiml.pwand/orcidinn.li— pick when DNS is ready; beta can beshort-beta.aimloperations.com)GET /landing page;GET /<code>302 to the long URL. Do not proxy/api/,/admin/,/debug/.shortener.aimloperations.com; betashortener-beta.aimloperations.com)POST/GET /api/links/— Bearer required. Not an open shortener. Drop/admin/.Ansible does not manage NPM (Ryan). This ticket is catalog + ports + DB + secrets + docs.
Checklist
1.
app_catalog(inventory/group_vars/all.yml)No compose
workerprofile.2. Host ports (
host_varsfor adama, roslin, starbuck, apollo, ai-server-4080)Next free Django ports after
monica_site(8004 / 8014):App compose default is
${WEB_PORT:-8005}.3. Postgres on
10.0.0.230Create DBs + grant
westfarn:url_shortener(prod)url_shortener_beta(beta)Do not share a DB across envs. Active/active replicas of the same env share one DB.
4. Secrets (control node)
Templates: app repo
.env.prod.example.DJANGO_ENVprodbetaDJANGO_DEBUGfalsefalseDJANGO_ALLOWED_HOSTSDATABASE_URL.../url_shortener.../url_shortener_betaWEB_PORT80058015SHORT_DOMAIN/PUBLIC_SHORT_URL/SHORT_PUBLIC_HOSTSaiml.pworcidinn.li(https)SHORT_API_HOSTSshortener.aimloperations.com(+ docker names if used)shortener-beta.aimloperations.comSHORT_ADMIN_HOSTSlocalhost,127.0.0.1SHORTENER_API_TOKENSmonica:<token_urlsafe(32)>SHORT_ALLOWED_HOSTSmkdrealtor.com(add more later)CLICK_IP_PEPPERDJANGO_SECRET_KEYGenerate tokens/peppers with
python -c "import secrets; print(secrets.token_urlsafe(32))". Never reuseDJANGO_SECRET_KEY.5. Docs
IMPLEMENTATION.md: apps / ports / NPM / DB tablesscripts/deploy.shhelp--applist6. NPM (manual, out of Ansible)
Same upstreams on every app host (
:8005prod /:8015beta):/+/[a-z0-9]{4,8}. 404/api/,/admin/,/debug/./api/only. 404/admin/.Django also 404s
/api/on the shortHostheader even if NPM is wrong. Bearer is the lock on/api/(empty tokens → 503; missing/wrong → 401).7. Deploy smoke
Confirm:
GET /healthz/→{"status":"ok"}on both portsGET /on the short host → landing page ("URL Shortening Service", aimloperations.com)GET /<code>→ 302 after minting via Bearer on the API hostPOST /api/links/without Bearer → 401GET /admin/on the public API host → 404Notes
monica_site) usesSHORTENER_BASE_URL=https://<api-host>andAuthorization: Bearer monica:<secret>. Do not mint via the short hostname./debug/mint UI is DEBUG only and never on the short host. Prod/beta stayDJANGO_DEBUG=false.