wiki-vault/Sources/Dev/pbs-membership-loe1-phase1-backend-foundation.md

5.9 KiB

created path project tags type
2026-06-09 Sources/Dev pbs-membership-loe1-phase1-backend-foundation
pbs
membership
loe1
flask
mysql
wordpress
auth
backend
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:

{
  "authenticated": true,
  "user_id": 47,
  "username": "janedoe",
  "display_name": "Jane Doe",
  "email": "jane@example.com",
  "roles": ["subscriber"]
}

Unauthenticated:

{ "authenticated": false }
  • user_idcritical; foreign key into pbs_garden interaction rows.
  • rolesarray; 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).