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

8.4 KiB

project type status tags created updated path
pbs-membership-loe1-recipe-saving project-plan active
pbs
flask
docker
mysql
membership
automation
2026-04-06 2026-04-06 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

Key principle: WordPress owns identity. pbs-hub owns membership features. The only bridge is the session cookie and read-only MySQL access.


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.

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).

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)

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):

{
    "post_id": 123,
    "action": "save",
    "metadata": null
}

Response (GET list):

{
    "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:

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