v4.0.5
Component architecture - Reorganise src/lib/components into pages/, sections/, ui/ subdirectories and update all imports across routes and tests. Owner admin dashboard (+2k lines) - Scheduled welcome-pack emails: durable queue with cancel/reschedule and a background sender loop (SCHEDULED_CHECK_INTERVAL_SECONDS, default 60s). - Custom welcome-pack subject line, preview recipients, and client BCC. - Add-client, edit client profile, and reset-onboarding flows. - "View as client" onboarding preview (owner impersonation, dry-run submit). - Tabbed owner welcome route (/owner/welcome/[[tab]]). MYOB integration - New mail_api/myob.py: create new clients as MYOB AccountRight customer contacts. Disabled until all MYOB_* env vars are set (no-op otherwise). Onboarding - New "Does your dog resource guard?" Yes/No question in the Behaviour step. - Persist vetAddress, flea/tick, and pet-insurance fields server-side. Misc - vite config, new hooks.ts, responsive CSS tweaks, deploy/docker config, mail-api README and start-dev.ps1. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -48,7 +48,7 @@ def setup_logging() -> logging.Logger:
|
||||
root.addHandler(rotating)
|
||||
|
||||
log = logging.getLogger("mail-api")
|
||||
log.info("Logging initialised → console=INFO, file=%s (DEBUG, rotating)", log_file)
|
||||
log.info("Logging initialised -> console=INFO, file=%s (DEBUG, rotating)", log_file)
|
||||
return log
|
||||
|
||||
|
||||
@@ -185,6 +185,10 @@ AUTH_IP_MAX_FAILURES = max(3, int(os.environ.get("AUTH_IP_MAX_FAILURES", "10")))
|
||||
AUTH_IP_FAILURE_WINDOW = max(60, int(os.environ.get("AUTH_IP_FAILURE_WINDOW", "600")))
|
||||
AUTH_IP_BLOCK_DURATION = max(60, int(os.environ.get("AUTH_IP_BLOCK_DURATION", "3600")))
|
||||
BIRTHDAY_CHECK_INTERVAL_SECONDS = max(3600, int(os.environ.get("BIRTHDAY_CHECK_INTERVAL_SECONDS", str(12 * 3600))))
|
||||
# How often the scheduled-email worker wakes to dispatch any sends that have
|
||||
# come due. Scheduled welcome emails need finer granularity than the birthday
|
||||
# sweep, so this defaults to one minute (floored at 15s).
|
||||
SCHEDULED_CHECK_INTERVAL_SECONDS = max(15, int(os.environ.get("SCHEDULED_CHECK_INTERVAL_SECONDS", "60")))
|
||||
|
||||
|
||||
def _split_csv_env(name: str, default: str) -> tuple[str, ...]:
|
||||
@@ -211,6 +215,10 @@ _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"
|
||||
# Durable queue of owner-scheduled emails (welcome packs queued for later send).
|
||||
# Mirrors the client_profiles persistence model: postgres admin_kv is canonical
|
||||
# in production, with this JSON file as the dev/local fallback and seed source.
|
||||
SCHEDULED_EMAILS_FILE = _DATA_DIR / "scheduled_emails.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
|
||||
@@ -221,6 +229,27 @@ LEGACY_SEED_FILE = Path(
|
||||
|
||||
LOGO_URL = "https://www.goodwalk.co.nz/images/goodwalk-auckland-dog-walking-logo.png"
|
||||
|
||||
# ── MYOB integration ───────────────────────────────────────────────────────────
|
||||
# Optional: create new clients as customer contacts in MYOB AccountRight Live.
|
||||
# The integration stays disabled until every required value is present, so an
|
||||
# unconfigured environment behaves exactly as before.
|
||||
#
|
||||
# MYOB_API_KEY / MYOB_API_SECRET the developer app's OAuth client id/secret
|
||||
# MYOB_REFRESH_TOKEN long-lived token used to mint access tokens
|
||||
# MYOB_COMPANY_FILE_URI base API URI of the target company file,
|
||||
# e.g. https://api.myob.com/accountright/<guid>
|
||||
# MYOB_CF_USERNAME / MYOB_CF_PASSWORD company-file login (password may be blank)
|
||||
# MYOB_TOKEN_URL OAuth token endpoint (override only for tests)
|
||||
MYOB_API_KEY = (os.environ.get("MYOB_API_KEY") or "").strip()
|
||||
MYOB_API_SECRET = (os.environ.get("MYOB_API_SECRET") or "").strip()
|
||||
MYOB_REFRESH_TOKEN = (os.environ.get("MYOB_REFRESH_TOKEN") or "").strip()
|
||||
MYOB_COMPANY_FILE_URI = (os.environ.get("MYOB_COMPANY_FILE_URI") or "").strip().rstrip("/")
|
||||
MYOB_CF_USERNAME = (os.environ.get("MYOB_CF_USERNAME") or "Administrator").strip()
|
||||
MYOB_CF_PASSWORD = os.environ.get("MYOB_CF_PASSWORD", "")
|
||||
MYOB_TOKEN_URL = (os.environ.get("MYOB_TOKEN_URL") or "https://secure.myob.com/oauth2/v1/authorize").strip()
|
||||
MYOB_HTTP_TIMEOUT_SECONDS = max(5, int(os.environ.get("MYOB_HTTP_TIMEOUT_SECONDS", "20")))
|
||||
MYOB_ENABLED = bool(MYOB_API_KEY and MYOB_API_SECRET and MYOB_REFRESH_TOKEN and MYOB_COMPANY_FILE_URI)
|
||||
|
||||
# ── Legacy module constants (kept for compatibility with the existing main.py) ──
|
||||
|
||||
OWNER_EMAIL = settings.owner_email
|
||||
@@ -268,3 +297,4 @@ logger.info(
|
||||
RATE_LIMIT_MIN_INTERVAL_SECONDS,
|
||||
EMAIL_SEND_TIMEOUT_SECONDS,
|
||||
)
|
||||
logger.info("MYOB integration: %s", "enabled" if MYOB_ENABLED else "disabled (set MYOB_* env vars to enable)")
|
||||
|
||||
Reference in New Issue
Block a user