July 3rd - Fix landing page not display

This commit is contained in:
2026-07-03 21:15:29 -04:00
parent 4b6401be43
commit 372a1f090a
3 changed files with 40 additions and 15 deletions
+1 -1
View File
@@ -1237,7 +1237,7 @@ set -a; . /etc/jqc/control.env; set +a
| 75 | **`delete_tenant()` + `_add_domains()` are retry-safe** | `_add_domains` uses `_upsert_domain()` (delete-then-insert) to handle orphan rows from failed partial runs. `delete_tenant()` also purges by derived domain string, not only by `tenant_id`, catching orphans whose parent tenant row was rolled back. |
| 76 | **`register-tenant-zero` never bootstraps and never drops the DB** | `register_tenant_zero()` only reads the existing head, inserts control rows, and maps domains. `delete_tenant()` on tenant-zero must never use `--drop-db` — the guard checks `db_name == db_name_for(slug)` and refuses non-provisioner-named DBs (LT's DB name is `jqc_lt`, not `jqc_lts`). |
| 84 | **Device registration is consolidated on `DeviceToken` / `api_device_tokens` — one handler only** | RESOLVED. There is exactly one `POST /api/v1/devices/register`, in `app/api/auth.py` (blueprint `api_auth`); it upserts `DeviceToken` (device_id, device_name, app_version, ios_version, apns_token, last_seen_at) which the admin Devices page reads. The former duplicate `api_devices` blueprint (`app/api/devices.py`) and the orphaned `DeviceRegistration` model / `device_registrations` table were **deleted** — that path wrote to a table phase31/32 drop. Do not reintroduce a second `/devices/register` route or a `device_registrations`-backed model. |
| 85 | **Apex host serves the public landing page; the landing route lives at `/welcome`, NOT `/`** | The dashboard owns `/` (login-gated) on tenant hosts, so the landing page cannot register a second `/` route (same collision class as rule 84). Instead the tenant middleware detects the apex host (`TENANT_BASE_DOMAIN` + `www.`) and calls `landing.index` directly for `/`, redirecting other non-exempt apex paths to `/`. `/welcome`, `/signup`, `/static/` are tenant-exempt. The apex handling is inert when `MULTI_TENANT_ENABLED=false`, so `/welcome` is the always-reachable preview URL. Requires the Nginx apex block to **proxy** (not 301-redirect) to port 8000 with `Host` passed through. |
| 85 | **Apex host serves the public landing page; the landing route lives at `/welcome`, NOT `/`** | The dashboard owns `/` (login-gated) on tenant hosts, so the landing page cannot register a second `/` route (same collision class as rule 84). Instead the tenant middleware detects the apex host (`TENANT_BASE_DOMAIN` + `www.`) and calls `landing.index` directly for `/`, redirecting other non-exempt apex paths to `/`. `/welcome`, `/signup`, `/static/` are tenant-exempt. **The apex check runs BEFORE the `MULTI_TENANT_ENABLED` gate** — it must work in single-tenant mode too, otherwise the app serves its default database (tenant-zero) for the apex host and the landing page never shows. Requires the Nginx apex block to **proxy** (not 301-redirect) to port 8000 with `Host` passed through, and `TENANT_BASE_DOMAIN` set correctly in the app environment. |
| 86 | **Free plan is free-forever, not a trial** | `signup.index()` passes `trial_days=0` for `plan_code == 'free'`; `create_tenant()` then sets `subscription_status='active'` (no `trial_ends_at`) so `_billing_gate()` never blocks it. Paid plans keep the 14-day trial (`trial_days=14`). The welcome email adapts via `trial_note` and hides the trial row when `trial_ends_at` is blank. Do not reintroduce a hardcoded `trial_days=14` in the signup path. |
---
+21 -14
View File
@@ -78,6 +78,26 @@ def init_tenancy(app):
g.billing_warning = None # MT-8: set to 'past_due' by _billing_gate when needed
g.is_apex = False # True when serving the public apex/landing host
# ── Public apex (marketing) host → landing page ──────────────────────
# The apex domain (TENANT_BASE_DOMAIN, e.g. jqc.app) and its www. variant
# are NEVER a tenant. Serve the public landing page for '/' and bounce any
# other non-exempt apex path back to '/'. This runs BEFORE the
# MULTI_TENANT_ENABLED gate on purpose: the marketing site must work even
# in single-tenant mode (MT flag off), where the app would otherwise serve
# its default database (tenant-zero) for every host. /welcome, /signup,
# and /static/ are exempt and pass straight through.
host = (request.host or '').split(':')[0].strip().lower()
base = (current_app.config.get('TENANT_BASE_DOMAIN') or '').strip().lower()
if base and host in (base, f'www.{base}'):
g.is_apex = True
if _is_exempt(request.path):
return
if request.path == '/':
# Serve the landing view without a redirect (the dashboard owns
# '/' on tenant hosts, so we can't register a second '/' route).
return current_app.view_functions['landing.index']()
return redirect('/')
if not current_app.config.get('MULTI_TENANT_ENABLED', False):
return # inert: default database serves everything (single-tenant)
@@ -127,21 +147,8 @@ def init_tenancy(app):
session.pop('impersonating_tenant_id', None)
session.pop('impersonating_superadmin_id', None)
# ── Apex (marketing) host → public landing page ──────────────────
# The apex domain (jqc.app) and its www. variant are NOT tenants. Serve
# the public landing page for '/' and send any other non-exempt apex
# path back to it. /signup, /static, /welcome are already exempt above.
host = (request.host or '').split(':')[0].strip().lower()
base = (current_app.config.get('TENANT_BASE_DOMAIN') or '').strip().lower()
if base and host in (base, f'www.{base}'):
g.is_apex = True
if request.path == '/':
# Serve the landing view without a redirect (dashboard owns '/'
# on tenant hosts, so we can't register a second '/' route).
return current_app.view_functions['landing.index']()
return redirect('/')
# ── Normal Host → tenant resolution ──────────────────────────────
host = (request.host or '').split(':')[0].strip().lower()
tenant = resolve_tenant(host)
if tenant is None:
return Response(_UNKNOWN_TENANT_PAGE, status=404, mimetype='text/html')
+18
View File
@@ -23,6 +23,24 @@ def test_welcome_renders_landing(app):
assert b'Simple plans that grow with you' in r.data # pricing section
def test_apex_serves_landing_even_with_mt_disabled(app):
"""Regression guard: the apex landing must work in single-tenant mode too.
The base `app` fixture has MULTI_TENANT_ENABLED=False and the default
TENANT_BASE_DOMAIN='jqc.app'. Before the fix, the apex fell through to the
default database (tenant-zero) instead of the landing page.
"""
client = app.test_client()
r = client.get('/', headers={'Host': 'jqc.app'})
assert r.status_code == 200
assert b'Quality control for janitorial contracts' in r.data
# A non-root apex path still bounces back to '/' even with MT off.
r2 = client.get('/dashboard', headers={'Host': 'jqc.app'})
assert r2.status_code == 302
assert r2.headers['Location'] == '/'
@pytest.fixture
def mt_app(app):
"""The shared app with multi-tenancy temporarily enabled (apex = jqc.app).