diff --git a/.env.example b/.env.example index 8cb9876..3ff7550 100644 --- a/.env.example +++ b/.env.example @@ -13,12 +13,13 @@ DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1,0.0.0.0,web,url-shortener SITE_NAME=URL Shortening Service CREDIT_NAME=AI ML Operations CREDIT_URL=https://aimloperations.com +CONTACT_URL=https://aimloperations.com/contact SHORT_DOMAIN=localhost:8005 # Origin printed in minted short_url (phones hit this). Local: this machine. PUBLIC_SHORT_URL=http://127.0.0.1:8005 # Host values that only serve GET / (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. 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. diff --git a/.env.prod.example b/.env.prod.example index a1c0980..151bed7 100644 --- a/.env.prod.example +++ b/.env.prod.example @@ -13,7 +13,7 @@ DJANGO_ENV=prod DJANGO_DEBUG=false DJANGO_SECRET_KEY=replace-with-a-long-random-secret # 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 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. WEB_PORT=8005 -SHORT_DOMAIN=go.mkdrealtor.com -PUBLIC_SHORT_URL=https://go.mkdrealtor.com -SHORT_PUBLIC_HOSTS=go.mkdrealtor.com +SHORT_DOMAIN=piha.lc +PUBLIC_SHORT_URL=https://piha.lc +SHORT_PUBLIC_HOSTS=piha.lc SHORT_API_HOSTS=shortener.aimloperations.com,url-shortener,web SHORT_ADMIN_HOSTS=localhost,127.0.0.1 # Generate: python -c "import secrets; print(secrets.token_urlsafe(32))" # This token is the only thing that authorizes minting. Treat it as a secret. # monica_site sends: Authorization: Bearer monica: 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 CLICK_IP_PEPPER=replace-with-a-distinct-pepper +CONTACT_URL=https://aimloperations.com/contact GUNICORN_WORKERS=2 GUNICORN_BIND=0.0.0.0:8000 @@ -44,11 +45,11 @@ GUNICORN_BIND=0.0.0.0:8000 # DJANGO_ENV=beta # DJANGO_DEBUG=false # 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 # WEB_PORT=8015 -# SHORT_DOMAIN=go-beta.example.com -# PUBLIC_SHORT_URL=https://go-beta.example.com -# SHORT_PUBLIC_HOSTS=go-beta.example.com +# SHORT_DOMAIN=beta.piha.li +# PUBLIC_SHORT_URL=https://beta.piha.li +# SHORT_PUBLIC_HOSTS=beta.piha.li # SHORTENER_API_TOKENS=monica:replace-with-a-different-token # CLICK_IP_PEPPER=replace-with-a-different-pepper diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 6dc17f0..360dc86 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -26,8 +26,8 @@ jobs: DATABASE_URL: "" DB_HOST: "" SHORTENER_API_TOKENS: monica:dev-only-token - PUBLIC_SHORT_URL: https://go.mkdrealtor.com - 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 SHORT_ALLOWED_HOSTS: mkdrealtor.com CLICK_IP_PEPPER: test-pepper diff --git a/.gitea/workflows/deploy-beta.yml b/.gitea/workflows/deploy-beta.yml index ae12964..cecdae9 100644 --- a/.gitea/workflows/deploy-beta.yml +++ b/.gitea/workflows/deploy-beta.yml @@ -26,8 +26,8 @@ jobs: DATABASE_URL: "" DB_HOST: "" SHORTENER_API_TOKENS: monica:dev-only-token - PUBLIC_SHORT_URL: https://go.mkdrealtor.com - 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 SHORT_ALLOWED_HOSTS: mkdrealtor.com CLICK_IP_PEPPER: test-pepper @@ -63,8 +63,8 @@ jobs: -e DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1,testserver \ -e DATABASE_URL=postgres://url_shortener:url_shortener@db:5432/url_shortener \ -e SHORTENER_API_TOKENS=monica:dev-only-token \ - -e PUBLIC_SHORT_URL=https://go.mkdrealtor.com \ - -e SHORT_PUBLIC_HOSTS=go.mkdrealtor.com \ + -e PUBLIC_SHORT_URL=https://piha.lc \ + -e SHORT_PUBLIC_HOSTS=piha.lc \ -e SHORT_API_HOSTS=testserver,localhost,127.0.0.1 \ -e SHORT_ALLOWED_HOSTS=mkdrealtor.com \ -e CLICK_IP_PEPPER=test-pepper \ diff --git a/.gitea/workflows/deploy-prod.yml b/.gitea/workflows/deploy-prod.yml index a2f8583..bbbe0d2 100644 --- a/.gitea/workflows/deploy-prod.yml +++ b/.gitea/workflows/deploy-prod.yml @@ -24,8 +24,8 @@ jobs: DATABASE_URL: "" DB_HOST: "" SHORTENER_API_TOKENS: monica:dev-only-token - PUBLIC_SHORT_URL: https://go.mkdrealtor.com - 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 SHORT_ALLOWED_HOSTS: mkdrealtor.com CLICK_IP_PEPPER: test-pepper @@ -61,8 +61,8 @@ jobs: -e DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1,testserver \ -e DATABASE_URL=postgres://url_shortener:url_shortener@db:5432/url_shortener \ -e SHORTENER_API_TOKENS=monica:dev-only-token \ - -e PUBLIC_SHORT_URL=https://go.mkdrealtor.com \ - -e SHORT_PUBLIC_HOSTS=go.mkdrealtor.com \ + -e PUBLIC_SHORT_URL=https://piha.lc \ + -e SHORT_PUBLIC_HOSTS=piha.lc \ -e SHORT_API_HOSTS=testserver,localhost,127.0.0.1 \ -e SHORT_ALLOWED_HOSTS=mkdrealtor.com \ -e CLICK_IP_PEPPER=test-pepper \ diff --git a/API.md b/API.md index eb34a5b..152aa14 100644 --- a/API.md +++ b/API.md @@ -3,13 +3,13 @@ For other services (`monica_site`, later callers) that mint short 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 /` (302). `/api/` there is **404**. | Host | Example | What you call | |------|---------|----------------| | API | `https://shortener.aimloperations.com` | `POST/GET /api/links/` | -| Short | `https://aiml.pw` (or `cidinn.li`) | phones only — `GET /` | +| Short | `https://piha.lc` (beta: `https://beta.piha.li`) | phones only — `GET /` | Local compose: API + redirects on `http://127.0.0.1:8005`. @@ -147,7 +147,7 @@ Content-Type: application/json ```json { "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", "title": "Oak St listing", "is_active": true, diff --git a/Implementation.md b/Implementation.md index 72f8068..b2ee51c 100644 --- a/Implementation.md +++ b/Implementation.md @@ -2,7 +2,7 @@ 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; -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 `monica_site`. @@ -69,7 +69,7 @@ cannot mint links without a configured Bearer token. Empty `SHORTENER_API_TOKENS ``` 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) │ /api/* → 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: -- 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. --- @@ -274,7 +274,7 @@ Response `201`: ```json { "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", "title": "Oak St listing", "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_ALLOWED_HOSTS` | public short host **and** API hostname | | `DATABASE_URL` | Postgres; empty → SQLite | -| `SHORT_DOMAIN` | public hostname phones use (`go.mkdrealtor.com` then `mkd.to`) | -| `PUBLIC_SHORT_URL` | origin for minted URLs, e.g. `https://go.mkdrealtor.com` (no trailing slash) | +| `SHORT_DOMAIN` | public hostname phones use (`piha.lc` prod, `beta.piha.li` beta) | +| `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_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) | diff --git a/README.md b/README.md index 027e259..607d635 100644 --- a/README.md +++ b/README.md @@ -125,7 +125,7 @@ short URL, logs, or git. 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/`. 2. API hostname — proxy `/api/` only. Drop `/admin/`. Add that Host to `DJANGO_ALLOWED_HOSTS` and `SHORT_API_HOSTS`. diff --git a/docker-compose.yml b/docker-compose.yml index e567448..92d966e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -28,7 +28,7 @@ services: DATABASE_URL: ${COMPOSE_DATABASE_URL:-postgres://url_shortener:url_shortener@db:5432/url_shortener} SHORT_DOMAIN: ${SHORT_DOMAIN:-localhost: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} SHORTENER_API_TOKENS: ${SHORTENER_API_TOKENS:-monica:dev-only-token} SHORT_ALLOWED_HOSTS: ${SHORT_ALLOWED_HOSTS:-mkdrealtor.com,aimloperations.com} diff --git a/site/core/context_processors.py b/site/core/context_processors.py index f67f41f..0c1fa1c 100644 --- a/site/core/context_processors.py +++ b/site/core/context_processors.py @@ -10,5 +10,6 @@ def branding(request): "PUBLIC_SHORT_URL": settings.PUBLIC_SHORT_URL, "CREDIT_NAME": settings.CREDIT_NAME, "CREDIT_URL": settings.CREDIT_URL, + "CONTACT_URL": settings.CONTACT_URL, "show_debug_create": settings.DEBUG and not is_public_host(request.get_host()), } diff --git a/site/core/static/core/css/overrides.css b/site/core/static/core/css/overrides.css new file mode 100644 index 0000000..5b293c1 --- /dev/null +++ b/site/core/static/core/css/overrides.css @@ -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; +} diff --git a/site/core/static/core/css/style.css b/site/core/static/core/css/style.css new file mode 100644 index 0000000..8c4dd79 --- /dev/null +++ b/site/core/static/core/css/style.css @@ -0,0 +1,1091 @@ +:root { + --bg-color: #0a0a0a; + --surface-color: #1a1a1a; + --primary-color: #00f3ff; + /* Neon Cyan */ + --secondary-color: #bc13fe; + /* Neon Purple */ + --text-color: #e0e0e0; + --text-muted: #a0a0a0; + --font-main: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; + --transition-speed: 0.3s; +} + +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + background-color: var(--bg-color); + color: var(--text-color); + font-family: var(--font-main); + line-height: 1.6; + overflow-x: hidden; + text-rendering: optimizeLegibility; +} + +a { + color: var(--primary-color); + text-decoration: none; + transition: color var(--transition-speed); +} + +a:hover { + color: var(--secondary-color); +} + +/* Navigation */ +nav { + background-color: rgba(10, 10, 10, 0.9); + backdrop-filter: blur(10px); + position: fixed; + top: 0; + width: 100%; + z-index: 1000; + padding: 1rem 2rem; + display: flex; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid rgba(255, 255, 255, 0.1); +} + +.brand-logo { + font-size: 1.5rem; + font-weight: 700; + color: white; + text-transform: uppercase; + letter-spacing: 1px; +} + +.nav-links { + display: flex; + gap: 2rem; + list-style: none; +} + +.nav-links a { + color: var(--text-color); + font-weight: 500; + font-size: 0.9rem; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.nav-links a:hover, +.nav-links a.active { + color: var(--primary-color); +} + +/* Hero Section */ +.hero-section { + position: relative; + height: 100vh; + min-height: 520px; + width: 100%; + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; +} + +.hero-section--compact { + height: 40vh; + min-height: 300px; +} + +#hero-canvas { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 0; + pointer-events: none; +} + +.hero-content { + position: relative; + z-index: 1; + text-align: center; + padding: 0 1rem; + min-height: 12rem; +} + +.hero-title { + font-size: clamp(2.5rem, 8vw, 4rem); + font-weight: 800; + line-height: 1.1; + min-height: 1.1em; + background: linear-gradient(45deg, var(--primary-color), var(--secondary-color)); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + margin-bottom: 1rem; + text-shadow: 0 0 20px rgba(0, 243, 255, 0.3); +} + +.hero-subtitle { + font-size: 1.5rem; + color: var(--text-muted); + max-width: 600px; + margin: 0 auto; +} + +/* Sections */ +.section { + padding: 5rem 2rem; +} + +.container { + max-width: 1200px; + margin: 0 auto; +} + +.section-title { + font-size: 2.5rem; + font-weight: 700; + margin-bottom: 3rem; + text-align: center; + color: white; +} + +.section-title::after { + content: ''; + display: block; + width: 60px; + height: 4px; + background: var(--primary-color); + margin: 1rem auto 0; + border-radius: 2px; +} + +/* Contact Page */ +.contact-grid { + display: grid; + grid-template-columns: 2fr 1fr; + gap: 4rem; +} + +.form-row { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 1rem; +} + +/* Cards */ +.card-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 2rem; +} + +.card { + background: var(--surface-color); + border: 1px solid rgba(255, 255, 255, 0.05); + border-radius: 12px; + padding: 2rem; + transition: transform var(--transition-speed), box-shadow var(--transition-speed); +} + +.card:hover { + transform: translateY(-5px); + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3); + border-color: var(--primary-color); +} + +.card-title { + font-size: 1.25rem; + font-weight: 600; + margin-bottom: 1rem; + color: white; + display: block; +} + +.card-text { + color: var(--text-muted); + font-size: 0.95rem; +} + +/* Footer */ +.footer { + background: var(--surface-color); + padding: 3rem 2rem; + text-align: center; + border-top: 1px solid rgba(255, 255, 255, 0.05); +} + +.footer-text { + color: var(--text-muted); + font-size: 0.9rem; +} + +/* Mobile Nav */ +.mobile-menu-btn { + display: none; + background: none; + border: none; + color: white; + font-size: 1.5rem; + cursor: pointer; +} + +@media (max-width: 768px) { + .hero-title { + font-size: 2.5rem; + } + + .nav-links { + display: none; + flex-direction: column; + position: absolute; + top: 100%; + left: 0; + width: 100%; + background-color: rgba(10, 10, 10, 0.95); + backdrop-filter: blur(10px); + padding: 1rem; + border-bottom: 1px solid rgba(255, 255, 255, 0.1); + } + + .nav-links.active { + display: flex; + } + + .nav-links li { + margin: 1rem 0; + text-align: center; + } + + .mobile-menu-btn { + display: block; + } + + /* Mobile Dropdown adjustments */ + .dropdown-content { + position: static; + background: transparent; + box-shadow: none; + border: none; + min-width: auto; + padding-top: 0.5rem; + } + + .dropdown-content a { + padding: 8px; + font-size: 0.85rem; + color: var(--text-muted); + } + + .contact-grid { + grid-template-columns: 1fr; + gap: 2rem; + } + + .form-row { + grid-template-columns: 1fr; + } +} + +/* Forms */ +.form-group { + margin-bottom: 1.5rem; +} + +.form-label { + display: block; + margin-bottom: 0.5rem; + color: var(--text-color); + font-weight: 500; + font-size: 0.95rem; +} + +.contact-info-list { + list-style: none; +} + +.contact-info-item { + margin-bottom: 1rem; + display: flex; + align-items: center; + gap: 1rem; +} + +.contact-info-icon { + color: var(--primary-color); +} + +.visually-hidden { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +.form-control { + width: 100%; + padding: 1rem; + background: var(--bg-color); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 8px; + color: white; + font-family: var(--font-main); + font-size: 1rem; + transition: border-color var(--transition-speed); +} + +.form-control:focus { + outline: none; + border-color: var(--primary-color); +} + +.btn { + display: inline-block; + padding: 1rem 2rem; + background: var(--primary-color); + color: black; + font-weight: 700; + border: none; + border-radius: 30px; + cursor: pointer; + text-transform: uppercase; + transition: transform var(--transition-speed), box-shadow var(--transition-speed); +} + +.btn:hover { + transform: translateY(-2px); + box-shadow: 0 5px 15px rgba(0, 243, 255, 0.3); +} + +.alert { + padding: 1rem; + border-radius: 8px; + margin-bottom: 1rem; +} + +.alert-success { + background: rgba(0, 255, 0, 0.1); + border: 1px solid rgba(0, 255, 0, 0.3); + color: #00ff00; +} + +.alert-danger { + background: rgba(255, 0, 0, 0.1); + border: 1px solid rgba(255, 0, 0, 0.3); + color: #ff0000; +} + +/* Dropdown */ +.dropdown { + position: relative; + display: inline-block; +} + +.dropdown-content { + display: none; + position: absolute; + background-color: var(--surface-color); + min-width: 200px; + box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.2); + z-index: 1001; + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 8px; + top: 100%; + left: 0; + padding: 0.5rem 0; +} + +.dropdown-content li { + list-style: none; +} + +.dropdown-content a { + color: var(--text-color); + padding: 12px 16px; + text-decoration: none; + display: block; + transition: background-color var(--transition-speed); +} + +.dropdown-content a:hover { + background-color: rgba(255, 255, 255, 0.05); + color: var(--primary-color); +} + +.dropdown:hover .dropdown-content { + display: block; +} + +.dropdown.dropdown-open .dropdown-content { + display: block; +} + +.nav-dropdown-trigger { + background: none; + border: none; + cursor: pointer; + font: inherit; + color: var(--text-color); + font-weight: 500; + font-size: 0.9rem; + text-transform: uppercase; + letter-spacing: 0.5px; + padding: 0; +} + +.nav-dropdown-trigger:hover, +.nav-dropdown-trigger.active { + color: var(--primary-color); +} + +.dropdown .nav-dropdown-trigger::after { + content: ' ▼'; + font-size: 0.7em; + margin-left: 5px; +} + +/* Profile Dropdown */ +.profile-icon-link { + display: flex; + align-items: center; + padding: 0.25rem; + background: none; + border: none; + cursor: pointer; + font: inherit; +} + +.profile-icon-link::after { + display: none !important; +} + +/* Trusted-by marquee */ +.trusted-by-section { + padding: 2.5rem 0; + background: var(--bg-color); +} + +.trusted-by-label { + text-align: center; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.12em; + font-size: 0.8rem; + margin-bottom: 1.25rem; +} + +.trusted-by-marquee { + overflow: hidden; + min-height: 48px; +} + +.trusted-by-marquee-row { + display: flex; + width: max-content; +} + +.trusted-by-track { + display: flex; + align-items: center; + gap: 3rem; + padding-right: 3rem; + min-height: 48px; +} + +.trusted-by-logo img { + display: block; + height: 48px; + width: auto; + max-width: 320px; +} + +@media (prefers-reduced-motion: no-preference) { + .trusted-by-marquee-row { + animation: trusted-by-scroll 30s linear infinite; + } +} + +@keyframes trusted-by-scroll { + from { + transform: translateX(0); + } + + to { + transform: translateX(-50%); + } +} + +.profile-icon { + width: 28px; + height: 28px; + color: var(--text-color); + transition: color var(--transition-speed), filter var(--transition-speed); +} + +.profile-icon-link:hover .profile-icon { + color: var(--primary-color); + filter: drop-shadow(0 0 6px rgba(0, 243, 255, 0.5)); +} + +.profile-dropdown-content { + right: 0; + left: auto; + min-width: 180px; +} + +.profile-name-item { + padding: 12px 16px; + color: var(--primary-color); + font-weight: 600; + font-size: 0.9rem; + border-bottom: 1px solid rgba(255, 255, 255, 0.08); + text-transform: none; + letter-spacing: 0; + list-style: none; +} + +.profile-logout-btn { + width: 100%; + background: none; + border: none; + color: #ff4444; + padding: 12px 16px; + text-align: left; + font-family: var(--font-main); + font-size: 0.9rem; + font-weight: 600; + cursor: pointer; + text-transform: uppercase; + letter-spacing: 0.5px; + transition: background-color var(--transition-speed); +} + +.profile-logout-btn:hover { + background-color: rgba(255, 68, 68, 0.1); +} + +.text-cyber-cyan { + --tw-text-opacity: 1; + color: rgb(0 243 255 / var(--tw-text-opacity, 1)); +} + +/* Product Hero */ +.product-hero { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 4rem; + align-items: center; + min-height: 80vh; + padding: 8rem 2rem 4rem; + max-width: 1400px; + margin: 0 auto; +} + +.product-hero-content { + text-align: left; +} + +.product-hero-title { + font-size: 4rem; + font-weight: 800; + line-height: 1.1; + margin-bottom: 1.5rem; + background: linear-gradient(45deg, #fff, var(--text-muted)); + -webkit-background-clip: text; + background-clip: text; + color: transparent; +} + +.product-hero-subtitle { + font-size: 1.25rem; + color: var(--text-muted); + margin-bottom: 2.5rem; + line-height: 1.6; +} + +.hero-image-container { + position: relative; + perspective: 1000px; +} + +.hero-image { + width: 100%; + border-radius: 12px; + box-shadow: 0 20px 50px rgba(0, 0, 0, 0.5); + border: 1px solid rgba(255, 255, 255, 0.1); + transform: rotateY(-5deg) rotateX(2deg); + transition: transform 0.5s ease; +} + +.hero-image:hover { + transform: rotateY(0) rotateX(0); +} + +@media (max-width: 968px) { + .product-hero { + grid-template-columns: 1fr; + text-align: center; + padding-top: 6rem; + gap: 3rem; + } + + .product-hero-content { + text-align: center; + } + + .product-hero-title { + font-size: 3rem; + } + + .hero-image { + transform: none; + } +} + +/* Table Styles */ +.table { + width: 100%; + margin-bottom: 1rem; + color: var(--text-color); + border-collapse: collapse; +} + +.table th, +.table td { + padding: 1rem; + vertical-align: top; + border-top: 1px solid rgba(255, 255, 255, 0.1); +} + +.table thead th { + vertical-align: bottom; + border-bottom: 2px solid rgba(255, 255, 255, 0.1); + color: var(--primary-color); + text-align: left; + font-weight: 600; +} + +.table-responsive { + display: block; + width: 100%; + overflow-x: auto; + -webkit-overflow-scrolling: touch; +} + +/* Django Form Styling defaults */ +input[type="text"], +input[type="number"], +input[type="email"], +input[type="password"], +input[type="date"], +input[type="time"], +input[type="datetime-local"], +select, +textarea { + width: 100%; + padding: 0.75rem 1rem; + background: var(--bg-color); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 8px; + color: white; + font-family: var(--font-main); + font-size: 1rem; + margin-bottom: 1rem; + transition: border-color var(--transition-speed); +} + +input:focus, select:focus, textarea:focus { + outline: none; + border-color: var(--primary-color); +} + +.helptext { + color: var(--text-muted); + font-size: 0.85rem; + display: block; + margin-bottom: 1rem; + margin-top: -0.5rem; +} + +/* Footer links */ +.footer-links { + margin: 0.5rem 0; + font-size: 0.9rem; +} + +.footer-links a { + color: var(--text-muted); +} + +.footer-links a:hover { + color: var(--primary-color); +} + +.footer-link-btn { + background: none; + border: none; + padding: 0; + color: var(--text-muted); + font: inherit; + font-size: 0.9rem; + cursor: pointer; + text-decoration: none; +} + +.footer-link-btn:hover { + color: var(--primary-color); +} + +.footer-separator { + color: var(--text-muted); + margin: 0 0.5rem; +} + +/* Legal pages */ +.legal-page { + max-width: 820px; +} + +.legal-updated { + text-align: center; + color: var(--text-muted); + margin-bottom: 2rem; +} + +.legal-content h2 { + color: var(--primary-color); + font-size: 1.25rem; + margin: 2rem 0 0.75rem; +} + +.legal-content h3 { + color: var(--text-color); + font-size: 1.05rem; + margin: 1.5rem 0 0.5rem; +} + +.legal-content p, +.legal-content li { + color: var(--text-muted); + margin-bottom: 1rem; +} + +.legal-content ul { + margin: 0 0 1rem 1.5rem; +} + +.contact-captcha-group { + margin-top: 1rem; +} + +.contact-captcha-group + .btn { + margin-top: 1rem; +} + +/* Cookie consent banner */ +.cookie-consent-banner { + position: fixed; + bottom: 0; + left: 0; + right: 0; + z-index: 1100; + padding: 1rem; + background: rgba(26, 26, 26, 0.98); + border-top: 1px solid rgba(0, 243, 255, 0.25); + backdrop-filter: blur(10px); +} + +.cookie-consent-content { + max-width: 960px; + margin: 0 auto; + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + gap: 1rem; +} + +.cookie-consent-text { + flex: 1 1 420px; + color: var(--text-muted); + font-size: 0.95rem; + margin: 0; +} + +.cookie-consent-text-short { + display: none; +} + +.cookie-consent-actions { + display: flex; + gap: 0.75rem; + flex-shrink: 0; +} + +.btn-outline { + background: transparent; + border: 1px solid rgba(255, 255, 255, 0.25); + color: var(--text-color); +} + +.btn-outline:hover { + border-color: var(--primary-color); + color: var(--primary-color); +} + +@media (max-width: 640px) { + .cookie-consent-banner { + padding: 0.625rem 0.75rem; + padding-bottom: max(0.625rem, env(safe-area-inset-bottom)); + } + + .cookie-consent-content { + flex-direction: column; + align-items: stretch; + gap: 0.625rem; + } + + .cookie-consent-text-short { + display: inline; + } + + .cookie-consent-text-full { + display: none; + } + + .cookie-consent-text { + flex: none; + font-size: 0.8125rem; + line-height: 1.35; + } + + .cookie-consent-actions { + width: 100%; + gap: 0.5rem; + } + + .cookie-consent-actions .btn { + flex: 1; + padding: 0.5rem 0.75rem; + font-size: 0.75rem; + border-radius: 6px; + text-transform: none; + } +} + +/* Web design interactive pricing estimator */ +.pricing-intro { + text-align: center; + max-width: 720px; + margin: 0 auto 2.5rem; + color: var(--text-muted); + font-size: 1.05rem; +} + +.pricing-estimator { + display: grid; + grid-template-columns: minmax(0, 1.6fr) minmax(260px, 0.9fr); + gap: 1.5rem; + align-items: start; +} + +.pricing-features-panel, +.pricing-estimate-panel { + background: var(--bg-color); + border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: 12px; + padding: 1.5rem; +} + +.pricing-panel-title { + font-size: 1.35rem; + font-weight: 600; + color: #fff; + margin: 0 0 1.25rem; +} + +.pricing-feature-list { + list-style: none; + margin: 0; + padding: 0; +} + +.pricing-feature { + border-top: 1px solid rgba(255, 255, 255, 0.06); +} + +.pricing-feature:first-child { + border-top: none; +} + +.pricing-feature-label { + display: grid; + grid-template-columns: auto minmax(0, 1fr) auto; + gap: 0.85rem 1rem; + align-items: start; + padding: 1rem 0.25rem; + cursor: pointer; +} + +.pricing-feature.is-required .pricing-feature-label { + cursor: default; +} + +.pricing-feature-check { + appearance: none; + width: 1.15rem; + height: 1.15rem; + margin-top: 0.2rem; + border: 1.5px solid rgba(255, 255, 255, 0.35); + border-radius: 4px; + background: transparent; + display: grid; + place-content: center; + flex-shrink: 0; + cursor: pointer; +} + +.pricing-feature-check::before { + content: ""; + width: 0.65rem; + height: 0.65rem; + transform: scale(0); + transition: transform 0.12s ease-in-out; + box-shadow: inset 1em 1em var(--bg-color); + clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%); +} + +.pricing-feature-check:checked { + background: var(--primary-color); + border-color: var(--primary-color); +} + +.pricing-feature-check:checked::before { + transform: scale(1); +} + +.pricing-feature-check:disabled { + background: rgba(255, 255, 255, 0.18); + border-color: rgba(255, 255, 255, 0.25); + cursor: not-allowed; +} + +.pricing-feature-check:disabled:checked::before { + box-shadow: inset 1em 1em rgba(10, 10, 10, 0.75); +} + +.pricing-feature-check:focus-visible { + outline: 2px solid var(--primary-color); + outline-offset: 2px; +} + +.pricing-feature.is-selected:not(.is-required) { + background: rgba(0, 243, 255, 0.04); +} + +.pricing-feature-name { + display: block; + color: #fff; + font-weight: 600; + margin-bottom: 0.2rem; +} + +.pricing-feature-desc { + display: block; + color: var(--text-muted); + font-size: 0.92rem; + line-height: 1.45; +} + +.pricing-feature-note { + display: block; + margin-top: 0.4rem; + color: #ffb347; + font-size: 0.85rem; +} + +.pricing-feature-costs { + text-align: right; + color: var(--text-muted); + font-size: 0.9rem; + white-space: nowrap; + line-height: 1.45; +} + +.pricing-feature-costs strong { + color: var(--primary-color); + font-weight: 700; +} + +.pricing-feature-monthly { + display: block; +} + +.pricing-estimate-panel { + position: sticky; + top: 5.5rem; +} + +.pricing-estimate-row { + display: flex; + justify-content: space-between; + align-items: baseline; + gap: 1rem; + margin-bottom: 0.85rem; + color: var(--text-muted); +} + +.pricing-estimate-row strong { + color: var(--primary-color); + font-size: 1.5rem; + font-weight: 700; +} + +.pricing-estimate-count { + margin: 1.25rem 0 0.75rem; + color: #fff; + font-size: 0.95rem; +} + +.pricing-estimate-disclaimer { + color: var(--text-muted); + font-size: 0.85rem; + line-height: 1.45; + margin-bottom: 1.5rem; +} + +.pricing-estimate-cta { + display: inline-block; + width: 100%; + text-align: center; +} + +@media (max-width: 900px) { + .pricing-estimator { + grid-template-columns: 1fr; + } + + .pricing-estimate-panel { + position: static; + } + + .pricing-feature-label { + grid-template-columns: auto minmax(0, 1fr); + } + + .pricing-feature-costs { + grid-column: 2; + text-align: left; + } + + .pricing-feature-monthly { + display: inline; + margin-left: 0.5rem; + } +} diff --git a/site/core/static/core/img/logo.png b/site/core/static/core/img/logo.png new file mode 100644 index 0000000..74fdc92 Binary files /dev/null and b/site/core/static/core/img/logo.png differ diff --git a/site/core/templates/core/base.html b/site/core/templates/core/base.html index 9cc3a09..25800bc 100644 --- a/site/core/templates/core/base.html +++ b/site/core/templates/core/base.html @@ -1,128 +1,67 @@ +{% load static %} - - - {% block title %}{{ SITE_NAME }}{% endblock %} - + + + {% block title %}{{ SITE_NAME }} · {{ CREDIT_NAME }}{% endblock %} + + + + + + + - + {% if debug %} - +
Debug — mint form is local only{% if show_debug_create %} · Create a short link{% endif %}
{% endif %} -
+ + + +
{% block content %}{% endblock %} -
+ +
+
+ + + +
+
+ + diff --git a/site/core/templates/core/landing.html b/site/core/templates/core/landing.html index e83a09d..cbb943a 100644 --- a/site/core/templates/core/landing.html +++ b/site/core/templates/core/landing.html @@ -1,14 +1,57 @@ {% extends "core/base.html" %} -{% block title %}{{ SITE_NAME }}{% endblock %} +{% block title %}{{ SHORT_DOMAIN }} · URL shortener · {{ CREDIT_NAME }}{% endblock %} {% block content %} -

