mcp: project-plan — PBS Membership LOE 1 Phase 3a — Garden Skeleton
This commit is contained in:
parent
e3e4bb40eb
commit
5dbfcbcd88
138
Sources/Dev/pbs-membership-loe1-phase3a-garden-skeleton.md
Normal file
138
Sources/Dev/pbs-membership-loe1-phase3a-garden-skeleton.md
Normal file
@ -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).
|
||||||
Loading…
Reference in New Issue
Block a user