pbs-projects/PBS/Tech/Projects/pbs-membership-loe1-recipe-saving.md

8.3 KiB


project: pbs-membership-loe1-recipe-saving type: project-plan status: active tags:

  • pbs
  • flask
  • docker
  • mysql
  • membership
  • automation created: 2026-04-06 updated: 2026-04-06 path: PBS/Tech/Projects/

PBS Membership Platform — LOE 1: Recipe Saving

Vision

The PBS Membership Platform is a long-term initiative to transform plantbasedsoutherner.com from a content site into a full lifestyle platform. The end-state vision includes personalized recipe recommendations, a whole food diversity tracker, meal planning, a companion app, and a custom LLM trained on plant-based cooking knowledge.

Recipe Saving is LOE 1 — the foundational project that establishes the membership infrastructure all other LOEs build on.

LOE 1 Phases Overview

  • Phase 0 — Ultimate Member setup (registration, login, user roles, basic profile page)
  • Phase 1 — Backend foundation (auth middleware, database, API endpoints) ← THIS DOC
  • Phase 2 — Save button on WordPress recipe pages (JavaScript widget)
  • Phase 3 — Saved recipes page in pbs-hub (frontend UI)
  • Phase 4 — Collections (named groups, organize saved recipes)
  • Phase 5 — Polish (mobile optimization, loading states, error handling)

All 4 Lines of Effort

LOE Project Status
1 Recipe Saving Active — Phase 1
2 Weekly Meal Plan Viewer Queued
3 Whole Food Diversity Tracker Queued
4 User Profiles & Preferences Queued

Phase 1: Backend Foundation

Goal

Build the API layer and database schema that powers recipe saving. No frontend — just a working API testable via curl and verifiable in phpMyAdmin.

Estimated Time: 4-6 hours


Architecture

Sunnie browser → WordPress cookie (set by Ultimate Member login) → Request to pbs-hub API → Flask auth middleware validates cookie against wp_users (read-only) → API endpoint processes request → Writes to pbs_hub database → Returns JSON response

Database Design

New database: pbs_hub

user_interactions — generic table for all post-related user actions (save, like, cooked, planned, rated). Serves LOE 1 now and LOEs 2-4 later.

sql CREATE DATABASE pbs_hub;

CREATE TABLE pbs_hub.user_interactions ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, wp_user_id INT UNSIGNED NOT NULL, wp_post_id BIGINT UNSIGNED NOT NULL, action VARCHAR(50) NOT NULL, metadata JSON NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY unique_action (wp_user_id, wp_post_id, action) );

user_preferences — key-value store for platform-specific user settings (LOE 4, but created now for completeness).

sql CREATE TABLE pbs_hub.user_preferences ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, wp_user_id INT UNSIGNED NOT NULL, pref_key VARCHAR(100) NOT NULL, pref_value JSON NOT NULL, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY unique_pref (wp_user_id, pref_key) );

MySQL user (scoped permissions)

sql CREATE USER 'pbshub_app'@'%' IDENTIFIED BY 'strong_password_here';

-- Read-only on WordPress (auth + recipe display) GRANT SELECT ON wordpress.wp_users TO 'pbshub_app'@'%'; GRANT SELECT ON wordpress.wp_usermeta TO 'pbshub_app'@'%'; GRANT SELECT ON wordpress.wp_posts TO 'pbshub_app'@'%';

-- Full control on pbs_hub GRANT ALL ON pbs_hub.* TO 'pbshub_app'@'%';

FLUSH PRIVILEGES;

Note: Also audit existing MySQL users (pbs-api, n8n) and scope their permissions. Stop using root for application access.

API Endpoints

Method Path Description Auth Tier
POST /api/interactions Create interaction (save, like, etc.)
Required Free
DELETE /api/interactions Remove interaction Required Free
GET /api/interactions?action=save List user's interactions by action
type Required Free
GET /api/interactions/check?post_id=123&action=save Check if specific
interaction exists Required Free

