From 5dbfcbcd881c5de81bbd3498501d778ca5f9f4e3 Mon Sep 17 00:00:00 2001 From: Lovebug MCP Date: Tue, 9 Jun 2026 17:52:00 +0000 Subject: [PATCH] =?UTF-8?q?mcp:=20project-plan=20=E2=80=94=20PBS=20Members?= =?UTF-8?q?hip=20LOE=201=20Phase=203a=20=E2=80=94=20Garden=20Skeleton?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...membership-loe1-phase3a-garden-skeleton.md | 138 ++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 Sources/Dev/pbs-membership-loe1-phase3a-garden-skeleton.md diff --git a/Sources/Dev/pbs-membership-loe1-phase3a-garden-skeleton.md b/Sources/Dev/pbs-membership-loe1-phase3a-garden-skeleton.md new file mode 100644 index 0000000..81c02a0 --- /dev/null +++ b/Sources/Dev/pbs-membership-loe1-phase3a-garden-skeleton.md @@ -0,0 +1,138 @@ +--- +created: '2026-06-09' +path: Sources/Dev +project: pbs-membership-loe1-phase3a-garden-skeleton +tags: +- pbs +- membership +- garden +- flask +- traefik +- wordpress +- auth +- loe1 +type: project-plan +--- + +## Supersedes / Source-of-Truth Note + +This plan reflects decisions made in a planning session that **override** parts of two earlier vault docs. Where they disagree, **this plan wins**: + +- `pbs-membership-loe1-recipe-saving` (Phase 1) — **superseded on:** deployment topology (Phase 1 left "extend pbs-hub vs. standalone" open; now settled as **standalone**), database name (`pbs_hub` → **`pbs_garden`**, garden-owned), and auth mechanism (Phase 1 sketched the garden **reimplementing** WP cookie validation; now settled as **WordPress validates its own cookie**, Option 2). +- `pbs-membership-loe1-phase3` (Phase 3) — **superseded on:** the garden living "in pbs-hub" (now a **separate app/container**). + +The Phase 1 and Phase 3 doc bodies are immutable and remain stale on the above points. Read this plan as current truth for topology and auth. + +--- + +## Scope + +Phase 3a delivers the **shell** of the Sunflower Garden: a logged-in Sunnie navigates to `/the-garden/` and sees the branded member area with its own chrome, a welcome hero, and an empty card grid — **no real saved-recipe data yet** (that arrives in 3b). This phase exists to stand up the standalone app, its routing, and its auth boundary. + +--- + +## Settled Decisions + +### Topology +- The Garden is a **standalone Flask app in its own container**, separate from `pbs-hub`. +- Rationale: `pbs-hub` is a live team/shoot-day tool; coupling deploys would put it at risk from garden iteration. The garden and hub share *data infrastructure* (same MySQL server) but **no application logic and no application tables**. +- Blast-radius isolation: deploying the garden never touches the hub, and vice versa. + +### Database +- The Garden **owns its own database, `pbs_garden`**, within the shared MySQL server. +- Holds membership data: `user_interactions` (saves/likes/cooked/planned/rated), `user_preferences`, etc. +- The hub **never** reads or writes `pbs_garden`; the garden **never** reads or writes the hub's operational tables. +- **Read-only bridge to WordPress tables** (e.g., `wp_posts` for recipe titles/slugs) remains, exactly as Phase 1 designed — one-directional, read-only. +- Garden's scoped MySQL user: full control on `pbs_garden`, read-only on the needed WordPress tables, nothing else. + +### URL / Routing +- Public path: `plantbasedsoutherner.com/the-garden/` (path-based, same domain as WordPress). +- Routed via Traefik to the garden container. +- **Flask Blueprint owns the prefix** (`url_prefix='/the-garden'`); **no prefix stripping** — app internal URLs match public URLs so `url_for`, redirects, and static assets work without prefix-juggling. +- Explicit router **priority** set so the garden's specific path rule beats the WordPress catch-all (deterministic, not relying on auto-priority). + +Traefik labels (port is placeholder — set to whatever Flask binds to at build): + +```yaml +labels: + - "traefik.enable=true" + - "traefik.http.routers.garden.rule=Host(`plantbasedsoutherner.com`) && PathPrefix(`/the-garden`)" + - "traefik.http.routers.garden.entrypoints=websecure" + - "traefik.http.routers.garden.tls=true" + - "traefik.http.routers.garden.tls.certresolver=letsencrypt" + - "traefik.http.routers.garden.priority=100" + - "traefik.http.services.garden.loadbalancer.server.port=5000" +``` + +### Auth — Mechanism +- Member auth is handled by **WordPress (Ultimate Member)**, not Authelia. Authelia/Cloudflare Access remains **team/infra-only** (the hub, Portainer, n8n, etc.) — a separate, non-overlapping domain. +- **Option 2 — WordPress validates its own cookie.** On each garden request, the garden forwards the incoming `wordpress_logged_in_*` cookie to a small **internal** WordPress validation endpoint (mu-plugin / REST route). WordPress reads its own session server-side and returns the user. +- Why Option 2 over reimplementing validation in the garden: secure (WP secret keys + auth crypto stay inside hardened WordPress; never copied into the garden), and reliable (WordPress can't drift out of sync with itself; survives WP/plugin/security-stack changes). The garden already depends on WP at request time (read-only recipe data), so Option 1's "no runtime WP dependency" advantage buys nothing here. +- Endpoint is **internal-network only** (server-to-server on the Docker network), never exposed publicly via Traefik/Cloudflare. +- Garden **caches the validation response per-session**, short TTL (~5 min), to avoid calling WP on every page navigation. Logout/expiry still works because the cookie itself becomes invalid. + +### Auth — Contract (WordPress → Garden) + +Request: garden forwards the member's `wordpress_logged_in_*` cookie to the internal endpoint. + +Authenticated response: +```json +{ + "authenticated": true, + "user_id": 47, + "username": "janedoe", + "display_name": "Jane Doe", + "email": "jane@example.com", + "roles": ["subscriber"] +} +``` + +Unauthenticated response: +```json +{ "authenticated": false } +``` + +Field notes: +- `authenticated` — the gate. `false` → bounce to Ultimate Member login. +- `user_id` — **critical**; the foreign key written into `pbs_garden.user_interactions`. Every saved recipe keys to this. +- `username` — stable internal handle for logging/debugging. +- `display_name` — lets the hero greet the member without a second lookup. +- `email` — included now; cheap and wanted for upcoming account/notification features. +- `roles` — **array**. Membership **tiers ride in as additional role values (Path A)** — WordPress/Ultimate Member owns tier assignment as roles/capabilities; the garden is a pure reader. No separate "tier" field, no second source of truth. Supports multi-tier from day one. + +### `before_request` Flow (three outcomes per garden route) +1. **Valid member cookie** → attach `user_id` (+ display data) to the request, render. +2. **No / invalid cookie** (`authenticated: false`) → redirect to Ultimate Member login with redirect-back to the requested garden URL. +3. **Valid cookie, not authorized** (logged-in WP user lacking the required role/tier) → authorization decision distinct from authentication. For 3a a logged-in user may be treated as sufficient; `roles` is in the contract so tier-gating can be added later without a contract change. + +### Visual Direction +- **Option D — the Garden owns its own look.** Intentionally more "crafted/fancy" than the main WordPress site, so members know they've crossed into the member space. No Astra header/footer matching, no header/footer sync dependency — the garden renders its own chrome. +- The live "Recipe Packet №N" card on the main site already establishes the garden's visual language (dark warm palette, "Southern Heirloom" framing, "Harvested [date]" treatment). + +### Card Art +- **Hero:** existing chef-hat Sunnie asset is sufficient for 3a. 3D render is later polish, not a blocker. +- **Cards:** seed-packet treatment — **CSS ported from the existing main WordPress site** (proven, grab-and-adapt, not designed from scratch). Once ported it *becomes* the garden's own component; intentional divergence from the WP version over time is expected and fine. + +### Layout Shell (structure agreed; design deferred to execution) +Design of these elements is intentionally **deferred to build** — expected to iterate in-browser; pinning pixels in the plan would just go stale. Structure only: +- Garden-owned **header / footer / nav** (Option D). +- **Welcome hero** featuring chef Sunnie. +- **Card grid** of seed-packet LOE cards: *Saved Recipes* active; other LOEs coming-soon / muted. +- **Logout** flow → WP logout → WP home. + +--- + +## Build-Time Verifications (not decisions — confirm during build) +- [ ] Confirm WordPress's Traefik router priority is **below** the garden's `priority=100`; bump if they collide. +- [ ] Set the garden service port label to whatever the Flask app actually binds to inside the container. +- [ ] Confirm the internal WP validation endpoint is reachable **only** server-to-server (not via Traefik/Cloudflare). +- [ ] Confirm Ultimate Member can assign tier roles/capabilities the way tiering will need (contract is unaffected either way, but flag if UM's role handling is awkward). + +## Shared Dependency +- The WP-cookie validation endpoint is the one piece spanning WordPress and the garden. WordPress side (mu-plugin/endpoint) is Travis's build; garden side is the Lovebug handoff. The auth contract above is the agreed seam. + +## Out of Scope (later phases) +- Real saved-recipe data + card population (3b). +- Recipe detail page (3c), recently-added strip + empty state (3d), coming-soon cards polish (3e). +- Making the **core WordPress site** member-aware for signed-in users (separate future phase). +- 3D Sunnie render (polish). \ No newline at end of file