mcp: project-plan — PBS Membership LOE 1 Phase 1 — Backend Foundation
This commit is contained in:
parent
5dbfcbcd88
commit
e7e905dcd7
93
Sources/Dev/pbs-membership-loe1-phase1-backend-foundation.md
Normal file
93
Sources/Dev/pbs-membership-loe1-phase1-backend-foundation.md
Normal file
@ -0,0 +1,93 @@
|
||||
---
|
||||
created: '2026-06-09'
|
||||
path: Sources/Dev
|
||||
project: pbs-membership-loe1-phase1-backend-foundation
|
||||
tags:
|
||||
- pbs
|
||||
- membership
|
||||
- loe1
|
||||
- flask
|
||||
- mysql
|
||||
- wordpress
|
||||
- auth
|
||||
- backend
|
||||
type: project-plan
|
||||
---
|
||||
|
||||
## Supersedes / Source-of-Truth Note
|
||||
|
||||
This plan replaces the earlier `pbs-membership-loe1-recipe-saving` doc as the current truth for Phase 1. Where they disagree, **this plan wins**. The earlier doc's body is immutable and remains stale on three points it got differently:
|
||||
|
||||
- **Database name/ownership:** was `pbs_hub`; now **`pbs_garden`**, owned by the standalone Garden app (see Topology).
|
||||
- **Topology:** the membership data layer is **not** an extension of the `pbs-hub` container; it belongs to a separate Garden application.
|
||||
- **Auth approach:** the earlier doc sketched the membership app **reimplementing** WordPress cookie validation in Python. That is replaced by **WordPress validating its own cookie** via an internal endpoint (see Auth).
|
||||
|
||||
---
|
||||
|
||||
## What Phase 1 Is
|
||||
|
||||
The backend foundation for membership: the database, the data layer, and the WordPress-side auth endpoint that everything else (the save button in Phase 2, the member area later) builds on. No member-facing UI in this phase. Success = the data layer exists, a member's WordPress login can be validated server-side, and an authenticated request can write/read an interaction keyed to a WordPress user.
|
||||
|
||||
## Architectural Foundation (settled)
|
||||
|
||||
### Identity vs. features
|
||||
- **WordPress (Ultimate Member) owns identity.** Registration, login, profiles, roles/tiers. The membership backend never manages credentials.
|
||||
- **The membership backend owns features.** Saves, and later likes/cooked/planned/rated, preferences.
|
||||
- **The bridge between them** is the validated WordPress session plus read-only access to WordPress tables.
|
||||
|
||||
### Topology
|
||||
- The membership data layer belongs to a **standalone Flask application in its own container**, separate from `pbs-hub`. `pbs-hub` is a live team/production tool; the membership app must not share its deploy lifecycle or blast radius.
|
||||
- Shared **MySQL server**, but **no shared application tables** and **no shared application logic** with `pbs-hub`.
|
||||
|
||||
### Database — `pbs_garden`
|
||||
- The membership app **owns a dedicated database, `pbs_garden`**, on the shared MySQL server.
|
||||
- Holds the generic interactions model — a single `user_interactions` table designed to serve all interaction types (save / like / cooked / planned / rated) rather than one table per feature, plus `user_preferences` as needed.
|
||||
- `user_id` (the WordPress user ID) is the foreign key on interaction rows.
|
||||
- **Read-only bridge to WordPress tables** (e.g. `wp_posts` for recipe title/slug) — one-directional, read-only.
|
||||
- Scoped MySQL user: full control on `pbs_garden`, read-only on the specific WordPress tables needed, nothing else.
|
||||
|
||||
### Auth — mechanism
|
||||
- **WordPress validates its own cookie.** On an authenticated request, the membership app forwards the incoming `wordpress_logged_in_*` cookie to a small **internal** WordPress endpoint (mu-plugin / REST route). WordPress reads its own session server-side and returns the user.
|
||||
- Chosen for **security** (WP secret keys and auth crypto stay inside hardened WordPress — never copied into the membership app) and **reliability** (WordPress cannot drift out of sync with its own session format; survives WP/plugin/security-stack changes). The app already depends on WordPress at request time for read-only recipe data, so there is no runtime-independence benefit lost by this choice.
|
||||
- Endpoint is **internal-network only** (server-to-server on the Docker network), never exposed via Traefik/Cloudflare.
|
||||
- Validation responses are **cached per-session, short TTL (~5 min)**, so WordPress isn't queried on every request. Logout/expiry still works because the cookie itself becomes invalid.
|
||||
|
||||
### Auth — contract (WordPress endpoint → membership app)
|
||||
|
||||
Authenticated:
|
||||
```json
|
||||
{
|
||||
"authenticated": true,
|
||||
"user_id": 47,
|
||||
"username": "janedoe",
|
||||
"display_name": "Jane Doe",
|
||||
"email": "jane@example.com",
|
||||
"roles": ["subscriber"]
|
||||
}
|
||||
```
|
||||
Unauthenticated:
|
||||
```json
|
||||
{ "authenticated": false }
|
||||
```
|
||||
|
||||
- `user_id` — **critical**; foreign key into `pbs_garden` interaction rows.
|
||||
- `roles` — **array**; membership **tiers are expressed as WordPress roles** (WordPress/Ultimate Member assigns them; the app is a pure reader). No separate tier field, no second source of truth. Multi-tier supported from day one.
|
||||
- `email` included now (cheap; wanted for later account/notification features).
|
||||
|
||||
## Build Surface (Phase 1 deliverables)
|
||||
- [ ] `pbs_garden` database + `user_interactions` (generic interaction model) and any `user_preferences` table
|
||||
- [ ] Scoped MySQL user (full on `pbs_garden`, read-only on needed WP tables)
|
||||
- [ ] Standalone Flask app skeleton (its own container) with the data layer / models
|
||||
- [ ] WordPress-side internal validation endpoint (mu-plugin or REST route) returning the contract above
|
||||
- [ ] App-side auth: forward cookie → call endpoint → cache per-session → attach `user_id` to request
|
||||
- [ ] Interaction read/write endpoints (create/list a saved interaction for the authenticated user)
|
||||
|
||||
## Open Questions (carried from original Phase 1; confirm at build)
|
||||
- [ ] **Recipe post type:** are recipes the `wprm_recipe` post type or regular WordPress posts? Determines what the read-only WP bridge joins against.
|
||||
- [ ] **`user_interactions` schema specifics:** confirm column set (interaction type enum, target post ID as the right type, timestamps) before first migration. *(Note: Instagram-style IDs are not relevant here, but confirm the recipe/post ID type matches WordPress.)*
|
||||
- [ ] **Ultimate Member tier roles:** confirm UM can assign tier roles/capabilities the way tiering will need (contract is unaffected either way; flag if awkward).
|
||||
|
||||
## Out of Scope
|
||||
- Save button UI on WordPress (Phase 2).
|
||||
- Member area / browsing UI (later).
|
||||
- Tier *gating* logic (the contract carries roles; enforcing tier access comes when tiers exist).
|
||||
Loading…
Reference in New Issue
Block a user