{{ SITE_NAME }}

-

{{ SHORT_DOMAIN }}

-

- This is a URL shortening service. A path like - /a3k9xm sends you to the long HTTPS URL. -

-

- Created by {{ CREDIT_NAME }} - · aimloperations.com -

+
+
+

{{ SHORT_DOMAIN }}

+

+ Short links for campaigns and trusted callers. Built by + {{ CREDIT_NAME }} — not an open public shortener. +

+ +
+
+ +
+
+

A shortener you can hand to SMS

+

+ {{ SHORT_DOMAIN }} turns a long HTTPS URL into a short path like + /a3k9xm. Recipients tap it and get a 302 to your site. + Creating links is Bearer-authenticated — only onboarded services can mint. +

+
+
+ +
+
+

Want access?

+
+
+

For your product

+

+ We issue a named token and allowlist your destination hosts. + Your app POSTs to the API and drops short_url into SMS or email. +

+
+
+

Not self-serve

+

+ There is no public signup. If you want this for a campaign or another + {{ CREDIT_NAME }} property, talk to us. +

+
+
+

Talk to us

+

+ Contact aimloperations.com and tell us + which hosts you need to shorten to. +

+ Contact +
+
+
+
{% endblock %} diff --git a/site/links/templates/links/debug_create.html b/site/links/templates/links/debug_create.html index be171ba..0e24a57 100644 --- a/site/links/templates/links/debug_create.html +++ b/site/links/templates/links/debug_create.html @@ -1,35 +1,43 @@ {% extends "core/base.html" %} {% block title %}Create short link · debug{% endblock %} {% block content %} -

