piha.lc / beta.piha.li + company_site marketing landing (#4)
Deploy Beta / unit-tests (push) Successful in 4s
Deploy Beta / docker (push) Successful in 10s
Deploy Beta / deploy-beta (push) Successful in 1m3s

## Summary

- Public short domain is **`piha.lc`** (prod) and **`beta.piha.li`** (beta)
- `/` is a marketing page in [aimloperations.com](https://aimloperations.com) / `company_site` styling (Inter, dark, cyan)
- CTA: want access → [contact](https://aimloperations.com/contact)
- Nav/logo/footer match the company site; debug mint form uses the same chrome

Closes #3.

## Secrets (control node)

Update hostnames only — no new token/pepper unless you are rotating:

**`url_shortening_service_prod.env`**
```text
DJANGO_ALLOWED_HOSTS=piha.lc,shortener.aimloperations.com,url-shortener,web
SHORT_DOMAIN=piha.lc
PUBLIC_SHORT_URL=https://piha.lc
SHORT_PUBLIC_HOSTS=piha.lc
CONTACT_URL=https://aimloperations.com/contact
```

**`url_shortening_service_beta.env`**
```text
DJANGO_ALLOWED_HOSTS=beta.piha.li,shortener-beta.aimloperations.com,url-shortener,web
SHORT_DOMAIN=beta.piha.li
PUBLIC_SHORT_URL=https://beta.piha.li
SHORT_PUBLIC_HOSTS=beta.piha.li
CONTACT_URL=https://aimloperations.com/contact
```

NPM: `piha.lc` / `beta.piha.li` → this container. Still do not proxy `/api/` on the short host.

## Test plan
- [ ] `cd site && uv run python manage.py test`
- [ ] `GET /` looks like aimloperations.com; Request access / Contact go to aimloperations.com/contact
- [ ] `GET /<code>` still 302
- [ ] `/api/` on Host `piha.lc` is 404

Reviewed-on: #4
This commit was merged in pull request #4.
This commit is contained in:
2026-08-30 06:55:43 -07:00
parent 630b770c12
commit ccb4d0097d
18 changed files with 1373 additions and 214 deletions
+2 -1
View File
@@ -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
View File
@@ -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
+2 -2
View File
@@ -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
+4 -4
View File
@@ -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 \
+4 -4
View File
@@ -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 -3
View File
@@ -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
View File
@@ -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) |
+1 -1
View File
@@ -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
View File
@@ -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}
+1
View File
@@ -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()),
} }
+69
View File
@@ -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

+57 -118
View File
@@ -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 &amp; 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">&copy; 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>
+54 -11
View File
@@ -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
</p> {{ CREDIT_NAME }} — not an open public shortener.
<p class="lede"> </p>
Created by <a href="{{ CREDIT_URL }}">{{ CREDIT_NAME }}</a> <div class="hero-cta">
· <a href="{{ CREDIT_URL }}">aimloperations.com</a> <a href="{{ CONTACT_URL }}" class="btn">Request access</a>
</p> </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>
</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 %}
+36 -28
View File
@@ -1,35 +1,43 @@
{% 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>
{% endif %}
<form method="post" action="{% url 'debug-create' %}">
{% csrf_token %}
{{ form.non_field_errors }}
{% for field in form %}
<label>
{{ field.label }}
<input class="form-control" type="{{ field.field.widget.input_type|default:'text' }}"
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>
{% endfor %}
<button type="submit" class="btn">Mint</button>
</form>
</div> </div>
{% endif %} </div>
<form method="post" action="{% url 'debug-create' %}">
{% csrf_token %}
{{ form.non_field_errors }}
{% for field in form %}
<label>
{{ field.label }}
{{ field }}
{% if field.errors %}<span class="error">{{ field.errors|striptags }}</span>{% endif %}
</label>
{% endfor %}
<button type="submit">Mint</button>
</form>
{% endblock %} {% endblock %}
+27 -24
View File
@@ -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)
+5 -2
View File
@@ -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"
)