Files
gw-svelte/mail-api/mail_api/models.py
T

240 lines
7.0 KiB
Python
Raw Normal View History

2026-05-19 23:36:58 +12:00
"""Pydantic request/response models for the mail API."""
from __future__ import annotations
2026-05-26 08:30:08 +12:00
from typing import Any, Literal
2026-05-19 23:36:58 +12:00
from pydantic import BaseModel, EmailStr
class BaseSubmission(BaseModel):
fullName: str
email: EmailStr
phone: str
website: str = ""
formStartedAt: int | None = None
visitStartedAt: int | None = None
pageEnteredAt: int | None = None
firstInteractionAt: int | None = None
sendClickedAt: int | None = None
referrer: str = ""
page: str = ""
class BookingSubmission(BaseSubmission):
enquiryType: str = "booking"
petName: str = ""
location: str = ""
message: str = ""
services: list[str] = []
stepChanges: int = 0
journey: list[str] = []
class OnboardingSubmission(BaseSubmission):
address: str
dogName: str
dogBreed: str
dogAge: str = ""
servicesNeeded: list[str] = []
temperament: str = ""
medicalNotes: str = ""
accessInstructions: str = ""
vetName: str
2026-06-17 21:51:29 +12:00
vetAddress: str
2026-05-19 23:36:58 +12:00
vetPhone: str
emergencyContactName: str
emergencyContactPhone: str
2026-06-17 21:51:29 +12:00
regularFleaTickTreatment: str = ""
petInsurance: str = ""
petInsuranceOwnerExpenseAccepted: bool = False
2026-05-19 23:36:58 +12:00
councilRegistrationConfirmed: bool = False
vaccinationsConfirmed: bool = False
emergencyVetConsent: bool = False
termsAccepted: bool = False
signatureDataUrl: str
submissionSnapshot: dict[str, Any] = {}
class WelcomePackEmailRequest(BaseModel):
email: EmailStr
2026-06-17 21:51:29 +12:00
# Owner-set subject line. Defaults to "Goodwalk Onboarding - <Dog Name>" when
# blank (resolved server-side so scheduled sends keep a sensible subject too).
subject: str = ""
2026-07-04 10:00:16 +12:00
# Structured offer details rendered in the details table. Now fully optional —
# the owner can hide the table entirely (showDetails=False) and write a free
# email instead. Left blank, a row is simply omitted from the table.
serviceType: str = ""
priceDetails: str = ""
startDate: str = ""
# Free-text customisation. All blank => the email falls back to the original
# default copy, so existing callers keep working unchanged. `{first_name}`
# and `{dog_name}` tokens are substituted server-side.
heading: str = ""
intro: str = ""
outro: str = ""
tagLabel: str = ""
# Customisable button block. Defaults to "Complete onboarding" pointing at the
# client onboarding portal. Clearing either field hides the button.
ctaLabel: str = ""
ctaUrl: str = ""
# Whether to render the Service / Price / Start date table at all.
showDetails: bool = True
# Rich-text (WYSIWYG) message body. When present it replaces the
# heading/intro/outro copy above and is sanitised to an email-safe subset of
# HTML server-side. `includeButton` toggles the onboarding button beneath it.
bodyHtml: str = ""
includeButton: bool = True
2026-05-19 23:36:58 +12:00
preview: bool = False
2026-06-17 21:51:29 +12:00
previewRecipients: list[EmailStr] = []
2026-07-04 10:00:16 +12:00
# When sending for real (not a preview), also copy the owner so they keep a
# record of exactly what the client received. CC is visible to the client;
# BCC is hidden. Independent. Ignored on previews and scheduled sends.
ccOwner: bool = False
bccOwner: bool = False
2026-06-17 21:51:29 +12:00
# ISO 8601 local datetime (e.g. "2026-06-20T14:30"). When set on a non-preview
# request, the welcome email is queued for reliable delivery at that time
# instead of being sent immediately.
scheduledFor: str | None = None
class ScheduledEmailCancelRequest(BaseModel):
id: str
class ScheduledEmailRescheduleRequest(BaseModel):
id: str
scheduledFor: str
2026-05-19 23:36:58 +12:00
class BirthdayEmailRequest(BaseModel):
email: EmailStr
2026-07-04 09:59:57 +12:00
dogId: str | None = None
2026-07-04 10:00:16 +12:00
# Owner-editable subject line. Blank falls back to the default
# "Happy birthday <dog>" subject.
subject: str = ""
2026-05-19 23:36:58 +12:00
preview: bool = False
2026-06-17 21:51:29 +12:00
previewRecipients: list[EmailStr] = []
2026-05-19 23:36:58 +12:00
class BirthdayAutoSendRequest(BaseModel):
email: EmailStr
2026-07-04 09:59:57 +12:00
dogId: str | None = None
2026-05-19 23:36:58 +12:00
enabled: bool
2026-05-26 08:30:08 +12:00
# 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 = ""
2026-06-17 21:51:29 +12:00
class ClientProfileUpdate(BaseModel):
email: EmailStr
nextEmail: EmailStr
fullName: str
phone: str = ""
address: str = ""
dogName: str
2026-07-04 09:59:57 +12:00
dogBreed: str = ""
dogAge: str = ""
2026-06-17 21:51:29 +12:00
class ResetOnboardingRequest(BaseModel):
"""Owner-initiated reset: keep the client's saved details but mark their
onboarding incomplete so they can sign in and complete the new form
(used for clients imported from the legacy Gravity Forms data)."""
email: EmailStr
2026-07-04 10:00:16 +12:00
class DeleteClientRequest(BaseModel):
"""Owner-initiated permanent deletion of a client and their access."""
email: EmailStr
2026-06-17 21:51:29 +12:00
class NewClientRequest(BaseModel):
"""Owner-initiated client creation (e.g. leads from Instagram / Facebook
that never came through the public enquiry form). Registers the email so
the client can access the onboarding form, and seeds a profile."""
email: EmailStr
fullName: str
phone: str = ""
address: str = ""
dogName: str = ""
dogBreed: str = ""
2026-07-04 09:59:57 +12:00
dogAge: str = ""
2026-06-17 21:51:29 +12:00
# Where the client came from, e.g. "Instagram", "Facebook", "Referral".
source: str = ""
2026-07-04 10:00:16 +12:00
# The date the client joined Goodwalk (ISO yyyy-mm-dd). Owner-supplied, since
# only the owner knows it. Used to surface joining anniversaries.
joiningDate: str = ""
2026-06-17 21:51:29 +12:00
# When true (and the MYOB integration is configured), also create the client
# as a customer contact in MYOB. Non-fatal: a MYOB failure never blocks the
# local client from being created.
createInMyob: bool = False
2026-07-04 09:59:57 +12:00
class ClientDogUpsertRequest(BaseModel):
email: EmailStr
dogId: str | None = None
dogName: str
dogBreed: str = ""
dogAge: str = ""
birthdayAutoSend: bool = False
2026-05-19 23:36:58 +12:00
class ContractSubmission(BaseSubmission):
address: str
dogName: str
dogBreed: str
dogAge: str = ""
serviceType: str
startDate: str
walkFrequency: str = ""
additionalNotes: str = ""
agreeServiceTerms: bool = False
agreeCancellation: bool = False
agreePayment: bool = False
agreeEmergency: bool = False
agreeLiability: bool = False
agreeAccuracy: bool = False
signatureDataUrl: str
class RenderMessageRequest(BaseModel):
templateId: str
heading: str = ""
body: str = ""
ctaLabel: str = ""
ctaUrl: str = ""
subHeading: str = ""
highlightText: str = ""
signOff: str = ""
footerNote: str = ""
fontId: str = "system"
class SendMessageRequest(BaseModel):
templateId: str
subject: str
heading: str = ""
body: str = ""
ctaLabel: str = ""
ctaUrl: str = ""
subHeading: str = ""
highlightText: str = ""
signOff: str = ""
footerNote: str = ""
fontId: str = "system"
recipients: list[EmailStr] = []
preview: bool = False
2026-06-17 21:51:29 +12:00
previewRecipients: list[EmailStr] = []