Debug only

-

Create a short link

-

Uses the same allowlist as the API. Not served when DEBUG is false.

+
+
+

Create a short link

+

+ DEBUG only. Same allowlist as the API. Not served when DEBUG is false. +

- {% if messages %} -
    - {% for message in messages %}
  • {{ message }}
  • {% endfor %} -
- {% endif %} + {% if messages %} +
    + {% for message in messages %}
  • {{ message }}
  • {% endfor %} +
+ {% endif %} - {% if created_link %} -
-

Short URL

-

{{ created_link.public_short_url }}

-

Target: {{ created_link.target_url }}

- Open + {% if created_link %} +
+

Short URL

+

{{ created_link.public_short_url }}

+

Target: {{ created_link.target_url }}

+ Open +
+ {% endif %} + +
+ {% csrf_token %} + {{ form.non_field_errors }} + {% for field in form %} + + {% endfor %} + +
- {% endif %} - -
- {% csrf_token %} - {{ form.non_field_errors }} - {% for field in form %} - - {% endfor %} - -
+
{% endblock %} diff --git a/site/links/tests.py b/site/links/tests.py index fcf5d25..0c4dcf7 100644 --- a/site/links/tests.py +++ b/site/links/tests.py @@ -18,8 +18,9 @@ TOKENS = [("monica", "dev-only-token")] SETTINGS = dict( SHORTENER_API_TOKENS=TOKENS, - PUBLIC_SHORT_URL="https://go.mkdrealtor.com", - SHORT_PUBLIC_HOSTS=["go.mkdrealtor.com"], + SHORT_DOMAIN="piha.lc", + PUBLIC_SHORT_URL="https://piha.lc", + SHORT_PUBLIC_HOSTS=["piha.lc"], SHORT_API_HOSTS=["testserver", "localhost", "127.0.0.1", "shortener.example.com"], SHORT_ADMIN_HOSTS=["localhost", "127.0.0.1"], SHORT_ALLOWED_HOSTS=["mkdrealtor.com"], @@ -28,7 +29,7 @@ SETTINGS = dict( "testserver", "localhost", "127.0.0.1", - "go.mkdrealtor.com", + "piha.lc", "shortener.example.com", ], SHORT_CODE_LENGTH=6, @@ -93,7 +94,7 @@ class HostSplitTests(TestCase): data=json.dumps({"target_url": "https://mkdrealtor.com/x"}), content_type="application/json", HTTP_AUTHORIZATION=AUTH, - HTTP_HOST="go.mkdrealtor.com", + HTTP_HOST="piha.lc", ) self.assertEqual(response.status_code, 404) @@ -103,7 +104,7 @@ class HostSplitTests(TestCase): "localhost", "127.0.0.1", "shortener.example.com", - "go.mkdrealtor.com", + "piha.lc", ] ) 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"}), content_type="application/json", HTTP_AUTHORIZATION=AUTH, - HTTP_HOST="go.mkdrealtor.com", + HTTP_HOST="piha.lc", ) self.assertEqual(response.status_code, 404) 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) def test_public_api_host_without_bearer_401(self): @@ -155,7 +156,7 @@ class HostSplitTests(TestCase): self.assertEqual(response.status_code, 404) 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) self.assertEqual(response.status_code, 200) self.assertEqual(_json(response), {"status": "ok"}) @@ -216,7 +217,7 @@ class RedirectTests(TestCase): def test_active_code_302_to_target(self): 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.assertNotEqual(response.status_code, 301) @@ -226,7 +227,7 @@ class RedirectTests(TestCase): def test_head_also_302_without_click(self): 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( @@ -239,34 +240,36 @@ class RedirectTests(TestCase): def test_inactive_404(self): self.link.is_active = False 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) def test_expired_404(self): self.link.expires_at = timezone.now() - timedelta(minutes=1) 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) 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) 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) 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.assertContains(response, "URL Shortening Service") + self.assertContains(response, "piha.lc") + self.assertContains(response, "Request access") self.assertContains(response, "aimloperations.com") + self.assertContains(response, "Contact") self.assertNotContains(response, "Create a short link") def test_click_row_and_count(self): self.client.get( "/a3k9xm", - HTTP_HOST="go.mkdrealtor.com", + HTTP_HOST="piha.lc", HTTP_USER_AGENT="sms-client", ) self.link.refresh_from_db() @@ -296,7 +299,7 @@ class CreateTests(TestCase): ) self.assertEqual(response.status_code, 201) 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"]) def test_idempotent_same_target_and_external_ref(self): @@ -349,9 +352,9 @@ class CreateTests(TestCase): self.assertEqual(first.status_code, 200) self.assertEqual(second.status_code, 200) 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 - 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) @@ -379,12 +382,12 @@ class UseCaseTests(TestCase): self.assertEqual(create.status_code, 201) body = _json(create) 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( f"/{body['code']}", - HTTP_HOST="go.mkdrealtor.com", + HTTP_HOST="piha.lc", follow=False, ) self.assertEqual(follow.status_code, 302) @@ -408,7 +411,7 @@ class DebugCreateTests(TestCase): @override_settings(DEBUG=True) 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) @override_settings(DEBUG=True) @@ -494,7 +497,7 @@ class AdminTests(TestCase): def test_admin_404_on_public_hosts(self): 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) response = self.client.get("/admin/", HTTP_HOST="shortener.example.com") self.assertEqual(response.status_code, 404) diff --git a/site/shortener/settings/base.py b/site/shortener/settings/base.py index 793c34a..7d7be7c 100644 --- a/site/shortener/settings/base.py +++ b/site/shortener/settings/base.py @@ -89,7 +89,7 @@ DEBUG = env_bool("DJANGO_DEBUG", False) allowed_hosts = env_list( "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 ["*"] @@ -176,7 +176,7 @@ DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" # --- Shortener --- 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]) @@ -196,3 +196,6 @@ CODE_ALPHABET = "23456789abcdefghjkmnpqrstuvwxyz" 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_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" +)