Create pbs-membership-loe1-recipe-saving.md via n8n
This commit is contained in:
parent
f8127eea9a
commit
912eaff153
287
PBS/Tech/Projects/pbs-membership-loe1-recipe-saving.md
Normal file
287
PBS/Tech/Projects/pbs-membership-loe1-recipe-saving.md
Normal file
@ -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
|
||||||
Loading…
Reference in New Issue
Block a user