piha.lc / beta.piha.li + company_site marketing landing (#4)
## 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:
@@ -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()),
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
{% load static %}
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{% block title %}{{ SITE_NAME }}{% endblock %}</title>
|
||||
<style>
|
||||
:root {
|
||||
--ink: #1c1914;
|
||||
--paper: #f3ead8;
|
||||
--rule: #c9b896;
|
||||
--pine: #2a4a3c;
|
||||
--rust: #9a3f24;
|
||||
--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>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{% block title %}{{ SITE_NAME }} · {{ CREDIT_NAME }}{% endblock %}</title>
|
||||
<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 %}">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;800&display=swap" rel="stylesheet">
|
||||
<link href="{% static 'core/css/style.css' %}" rel="stylesheet">
|
||||
<link href="{% static 'core/css/overrides.css' %}" rel="stylesheet">
|
||||
<link rel="icon" type="image/png" href="{% static 'core/img/logo.png' %}">
|
||||
</head>
|
||||
<body>
|
||||
<body{% if debug %} class="has-debug-banner"{% endif %}>
|
||||
{% 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 %}
|
||||
<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 %}
|
||||
<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>
|
||||
|
||||
<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>
|
||||
</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 %}
|
||||
<p class="eyebrow">{{ SITE_NAME }}</p>
|
||||
<h1>{{ SHORT_DOMAIN }}</h1>
|
||||
<p class="lede">
|
||||
This is a URL shortening service. A path like
|
||||
<strong>/a3k9xm</strong> sends you to the long HTTPS URL.
|
||||
</p>
|
||||
<p class="lede">
|
||||
Created by <a href="{{ CREDIT_URL }}">{{ CREDIT_NAME }}</a>
|
||||
· <a href="{{ CREDIT_URL }}">aimloperations.com</a>
|
||||
</p>
|
||||
<div class="hero-section">
|
||||
<div class="hero-content">
|
||||
<h1 class="hero-title">{{ SHORT_DOMAIN }}</h1>
|
||||
<p class="hero-subtitle hero-subtitle--wide">
|
||||
Short links for campaigns and trusted callers. Built by
|
||||
{{ CREDIT_NAME }} — not an open public shortener.
|
||||
</p>
|
||||
<div class="hero-cta">
|
||||
<a href="{{ CONTACT_URL }}" class="btn">Request access</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>
|
||||
</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 %}
|
||||
|
||||
@@ -1,35 +1,43 @@
|
||||
{% extends "core/base.html" %}
|
||||
{% block title %}Create short link · debug{% endblock %}
|
||||
{% block content %}
|
||||
<p class="eyebrow">Debug only</p>
|
||||
<h1>Create a short link</h1>
|
||||
<p class="lede">Uses the same allowlist as the API. Not served when <code>DEBUG</code> is false.</p>
|
||||
<div class="section debug-section">
|
||||
<div class="container" style="max-width: 40rem;">
|
||||
<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 %}
|
||||
<ul class="messages">
|
||||
{% for message in messages %}<li>{{ message }}</li>{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% if 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 %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
{% if created_link %}
|
||||
<div class="result">
|
||||
<p>Short URL</p>
|
||||
<p><code>{{ created_link.public_short_url }}</code></p>
|
||||
<p>Target: {{ created_link.target_url }}</p>
|
||||
<a class="btn" href="{{ created_link.public_short_url }}">Open</a>
|
||||
{% if created_link %}
|
||||
<div class="card debug-result">
|
||||
<p class="card-title">Short URL</p>
|
||||
<p><code>{{ created_link.public_short_url }}</code></p>
|
||||
<p class="card-text">Target: {{ created_link.target_url }}</p>
|
||||
<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>
|
||||
{% endif %}
|
||||
|
||||
<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>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
+27
-24
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user