8.7 KiB
| created | path | project | tags | type | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 2026-06-09 | Sources/Dev | pbs-membership-loe1-phase3a-garden-skeleton |
|
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-hubis 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_postsfor 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 sourl_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):
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:
{
"authenticated": true,
"user_id": 47,
"username": "janedoe",
"display_name": "Jane Doe",
"email": "jane@example.com",
"roles": ["subscriber"]
}
Unauthenticated response:
{ "authenticated": false }
Field notes:
authenticated— the gate.false→ bounce to Ultimate Member login.user_id— critical; the foreign key written intopbs_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)
- Valid member cookie → attach
user_id(+ display data) to the request, render. - No / invalid cookie (
authenticated: false) → redirect to Ultimate Member login with redirect-back to the requested garden URL. - 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;
rolesis 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).