Request body (POST): json { "post_id": 123, "action": "save", "metadata": null }

Response (GET list): json { "interactions": [ { "post_id": 123, "action": "save", "post_title": "Southern Black-Eyed Pea Stew", "post_slug": "southern-black-eyed-pea-stew", "saved_at": "2026-04-06T12:00:00" } ] }

Note: The GET list endpoint joins pbs_hub.user_interactions with wordpress.wp_posts to return recipe title and slug. Query filters on post_status = 'publish' and post_type IN ('post', 'wprm_recipe') to exclude revisions, drafts, and other post types.

Auth Middleware

Flask before_request middleware that:

  1. Reads wordpress_logged_in_{hash} cookie from request
  2. Extracts user ID and token from cookie value
  3. Validates HMAC against wp_users password hash + WordPress secret keys
  4. Sets g.current_user (user ID) and g.user_tier (role) for the request
  5. Returns 401 if missing or invalid

WordPress secret keys needed from wp-config.php:

  • LOGGED_IN_KEY
  • LOGGED_IN_SALT

Security considerations:

  • HTTPS enforced everywhere (Traefik + Cloudflare)
  • Read-only MySQL user for WordPress tables
  • CSRF protection via Flask-WTF (for future form submissions)
  • Cookie is httponly and secure (WordPress default)

Tier Authorization (Future-Proofed)

Not needed for Phase 1 (everything is free), but the pattern is established:

python TIER_PERMISSIONS = { "free": ["save", "like"], "basic": ["save", "like", "planned", "cooked"], "premium": ["save", "like", "planned", "cooked", "collections", "ai_suggest"], }

Adding paid tiers later is a code change, not a schema change.

Phase 1 Checklist

  • Create pbs_hub database on staging MySQL
  • Create user_interactions table
  • Create user_preferences table
  • Create pbshub_app MySQL user with scoped permissions
  • Audit and scope existing MySQL users (root cleanup)
  • Extract WordPress secret keys for cookie validation
  • Build Flask auth middleware (cookie → user ID)
  • Build POST /api/interactions endpoint
  • Build DELETE /api/interactions endpoint
  • Build GET /api/interactions endpoint (with wp_posts join)
  • Build GET /api/interactions/check endpoint
  • Test all endpoints via curl
  • Verify data in phpMyAdmin
  • Error handling and logging

Key Decisions Made

Decision Choice Rationale
Backend language Python / Flask Travis's strongest language. Python
handles PBS scale easily. Revisit Go/Rust only if a specific bottleneck
appears.
Auth approach WordPress cookie validation Ultimate Member handles
registration/login (Phase 0). pbs-hub validates the cookie for API access.
Plugin is treated as disposable scaffolding.
Database MySQL (pbs_hub database) Same server, cross-database
read-only joins to WordPress. No data duplication.
Interaction table Generic user_interactions Single table serves
saves, likes, cooked, planned, rated across all LOEs. Action type is a
column, not a separate table.
Recipe data source WordPress wp_posts (read-only join) Title and slug
come from wp_posts directly, filtered by post_status and post_type to
exclude revisions. No dependency on pbs_recipes (Instagram automation stays
independent).
Paid tier authorization Code-level, not schema-level TIER_PERMISSIONS
dictionary gates actions in the API layer. Database doesn't know tiers
exist.
MySQL user Scoped pbshub_app Read-only on WordPress tables, full
access on pbs_hub. No more root for applications.

Open Questions

  • WordPress cookie hash suffix: Need to check wp-config.php for the exact cookie name (wordpress_logged_in_{hash} where hash is derived from site URL)
  • WPRM post type: Confirm whether recipes are stored as 'wprm_recipe' post type or as regular 'post' with recipe metadata
  • pbs-hub deployment: Does this extend the existing pbs-hub container or is Phase 1 a standalone Flask app for testing?

Next Step: Create pbs_hub database and tables on staging, then build the auth middleware

...sent from Jenny & Travis