diff --git a/PBS/Tech/Projects/instagram-reel-sync-phase4.md b/PBS/Tech/Projects/instagram-reel-sync-phase4.md new file mode 100644 index 0000000..36ebcd7 --- /dev/null +++ b/PBS/Tech/Projects/instagram-reel-sync-phase4.md @@ -0,0 +1,223 @@ +--- +project: instagram-reel-sync-phase4 +type: project-plan +status: active +tags: + - pbs + - n8n + - instagram + - mysql + - automation +created: 2026-03-17 +updated: 2026-03-17 +path: PBS/Tech/Projects/ +--- + +# Phase 4 Handoff — Instagram Reel Detection & Sync + +## Context for New Chat Session + +This is a handoff document from the previous chat session. Paste this into the new +chat along with the standard project context to get up to speed quickly. + +--- + +## Current State — What's Already Built + +### MySQL Schema (pbs_automation database) + +**`pbs_recipes`:** +```sql +CREATE TABLE IF NOT EXISTS pbs_recipes ( +    id           BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, +    post_id      BIGINT UNSIGNED NOT NULL, +    recipe_title        VARCHAR(500)    NOT NULL, +    recipe_url          VARCHAR(500)    NOT NULL, +    keyword      VARCHAR(100)    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, +    pbs_post_id      BIGINT UNSIGNED NULL, +    instragram_caption  TEXT    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 schema decisions:** +- `post_id` is nullable in `instagram_posts` — NULL means unmatched reel +- `reel_id` is VARCHAR not BIGINT (Instagram ID integer overflow issue) +- Normalized — keyword and URL live in `pbs_recipes` only, JOIN at query time +- Foreign key constraint means `pbs_recipes` record must exist before `instagram_posts` insert + +### WordPress → pbs_recipes Sync (Complete ✅) +- WPCode Lite PHP snippet fires on every post save (admin only) +- Payload: post_id, title, url, tags, categories +- HMAC signature via X-PBS-Signature header (raw body via X-PBS-Body base64) +- n8n workflow verifies signature, upserts into pbs_recipes +- Keyword populated from first WordPress tag if present +- COALESCE logic: only sets keyword if currently NULL (preserves manual edits) + +### Instagram Comment Reply Workflow (Complete ✅) +- Handles incoming Instagram comment webhooks +- Hash verification → subscribe check → PBS message filter +- Looks up reel in `instagram_posts` table +- Joins to `pbs_recipes` for keyword and URL +- Sends DM + public reply on keyword match +- All dead ends notify Travis via Google Chat + +### Notify Travis Workflow (Complete ✅) +- Webhook trigger at: https://n8n.plantbasedsoutherner.com/webhook/notify-pbschat +- Routes all alerts to Google Chat +- Payload format: +```json +{ + "title": "Alert title", + "message": "Detail message", + "timestamp": "={{ $now.toFormat('MMM dd, HH:mm:ss') }}" +} +``` + +--- + +## Phase 4 Goal — Automate Instagram Reel Detection + +### The Problem +When Jenny publishes a new Instagram Reel, someone currently has to manually: +1. Get the WordPress post ID +2. Add it to the reel caption +3. Add the reel ID to the `instagram_posts` table + +### The Solution — Three-Tier Detection System + +**Tier 1 — Comment Triggered (real time)** +When a comment comes in and `instagram_posts` lookup returns empty: +- Instead of just notifying Travis, trigger the "Fetch Reel Data" workflow +- Pass enough context to reply to the original commenter if reel is found +- First commenter may experience a few seconds delay — acceptable + +**Tier 2 — Scheduled Poll (every 12 hours)** +- n8n Schedule Trigger polls Instagram media API +- Catches reels that haven't received a comment yet +- Proactively keeps table up to date + +**Tier 3 — Manual Trigger (Content Hub)** +- "Sync Reels" button in PBS Content Hub Flask app +- Jenny or assistant can trigger on demand after publishing + +### Queue and Retry Pattern (Option B) +When Tier 1 triggers: +1. Comment comes in → no record found → store original comment data +2. Trigger "Fetch Reel Data" workflow with comment context +3. If reel found → insert into `instagram_posts` → retry reply to original commenter +4. If reel not found → Notify Travis with details + +--- + +## Phase 4 Architecture + +### New Workflow — "Fetch Reel Data" + +**Triggers:** +- Called from comment reply workflow (Tier 1) with single reel ID +- Called from Schedule Trigger (Tier 2) with last 20 media items +- Called from Content Hub webhook (Tier 3) manually + +**Core loop logic (Loop Over Items node):** +``` +[Get reels from Instagram API] + → [Code Node: extract post_id from caption] + → [Loop Over Items] + → [MySQL SELECT: pbs_recipes WHERE post_id = extracted_id] + → [IF: record found?] + → TRUE → [MySQL UPSERT: instagram_posts] + → FALSE → [Notify Travis: Bad/missing post ID in caption] + → [After loop: if Tier 1 triggered → retry comment reply] +``` + +**Handoff payload from comment reply workflow (Tier 1):** +```json +{ + "resolved_media_id": "the reel ID", + "comment_text": "original comment text", + "from_username": "commenter username", + "comment_id": "needed to post public reply" +} +``` + +### Instagram Media API Details + +**No webhook exists for new reel publications** — confirmed via Meta docs research. +Polling is the only option. + +**API endpoint:** +``` +GET https://graph.facebook.com/v25.0/{ig-user-id}/media + ?fields=id,caption,media_type,media_product_type,permalink,timestamp + &limit=20 + &access_token={token} +``` + +**Key field:** `media_product_type === "REELS"` — do NOT use `media_type`, +it returns "VIDEO" for both regular videos and Reels. + +**Rate limits:** Generous — 4,800 × account impressions/24hrs. Polling every +12 hours is negligible. + +**Token:** Long-lived user access token (60-day expiry). Build monitoring +for token expiry — biggest operational risk. + +--- + +## Key n8n Patterns Established + +- **Always Output Data** on MySQL nodes so IF nodes can evaluate empty results +- **Check field exists** (`id exists`) not `length > 0` when Always Output Data is on +- **Google Chat HTTP Request** must use "Using Fields Below" not raw JSON +- **Message strings** built as single `={{ }}` expression with `+` concatenation +- **`$now.toFormat()`** works in test and live contexts (not `$execution.startedAt`) +- **MySQL query parameters** — do NOT use `=` prefix, plain comma separated values +- **Loop Over Items** — use explicit `$('Node Name').first().json` not `$json` + inside loops since `$json` only references immediate previous node +- **Set node before loops** — consolidate upstream data needed inside loop + so it's always accessible as `$json.field` +- **`reel_id` always VARCHAR** — Instagram IDs exceed JS safe integer limit + +--- + +## Remaining Work After Phase 4 + +- [ ] Refactor comment reply workflow to split at `Check if we sent` + into separate "Instagram Reply Handler" workflow (cleaner logs) +- [ ] PBS Content Hub — Phase 5 planning session required before building +- [ ] Authelia SSO for admin tools (Portainer, n8n, Uptime Kuma, phpMyAdmin) +- [ ] Deploy all staging changes to production + +--- + +## Infrastructure Quick Reference + +- **Staging:** staging.plantbasedsoutherner.com +- **n8n:** n8n.plantbasedsoutherner.com +- **phpMyAdmin:** phpmyadmin.staging.plantbasedsoutherner.com +- **PBS-API:** internal Docker service at http://pbs-api:5000 +- **MySQL database:** pbs_automation +- **Google Chat alerts:** via Notify Travis workflow webhook + +--- + +*Last Updated: March 17, 2026* +*Maintained by: Travis* +*Project: Plant Based Southerner — Instagram Automation Phase 4* \ No newline at end of file