From 0416dc8d394ea57bccb9699a5f58315a8c3b33f6 Mon Sep 17 00:00:00 2001 From: Matt Date: Tue, 2 Jun 2026 17:22:12 +0200 Subject: [PATCH] docs(launch): website-integration env vars + cutover sequence deployment-plan.md gains a full env-var reference (CRM + website) and the cutover env-flip sequence; launch-readiness.md gets the 2026-06-02 closeout; BACKLOG.md adds the deferred integration-health-panel idea (section L). Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/BACKLOG.md | 14 ++++++++ docs/deployment-plan.md | 73 +++++++++++++++++++++++++++++++++++++++- docs/launch-readiness.md | 72 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 158 insertions(+), 1 deletion(-) diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index f3f17701..a3921945 100644 --- a/docs/BACKLOG.md +++ b/docs/BACKLOG.md @@ -435,3 +435,17 @@ not surfaced in §C above were resolved via the `fix(audit): …` commits - [`berth-recommender-and-pdf-plan.md`](./berth-recommender-and-pdf-plan.md) — berth recommender + per-berth PDF plan (Phases 0–8 shipped) - [`documenso-integration-audit.md`](./documenso-integration-audit.md) — Documenso integration spec (drives §A) - [`website-refactor.md`](./website-refactor.md) — public website cutover plan + +--- + +## L. Website "integration health" panel (post-launch idea) + +**Raised 2026-06-02 (Matt); verdict: NOT worth building pre-launch.** + +A CRM admin "control panel" to watch website log events / activity. Reasons to defer: + +- Inquiry visibility already exists: `website_submissions` + its triage inbox (open / assigned / converted / dismissed). +- Website traffic/events are already covered by Umami; app + error logs belong in pino/server logs (or a log tool), not a hand-rolled CRM page. Shipping website logs into the CRM is net-new bidirectional plumbing plus a security surface for marginal gain. +- Not launch-blocking; classic pre-launch scope creep. + +**If revisited post-launch:** scope it to ONE small "integration health" card on the admin dashboard — last website submission received, count today, intake 4xx/5xx rate, inquiry email-send success. Pure read, no new ingestion pipeline. Build only if a real operational need surfaces; during cutover this is observable via logs + the submissions inbox. diff --git a/docs/deployment-plan.md b/docs/deployment-plan.md index 05b91040..6df51869 100644 --- a/docs/deployment-plan.md +++ b/docs/deployment-plan.md @@ -97,7 +97,7 @@ nginx vhost exists yet (fresh setup). Template: `portnimara_dev.conf` 1. Create `/etc/nginx/sites-available/crm_portnimara.conf` modelled on `portnimara_dev.conf`: port-80 → 443 redirect + `.well-known/acme-challenge` location; port-443 server `proxy_pass http://127.0.0.1:7100` with the same - header block (Host, X-Real-IP, CF-Connecting-IP, X-Forwarded-_, websocket + header block (Host, X-Real-IP, CF-Connecting-IP, X-Forwarded-\_, websocket `Upgrade`/`Connection` for socket.io), `client_max_body_size 64M`, `proxy_read_timeout 300`, buffering off. **HTTP-only first** (no `ssl\__` lines yet) so Certbot can complete the challenge. @@ -236,3 +236,74 @@ successfully applied`, 140→157, none unfinished), app boots (home 302, removed; restored clone gone, off-box dump retained). Compose file kept at `private/documenso-dryrun/docker-compose.yml` for a re-run. Prod still untouched. + +--- + +## Environment variables — initial deployment + cutover + +> Single source of truth for the env each instance needs for the +> website<->CRM integration (added 2026-06-02). **Every website-side CRM +> var is a no-op when unset**, so the marketing site behaves exactly as +> today until these are filled at cutover. Full CRM schema: `src/lib/env.ts`. + +### CRM instance (`crm.portnimara.com`) + +| Var | Value | Notes | +| ------------------------------------------------------------------------------------- | ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | +| `APP_URL` | `https://crm.portnimara.com` | Absolute URLs + email links (the inquiry sales-alert "Open in CRM" button). | +| `WEBSITE_INTAKE_SECRET` | shared secret | **MUST equal** the website's `CRM_INTAKE_SECRET`. If unset, `/api/public/website-inquiries` returns **503** and refuses all intake. | +| `EMAIL_REDIRECT_TO` | **unset in prod** | Dev-only reroute; the prod build guard fails if it is set. | +| `DATABASE_URL`, `REDIS_*`, storage/MinIO, `DOCUMENSO_*`, `SMTP_*`, better-auth secret | per `.env` | Standard (see Phase 1 Pre-flight). | + +Per-port **settings** (stored in `system_settings`, set via Admin UI — NOT env): + +- `website_intake_email_enabled` — boolean, **default OFF**. Flip ON at + cutover so the CRM sends the registrant confirmation + staff alert for + website inquiries (berth / residence / contact), reusing the branded + templates + per-port From. Keep OFF until the website's own sending is + turned off (see `WEBSITE_INQUIRY_EMAILS_DISABLED`) to avoid double-sends. +- `inquiry_notification_recipients` (JSON string[]) — staff who receive + berth + contact-form inquiry alerts. +- `residential_notification_recipients` (JSON string[]) — staff who receive + residence inquiry alerts. +- `inquiry_contact_email` (string) — fallback alert recipient + reply-to. + +### Website instance (Nuxt marketing site — repo `ron/website.git`) + +New vars for the CRM integration (read via `process.env` in Nitro; +**all no-op when unset → site unchanged**): + +| Var | Value | Enables | Set when | +| --------------------------------- | ----------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | +| `CRM_INTAKE_URL` | `https://crm.portnimara.com` (bare host, no trailing slash) | Inquiry dual-write delivery + base URL for the berth feed | Cutover (safe earlier; just starts populating `website_submissions`) | +| `CRM_INTAKE_SECRET` | shared secret | Auth for the dual-write (`X-Webhook-Secret`); **MUST equal** CRM `WEBSITE_INTAKE_SECRET` | With `CRM_INTAKE_URL` | +| `CRM_BERTHS_ENABLED` | `1` (or `true`/`yes`) | Switches the public berth map/list to read from CRM `/api/public/berths` instead of NocoDB (requires `CRM_INTAKE_URL`) | Cutover, after CRM berth data is migrated + verified | +| `WEBSITE_INQUIRY_EMAILS_DISABLED` | `1` | Turns OFF the website's own Gmail confirmation + alert emails, handing email ownership to the CRM | Cutover, flipped **together** with CRM `website_intake_email_enabled = ON` | + +UTM: **no env var** — cookieless; the client plugin reads `utm_*` from the +landing URL and forwards them via an `x-utm` header. + +Existing website env (keep, unchanged): NocoDB url/token, SMTP user/pass, +`alertRecipientsBerths/Residences/Contact`, `RECAPTCHA_SECRET`, +`NUXT_PUBLIC_RECAPTCHA_SITE_KEY`, Directus url. NocoDB stays as the berth +fallback + the dual-write's primary target until the old system is retired; +SMTP + alert recipients stay until `WEBSITE_INQUIRY_EMAILS_DISABLED` is set. + +### Cutover env-flip sequence (website) + +1. Confirm CRM is up, berth data migrated, and `WEBSITE_INTAKE_SECRET` set on the CRM. +2. Set website `CRM_INTAKE_URL` + `CRM_INTAKE_SECRET` → verify a test inquiry lands in `website_submissions`. +3. Flip CRM `website_intake_email_enabled = ON` **and** website `WEBSITE_INQUIRY_EMAILS_DISABLED = 1` together → CRM is the single email owner. +4. Set website `CRM_BERTHS_ENABLED = 1` → public map reads from the CRM. +5. Watch errors; rollback = unset the website vars (instant revert to NocoDB + website email). + +## Progress log (cont.) + +- 2026-06-02: **Website integration prep (local only; no prod changes, nothing pushed).** + Website repo (`main`, uncommitted): env-gated berth feed (`CRM_BERTHS_ENABLED`), + cookieless UTM forwarding (no env), inquiry dual-write (pre-existing). Website + email kill-switch added (`WEBSITE_INQUIRY_EMAILS_DISABLED`). CRM repo: flag-gated + email ownership (`website_intake_email_enabled`, default OFF) reusing the inquiry + - residential templates plus a new contact-form alert template, hooked into + `/api/public/website-inquiries`. New website env vars documented above. CRM + tsc-clean + unit test added; website berth/UTM vue-tsc-clean. Nothing deployed. diff --git a/docs/launch-readiness.md b/docs/launch-readiness.md index cb501694..6226cfb9 100644 --- a/docs/launch-readiness.md +++ b/docs/launch-readiness.md @@ -644,3 +644,75 @@ unproven full builds onto a same-day prod launch. - **Marketing + Financial reports** — remain unbuilt + now hidden; gated on Init 1b (website UTM/inquiry cutover) and Init 1c (invoices-module decision) respectively. + +--- + +## 2026-06-02 (session) — Pre-deployment closeout + +Driving toward cutover. Decisions + state captured this session. + +### Decisions locked + +- **Email ownership = the new CRM, flag-gated (option A).** Registrant + confirmations + staff alerts move from the website to the CRM. The CRM + intake path sends them behind a per-port flag (default OFF); the website + keeps sending until cutover, then flips off in one coordinated step (no + gap, no double-send). Preserve asymmetry: berth/residence = confirmation + - alert; contact form = alert only. Prove end-to-end before the flip. + Cross-ref: `project_email_ownership_at_cutover` memory. +- **`feature/video-headers` is excluded from this launch** (per Matt) — a + separate workstream, do not touch. Launch base = `main`. +- **Berth-read swap is a cutover-time flip, not now.** Until cutover NocoDB + is the live source staff update; the public map must mirror it. +- **UTM capture is cookieless** (no consent banner exists). In-memory only; + no cookie / localStorage / sessionStorage (ePrivacy/PECR). Session-scoped. + +### Marketing website (repo: code.portnimara.com/ron/website.git, branch `main`) + +Local `main` was 1 unpushed commit ahead (the CRM dual-write) + an uncommitted +Umami refactor; nothing to pull from the remote. Reconciled: + +- Committed the Umami refactor (`7e111b3`, `d03fcee`) to set a clean base. + `main` is NOT yet pushed (unpushed since 2026-05-04). +- **Inquiry dual-write**: pre-existing, dormant (no-ops without env). Contract + verified matching CRM `/api/public/website-inquiries` (header, payload, + `kind` enum, idempotent UUID). +- **Berth-read swap**: SHIPPED locally, default-OFF. `server/utils/berths.ts` + reads CRM `/api/public/berths` (+ single) when `CRM_BERTHS_ENABLED` truthy + and `CRM_INTAKE_URL` set; else NocoDB. `register.ts` skips the NocoDB + interest->berth link in CRM mode (UUID ids; CRM links via dual-write). + `PublicBerth` confirmed a verbatim superset of the website `Berth` type + incl. `Map Data` {path,x,y,transform,fontSize} -> the CRM fully backs the + website berth map. vue-tsc: type-clean. +- **UTM forwarding**: SHIPPED locally, cookieless. `plugins/utm.client.ts` + reads utm\_\* from the landing URL into memory and adds an `x-utm` header on + /api/register + /api/contact POSTs; `server/utils/utm.ts#readUtm` parses it; + `crmIntake.ts` forwards the five fields (CRM schema already accepts them). + Zero form-component changes; a misfire degrades to null UTM (no breakage). + vue-tsc: type-clean. +- **Uncommitted**: the berth-swap + UTM changes are in the working tree, not + yet committed (awaiting go). +- Pre-existing bugs noted (not fixed): `pages/berths/[mooringNumber].vue:123` + uses `.MooringNumber` (should be `["Mooring Number"]`); + `berths-item/introduction.vue:32` mis-indexes `"Water Depth"`. The website + repo has no typecheck/lint step (recommend wiring one pre-deploy). + +### Env flips required at cutover (website) + +- `CRM_INTAKE_URL` + `CRM_INTAKE_SECRET` (turn on inquiry dual-write delivery) +- `CRM_BERTHS_ENABLED=1` (switch the public berth map/list to the CRM) +- website email sending OFF + CRM email flag ON (single owner) + +### Closeout sequence + +1. Local website (no prod risk): berth swap DONE, UTM DONE. +2. CRM-side email ownership build (flag-gated) + website email-off toggle; + prove end-to-end. NEXT. +3. [GATED] Documenso v1.13.1 -> v2.11.0 prod upgrade (dry-run passed + 2026-06-01; Phases A-E sober/scheduled, per-step approval). +4. [GATED] Prod CRM deploy (Phase 1: nginx/certbot/compose/.env). Inputs + needed: Postgres own/shared, deploy dir, registry token, Documenso API + token. +5. [GATED] Data migration + cutover (MinIO EOI backfill, final NocoDB + reconcile, freeze + website env flips) in a maintenance window, explicit go. +6. Post-launch: M23/M25 migrations; e2e in CI; website typecheck/lint.