diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md index 807ce9b..d24b912 100644 --- a/DEPLOYMENT.md +++ b/DEPLOYMENT.md @@ -73,7 +73,7 @@ mkdir -p /docker/goodwalk-svelte It is created from [deploy.env.template](deploy.env.template). Current template contents: ```env -APP_VERSION=4.0.2 +APP_VERSION=4.2.2 ENABLE_GENERAL_ENQUIRIES=false TZ=Pacific/Auckland diff --git a/Dockerfile b/Dockerfile index e42df7d..3066721 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -ARG APP_VERSION=4.0.2 +ARG APP_VERSION=4.2.2 FROM node:22-alpine AS builder ARG APP_VERSION diff --git a/deploy.env.template b/deploy.env.template index 0b38336..588c2c5 100644 --- a/deploy.env.template +++ b/deploy.env.template @@ -1,4 +1,4 @@ -APP_VERSION=4.0.2 +APP_VERSION=4.2.2 TZ=Pacific/Auckland POSTGRES_DB=goodwalk diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 063b15e..6496bee 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -3,10 +3,10 @@ services: build: context: . args: - APP_VERSION: ${APP_VERSION:-4.0.2} + APP_VERSION: ${APP_VERSION:-4.2.2} container_name: goodwalk_svelte_app environment: - APP_VERSION: ${APP_VERSION:-4.0.2} + APP_VERSION: ${APP_VERSION:-4.2.2} DATABASE_URL: postgresql://${POSTGRES_USER:-goodwalk}:${POSTGRES_PASSWORD_URLENCODED:-goodwalk}@db:5432/${POSTGRES_DB:-goodwalk} NODE_ENV: production PORT: 3000 @@ -25,10 +25,10 @@ services: build: context: ./mail-api args: - APP_VERSION: ${APP_VERSION:-4.0.2} + APP_VERSION: ${APP_VERSION:-4.2.2} container_name: goodwalk_svelte_mail_api environment: - APP_VERSION: ${APP_VERSION:-4.0.2} + APP_VERSION: ${APP_VERSION:-4.2.2} RESEND_API_KEY: ${RESEND_API_KEY} OWNER_EMAIL: ${OWNER_EMAIL} OWNER_BCC: ${OWNER_BCC:-} diff --git a/docker-compose.yml b/docker-compose.yml index 0b3cd81..5bf1a1d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,9 +3,9 @@ services: build: context: . args: - APP_VERSION: ${APP_VERSION:-4.0.2} + APP_VERSION: ${APP_VERSION:-4.2.2} environment: - APP_VERSION: ${APP_VERSION:-4.0.2} + APP_VERSION: ${APP_VERSION:-4.2.2} DATABASE_URL: postgresql://${POSTGRES_USER:-goodwalk}:${POSTGRES_PASSWORD:-goodwalk}@db:5432/${POSTGRES_DB:-goodwalk} NODE_ENV: production PORT: ${APP_PORT:-3000} @@ -19,9 +19,9 @@ services: build: context: ./mail-api args: - APP_VERSION: ${APP_VERSION:-4.0.2} + APP_VERSION: ${APP_VERSION:-4.2.2} environment: - APP_VERSION: ${APP_VERSION:-4.0.2} + APP_VERSION: ${APP_VERSION:-4.2.2} RESEND_API_KEY: ${RESEND_API_KEY} OWNER_EMAIL: ${OWNER_EMAIL} OWNER_BCC: ${OWNER_BCC:-} diff --git a/mail-api/Dockerfile b/mail-api/Dockerfile index 37187cf..8a161a9 100644 --- a/mail-api/Dockerfile +++ b/mail-api/Dockerfile @@ -1,4 +1,4 @@ -ARG APP_VERSION=4.0.2 +ARG APP_VERSION=4.2.2 FROM python:3.12-slim ARG APP_VERSION diff --git a/mail-api/main.py b/mail-api/main.py index 77ed169..ec6db51 100644 --- a/mail-api/main.py +++ b/mail-api/main.py @@ -141,6 +141,7 @@ logger.info( ) app = FastAPI(title="GoodWalk Mail API") +STARTUP_TEST_RECIPIENT = OWNER_BCC if OWNER_BCC and OWNER_BCC.lower() != "example@example.com" else "" app.add_middleware( CORSMiddleware, @@ -184,6 +185,12 @@ class BookingSubmission(BaseModel): services: list[str] = [] website: str = "" formStartedAt: int | None = None + visitStartedAt: int | None = None + pageEnteredAt: int | None = None + firstInteractionAt: int | None = None + sendClickedAt: int | None = None + stepChanges: int = 0 + journey: list[str] = [] referrer: str = "" page: str = "" @@ -382,6 +389,13 @@ def _normalize_submission(data: BookingSubmission) -> None: data.referrer = _trimmed(data.referrer) data.page = _trimmed(data.page) data.services = [_trimmed(service) for service in data.services if _trimmed(service)] + data.journey = [_trimmed(step) for step in data.journey if _trimmed(step)][:12] + data.stepChanges = max(0, data.stepChanges) + + for field_name in ("visitStartedAt", "pageEnteredAt", "firstInteractionAt", "sendClickedAt"): + value = getattr(data, field_name) + if value is None or value <= 0: + setattr(data, field_name, None) if _is_general_enquiry(data): data.petName = "" @@ -430,6 +444,33 @@ def _meta_row(label: str, value: str) -> str: """ +def _format_duration_ms(duration_ms: int | None) -> str: + if duration_ms is None or duration_ms < 0: + return "" + + total_seconds = int(round(duration_ms / 1000)) + minutes, seconds = divmod(total_seconds, 60) + hours, minutes = divmod(minutes, 60) + + if hours > 0: + return f"{hours}h {minutes}m" + if minutes > 0: + return f"{minutes}m {seconds}s" + return f"{seconds}s" + + +def _duration_between(start_ms: int | None, end_ms: int | None) -> str: + if start_ms is None or end_ms is None or end_ms < start_ms: + return "" + return _format_duration_ms(end_ms - start_ms) + + +def _journey_text(journey: list[str]) -> str: + if not journey: + return "" + return " -> ".join(journey) + + # ── Email templates ────────────────────────────────────────────────────────── def _logo_header(badge_html: str = "", subtitle: str = "") -> str: @@ -620,6 +661,12 @@ def owner_email(data: BookingSubmission, ip: str, browser: str) -> str: referrer_row = _meta_row("Came from", data.referrer) if data.referrer else _meta_row("Came from", "Direct / bookmark") page_row = _meta_row("Page", data.page) if data.page else "" + visit_time_row = _meta_row("Time on site", _duration_between(data.visitStartedAt, data.sendClickedAt)) + page_time_row = _meta_row("Time on page", _duration_between(data.pageEnteredAt, data.sendClickedAt)) + active_time_row = _meta_row("Active form time", _duration_between(data.firstInteractionAt, data.sendClickedAt)) + form_time_row = _meta_row("Form open time", _duration_between(data.formStartedAt, data.sendClickedAt)) + step_changes_row = _meta_row("Step changes", str(data.stepChanges)) if data.stepChanges else "" + journey_row = _meta_row("Journey", _journey_text(data.journey)) detail_heading = "Enquiry details" if is_general else "Dog & services" detail_rows = [_detail_row("Type", _enquiry_type_label(data))] @@ -642,29 +689,104 @@ def owner_email(data: BookingSubmission, ip: str, browser: str) -> str: + + {email_title} - - + + + + +
- + box-shadow:0 4px 24px rgba(0,0,0,0.08);background:#ffffff;"> {_logo_header(badge_html=badge)} -