diff --git a/PBS/Tech/Projects/pbs-membership-loe1-recipe-saving.md b/PBS/Tech/Projects/pbs-membership-loe1-recipe-saving.md new file mode 100644 index 0000000..a24fb89 --- /dev/null +++ b/PBS/Tech/Projects/pbs-membership-loe1-recipe-saving.md @@ -0,0 +1,287 @@ +--- +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 +``` + +**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. + +```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 \ No newline at end of file