This commit is contained in:
2026-05-26 08:30:08 +12:00
parent 005aab8139
commit 135a5a3b83
75 changed files with 22417 additions and 4288 deletions
+13
View File
@@ -211,6 +211,13 @@ _DATA_DIR = Path(os.environ.get("DATA_DIR", "data"))
ALLOWED_EMAILS_FILE = _DATA_DIR / "allowed_emails.json"
CLIENT_PROFILES_FILE = _DATA_DIR / "client_profiles.json"
DRAFTS_FILE = _DATA_DIR / "drafts.json"
# Legacy seed lives OUTSIDE the data volume — it's shipped in the image so a
# fresh deploy always carries the same baked-in copy. On boot the mail-api
# merges this dict into _client_profiles, adding any emails that aren't
# already present. It never overwrites a live entry.
LEGACY_SEED_FILE = Path(
os.environ.get("LEGACY_SEED_FILE", "/app/legacy-clients-seed.json")
)
LOGO_URL = "https://www.goodwalk.co.nz/images/goodwalk-auckland-dog-walking-logo.png"
@@ -235,6 +242,12 @@ EMAIL_SEND_TIMEOUT_SECONDS = settings.email_send_timeout_seconds
# Owner-BCC placeholder used by deploy.env.template; treat it as "unset" for the smoke email.
STARTUP_TEST_RECIPIENT = OWNER_BCC if OWNER_BCC and OWNER_BCC.lower() != "example@example.com" else ""
# Shared secret presented by the deploy script's post-deploy form smoke tests.
# When set, requests carrying a matching X-Deploy-Smoke header short-circuit the
# form handlers before any email/db side effects so production submissions stay
# clean. When unset, the bypass is fully disabled.
DEPLOY_SMOKE_SECRET = (os.environ.get("DEPLOY_SMOKE_SECRET") or "").strip()
logger.info(
"Mail API config: version=%r timezone=%r from=%r reply_to=%r owner=%r cp_admins=%r owner_bcc=%r client_bcc=%r general_enquiries=%r max_attempts=%d form_min=%ss form_max=%ss rate_window=%ss per_ip=%d per_email=%d min_interval=%ss send_timeout=%ss",
APP_VERSION,
+12 -1
View File
@@ -2,7 +2,7 @@
from __future__ import annotations
from typing import Any
from typing import Any, Literal
from pydantic import BaseModel, EmailStr
@@ -70,6 +70,17 @@ class BirthdayAutoSendRequest(BaseModel):
enabled: bool
# Client lifecycle status. Soft-delete only — every client stays on file so the
# history is preserved for future newsletter / retention work.
ClientLifecycleStatus = Literal["active", "paused", "cancelled", "archived"]
class ClientStatusUpdate(BaseModel):
email: EmailStr
status: ClientLifecycleStatus
reason: str = ""
class ContractSubmission(BaseSubmission):
address: str
dogName: str