Files
gw-svelte/mail-api/mail_api/models.py
T
2026-07-04 09:59:57 +12:00

202 lines
5.2 KiB
Python

"""Pydantic request/response models for the mail API."""
from __future__ import annotations
from typing import Any, Literal
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
vetAddress: str
vetPhone: str
emergencyContactName: str
emergencyContactPhone: str
regularFleaTickTreatment: str = ""
petInsurance: str = ""
petInsuranceOwnerExpenseAccepted: bool = False
councilRegistrationConfirmed: bool = False
vaccinationsConfirmed: bool = False
emergencyVetConsent: bool = False
termsAccepted: bool = False
signatureDataUrl: str
submissionSnapshot: dict[str, Any] = {}
class WelcomePackEmailRequest(BaseModel):
email: EmailStr
# 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 = ""
serviceType: str
priceDetails: str
startDate: str
preview: bool = False
previewRecipients: list[EmailStr] = []
# 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
class BirthdayEmailRequest(BaseModel):
email: EmailStr
dogId: str | None = None
preview: bool = False
previewRecipients: list[EmailStr] = []
class BirthdayAutoSendRequest(BaseModel):
email: EmailStr
dogId: str | None = None
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 ClientProfileUpdate(BaseModel):
email: EmailStr
nextEmail: EmailStr
fullName: str
phone: str = ""
address: str = ""
dogName: str
dogBreed: str = ""
dogAge: str = ""
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
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 = ""
dogAge: str = ""
# Where the client came from, e.g. "Instagram", "Facebook", "Referral".
source: str = ""
# 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
class ClientDogUpsertRequest(BaseModel):
email: EmailStr
dogId: str | None = None
dogName: str
dogBreed: str = ""
dogAge: str = ""
birthdayAutoSend: bool = False
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
previewRecipients: list[EmailStr] = []