Jul 28 - Update the demo request button, and add demo registration page

This commit is contained in:
2026-07-28 14:18:22 -04:00
parent 6b904aead9
commit ea9458395d
17 changed files with 905 additions and 13 deletions
+43 -3
View File
@@ -25,10 +25,14 @@ Ubuntu 24.04. Fonts via Google Fonts (Bricolage Grotesque, IBM Plex Sans/Mono).
## Structure
- `app.py` — factory, models (`Section`, `Topic`, `AuditLog`), `log_action()`,
ProxyFix wrap, `_configure_auth_logger()`, public routes `/`, `/healthz`.
- `app.py` — factory, models (`Section`, `Topic`, `DemoRequest`, `AuditLog`),
`log_action()`, ProxyFix wrap, `_configure_auth_logger()`, demo helpers
(`demo_slots`, `_rate_limited`, `_notify_demo`), public routes `/`, `/demo`,
`/demo/thanks`, `/healthz`.
- `mailer.py` — stdlib SMTP sender; `send_email()` / `send_email_async()`.
- `admin.py` — blueprint `/admin`: session login, section/topic CRUD, `/admin/audit`
read-only audit viewer (filter by action/entity, 50/page).
read-only audit viewer (filter by action/entity, 50/page), `/admin/demos`
appointment inbox (+ status / delete).
- `config.py` — env-driven config + `_load_dotenv()` (no-expansion loader).
- `templates/` — public `index.html`; `admin/` base+login+dashboard+forms.
- `static/css/style.css` (public), `static/css/admin.css`, `static/js/main.js`.
@@ -42,6 +46,8 @@ Ubuntu 24.04. Fonts via Google Fonts (Bricolage Grotesque, IBM Plex Sans/Mono).
link_label, media_type[none|image|video|embed], media_url, media_caption,
sort_order, is_published)`
- `audit_log(id, actor, action, entity, entity_id, detail, created_at)`
- `demo_request(id, name, company, email, phone, preferred_date, preferred_time,
message, status[new|scheduled|done|cancelled], source_ip, created_at)`
Public page orders sections by `sort_order, num`; topics by `sort_order`, and
shows only `is_published` topics (sections with no published topics are hidden).
@@ -165,11 +171,45 @@ shows only `is_published` topics (sections with no published topics are hidden).
Google Fonts — that's why libs are vendored. Fonts still load fine on the real
server; they degrade to system fonts if blocked.
## Demo appointments (public booking + owner notification)
- CTA button now goes to **`/demo`** (a real form), not the `mailto:`.
`DEMO_CONTACT_URL` survives as the "email us directly" fallback link.
- `GET/POST /demo` → `templates/demo.html`; success redirects to
`/demo/thanks` (POST-redirect-GET, so a refresh can't double-book).
- Slots come from `demo_slots()` — `DEMO_HOUR_START/END` + `DEMO_SLOT_MINUTES`
(default 08:0016:30 every 30 min). Server-side validation is the real gate:
the posted time must be in that list, the date must parse, be today-or-later,
within `DEMO_MAX_DAYS_AHEAD`, and be a weekday. Dates/times are stored exactly
as picked; `DEMO_TIMEZONE_LABEL` is display-only (no tz conversion anywhere).
- **One appointment per slot:** a request for a (date, time) that already has a
non-`cancelled` row is rejected with a flash. Cancelling frees the slot.
- **Abuse control:** hidden `website` honeypot (filled → fake success, nothing
stored) + per-IP hourly cap `DEMO_RATE_LIMIT` held in `_demo_hits` (in-memory,
therefore per gunicorn worker — it's a nuisance filter, not a DDoS defence;
use nginx `limit_req` if that ever matters). CSRF via the global `CSRFProtect`.
- **Notification:** `_notify_demo()` mails `DEMO_NOTIFY_EMAIL` with the details
and a link to `/admin/demos`, `Reply-To` set to the customer; with
`DEMO_CONFIRM_CUSTOMER=1` the customer also gets a copy. Both go through
`send_email_async()` on a daemon thread **after** the row is committed —
mail is best-effort and never raises, so a dead SMTP host loses the email,
never the appointment. Blank `SMTP_HOST`/`MAIL_FROM` = mail silently off
(logged via `jqc.mail`); requests still land in the admin inbox.
- Header values are CR/LF-stripped (`_clean_header`) so a submitted name can't
inject SMTP headers.
- **Admin inbox** `/admin/demos`: default view shows `new` + `scheduled`,
soonest first; chips filter by status; per-row status select and delete.
Nav badge counts `new`. Status POSTs redirect via a `back_status` form field —
never `request.referrer` (open-redirect). Every create/status/delete writes an
audit row with entity `demo` (the audit filter accepts it).
## Deploy delta cheatsheet
```bash
sudo mysql < add_admin.sql # audit_log (existing DBs)
sudo mysql < add_publish.sql # topic.is_published (existing DBs)
sudo mysql < add_demo.sql # demo_request table (existing DBs)
# .env: DEMO_NOTIFY_EMAIL + SMTP_HOST/PORT/USER/PASSWORD/MAIL_FROM for notifications
# .env: SECRET_KEY, ADMIN_USERNAME, ADMIN_PASSWORD_HASH, SESSION_COOKIE_SECURE=1
sudo ./venv/bin/pip install -r requirements.txt # bleach[css] + tinycss2 (CSS sanitize)
sudo -u jqc mkdir -p static/uploads # in-body image uploads (writable)