--- created: 2026-03-16 path: Sources/Dev project: instagram-automation-session-notes status: active tags: - pbs - n8n - instagram - mysql - wordpress - automation - docker type: session-notes updated: 2026-03-16 --- # Session Notes — MySQL Schema, Error Checking & The Great Lockout of 2026 ## ✅ Accomplished This Session ### Error Checking — Instagram Reply Workflow All four error checks are now wired to Notify Travis → Google Chat: - ✅ Subscribe check notification - ✅ Hash verification failure notification (with ⛔ alert level) - ✅ Empty table lookup notification (includes Reel ID, commenter, comment text) - ✅ Keyword not matched notification **Key learnings:** - n8n "Always Output Data" needed on MySQL node so IF node can evaluate empty results - When "Always Output Data" is on, check `$('node').first().json.id` exists instead of `length > 0` (length returns 1 for empty item) - Google Chat HTTP Request node must use "Using Fields Below" mode, not raw JSON — literal newlines from expressions break JSON - Build message strings as single `={{ }}` expression using `+` concatenation to keep `\n` as escaped characters - `$now.toFormat('MMM dd, HH:mm:ss')` works in both test and live contexts (unlike `$execution.startedAt`) - Timestamp and title passed from calling workflow, Notify Travis just passes them through --- ### MySQL Schema — New Tables Created **`pbs_recipes`:** ```sql CREATE TABLE IF NOT EXISTS pbs_recipes ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, post_id BIGINT UNSIGNED NOT NULL, title VARCHAR(500) NOT NULL, url VARCHAR(500) NOT NULL, keyword VARCHAR(100) NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY (post_id) ); ``` **`instagram_posts`:** ```sql CREATE TABLE IF NOT EXISTS instagram_posts ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, reel_id VARCHAR(50) NOT NULL, post_id BIGINT UNSIGNED NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY (reel_id), FOREIGN KEY (post_id) REFERENCES pbs_recipes(post_id) ); ``` **Key decisions:** - `post_id` is nullable in `instagram_posts` — NULL means unmatched reel, not an error - `reel_id` stored as VARCHAR not BIGINT (Instagram ID integer overflow issue) - Normalized schema — keyword and URL live in `pbs_recipes` only, JOIN at query time - Staying on shared MySQL instance — at current scale (2k/month, 10-20 replies) separate instance is overkill - phpMyAdmin added back to Docker Compose for direct MySQL access --- ### WordPress Publish Webhook — Proof of Concept Complete - WPCode Lite snippet fires on post publish (admin only) - Payload: post_id, title, url, tags, categories - HMAC signature verification via X-PBS-Signature header - Raw body passed via X-PBS-Body header (base64 encoded) for exact signature verification - n8n Code node verifies signature before processing - Ready to wire into `pbs_recipes` table insert workflow --- ### Architecture Decision — Reply Workflow Refactor Current two-table lookup + merge is being replaced with: - Single `instagram_posts` lookup only - URL stored directly in `instagram_posts` (populated at reel insert time from `pbs_recipes`) - Simpler, faster, fewer failure points --- ## 🔧 The Great WordPress Lockout of 2026 **What happened:** Wordfence locked out staging WP admin. Unlock email never arrived. **What we tried (none of it worked):** - WP-CLI wf:unblock-ip command (not a registered command) - Clearing wp_options Wordfence entries in MySQL - Disabling Wordfence plugin - Restarting WordPress container - Deleting wflogs directory **Root cause discovered:** Wordfence WAF runs as a PHP auto_prepend_file — completely independent of the plugin being active. Found in .htaccess and wordfence-waf.php in WordPress root. **Actual fix:** Wrong email address on the admin account. Changed it, logged right in. 🤦 **Real lesson learned:** Read the error message carefully before spending an hour troubleshooting! 😄 **Lockout duration if we hadn't figured it out:** 1 MONTH --- ## 📋 Runbook Addition — WordPress Lockout Recovery ```bash # Get into WordPress container docker exec -it wordpress bash cd /var/www/html # List all users wp --allow-root user list # Reset password by username wp --allow-root user update admin --user_pass=newpassword # Reset password by email wp --allow-root user update user@email.com --user_pass=newpassword # If WAF is blocking login page independently of plugin: # 1. Remove Wordfence WAF block from .htaccess # 2. Rename wordfence-waf.php to wordfence-waf.php.disabled # 3. docker restart wordpress ``` --- ## 🔜 Next Steps - [ ] Wire WordPress publish webhook → n8n workflow → insert into `pbs_recipes` - [ ] Build one-time migration workflow — n8n datatables → MySQL - [ ] Finish reply workflow refactor — remove Merge node, single `instagram_posts` lookup - [ ] Fix production WordPress admin email (same issue exists there!) - [ ] Deploy all changes to production once staging is verified --- ## 📝 Notes - All development on staging (staging.plantbasedsoutherner.com) - n8n datatables to be retired once MySQL migration complete - Foreign key constraint requires `pbs_recipes` record to exist before `instagram_posts` insert - Use `WHERE post_id IS NULL` query to find unmatched reels for Content Hub dashboard --- *Last Updated: March 16, 2026* *Maintained by: Travis* *Project: Plant Based Southerner — Instagram Automation*