Restyle landing to match aimloperations.com and use piha.lc.
CI / test (pull_request) Successful in 4s
CI / test (pull_request) Successful in 4s
Public short hosts are piha.lc (prod) and beta.piha.li (beta). The root page is a marketing CTA to contact AI ML Operations for access. Closes #3.
This commit is contained in:
+2
-1
@@ -13,12 +13,13 @@ DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1,0.0.0.0,web,url-shortener
|
|||||||
SITE_NAME=URL Shortening Service
|
SITE_NAME=URL Shortening Service
|
||||||
CREDIT_NAME=AI ML Operations
|
CREDIT_NAME=AI ML Operations
|
||||||
CREDIT_URL=https://aimloperations.com
|
CREDIT_URL=https://aimloperations.com
|
||||||
|
CONTACT_URL=https://aimloperations.com/contact
|
||||||
|
|
||||||
SHORT_DOMAIN=localhost:8005
|
SHORT_DOMAIN=localhost:8005
|
||||||
# Origin printed in minted short_url (phones hit this). Local: this machine.
|
# Origin printed in minted short_url (phones hit this). Local: this machine.
|
||||||
PUBLIC_SHORT_URL=http://127.0.0.1:8005
|
PUBLIC_SHORT_URL=http://127.0.0.1:8005
|
||||||
# Host values that only serve GET /<code> (no /api/).
|
# Host values that only serve GET /<code> (no /api/).
|
||||||
SHORT_PUBLIC_HOSTS=go.mkdrealtor.com
|
SHORT_PUBLIC_HOSTS=piha.lc
|
||||||
# Host values that serve /api/ (Bearer required). May include a public DNS name.
|
# Host values that serve /api/ (Bearer required). May include a public DNS name.
|
||||||
SHORT_API_HOSTS=localhost,127.0.0.1,0.0.0.0,web,url-shortener
|
SHORT_API_HOSTS=localhost,127.0.0.1,0.0.0.0,web,url-shortener
|
||||||
# Django admin — keep local. Do not add the public API hostname.
|
# Django admin — keep local. Do not add the public API hostname.
|
||||||
|
|||||||
+10
-9
@@ -13,7 +13,7 @@ DJANGO_ENV=prod
|
|||||||
DJANGO_DEBUG=false
|
DJANGO_DEBUG=false
|
||||||
DJANGO_SECRET_KEY=replace-with-a-long-random-secret
|
DJANGO_SECRET_KEY=replace-with-a-long-random-secret
|
||||||
# Public short host AND public API hostname (plus docker names if used).
|
# Public short host AND public API hostname (plus docker names if used).
|
||||||
DJANGO_ALLOWED_HOSTS=go.mkdrealtor.com,shortener.aimloperations.com,url-shortener,web
|
DJANGO_ALLOWED_HOSTS=piha.lc,shortener.aimloperations.com,url-shortener,web
|
||||||
|
|
||||||
# Shared external Postgres
|
# Shared external Postgres
|
||||||
DATABASE_URL=postgres://westfarn:replace-db-password@10.0.0.230:5432/url_shortener
|
DATABASE_URL=postgres://westfarn:replace-db-password@10.0.0.230:5432/url_shortener
|
||||||
@@ -21,18 +21,19 @@ DATABASE_URL=postgres://westfarn:replace-db-password@10.0.0.230:5432/url_shorten
|
|||||||
# Host port (must match server-infra). NPM proxies SHORT_DOMAIN here.
|
# Host port (must match server-infra). NPM proxies SHORT_DOMAIN here.
|
||||||
WEB_PORT=8005
|
WEB_PORT=8005
|
||||||
|
|
||||||
SHORT_DOMAIN=go.mkdrealtor.com
|
SHORT_DOMAIN=piha.lc
|
||||||
PUBLIC_SHORT_URL=https://go.mkdrealtor.com
|
PUBLIC_SHORT_URL=https://piha.lc
|
||||||
SHORT_PUBLIC_HOSTS=go.mkdrealtor.com
|
SHORT_PUBLIC_HOSTS=piha.lc
|
||||||
SHORT_API_HOSTS=shortener.aimloperations.com,url-shortener,web
|
SHORT_API_HOSTS=shortener.aimloperations.com,url-shortener,web
|
||||||
SHORT_ADMIN_HOSTS=localhost,127.0.0.1
|
SHORT_ADMIN_HOSTS=localhost,127.0.0.1
|
||||||
# Generate: python -c "import secrets; print(secrets.token_urlsafe(32))"
|
# Generate: python -c "import secrets; print(secrets.token_urlsafe(32))"
|
||||||
# This token is the only thing that authorizes minting. Treat it as a secret.
|
# This token is the only thing that authorizes minting. Treat it as a secret.
|
||||||
# monica_site sends: Authorization: Bearer monica:<same-secret>
|
# monica_site sends: Authorization: Bearer monica:<same-secret>
|
||||||
SHORTENER_API_TOKENS=monica:replace-with-token-urlsafe-32
|
SHORTENER_API_TOKENS=monica:replace-with-token-urlsafe-32
|
||||||
SHORT_ALLOWED_HOSTS=mkdrealtor.com
|
SHORT_ALLOWED_HOSTS=mkdrealtor.com,aimloperations.com
|
||||||
SHORT_CODE_LENGTH=6
|
SHORT_CODE_LENGTH=6
|
||||||
CLICK_IP_PEPPER=replace-with-a-distinct-pepper
|
CLICK_IP_PEPPER=replace-with-a-distinct-pepper
|
||||||
|
CONTACT_URL=https://aimloperations.com/contact
|
||||||
|
|
||||||
GUNICORN_WORKERS=2
|
GUNICORN_WORKERS=2
|
||||||
GUNICORN_BIND=0.0.0.0:8000
|
GUNICORN_BIND=0.0.0.0:8000
|
||||||
@@ -44,11 +45,11 @@ GUNICORN_BIND=0.0.0.0:8000
|
|||||||
# DJANGO_ENV=beta
|
# DJANGO_ENV=beta
|
||||||
# DJANGO_DEBUG=false
|
# DJANGO_DEBUG=false
|
||||||
# DJANGO_SECRET_KEY=replace-with-a-different-beta-secret
|
# DJANGO_SECRET_KEY=replace-with-a-different-beta-secret
|
||||||
# DJANGO_ALLOWED_HOSTS=go-beta.example.com,shortener-beta.aimloperations.com,url-shortener,web
|
# DJANGO_ALLOWED_HOSTS=beta.piha.li,shortener-beta.aimloperations.com,url-shortener,web
|
||||||
# DATABASE_URL=postgres://westfarn:replace-db-password@10.0.0.230:5432/url_shortener_beta
|
# DATABASE_URL=postgres://westfarn:replace-db-password@10.0.0.230:5432/url_shortener_beta
|
||||||
# WEB_PORT=8015
|
# WEB_PORT=8015
|
||||||
# SHORT_DOMAIN=go-beta.example.com
|
# SHORT_DOMAIN=beta.piha.li
|
||||||
# PUBLIC_SHORT_URL=https://go-beta.example.com
|
# PUBLIC_SHORT_URL=https://beta.piha.li
|
||||||
# SHORT_PUBLIC_HOSTS=go-beta.example.com
|
# SHORT_PUBLIC_HOSTS=beta.piha.li
|
||||||
# SHORTENER_API_TOKENS=monica:replace-with-a-different-token
|
# SHORTENER_API_TOKENS=monica:replace-with-a-different-token
|
||||||
# CLICK_IP_PEPPER=replace-with-a-different-pepper
|
# CLICK_IP_PEPPER=replace-with-a-different-pepper
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ jobs:
|
|||||||
DATABASE_URL: ""
|
DATABASE_URL: ""
|
||||||
DB_HOST: ""
|
DB_HOST: ""
|
||||||
SHORTENER_API_TOKENS: monica:dev-only-token
|
SHORTENER_API_TOKENS: monica:dev-only-token
|
||||||
PUBLIC_SHORT_URL: https://go.mkdrealtor.com
|
PUBLIC_SHORT_URL: https://piha.lc
|
||||||
SHORT_PUBLIC_HOSTS: go.mkdrealtor.com
|
SHORT_PUBLIC_HOSTS: piha.lc
|
||||||
SHORT_API_HOSTS: testserver,localhost,127.0.0.1
|
SHORT_API_HOSTS: testserver,localhost,127.0.0.1
|
||||||
SHORT_ALLOWED_HOSTS: mkdrealtor.com
|
SHORT_ALLOWED_HOSTS: mkdrealtor.com
|
||||||
CLICK_IP_PEPPER: test-pepper
|
CLICK_IP_PEPPER: test-pepper
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ jobs:
|
|||||||
DATABASE_URL: ""
|
DATABASE_URL: ""
|
||||||
DB_HOST: ""
|
DB_HOST: ""
|
||||||
SHORTENER_API_TOKENS: monica:dev-only-token
|
SHORTENER_API_TOKENS: monica:dev-only-token
|
||||||
PUBLIC_SHORT_URL: https://go.mkdrealtor.com
|
PUBLIC_SHORT_URL: https://piha.lc
|
||||||
SHORT_PUBLIC_HOSTS: go.mkdrealtor.com
|
SHORT_PUBLIC_HOSTS: piha.lc
|
||||||
SHORT_API_HOSTS: testserver,localhost,127.0.0.1
|
SHORT_API_HOSTS: testserver,localhost,127.0.0.1
|
||||||
SHORT_ALLOWED_HOSTS: mkdrealtor.com
|
SHORT_ALLOWED_HOSTS: mkdrealtor.com
|
||||||
CLICK_IP_PEPPER: test-pepper
|
CLICK_IP_PEPPER: test-pepper
|
||||||
@@ -63,8 +63,8 @@ jobs:
|
|||||||
-e DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1,testserver \
|
-e DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1,testserver \
|
||||||
-e DATABASE_URL=postgres://url_shortener:url_shortener@db:5432/url_shortener \
|
-e DATABASE_URL=postgres://url_shortener:url_shortener@db:5432/url_shortener \
|
||||||
-e SHORTENER_API_TOKENS=monica:dev-only-token \
|
-e SHORTENER_API_TOKENS=monica:dev-only-token \
|
||||||
-e PUBLIC_SHORT_URL=https://go.mkdrealtor.com \
|
-e PUBLIC_SHORT_URL=https://piha.lc \
|
||||||
-e SHORT_PUBLIC_HOSTS=go.mkdrealtor.com \
|
-e SHORT_PUBLIC_HOSTS=piha.lc \
|
||||||
-e SHORT_API_HOSTS=testserver,localhost,127.0.0.1 \
|
-e SHORT_API_HOSTS=testserver,localhost,127.0.0.1 \
|
||||||
-e SHORT_ALLOWED_HOSTS=mkdrealtor.com \
|
-e SHORT_ALLOWED_HOSTS=mkdrealtor.com \
|
||||||
-e CLICK_IP_PEPPER=test-pepper \
|
-e CLICK_IP_PEPPER=test-pepper \
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ jobs:
|
|||||||
DATABASE_URL: ""
|
DATABASE_URL: ""
|
||||||
DB_HOST: ""
|
DB_HOST: ""
|
||||||
SHORTENER_API_TOKENS: monica:dev-only-token
|
SHORTENER_API_TOKENS: monica:dev-only-token
|
||||||
PUBLIC_SHORT_URL: https://go.mkdrealtor.com
|
PUBLIC_SHORT_URL: https://piha.lc
|
||||||
SHORT_PUBLIC_HOSTS: go.mkdrealtor.com
|
SHORT_PUBLIC_HOSTS: piha.lc
|
||||||
SHORT_API_HOSTS: testserver,localhost,127.0.0.1
|
SHORT_API_HOSTS: testserver,localhost,127.0.0.1
|
||||||
SHORT_ALLOWED_HOSTS: mkdrealtor.com
|
SHORT_ALLOWED_HOSTS: mkdrealtor.com
|
||||||
CLICK_IP_PEPPER: test-pepper
|
CLICK_IP_PEPPER: test-pepper
|
||||||
@@ -61,8 +61,8 @@ jobs:
|
|||||||
-e DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1,testserver \
|
-e DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1,testserver \
|
||||||
-e DATABASE_URL=postgres://url_shortener:url_shortener@db:5432/url_shortener \
|
-e DATABASE_URL=postgres://url_shortener:url_shortener@db:5432/url_shortener \
|
||||||
-e SHORTENER_API_TOKENS=monica:dev-only-token \
|
-e SHORTENER_API_TOKENS=monica:dev-only-token \
|
||||||
-e PUBLIC_SHORT_URL=https://go.mkdrealtor.com \
|
-e PUBLIC_SHORT_URL=https://piha.lc \
|
||||||
-e SHORT_PUBLIC_HOSTS=go.mkdrealtor.com \
|
-e SHORT_PUBLIC_HOSTS=piha.lc \
|
||||||
-e SHORT_API_HOSTS=testserver,localhost,127.0.0.1 \
|
-e SHORT_API_HOSTS=testserver,localhost,127.0.0.1 \
|
||||||
-e SHORT_ALLOWED_HOSTS=mkdrealtor.com \
|
-e SHORT_ALLOWED_HOSTS=mkdrealtor.com \
|
||||||
-e CLICK_IP_PEPPER=test-pepper \
|
-e CLICK_IP_PEPPER=test-pepper \
|
||||||
|
|||||||
@@ -3,13 +3,13 @@
|
|||||||
For other services (`monica_site`, later callers) that mint short links.
|
For other services (`monica_site`, later callers) that mint short links.
|
||||||
|
|
||||||
This service has two public hostnames. **Call the API host.** Never create links
|
This service has two public hostnames. **Call the API host.** Never create links
|
||||||
on the short domain (`aiml.pw` / `cidinn.li` / `go.mkdrealtor.com`). That host
|
on the short domain (`piha.lc` / `beta.piha.li`). That host
|
||||||
only serves `GET /` (landing) and `GET /<code>` (302). `/api/` there is **404**.
|
only serves `GET /` (landing) and `GET /<code>` (302). `/api/` there is **404**.
|
||||||
|
|
||||||
| Host | Example | What you call |
|
| Host | Example | What you call |
|
||||||
|------|---------|----------------|
|
|------|---------|----------------|
|
||||||
| API | `https://shortener.aimloperations.com` | `POST/GET /api/links/` |
|
| API | `https://shortener.aimloperations.com` | `POST/GET /api/links/` |
|
||||||
| Short | `https://aiml.pw` (or `cidinn.li`) | phones only — `GET /<code>` |
|
| Short | `https://piha.lc` (beta: `https://beta.piha.li`) | phones only — `GET /<code>` |
|
||||||
|
|
||||||
Local compose: API + redirects on `http://127.0.0.1:8005`.
|
Local compose: API + redirects on `http://127.0.0.1:8005`.
|
||||||
|
|
||||||
@@ -147,7 +147,7 @@ Content-Type: application/json
|
|||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"code": "a3k9xm",
|
"code": "a3k9xm",
|
||||||
"short_url": "https://aiml.pw/a3k9xm",
|
"short_url": "https://piha.lc/a3k9xm",
|
||||||
"target_url": "https://mkdrealtor.com/listings/oak-st?utm_source=monica&utm_medium=sms",
|
"target_url": "https://mkdrealtor.com/listings/oak-st?utm_source=monica&utm_medium=sms",
|
||||||
"title": "Oak St listing",
|
"title": "Oak St listing",
|
||||||
"is_active": true,
|
"is_active": true,
|
||||||
|
|||||||
+6
-6
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Django 6 service in Docker, packaged with `uv`. First caller is `monica_site` (SMS campaign
|
Django 6 service in Docker, packaged with `uv`. First caller is `monica_site` (SMS campaign
|
||||||
bodies and ad-hoc texts). A short public domain will be pointed at this service later;
|
bodies and ad-hoc texts). A short public domain will be pointed at this service later;
|
||||||
until then a subdomain (for example `go.mkdrealtor.com`) is enough.
|
until then a subdomain is enough. Public short hosts: `piha.lc` (prod), `beta.piha.li` (beta).
|
||||||
|
|
||||||
This document is the build spec. Implement in this repo. Do not fold the shortener into
|
This document is the build spec. Implement in this repo. Do not fold the shortener into
|
||||||
`monica_site`.
|
`monica_site`.
|
||||||
@@ -69,7 +69,7 @@ cannot mint links without a configured Bearer token. Empty `SHORTENER_API_TOKENS
|
|||||||
```
|
```
|
||||||
Internet
|
Internet
|
||||||
│
|
│
|
||||||
├─ NPM: SHORT_DOMAIN (mkd.to / go.mkdrealtor.com)
|
├─ NPM: SHORT_DOMAIN (piha.lc / beta.piha.li)
|
||||||
│ location ~*^/[a-z0-9]{4,8}$ → gunicorn (redirects only)
|
│ location ~*^/[a-z0-9]{4,8}$ → gunicorn (redirects only)
|
||||||
│ /api/* → 404 / drop
|
│ /api/* → 404 / drop
|
||||||
│ /admin/ → 404 / drop
|
│ /admin/ → 404 / drop
|
||||||
@@ -130,7 +130,7 @@ Caller (`monica_site`) is an HTTP client. It does not share this database.
|
|||||||
|
|
||||||
Until the purchased short domain exists:
|
Until the purchased short domain exists:
|
||||||
|
|
||||||
- Set `SHORT_DOMAIN=go.mkdrealtor.com` (or `localhost:8005` locally).
|
- Set `SHORT_DOMAIN=piha.lc` (or `localhost:8005` locally).
|
||||||
- Codes do not change when DNS is swapped. Only `SHORT_DOMAIN` / `PUBLIC_SHORT_URL` change.
|
- Codes do not change when DNS is swapped. Only `SHORT_DOMAIN` / `PUBLIC_SHORT_URL` change.
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -274,7 +274,7 @@ Response `201`:
|
|||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"code": "a3k9xm",
|
"code": "a3k9xm",
|
||||||
"short_url": "https://go.mkdrealtor.com/a3k9xm",
|
"short_url": "https://piha.lc/a3k9xm",
|
||||||
"target_url": "https://mkdrealtor.com/listings/oak-st?utm_source=monica&utm_medium=sms",
|
"target_url": "https://mkdrealtor.com/listings/oak-st?utm_source=monica&utm_medium=sms",
|
||||||
"title": "Oak St listing",
|
"title": "Oak St listing",
|
||||||
"is_active": true,
|
"is_active": true,
|
||||||
@@ -355,8 +355,8 @@ Reject codes that do not match `^[a-z0-9]{4,8}$` with 404 (no extra work).
|
|||||||
| `DJANGO_DEBUG` | false in prod |
|
| `DJANGO_DEBUG` | false in prod |
|
||||||
| `DJANGO_ALLOWED_HOSTS` | public short host **and** API hostname |
|
| `DJANGO_ALLOWED_HOSTS` | public short host **and** API hostname |
|
||||||
| `DATABASE_URL` | Postgres; empty → SQLite |
|
| `DATABASE_URL` | Postgres; empty → SQLite |
|
||||||
| `SHORT_DOMAIN` | public hostname phones use (`go.mkdrealtor.com` then `mkd.to`) |
|
| `SHORT_DOMAIN` | public hostname phones use (`piha.lc` prod, `beta.piha.li` beta) |
|
||||||
| `PUBLIC_SHORT_URL` | origin for minted URLs, e.g. `https://go.mkdrealtor.com` (no trailing slash) |
|
| `PUBLIC_SHORT_URL` | origin for minted URLs, e.g. `https://piha.lc` (no trailing slash) |
|
||||||
| `SHORT_PUBLIC_HOSTS` | comma list; Host values that only serve redirects |
|
| `SHORT_PUBLIC_HOSTS` | comma list; Host values that only serve redirects |
|
||||||
| `SHORT_API_HOSTS` | comma list; Host values that serve `/api/` (public API DNS and/or docker name) |
|
| `SHORT_API_HOSTS` | comma list; Host values that serve `/api/` (public API DNS and/or docker name) |
|
||||||
| `SHORT_ADMIN_HOSTS` | comma list; Host values that serve `/admin/` (default localhost only) |
|
| `SHORT_ADMIN_HOSTS` | comma list; Host values that serve `/admin/` (default localhost only) |
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ short URL, logs, or git.
|
|||||||
|
|
||||||
Two NPM hosts, same container `WEB_PORT` (default 8005 prod / 8015 beta):
|
Two NPM hosts, same container `WEB_PORT` (default 8005 prod / 8015 beta):
|
||||||
|
|
||||||
1. `SHORT_DOMAIN` (`aiml.pw` / `cidinn.li`) — `/` landing + `GET /[a-z0-9]{4,8}`.
|
1. `SHORT_DOMAIN` (`piha.lc` prod / `beta.piha.li` beta) — `/` landing + `GET /[a-z0-9]{4,8}`.
|
||||||
Drop `/api/`, `/admin/`, `/debug/`.
|
Drop `/api/`, `/admin/`, `/debug/`.
|
||||||
2. API hostname — proxy `/api/` only. Drop `/admin/`. Add that Host to
|
2. API hostname — proxy `/api/` only. Drop `/admin/`. Add that Host to
|
||||||
`DJANGO_ALLOWED_HOSTS` and `SHORT_API_HOSTS`.
|
`DJANGO_ALLOWED_HOSTS` and `SHORT_API_HOSTS`.
|
||||||
|
|||||||
+1
-1
@@ -28,7 +28,7 @@ services:
|
|||||||
DATABASE_URL: ${COMPOSE_DATABASE_URL:-postgres://url_shortener:url_shortener@db:5432/url_shortener}
|
DATABASE_URL: ${COMPOSE_DATABASE_URL:-postgres://url_shortener:url_shortener@db:5432/url_shortener}
|
||||||
SHORT_DOMAIN: ${SHORT_DOMAIN:-localhost:8005}
|
SHORT_DOMAIN: ${SHORT_DOMAIN:-localhost:8005}
|
||||||
PUBLIC_SHORT_URL: ${PUBLIC_SHORT_URL:-http://127.0.0.1:8005}
|
PUBLIC_SHORT_URL: ${PUBLIC_SHORT_URL:-http://127.0.0.1:8005}
|
||||||
SHORT_PUBLIC_HOSTS: ${SHORT_PUBLIC_HOSTS:-go.mkdrealtor.com}
|
SHORT_PUBLIC_HOSTS: ${SHORT_PUBLIC_HOSTS:-piha.lc}
|
||||||
SHORT_API_HOSTS: ${SHORT_API_HOSTS:-localhost,127.0.0.1,0.0.0.0,web,url-shortener}
|
SHORT_API_HOSTS: ${SHORT_API_HOSTS:-localhost,127.0.0.1,0.0.0.0,web,url-shortener}
|
||||||
SHORTENER_API_TOKENS: ${SHORTENER_API_TOKENS:-monica:dev-only-token}
|
SHORTENER_API_TOKENS: ${SHORTENER_API_TOKENS:-monica:dev-only-token}
|
||||||
SHORT_ALLOWED_HOSTS: ${SHORT_ALLOWED_HOSTS:-mkdrealtor.com,aimloperations.com}
|
SHORT_ALLOWED_HOSTS: ${SHORT_ALLOWED_HOSTS:-mkdrealtor.com,aimloperations.com}
|
||||||
|
|||||||
@@ -10,5 +10,6 @@ def branding(request):
|
|||||||
"PUBLIC_SHORT_URL": settings.PUBLIC_SHORT_URL,
|
"PUBLIC_SHORT_URL": settings.PUBLIC_SHORT_URL,
|
||||||
"CREDIT_NAME": settings.CREDIT_NAME,
|
"CREDIT_NAME": settings.CREDIT_NAME,
|
||||||
"CREDIT_URL": settings.CREDIT_URL,
|
"CREDIT_URL": settings.CREDIT_URL,
|
||||||
|
"CONTACT_URL": settings.CONTACT_URL,
|
||||||
"show_debug_create": settings.DEBUG and not is_public_host(request.get_host()),
|
"show_debug_create": settings.DEBUG and not is_public_host(request.get_host()),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
/* Shortener-only bits on top of company_site style.css */
|
||||||
|
|
||||||
|
.hero-cta {
|
||||||
|
margin-top: 1.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-subtitle--wide {
|
||||||
|
max-width: 40rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-banner {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 1100;
|
||||||
|
background: var(--secondary-color);
|
||||||
|
color: #fff;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
padding: 0.4rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-banner a {
|
||||||
|
color: #fff;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.has-debug-banner nav {
|
||||||
|
top: 1.7rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-main {
|
||||||
|
min-height: 60vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-section {
|
||||||
|
padding-top: 8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-section .form-control {
|
||||||
|
margin-top: 0.35rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-section label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 0.85rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-result {
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-result code {
|
||||||
|
color: var(--primary-color);
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-logo-img {
|
||||||
|
height: 28px;
|
||||||
|
width: auto;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
After Width: | Height: | Size: 99 KiB |
@@ -1,128 +1,67 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
|
{% load static %}
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>{% block title %}{{ SITE_NAME }}{% endblock %}</title>
|
<title>{% block title %}{{ SITE_NAME }} · {{ CREDIT_NAME }}{% endblock %}</title>
|
||||||
<style>
|
<meta name="description" content="{% block meta_description %}{{ SITE_NAME }} by {{ CREDIT_NAME }}. Short links for campaigns and trusted callers. Request access at aimloperations.com.{% endblock %}">
|
||||||
:root {
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
--ink: #1c1914;
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
--paper: #f3ead8;
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;800&display=swap" rel="stylesheet">
|
||||||
--rule: #c9b896;
|
<link href="{% static 'core/css/style.css' %}" rel="stylesheet">
|
||||||
--pine: #2a4a3c;
|
<link href="{% static 'core/css/overrides.css' %}" rel="stylesheet">
|
||||||
--rust: #9a3f24;
|
<link rel="icon" type="image/png" href="{% static 'core/img/logo.png' %}">
|
||||||
--muted: #6b6254;
|
|
||||||
}
|
|
||||||
* { box-sizing: border-box; }
|
|
||||||
html, body { margin: 0; min-height: 100%; }
|
|
||||||
body {
|
|
||||||
background: var(--paper);
|
|
||||||
color: var(--ink);
|
|
||||||
font-family: "Iowan Old Style", Palatino, "Palatino Linotype", "Times New Roman", serif;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
.shell {
|
|
||||||
max-width: 40rem;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 3.5rem 1.5rem 2rem;
|
|
||||||
}
|
|
||||||
.eyebrow {
|
|
||||||
font-family: ui-monospace, "Cascadia Code", "SF Mono", Menlo, monospace;
|
|
||||||
font-size: 0.72rem;
|
|
||||||
letter-spacing: 0.16em;
|
|
||||||
text-transform: uppercase;
|
|
||||||
color: var(--pine);
|
|
||||||
margin: 0 0 0.75rem;
|
|
||||||
}
|
|
||||||
h1 {
|
|
||||||
font-size: clamp(2.4rem, 8vw, 4.2rem);
|
|
||||||
font-weight: 700;
|
|
||||||
letter-spacing: -0.03em;
|
|
||||||
line-height: 0.95;
|
|
||||||
margin: 0 0 1.25rem;
|
|
||||||
}
|
|
||||||
p { margin: 0 0 1rem; font-size: 1.15rem; }
|
|
||||||
.lede { color: var(--muted); max-width: 32rem; }
|
|
||||||
a { color: var(--rust); }
|
|
||||||
a:hover { color: var(--ink); }
|
|
||||||
footer {
|
|
||||||
margin-top: 3.5rem;
|
|
||||||
padding-top: 1rem;
|
|
||||||
border-top: 1px solid var(--rule);
|
|
||||||
font-family: ui-monospace, "Cascadia Code", "SF Mono", Menlo, monospace;
|
|
||||||
font-size: 0.78rem;
|
|
||||||
color: var(--muted);
|
|
||||||
}
|
|
||||||
footer a { color: inherit; }
|
|
||||||
.banner {
|
|
||||||
font-family: ui-monospace, Menlo, monospace;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
letter-spacing: 0.08em;
|
|
||||||
text-transform: uppercase;
|
|
||||||
background: var(--ink);
|
|
||||||
color: var(--paper);
|
|
||||||
padding: 0.4rem 1.5rem;
|
|
||||||
}
|
|
||||||
form { margin-top: 1.75rem; display: grid; gap: 0.9rem; }
|
|
||||||
label {
|
|
||||||
display: grid;
|
|
||||||
gap: 0.3rem;
|
|
||||||
font-family: ui-monospace, Menlo, monospace;
|
|
||||||
font-size: 0.78rem;
|
|
||||||
letter-spacing: 0.04em;
|
|
||||||
text-transform: uppercase;
|
|
||||||
color: var(--pine);
|
|
||||||
}
|
|
||||||
input[type="url"], input[type="text"] {
|
|
||||||
font: 1rem/1.4 "Iowan Old Style", Palatino, serif;
|
|
||||||
padding: 0.55rem 0.65rem;
|
|
||||||
border: 1px solid var(--rule);
|
|
||||||
background: #fffdf6;
|
|
||||||
color: var(--ink);
|
|
||||||
}
|
|
||||||
button, .btn {
|
|
||||||
appearance: none;
|
|
||||||
border: 0;
|
|
||||||
background: var(--pine);
|
|
||||||
color: var(--paper);
|
|
||||||
font-family: ui-monospace, Menlo, monospace;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
letter-spacing: 0.08em;
|
|
||||||
text-transform: uppercase;
|
|
||||||
padding: 0.7rem 1rem;
|
|
||||||
cursor: pointer;
|
|
||||||
text-decoration: none;
|
|
||||||
display: inline-block;
|
|
||||||
width: fit-content;
|
|
||||||
}
|
|
||||||
button:hover, .btn:hover { background: var(--ink); }
|
|
||||||
.error { color: var(--rust); font-size: 0.95rem; }
|
|
||||||
.result {
|
|
||||||
margin-top: 1.5rem;
|
|
||||||
padding: 1rem;
|
|
||||||
border: 1px dashed var(--pine);
|
|
||||||
background: #fffdf6;
|
|
||||||
}
|
|
||||||
.result code {
|
|
||||||
font-size: 1.05rem;
|
|
||||||
word-break: break-all;
|
|
||||||
}
|
|
||||||
.messages { list-style: none; padding: 0; margin: 0 0 1rem; color: var(--pine); }
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body{% if debug %} class="has-debug-banner"{% endif %}>
|
||||||
{% if debug %}
|
{% if debug %}
|
||||||
<div class="banner">Debug — create page is local only</div>
|
<div class="debug-banner">Debug — mint form is local only{% if show_debug_create %} · <a href="{% url 'debug-create' %}">Create a short link</a>{% endif %}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<main class="shell">
|
|
||||||
|
<nav aria-label="Main navigation">
|
||||||
|
<a href="{{ CREDIT_URL }}" class="brand-logo">
|
||||||
|
<img src="{% static 'core/img/logo.png' %}" alt="{{ CREDIT_NAME }}" class="brand-logo-img" width="243" height="28">
|
||||||
|
</a>
|
||||||
|
<button type="button" class="mobile-menu-btn" aria-label="Open menu" aria-expanded="false" aria-controls="main-nav-links">☰</button>
|
||||||
|
<ul class="nav-links" id="main-nav-links">
|
||||||
|
<li><a href="{{ CREDIT_URL }}">Home</a></li>
|
||||||
|
<li><a href="{{ CREDIT_URL }}/web_design">Web Design</a></li>
|
||||||
|
<li><a href="{{ CONTACT_URL }}" class="btn" style="border: 1px solid var(--primary-color); padding: 0.4rem 1rem; border-radius: 4px; margin-left: 1rem; color: #000;">Contact</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<main class="page-main">
|
||||||
{% block content %}{% endblock %}
|
{% block content %}{% endblock %}
|
||||||
<footer>
|
|
||||||
Created by
|
|
||||||
<a href="{{ CREDIT_URL }}">{{ CREDIT_NAME }}</a>
|
|
||||||
{% if show_debug_create %}
|
|
||||||
· <a href="{% url 'debug-create' %}">Create a short link</a>
|
|
||||||
{% endif %}
|
|
||||||
</footer>
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
<footer class="footer">
|
||||||
|
<div class="container">
|
||||||
|
<p class="footer-text">{{ CREDIT_NAME }}, LLC — Forward-deployed AI engineering.</p>
|
||||||
|
<p class="footer-links">
|
||||||
|
<a href="{{ CREDIT_URL }}/terms">Terms & Privacy</a>
|
||||||
|
{% if show_debug_create %}
|
||||||
|
<span class="footer-separator">|</span>
|
||||||
|
<a href="{% url 'debug-create' %}">Create a short link</a>
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
<p class="footer-text footer-text--muted">© 2023 -
|
||||||
|
<script>document.write(new Date().getFullYear());</script> All rights reserved.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
const mobileBtn = document.querySelector('.mobile-menu-btn');
|
||||||
|
const navLinks = document.querySelector('.nav-links');
|
||||||
|
if (mobileBtn && navLinks) {
|
||||||
|
mobileBtn.addEventListener('click', function () {
|
||||||
|
const isOpen = navLinks.classList.toggle('active');
|
||||||
|
mobileBtn.setAttribute('aria-expanded', isOpen ? 'true' : 'false');
|
||||||
|
mobileBtn.setAttribute('aria-label', isOpen ? 'Close menu' : 'Open menu');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,14 +1,57 @@
|
|||||||
{% extends "core/base.html" %}
|
{% extends "core/base.html" %}
|
||||||
{% block title %}{{ SITE_NAME }}{% endblock %}
|
{% block title %}{{ SHORT_DOMAIN }} · URL shortener · {{ CREDIT_NAME }}{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<p class="eyebrow">{{ SITE_NAME }}</p>
|
<div class="hero-section">
|
||||||
<h1>{{ SHORT_DOMAIN }}</h1>
|
<div class="hero-content">
|
||||||
<p class="lede">
|
<h1 class="hero-title">{{ SHORT_DOMAIN }}</h1>
|
||||||
This is a URL shortening service. A path like
|
<p class="hero-subtitle hero-subtitle--wide">
|
||||||
<strong>/a3k9xm</strong> sends you to the long HTTPS URL.
|
Short links for campaigns and trusted callers. Built by
|
||||||
|
{{ CREDIT_NAME }} — not an open public shortener.
|
||||||
</p>
|
</p>
|
||||||
<p class="lede">
|
<div class="hero-cta">
|
||||||
Created by <a href="{{ CREDIT_URL }}">{{ CREDIT_NAME }}</a>
|
<a href="{{ CONTACT_URL }}" class="btn">Request access</a>
|
||||||
· <a href="{{ CREDIT_URL }}">aimloperations.com</a>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section" style="background: var(--surface-color);">
|
||||||
|
<div class="container">
|
||||||
|
<h2 class="section-title">A shortener you can hand to SMS</h2>
|
||||||
|
<p style="text-align: center; max-width: 820px; margin: 0 auto; color: var(--text-muted); font-size: 1.1rem;">
|
||||||
|
{{ SHORT_DOMAIN }} turns a long HTTPS URL into a short path like
|
||||||
|
<strong>/a3k9xm</strong>. Recipients tap it and get a 302 to your site.
|
||||||
|
Creating links is Bearer-authenticated — only onboarded services can mint.
|
||||||
</p>
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<div class="container">
|
||||||
|
<h2 class="section-title">Want access?</h2>
|
||||||
|
<div class="card-grid">
|
||||||
|
<div class="card">
|
||||||
|
<h3 class="card-title">For your product</h3>
|
||||||
|
<p class="card-text">
|
||||||
|
We issue a named token and allowlist your destination hosts.
|
||||||
|
Your app POSTs to the API and drops <code>short_url</code> into SMS or email.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<h3 class="card-title">Not self-serve</h3>
|
||||||
|
<p class="card-text">
|
||||||
|
There is no public signup. If you want this for a campaign or another
|
||||||
|
{{ CREDIT_NAME }} property, talk to us.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<h3 class="card-title">Talk to us</h3>
|
||||||
|
<p class="card-text">
|
||||||
|
Contact <a href="{{ CREDIT_URL }}">aimloperations.com</a> and tell us
|
||||||
|
which hosts you need to shorten to.
|
||||||
|
</p>
|
||||||
|
<a href="{{ CONTACT_URL }}" class="btn" style="margin-top: 1rem; padding: 0.7rem 1.4rem;">Contact</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -1,22 +1,25 @@
|
|||||||
{% extends "core/base.html" %}
|
{% extends "core/base.html" %}
|
||||||
{% block title %}Create short link · debug{% endblock %}
|
{% block title %}Create short link · debug{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<p class="eyebrow">Debug only</p>
|
<div class="section debug-section">
|
||||||
<h1>Create a short link</h1>
|
<div class="container" style="max-width: 40rem;">
|
||||||
<p class="lede">Uses the same allowlist as the API. Not served when <code>DEBUG</code> is false.</p>
|
<h1 class="hero-title" style="font-size: 2.2rem;">Create a short link</h1>
|
||||||
|
<p class="hero-subtitle" style="margin: 1rem 0 2rem; text-align: left;">
|
||||||
|
DEBUG only. Same allowlist as the API. Not served when <code>DEBUG</code> is false.
|
||||||
|
</p>
|
||||||
|
|
||||||
{% if messages %}
|
{% if messages %}
|
||||||
<ul class="messages">
|
<ul class="messages" style="list-style: none; padding: 0; margin: 0 0 1rem; color: var(--primary-color);">
|
||||||
{% for message in messages %}<li>{{ message }}</li>{% endfor %}
|
{% for message in messages %}<li>{{ message }}</li>{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if created_link %}
|
{% if created_link %}
|
||||||
<div class="result">
|
<div class="card debug-result">
|
||||||
<p>Short URL</p>
|
<p class="card-title">Short URL</p>
|
||||||
<p><code>{{ created_link.public_short_url }}</code></p>
|
<p><code>{{ created_link.public_short_url }}</code></p>
|
||||||
<p>Target: {{ created_link.target_url }}</p>
|
<p class="card-text">Target: {{ created_link.target_url }}</p>
|
||||||
<a class="btn" href="{{ created_link.public_short_url }}">Open</a>
|
<a class="btn" href="{{ created_link.public_short_url }}" style="margin-top: 1rem; padding: 0.7rem 1.4rem;">Open</a>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
@@ -26,10 +29,15 @@
|
|||||||
{% for field in form %}
|
{% for field in form %}
|
||||||
<label>
|
<label>
|
||||||
{{ field.label }}
|
{{ field.label }}
|
||||||
{{ field }}
|
<input class="form-control" type="{{ field.field.widget.input_type|default:'text' }}"
|
||||||
{% if field.errors %}<span class="error">{{ field.errors|striptags }}</span>{% endif %}
|
name="{{ field.name }}" value="{{ field.value|default_if_none:'' }}"
|
||||||
|
{% if field.field.widget.attrs.placeholder %}placeholder="{{ field.field.widget.attrs.placeholder }}"{% endif %}
|
||||||
|
{% if field.field.widget.attrs.autofocus %}autofocus{% endif %}>
|
||||||
|
{% if field.errors %}<span class="alert-text" style="color: #ff6b6b;">{{ field.errors|striptags }}</span>{% endif %}
|
||||||
</label>
|
</label>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<button type="submit">Mint</button>
|
<button type="submit" class="btn">Mint</button>
|
||||||
</form>
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
+27
-24
@@ -18,8 +18,9 @@ TOKENS = [("monica", "dev-only-token")]
|
|||||||
|
|
||||||
SETTINGS = dict(
|
SETTINGS = dict(
|
||||||
SHORTENER_API_TOKENS=TOKENS,
|
SHORTENER_API_TOKENS=TOKENS,
|
||||||
PUBLIC_SHORT_URL="https://go.mkdrealtor.com",
|
SHORT_DOMAIN="piha.lc",
|
||||||
SHORT_PUBLIC_HOSTS=["go.mkdrealtor.com"],
|
PUBLIC_SHORT_URL="https://piha.lc",
|
||||||
|
SHORT_PUBLIC_HOSTS=["piha.lc"],
|
||||||
SHORT_API_HOSTS=["testserver", "localhost", "127.0.0.1", "shortener.example.com"],
|
SHORT_API_HOSTS=["testserver", "localhost", "127.0.0.1", "shortener.example.com"],
|
||||||
SHORT_ADMIN_HOSTS=["localhost", "127.0.0.1"],
|
SHORT_ADMIN_HOSTS=["localhost", "127.0.0.1"],
|
||||||
SHORT_ALLOWED_HOSTS=["mkdrealtor.com"],
|
SHORT_ALLOWED_HOSTS=["mkdrealtor.com"],
|
||||||
@@ -28,7 +29,7 @@ SETTINGS = dict(
|
|||||||
"testserver",
|
"testserver",
|
||||||
"localhost",
|
"localhost",
|
||||||
"127.0.0.1",
|
"127.0.0.1",
|
||||||
"go.mkdrealtor.com",
|
"piha.lc",
|
||||||
"shortener.example.com",
|
"shortener.example.com",
|
||||||
],
|
],
|
||||||
SHORT_CODE_LENGTH=6,
|
SHORT_CODE_LENGTH=6,
|
||||||
@@ -93,7 +94,7 @@ class HostSplitTests(TestCase):
|
|||||||
data=json.dumps({"target_url": "https://mkdrealtor.com/x"}),
|
data=json.dumps({"target_url": "https://mkdrealtor.com/x"}),
|
||||||
content_type="application/json",
|
content_type="application/json",
|
||||||
HTTP_AUTHORIZATION=AUTH,
|
HTTP_AUTHORIZATION=AUTH,
|
||||||
HTTP_HOST="go.mkdrealtor.com",
|
HTTP_HOST="piha.lc",
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, 404)
|
self.assertEqual(response.status_code, 404)
|
||||||
|
|
||||||
@@ -103,7 +104,7 @@ class HostSplitTests(TestCase):
|
|||||||
"localhost",
|
"localhost",
|
||||||
"127.0.0.1",
|
"127.0.0.1",
|
||||||
"shortener.example.com",
|
"shortener.example.com",
|
||||||
"go.mkdrealtor.com",
|
"piha.lc",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
def test_short_host_never_serves_api_even_if_also_listed_as_api(self):
|
def test_short_host_never_serves_api_even_if_also_listed_as_api(self):
|
||||||
@@ -112,12 +113,12 @@ class HostSplitTests(TestCase):
|
|||||||
data=json.dumps({"target_url": "https://mkdrealtor.com/x"}),
|
data=json.dumps({"target_url": "https://mkdrealtor.com/x"}),
|
||||||
content_type="application/json",
|
content_type="application/json",
|
||||||
HTTP_AUTHORIZATION=AUTH,
|
HTTP_AUTHORIZATION=AUTH,
|
||||||
HTTP_HOST="go.mkdrealtor.com",
|
HTTP_HOST="piha.lc",
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, 404)
|
self.assertEqual(response.status_code, 404)
|
||||||
|
|
||||||
def test_public_host_admin_404(self):
|
def test_public_host_admin_404(self):
|
||||||
response = self.client.get("/admin/", HTTP_HOST="go.mkdrealtor.com")
|
response = self.client.get("/admin/", HTTP_HOST="piha.lc")
|
||||||
self.assertEqual(response.status_code, 404)
|
self.assertEqual(response.status_code, 404)
|
||||||
|
|
||||||
def test_public_api_host_without_bearer_401(self):
|
def test_public_api_host_without_bearer_401(self):
|
||||||
@@ -155,7 +156,7 @@ class HostSplitTests(TestCase):
|
|||||||
self.assertEqual(response.status_code, 404)
|
self.assertEqual(response.status_code, 404)
|
||||||
|
|
||||||
def test_healthz_on_public_and_api(self):
|
def test_healthz_on_public_and_api(self):
|
||||||
for host in ("go.mkdrealtor.com", "testserver", "shortener.example.com"):
|
for host in ("piha.lc", "testserver", "shortener.example.com"):
|
||||||
response = self.client.get("/healthz/", HTTP_HOST=host)
|
response = self.client.get("/healthz/", HTTP_HOST=host)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(_json(response), {"status": "ok"})
|
self.assertEqual(_json(response), {"status": "ok"})
|
||||||
@@ -216,7 +217,7 @@ class RedirectTests(TestCase):
|
|||||||
|
|
||||||
def test_active_code_302_to_target(self):
|
def test_active_code_302_to_target(self):
|
||||||
response = self.client.get(
|
response = self.client.get(
|
||||||
"/a3k9xm", HTTP_HOST="go.mkdrealtor.com", follow=False
|
"/a3k9xm", HTTP_HOST="piha.lc", follow=False
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, 302)
|
self.assertEqual(response.status_code, 302)
|
||||||
self.assertNotEqual(response.status_code, 301)
|
self.assertNotEqual(response.status_code, 301)
|
||||||
@@ -226,7 +227,7 @@ class RedirectTests(TestCase):
|
|||||||
|
|
||||||
def test_head_also_302_without_click(self):
|
def test_head_also_302_without_click(self):
|
||||||
response = self.client.head(
|
response = self.client.head(
|
||||||
"/a3k9xm", HTTP_HOST="go.mkdrealtor.com", follow=False
|
"/a3k9xm", HTTP_HOST="piha.lc", follow=False
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, 302)
|
self.assertEqual(response.status_code, 302)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
@@ -239,34 +240,36 @@ class RedirectTests(TestCase):
|
|||||||
def test_inactive_404(self):
|
def test_inactive_404(self):
|
||||||
self.link.is_active = False
|
self.link.is_active = False
|
||||||
self.link.save()
|
self.link.save()
|
||||||
response = self.client.get("/a3k9xm", HTTP_HOST="go.mkdrealtor.com")
|
response = self.client.get("/a3k9xm", HTTP_HOST="piha.lc")
|
||||||
self.assertEqual(response.status_code, 404)
|
self.assertEqual(response.status_code, 404)
|
||||||
|
|
||||||
def test_expired_404(self):
|
def test_expired_404(self):
|
||||||
self.link.expires_at = timezone.now() - timedelta(minutes=1)
|
self.link.expires_at = timezone.now() - timedelta(minutes=1)
|
||||||
self.link.save()
|
self.link.save()
|
||||||
response = self.client.get("/a3k9xm", HTTP_HOST="go.mkdrealtor.com")
|
response = self.client.get("/a3k9xm", HTTP_HOST="piha.lc")
|
||||||
self.assertEqual(response.status_code, 404)
|
self.assertEqual(response.status_code, 404)
|
||||||
|
|
||||||
def test_unknown_404(self):
|
def test_unknown_404(self):
|
||||||
response = self.client.get("/zzzzzz", HTTP_HOST="go.mkdrealtor.com")
|
response = self.client.get("/zzzzzz", HTTP_HOST="piha.lc")
|
||||||
self.assertEqual(response.status_code, 404)
|
self.assertEqual(response.status_code, 404)
|
||||||
|
|
||||||
def test_invalid_code_shape_404(self):
|
def test_invalid_code_shape_404(self):
|
||||||
response = self.client.get("/AB", HTTP_HOST="go.mkdrealtor.com")
|
response = self.client.get("/AB", HTTP_HOST="piha.lc")
|
||||||
self.assertEqual(response.status_code, 404)
|
self.assertEqual(response.status_code, 404)
|
||||||
|
|
||||||
def test_public_root_is_landing(self):
|
def test_public_root_is_landing(self):
|
||||||
response = self.client.get("/", HTTP_HOST="go.mkdrealtor.com")
|
response = self.client.get("/", HTTP_HOST="piha.lc")
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertContains(response, "URL Shortening Service")
|
self.assertContains(response, "piha.lc")
|
||||||
|
self.assertContains(response, "Request access")
|
||||||
self.assertContains(response, "aimloperations.com")
|
self.assertContains(response, "aimloperations.com")
|
||||||
|
self.assertContains(response, "Contact")
|
||||||
self.assertNotContains(response, "Create a short link")
|
self.assertNotContains(response, "Create a short link")
|
||||||
|
|
||||||
def test_click_row_and_count(self):
|
def test_click_row_and_count(self):
|
||||||
self.client.get(
|
self.client.get(
|
||||||
"/a3k9xm",
|
"/a3k9xm",
|
||||||
HTTP_HOST="go.mkdrealtor.com",
|
HTTP_HOST="piha.lc",
|
||||||
HTTP_USER_AGENT="sms-client",
|
HTTP_USER_AGENT="sms-client",
|
||||||
)
|
)
|
||||||
self.link.refresh_from_db()
|
self.link.refresh_from_db()
|
||||||
@@ -296,7 +299,7 @@ class CreateTests(TestCase):
|
|||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, 201)
|
self.assertEqual(response.status_code, 201)
|
||||||
body = _json(response)
|
body = _json(response)
|
||||||
self.assertTrue(body["short_url"].startswith("https://go.mkdrealtor.com/"))
|
self.assertTrue(body["short_url"].startswith("https://piha.lc/"))
|
||||||
self.assertNotIn("testserver", body["short_url"])
|
self.assertNotIn("testserver", body["short_url"])
|
||||||
|
|
||||||
def test_idempotent_same_target_and_external_ref(self):
|
def test_idempotent_same_target_and_external_ref(self):
|
||||||
@@ -349,9 +352,9 @@ class CreateTests(TestCase):
|
|||||||
self.assertEqual(first.status_code, 200)
|
self.assertEqual(first.status_code, 200)
|
||||||
self.assertEqual(second.status_code, 200)
|
self.assertEqual(second.status_code, 200)
|
||||||
self.assertFalse(_json(second)["is_active"])
|
self.assertFalse(_json(second)["is_active"])
|
||||||
self.client.get(f"/{code}", HTTP_HOST="go.mkdrealtor.com")
|
self.client.get(f"/{code}", HTTP_HOST="piha.lc")
|
||||||
# disabled → no redirect
|
# disabled → no redirect
|
||||||
response = self.client.get(f"/{code}", HTTP_HOST="go.mkdrealtor.com")
|
response = self.client.get(f"/{code}", HTTP_HOST="piha.lc")
|
||||||
self.assertEqual(response.status_code, 404)
|
self.assertEqual(response.status_code, 404)
|
||||||
|
|
||||||
|
|
||||||
@@ -379,12 +382,12 @@ class UseCaseTests(TestCase):
|
|||||||
self.assertEqual(create.status_code, 201)
|
self.assertEqual(create.status_code, 201)
|
||||||
body = _json(create)
|
body = _json(create)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
body["short_url"], f"https://go.mkdrealtor.com/{body['code']}"
|
body["short_url"], f"https://piha.lc/{body['code']}"
|
||||||
)
|
)
|
||||||
|
|
||||||
follow = self.client.get(
|
follow = self.client.get(
|
||||||
f"/{body['code']}",
|
f"/{body['code']}",
|
||||||
HTTP_HOST="go.mkdrealtor.com",
|
HTTP_HOST="piha.lc",
|
||||||
follow=False,
|
follow=False,
|
||||||
)
|
)
|
||||||
self.assertEqual(follow.status_code, 302)
|
self.assertEqual(follow.status_code, 302)
|
||||||
@@ -408,7 +411,7 @@ class DebugCreateTests(TestCase):
|
|||||||
|
|
||||||
@override_settings(DEBUG=True)
|
@override_settings(DEBUG=True)
|
||||||
def test_hidden_on_public_short_host_even_in_debug(self):
|
def test_hidden_on_public_short_host_even_in_debug(self):
|
||||||
response = self.client.get("/debug/", HTTP_HOST="go.mkdrealtor.com")
|
response = self.client.get("/debug/", HTTP_HOST="piha.lc")
|
||||||
self.assertEqual(response.status_code, 404)
|
self.assertEqual(response.status_code, 404)
|
||||||
|
|
||||||
@override_settings(DEBUG=True)
|
@override_settings(DEBUG=True)
|
||||||
@@ -494,7 +497,7 @@ class AdminTests(TestCase):
|
|||||||
|
|
||||||
def test_admin_404_on_public_hosts(self):
|
def test_admin_404_on_public_hosts(self):
|
||||||
self.client.logout()
|
self.client.logout()
|
||||||
response = self.client.get("/admin/", HTTP_HOST="go.mkdrealtor.com")
|
response = self.client.get("/admin/", HTTP_HOST="piha.lc")
|
||||||
self.assertEqual(response.status_code, 404)
|
self.assertEqual(response.status_code, 404)
|
||||||
response = self.client.get("/admin/", HTTP_HOST="shortener.example.com")
|
response = self.client.get("/admin/", HTTP_HOST="shortener.example.com")
|
||||||
self.assertEqual(response.status_code, 404)
|
self.assertEqual(response.status_code, 404)
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ DEBUG = env_bool("DJANGO_DEBUG", False)
|
|||||||
|
|
||||||
allowed_hosts = env_list(
|
allowed_hosts = env_list(
|
||||||
"DJANGO_ALLOWED_HOSTS",
|
"DJANGO_ALLOWED_HOSTS",
|
||||||
"localhost,127.0.0.1,0.0.0.0,testserver,web,url-shortener,go.mkdrealtor.com",
|
"localhost,127.0.0.1,0.0.0.0,testserver,web,url-shortener,piha.lc,beta.piha.li",
|
||||||
)
|
)
|
||||||
ALLOWED_HOSTS = allowed_hosts if allowed_hosts else ["*"]
|
ALLOWED_HOSTS = allowed_hosts if allowed_hosts else ["*"]
|
||||||
|
|
||||||
@@ -176,7 +176,7 @@ DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
|||||||
|
|
||||||
# --- Shortener ---
|
# --- Shortener ---
|
||||||
SHORT_DOMAIN = env("SHORT_DOMAIN", "localhost:8000") or "localhost:8000"
|
SHORT_DOMAIN = env("SHORT_DOMAIN", "localhost:8000") or "localhost:8000"
|
||||||
PUBLIC_SHORT_URL = (env("PUBLIC_SHORT_URL", "https://go.mkdrealtor.com") or "").rstrip(
|
PUBLIC_SHORT_URL = (env("PUBLIC_SHORT_URL", "https://piha.lc") or "").rstrip(
|
||||||
"/"
|
"/"
|
||||||
)
|
)
|
||||||
SHORT_PUBLIC_HOSTS = env_list("SHORT_PUBLIC_HOSTS", SHORT_DOMAIN.split(":")[0])
|
SHORT_PUBLIC_HOSTS = env_list("SHORT_PUBLIC_HOSTS", SHORT_DOMAIN.split(":")[0])
|
||||||
@@ -196,3 +196,6 @@ CODE_ALPHABET = "23456789abcdefghjkmnpqrstuvwxyz"
|
|||||||
SITE_NAME = env("SITE_NAME", "URL Shortening Service") or "URL Shortening Service"
|
SITE_NAME = env("SITE_NAME", "URL Shortening Service") or "URL Shortening Service"
|
||||||
CREDIT_NAME = env("CREDIT_NAME", "AI ML Operations") or "AI ML Operations"
|
CREDIT_NAME = env("CREDIT_NAME", "AI ML Operations") or "AI ML Operations"
|
||||||
CREDIT_URL = env("CREDIT_URL", "https://aimloperations.com") or "https://aimloperations.com"
|
CREDIT_URL = env("CREDIT_URL", "https://aimloperations.com") or "https://aimloperations.com"
|
||||||
|
CONTACT_URL = env("CONTACT_URL", "https://aimloperations.com/contact") or (
|
||||||
|
"https://aimloperations.com/contact"
|
||||||
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user