{"workflow":{"id":13406,"name":"Manage coupon campaigns and customer chats with WhatsApp and PostgreSQL","views":9,"recentViews":0,"totalViews":9,"createdAt":"2026-02-15T01:55:25.168Z","description":"# Coupon Bot Dashboard & WhatsApp Management System\n\nA complete bilingual admin dashboard and WhatsApp bot solution for managing discount coupons, partner companies, and customer conversations through an intuitive web interface and automated chatbot.\n\n## What does this workflow do?\n\nThis workflow provides a comprehensive coupon management system with dual interfaces: a modern Vue.js admin dashboard for managers and an interactive WhatsApp bot for customers. It handles everything from company onboarding and coupon issuance to real-time customer support and conversation tracking.\n\n### Key features:\n- 🎛️ **Admin Dashboard** - Full-featured Vue.js 3 interface with Tailwind CSS for managing companies, coupons, and viewing analytics\n- 🤖 **WhatsApp Bot** - Automated customer service bot with session-based state management\n- 🏢 **Company Management** - CRUD operations for partner companies and their coupon catalogs\n- 🎫 **Advanced Coupon System** - Support for percentage/fixed discounts, expiry dates, minimum purchase requirements, and maximum discount caps\n- 💬 **Real-time Inbox** - Live chat interface for viewing customer conversation history\n- 🌐 **Bilingual Support** - Full Arabic (RTL) and English interface support\n- 📊 **Analytics Dashboard** - Track customers, messages, coupons issued, and partner companies\n- 🔧 **Database Tools** - Built-in schema initialization and migration capabilities\n- 👤 **Role-Based Access** - Separate admin and customer flows with phone number authentication\n\n## Setup Requirements\n\n### Integrations Needed:\n1. **PostgreSQL** - Primary database for all system data\n2. **WhatsApp Business API** - For customer bot interactions via Facebook Graph API\n3. **n8n** - Workflow automation platform (self-hosted or cloud)\n\n### Credentials Required:\n- PostgreSQL database credentials (host, port, database, user, password)\n- WhatsApp Business API Bearer Token (Facebook Graph API v22.0)\n- Phone Number ID from WhatsApp Business account\n\n## How to Use\n\n### Initial Setup:\n1. **Import the workflow** into your n8n instance\n2. **Configure PostgreSQL credentials** - Connect to your database (named \"discounts DB\" in the workflow)\n3. **Configure WhatsApp credentials** - Add your Facebook Graph API Bearer Token\n4. **Initialize the database** - Send POST request to `/webhook/coupon-bot/execute-init` to create all tables\n5. **Set admin phone** - Access dashboard at `/webhook/coupon-bot/dashboard`, go to Settings, and set your admin phone number\n6. **Configure WhatsApp webhook** - Point your WhatsApp Business webhook to `/webhook/whatsapp`\n\n### Dashboard Access:\n- **URL**: `/webhook/coupon-bot/dashboard`\n- **Features**: Statistics, Company Management, Coupon Management, Inbox, System Settings\n\n### Admin WhatsApp Commands:\nOnce your phone is set as admin, send any message to the bot to access:\n- Add/Edit/Delete Companies\n- Add/Edit/Delete Coupons\n- Edit Welcome/End Messages\n- Browse as regular customer\n\n## Workflow Structure\n\n### 1. Dashboard Webhook (`/coupon-bot/dashboard`)\nServes the complete Vue.js 3 admin interface with:\n- Responsive RTL layout for Arabic\n- Mobile-optimized sidebar navigation\n- Real-time statistics cards\n- Company and coupon management forms\n- Interactive inbox with chat history\n- System settings and database maintenance tools\n\n### 2. API Endpoints (`/coupon-bot/api/*`)\nRESTful API routes for dashboard operations:\n- `POST /stats` - System analytics (customers, messages, records, companies)\n- `POST /companies` - CRUD operations for partner companies\n- `POST /coupons` - CRUD operations with advanced fields (percentage, expiry, limits)\n- `POST /inbox` - Retrieve unique customer conversations\n- `POST /history` - Fetch complete chat history for a customer\n- `POST /settings` - Manage admin phone and message templates\n- `POST /customers` - Customer records retrieval\n- `POST /records` - Coupon redemption tracking\n\n### 3. Database Management (`/coupon-bot/execute-init`, `/coupon-bot/migrate`)\n- **Full Initialization** - Creates all tables: customers, companies, coupons, records, messages, settings, sessions\n- **Schema Migration** - Non-destructive updates to add missing columns and constraints\n- **Repair Tools** - Fixes corrupted data and ensures schema consistency\n\n### 4. WhatsApp Bot Handler (`/webhook/whatsapp`)\nProcesses incoming WhatsApp messages with:\n- **Authentication** - Detects admin vs. regular customers by phone number\n- **Session Management** - PostgreSQL-based state machine for multi-step conversations\n- **Interactive Messages** - List menus for company/coupon selection\n- **Message Logging** - All incoming/outgoing messages stored for inbox feature\n\n### 5. Customer Flow (Regular Users)\n1. Receive welcome message from settings\n2. Browse list of partner companies\n3. Select company to view available coupons\n4. Choose coupon to receive details\n5. Get end message with coupon code and terms\n\n### 6. Admin Flow (Authenticated Users)\n1. Access main management menu\n2. Manage companies (add, edit, delete)\n3. Manage coupons (add with advanced options, edit, delete)\n4. Customize welcome and end messages\n5. Test customer experience\n\n## Database Schema\n\n### customers\n```sql\n- customer_id (SERIAL PRIMARY KEY)\n- name (VARCHAR 255)\n- phone (VARCHAR 50, UNIQUE)\n- selected_sites (TEXT)\n- created_at (TIMESTAMP)\n- updated_at (TIMESTAMP)\n```\n\n### companies\n```sql\n- company_id (SERIAL PRIMARY KEY)\n- name (VARCHAR 255, NOT NULL)\n- info (TEXT)\n```\n\n### coupons\n```sql\n- coupon_id (SERIAL PRIMARY KEY)\n- company_id (INT, FOREIGN KEY)\n- coupon_text (TEXT, NOT NULL) -- The actual coupon code\n- coupon_value (NUMERIC 10,2)\n- is_percentage (BOOLEAN DEFAULT FALSE)\n- has_max (BOOLEAN DEFAULT FALSE)\n- max_value (NUMERIC 10,2)\n- is_max_percentage (BOOLEAN DEFAULT FALSE)\n- has_min_purchase (BOOLEAN DEFAULT FALSE)\n- min_purchase_value (NUMERIC 10,2)\n- is_on_items_quantity (BOOLEAN DEFAULT FALSE)\n- registration_date (TIMESTAMP)\n- has_expiry (BOOLEAN DEFAULT FALSE)\n- expiry_date (TIMESTAMP)\n- other_info (TEXT)\n```\n\n### records\n```sql\n- record_id (SERIAL PRIMARY KEY)\n- customer_id (INT, FOREIGN KEY)\n- company_id (INT, FOREIGN KEY)\n- coupon_id (INT, FOREIGN KEY)\n- created_at (TIMESTAMP)\n```\n\n### messages\n```sql\n- message_id (SERIAL PRIMARY KEY)\n- customer_id (INT, FOREIGN KEY, nullable)\n- sender_phone (VARCHAR 50)\n- sender_name (VARCHAR 255)\n- external_message_id (VARCHAR 255)\n- message_text (TEXT, NOT NULL)\n- message_type (VARCHAR 50 DEFAULT 'text')\n- direction (VARCHAR 20 DEFAULT 'INCOMING')\n- is_from_customer (BOOLEAN DEFAULT TRUE)\n- created_at (TIMESTAMP)\n```\n\n### settings\n```sql\n- key (VARCHAR 50 PRIMARY KEY)\n- value (TEXT)\n-- Keys: admin_phone, welcome_message, end_message\n```\n\n### sessions\n```sql\n- phone (VARCHAR 50 PRIMARY KEY)\n- state (VARCHAR 50, NOT NULL)\n- data (JSONB DEFAULT '{}')\n- last_updated (TIMESTAMP)\n```\n\n## Session States (State Machine)\n\nThe bot uses PostgreSQL sessions for conversation persistence:\n- `IDLE` - Default state, waiting for input\n- `WAITING_COMPANY_NAME` - Admin adding new company\n- `WAITING_EDIT_COMPANY_SELECT` - Admin selecting company to edit\n- `WAITING_EDIT_COMPANY_NAME` - Admin entering new company name\n- `WAITING_DELETE_COMPANY_SELECT` - Admin selecting company to delete\n- `WAITING_ADD_COUPON_COMPANY` - Admin selecting company for new coupon\n- `WAITING_ADD_COUPON_TEXT` - Admin entering coupon code\n- `WAITING_ADD_COUPON_VALUE` - Admin entering discount value\n- `WAITING_EDIT_COUPON_SELECT` - Admin selecting coupon to edit\n- `WAITING_EDIT_COUPON_TEXT` - Admin entering new coupon code\n- `WAITING_DELETE_COUPON_SELECT` - Admin selecting coupon to delete\n- `EDIT_WELCOME` - Admin updating welcome message\n- `EDIT_END` - Admin updating end message\n\n## Message Templates\n\n### Default Welcome Message (Customizable):\n\n### Default End Message (Customizable):\n\n## Customization Tips\n\n- **Add more languages**: Extend the dashboard and bot to support additional languages beyond Arabic/English\n- **Coupon restrictions**: Use the advanced coupon fields to set minimum purchase amounts, maximum discount caps, and expiry dates\n- **Analytics enhancement**: Add more statistics nodes to track coupon usage by company or time period\n- **Telegram integration**: Duplicate the WhatsApp webhook nodes and modify for Telegram Bot API\n- **Email notifications**: Add SMTP nodes to send coupon details via email\n- **Multi-admin support**: Modify the settings schema to support multiple admin phone numbers\n- **Coupon categories**: Add a categories table and filter coupons by category in the bot\n\n## Use Cases\n\n- Marketing agencies managing discount campaigns for multiple clients\n- E-commerce platforms offering partner store coupons\n- Loyalty programs with tiered discount systems\n- Event-based promotional campaigns with expiry dates\n- Multi-vendor marketplaces with individual seller coupons\n- Customer retention programs with personalized offers\n\n## Technical Notes\n\n- **Frontend**: Vue.js 3 (Composition API) + Tailwind CSS + Font Awesome icons\n- **Backend**: n8n workflow automation with PostgreSQL\n- **WhatsApp API**: Facebook Graph API v22.0 with interactive list messages\n- **Security**: Phone number-based admin authentication, SQL injection protection via parameterized queries\n- **Performance**: Database indexing on phone numbers and foreign keys, session cleanup recommended\n- **Mobile**: Fully responsive dashboard with mobile-optimized WhatsApp interactions\n\n---\n\n**Tags:** #n8n #whatsapp #coupons #dashboard #vuejs #postgresql #automation #customer-service #bilingual","workflow":{"meta":{"instanceId":"473168f370e695cf2681bfe0d87794a129a4b6b91ce9730668db1c27be277998"},"nodes":[{"id":"7974c25c-9cd3-470c-859b-b8ac6b56690d","name":"Dashboard Webhook","type":"n8n-nodes-base.webhook","position":[-2704,6448],"webhookId":"fd8c35af-38d5-494d-ad34-cd9aa3478b57","parameters":{"path":"coupon-bot/dashboard","options":{},"responseMode":"responseNode"},"typeVersion":1},{"id":"822890c1-ed25-4a60-9df2-adfa470dfd0f","name":"Respond with Dashboard HTML","type":"n8n-nodes-base.respondToWebhook","position":[-2496,6448],"parameters":{"options":{"responseHeaders":{"entries":[{"name":"Content-Type","value":"text/html"}]}},"respondWith":"text","responseBody":"<!DOCTYPE html>\n<html lang=\"en\" dir=\"ltr\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>Control Panel | Coupon Bot</title>\n    <script src=\"https://cdn.tailwindcss.com\"></script>\n    <script src=\"https://unpkg.com/vue@3/dist/vue.global.js\"></script>\n    <link href=\"https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;700;900&display=swap\" rel=\"stylesheet\">\n    <link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css\">\n    <style>\n        [v-cloak] { display: none; }\n        body { font-family: 'Inter', sans-serif; background-color: #f8fafc; color: #1e293b; overflow-x: hidden; }\n        .glass-sidebar { background: rgba(255, 255, 255, 0.85); backdrop-filter: blur(20px); border-right: 1px solid rgba(0,0,0,0.05); }\n        .nav-item { transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); margin: 6px 16px; border-radius: 14px; }\n        .nav-item.active { background: linear-gradient(135deg, #2563eb, #3b82f6); color: white; box-shadow: 0 10px 20px -5px rgba(37, 99, 235, 0.3); }\n        .card { background: white; border-radius: 24px; box-shadow: 0 4px 20px -2px rgba(0,0,0,0.03); transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1); border: 1px solid rgba(0,0,0,0.02); }\n        .card:hover { transform: translateY(-4px) scale(1.01); box-shadow: 0 20px 25px -5px rgba(0,0,0,0.05); }\n        .fade-enter-active, .fade-leave-active { transition: opacity 0.5s ease, transform 0.5s ease; }\n        .fade-enter-from, .fade-leave-to { opacity: 0; transform: translateY(10px); }\n        @media (max-width: 768px) {\n            .sidebar-closed { transform: translateX(100%); }\n            .sidebar-open { transform: translateX(0); }\n        }\n        .ltr { direction: ltr; text-align: left; }\n    </style>\n</head>\n<body class=\"h-screen overflow-hidden flex bg-slate-50\">\n    <div id=\"app\" class=\"flex w-full h-full relative\" v-cloak>\n        <!-- Mobile Header -->\n        <div class=\"md:hidden fixed top-0 left-0 right-0 h-16 bg-white/80 backdrop-blur-md z-30 flex items-center justify-between px-6 border-b border-slate-200\">\n            <button @click=\"sidebarOpen = !sidebarOpen\" class=\"w-10 h-10 flex items-center justify-center rounded-xl bg-slate-100 text-slate-600 transition-transform active:scale-90\"><i class=\"fa-solid\" :class=\"sidebarOpen ? 'fa-xmark' : 'fa-bars'\"></i></button>\n            <span class=\"font-black text-slate-800 tracking-tight\">Coupon Bot</span>\n            <div class=\"w-10\"></div>\n        </div>\n\n        <!-- Overlay -->\n        <transition name=\"fade\">\n            <div v-if=\"sidebarOpen\" @click=\"sidebarOpen = false\" class=\"md:hidden fixed inset-0 bg-slate-900/40 backdrop-blur-sm z-40\"></div>\n        </transition>\n\n        <!-- Sidebar -->\n        <aside :class=\"sidebarOpen ? 'sidebar-open' : 'sidebar-closed'\" class=\"fixed md:relative w-72 glass-sidebar h-full flex flex-col shadow-2xl z-50 transition-all duration-500 ease-in-out md:translate-x-0\">\n            <div class=\"p-8 flex items-center gap-4\">\n                <div class=\"w-12 h-12 bg-blue-600 rounded-2xl flex items-center justify-center text-white shadow-lg shadow-blue-200\"><i class=\"fa-solid fa-robot text-2xl\"></i></div>\n                <h1 class=\"text-xl font-black text-slate-800\">Coupon Bot</h1>\n            </div>\n            <nav class=\"flex-1 space-y-1 overflow-y-auto px-2\">\n                <div v-for=\"item in navItems\" :key=\"item.id\" @click=\"setTab(item.id)\" :class=\"{'active': currentTab === item.id}\" class=\"nav-item flex items-center gap-4 p-4 cursor-pointer font-bold text-slate-500 hover:bg-slate-50\">\n                    <i :class=\"item.icon\" class=\"text-lg\"></i> <span class=\"flex-1\">{{ item.label }}</span>\n                </div>\n            </nav>\n        </aside>\n\n        <!-- Content -->\n        <main class=\"flex-1 h-full flex flex-col min-w-0\">\n            <header class=\"hidden md:flex bg-white h-20 items-center justify-between px-10 border-b border-slate-100\">\n                <h2 class=\"text-2xl font-black text-slate-800 transition-all\">{{ pageTitle }}</h2>\n                <div class=\"flex items-center gap-6\">\n                    <button @click=\"fetchAll\" class=\"text-slate-400 hover:text-blue-600 transition-colors\"><i class=\"fa-solid fa-arrows-rotate\"></i></button>\n                    <div class=\"text-sm text-slate-400 font-medium bg-slate-50 px-4 py-2 rounded-full\">{{ currentDate }}</div>\n                </div>\n            </header>\n\n            <div class=\"flex-1 overflow-y-auto p-4 md:p-10 pt-20 md:pt-10\">\n                <transition name=\"fade\" mode=\"out-in\">\n                    <!-- Dashboard -->\n                    <div v-if=\"currentTab === 'dashboard'\" key=\"dash\" class=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-8\">\n                        <div v-for=\"(stat, key) in statsDisplay\" :key=\"key\" class=\"card p-8\">\n                            <div class=\"flex justify-between mb-6\">\n                                <div :class=\"stat.bgClass\" class=\"w-14 h-14 rounded-2xl flex items-center justify-center shadow-inner\"><i :class=\"stat.iconClass\" class=\"text-xl\"></i></div>\n                            </div>\n                            <p class=\"text-slate-400 text-xs font-bold uppercase tracking-wider mb-2\">{{ stat.label }}</p>\n                            <h3 class=\"text-4xl font-black text-slate-800 tracking-tight\">{{ stat.value }}</h3>\n                        </div>\n                    </div>\n\n                    <!-- Companies -->\n                    <div v-if=\"currentTab === 'companies'\" key=\"comp\" class=\"space-y-8 max-w-5xl mx-auto\">\n                        <div class=\"card p-8 border-t-4 border-blue-600\">\n                            <h3 class=\"font-black text-xl mb-6 flex items-center gap-2\"><i class=\"fa-solid fa-plus-circle text-blue-600\"></i> Add Partner Company</h3>\n                            <form @submit.prevent=\"addCompany\" class=\"grid grid-cols-1 md:grid-cols-3 gap-6\">\n                                <input v-model=\"newCompany.name\" placeholder=\"Company Name\" required class=\"p-4 border border-slate-200 rounded-2xl focus:ring-2 focus:ring-blue-500 outline-none transition-all\">\n                                <input v-model=\"newCompany.info\" placeholder=\"Brief Description\" class=\"p-4 border border-slate-200 rounded-2xl focus:ring-2 focus:ring-blue-500 outline-none transition-all\">\n                                <button type=\"submit\" class=\"bg-blue-600 text-white rounded-2xl font-bold hover:bg-blue-700 transition-all shadow-lg shadow-blue-200 active:scale-95\">Add Now</button>\n                            </form>\n                        </div>\n                        <div class=\"grid gap-4\">\n                            <div v-for=\"company in companies\" :key=\"company.company_id\" class=\"card p-6 flex justify-between items-center bg-white\">\n                                <div class=\"flex items-center gap-4\">\n                                    <div class=\"w-12 h-12 bg-slate-50 rounded-xl flex items-center justify-center font-black text-slate-400\">#{{ company.company_id }}</div>\n                                    <div><h4 class=\"font-black text-slate-800 text-lg\">{{ company.name }}</h4><p class=\"text-sm text-slate-400\">{{ company.info || 'No additional information' }}</p></div>\n                                </div>\n                                <button @click=\"deleteCompany(company.company_id)\" class=\"w-10 h-10 flex items-center justify-center text-red-100 bg-red-500 rounded-xl hover:bg-red-600 transition-all active:scale-90\"><i class=\"fa-solid fa-trash-can\"></i></button>\n                            </div>\n                        </div>\n                    </div>\n\n                    <!-- Coupons -->\n                    <div v-if=\"currentTab === 'coupons'\" key=\"coup\" class=\"space-y-8 max-w-5xl mx-auto\">\n                        <div class=\"card p-8 border-t-4 border-emerald-500\">\n                            <div class=\"flex justify-between items-center mb-6\">\n                                <h3 class=\"font-black text-xl flex items-center gap-2\"><i class=\"fa-solid fa-ticket text-emerald-500\"></i> Issue New Coupon</h3>\n                                <button type=\"button\" @click=\"advancedSettingsOpen = !advancedSettingsOpen\" class=\"text-sm font-bold text-slate-400 hover:text-emerald-500 transition-colors uppercase tracking-widest\">\n                                    {{ advancedSettingsOpen ? 'Hide Advanced Settings' : 'Show Advanced Settings' }}\n                                </button>\n                            </div>\n                            <form @submit.prevent=\"addCoupon\" class=\"space-y-6\">\n                                <div class=\"grid grid-cols-1 md:grid-cols-3 gap-6\">\n                                    <select v-model=\"newCoupon.company_id\" required class=\"p-4 border border-slate-200 rounded-2xl focus:ring-2 focus:ring-emerald-500 outline-none bg-white font-bold\">\n                                        <option value=\"\" disabled>Select Company</option>\n                                        <option v-for=\"c in companies\" :key=\"c.company_id\" :value=\"c.company_id\">{{ c.name }}</option>\n                                    </select>\n                                    <input v-model=\"newCoupon.coupon_text\" placeholder=\"Coupon Code (e.g.: SAVE20)\" required class=\"p-4 border border-slate-200 rounded-2xl focus:ring-2 focus:ring-emerald-500 outline-none font-black ltr\">\n                                    <div class=\"relative\">\n                                        <input v-model=\"newCoupon.coupon_value\" type=\"number\" placeholder=\"Value\" required class=\"w-full p-4 border border-slate-200 rounded-2xl focus:ring-2 focus:ring-emerald-500 outline-none font-black\">\n                                        <div class=\"absolute left-4 top-1/2 -translate-y-1/2 flex items-center gap-2 bg-slate-50 p-1 px-2 rounded-lg\">\n                                            <input type=\"checkbox\" v-model=\"newCoupon.is_percentage\" id=\"is_perc\" class=\"accent-emerald-500\">\n                                            <label for=\"is_perc\" class=\"text-xs font-bold\">Percentage?</label>\n                                        </div>\n                                    </div>\n                                </div>\n\n                                <!-- Advanced Section -->\n                                <transition name=\"fade\">\n                                    <div v-if=\"advancedSettingsOpen\" class=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 pt-6 border-t border-slate-100\">\n                                        <!-- Max Discount -->\n                                        <div class=\"space-y-2\">\n                                            <label class=\"text-xs font-bold text-slate-400 px-2 uppercase\">Maximum Discount</label>\n                                            <div class=\"relative\">\n                                                <input v-model=\"newCoupon.max_value\" type=\"number\" class=\"w-full p-4 bg-slate-50 border-none rounded-2xl focus:ring-2 focus:ring-emerald-500 outline-none font-bold\">\n                                                <div class=\"absolute left-4 top-1/2 -translate-y-1/2 flex items-center gap-2\">\n                                                    <input type=\"checkbox\" v-model=\"newCoupon.has_max\" class=\"accent-emerald-500\"> \n                                                    <span class=\"text-[10px] font-bold\">Activate</span>\n                                                </div>\n                                            </div>\n                                        </div>\n                                        <!-- Min Purchase -->\n                                        <div class=\"space-y-2\">\n                                            <label class=\"text-xs font-bold text-slate-400 px-2 uppercase\">Minimum Purchase</label>\n                                            <div class=\"relative\">\n                                                <input v-model=\"newCoupon.min_purchase_value\" type=\"number\" class=\"w-full p-4 bg-slate-50 border-none rounded-2xl focus:ring-2 focus:ring-emerald-500 outline-none font-bold\">\n                                                <div class=\"absolute left-4 top-1/2 -translate-y-1/2 flex items-center gap-2\">\n                                                    <input type=\"checkbox\" v-model=\"newCoupon.has_min_purchase\" class=\"accent-emerald-500\"> \n                                                    <span class=\"text-[10px] font-bold\">Activate</span>\n                                                </div>\n                                            </div>\n                                        </div>\n                                        <!-- Expiry Date -->\n                                        <div class=\"space-y-2\">\n                                            <label class=\"text-xs font-bold text-slate-400 px-2 uppercase\">Expiry Date</label>\n                                            <div class=\"relative\">\n                                                <input v-model=\"newCoupon.expiry_date\" type=\"date\" class=\"w-full p-4 bg-slate-50 border-none rounded-2xl focus:ring-2 focus:ring-emerald-500 outline-none font-bold\">\n                                                <div class=\"absolute left-4 top-1/2 -translate-y-1/2 flex items-center gap-2 mr-10\">\n                                                    <input type=\"checkbox\" v-model=\"newCoupon.has_expiry\" class=\"accent-emerald-500\"> \n                                                </div>\n                                            </div>\n                                        </div>\n                                    </div>\n                                </transition>\n                                \n                                <button type=\"submit\" class=\"w-full bg-emerald-500 text-white p-5 rounded-2xl font-black hover:bg-emerald-600 transition-all shadow-lg shadow-emerald-200 active:scale-[0.98] flex items-center justify-center gap-2\">\n                                    <i class=\"fa-solid fa-wand-magic-sparkles\"></i>\n                                    Issue Coupon\n                                </button>\n                            </form>\n                        </div>\n                        <!-- Coupons Grid -->\n                        <div class=\"grid grid-cols-1 md:grid-cols-2 gap-6\">\n                            <div v-for=\"coupon in coupons\" :key=\"coupon.coupon_id\" class=\"card p-6 flex flex-col justify-between border-r-8 border-emerald-500 group relative overflow-hidden\">\n                                <div class=\"absolute top-0 left-0 w-32 h-32 bg-emerald-50 rounded-full -translate-x-16 -translate-y-16 opacity-40 transition-transform group-hover:scale-150\"></div>\n                                <div class=\"relative\">\n                                    <div class=\"flex justify-between items-start mb-4\">\n                                        <div>\n                                            <span class=\"px-3 py-1 bg-emerald-50 text-emerald-600 rounded-full text-[10px] font-black mb-2 inline-block uppercase tracking-wider\">{{ coupon.company_name }}</span>\n                                            <h4 class=\"text-2xl font-black text-slate-800 tracking-widest ltr\">{{ coupon.coupon_text }}</h4>\n                                        </div>\n                                        <div class=\"text-right\">\n                                            <div class=\"text-3xl font-black text-emerald-500\">{{ coupon.coupon_value }}{{ coupon.is_percentage ? '%' : 'SAR' }}</div>\n                                            <div class=\"text-[10px] text-slate-400 font-bold uppercase tracking-widest\">Discount</div>\n                                        </div>\n                                    </div>\n                                    <div class=\"grid grid-cols-2 gap-2 mt-4\">\n                                        <div v-if=\"coupon.has_min_purchase\" class=\"bg-slate-50 p-2 rounded-xl text-center\">\n                                            <p class=\"text-[9px] text-slate-400 font-bold\">Minimum</p>\n                                            <p class=\"text-xs font-black text-slate-600\">{{ coupon.min_purchase_value }} SAR</p>\n                                        </div>\n                                        <div v-if=\"coupon.has_max\" class=\"bg-slate-50 p-2 rounded-xl text-center\">\n                                            <p class=\"text-[9px] text-slate-400 font-bold\">Maximum</p>\n                                            <p class=\"text-xs font-black text-slate-600\">{{ coupon.max_value }} SAR</p>\n                                        </div>\n                                    </div>\n                                    <div class=\"flex justify-between items-center pt-4 mt-4 border-t border-slate-50\">\n                                        <span class=\"text-[10px] text-slate-400 font-bold flex items-center gap-1\"><i class=\"fa-regular fa-clock\"></i> {{ coupon.has_expiry ? formatDate(coupon.expiry_date) : 'Permanent' }}</span>\n                                        <button @click=\"deleteCoupon(coupon.coupon_id)\" class=\"w-8 h-8 flex items-center justify-center text-slate-300 hover:text-red-500 hover:bg-red-50 rounded-lg transition-all\"><i class=\"fa-solid fa-trash-can text-xs\"></i></button>\n                                    </div>\n                                </div>\n                            </div>\n                        </div>\n                    </div>\n\n                    <!-- Inbox -->\n                    <div v-if=\"currentTab === 'inbox'\" key=\"inbox\" class=\"card h-[calc(100vh-180px)] md:h-[calc(100vh-160px)] flex overflow-hidden\">\n                        <!-- List -->\n                        <div :class=\"{'hidden': selectedConversation && isMobile, 'w-full md:w-80 lg:w-96': true}\" class=\"border-l border-slate-100 flex flex-col bg-slate-50/30\">\n                            <div class=\"p-6 border-b bg-white flex justify-between items-center\">\n                                <h3 class=\"font-black text-lg\">Conversations</h3>\n                                <button @click=\"fetchInbox\" class=\"text-blue-600 active:rotate-180 transition-all duration-700\"><i class=\"fa-solid fa-rotate-right\"></i></button>\n                            </div>\n                            <div class=\"flex-1 overflow-y-auto\">\n                                <div v-for=\"conv in inboxMessages\" @click=\"selectConversation(conv)\" :class=\"{'bg-blue-50/50 border-r-4 border-blue-600 shadow-sm': selectedConversation?.customer_phone === conv.customer_phone}\" class=\"p-6 border-b border-slate-50 cursor-pointer transition-all hover:bg-slate-50 group\">\n                                    <div class=\"flex justify-between mb-2\">\n                                        <span class=\"font-black group-hover:translate-x-1 transition-transform\" :class=\"selectedConversation?.customer_phone === conv.customer_phone ? 'text-blue-700' : 'text-slate-800'\">{{ conv.customer_name }}</span>\n                                        <span class=\"text-[10px] opacity-60\">{{ formatTime(conv.created_at) }}</span>\n                                    </div>\n                                    <div class=\"text-sm truncate opacity-60\">{{ conv.message_text }}</div>\n                                </div>\n                            </div>\n                        </div>\n                        <!-- Chat -->\n                        <div :class=\"{'hidden': !selectedConversation && isMobile, 'flex': true}\" class=\"flex-1 flex flex-col bg-white\">\n                            <template v-if=\"selectedConversation\">\n                                <div class=\"p-6 border-b flex items-center justify-between bg-white z-10 shadow-sm\">\n                                    <div class=\"flex items-center gap-4\">\n                                        <button v-if=\"isMobile\" @click=\"selectedConversation = null\" class=\"w-10 h-10 flex items-center justify-center rounded-xl bg-slate-100 text-slate-500\"><i class=\"fa-solid fa-chevron-right\"></i></button>\n                                        <div class=\"w-12 h-12 bg-blue-100 rounded-2xl flex items-center justify-center text-blue-600 font-bold text-xl\">{{ selectedConversation.customer_name[0] }}</div>\n                                        <div>\n                                            <div class=\"font-black text-slate-800\">{{ selectedConversation.customer_name }}</div>\n                                            <div class=\"text-xs text-slate-400\">{{ selectedConversation.customer_phone }}</div>\n                                        </div>\n                                    </div>\n                                </div>\n                                <div class=\"flex-1 overflow-y-auto p-6 space-y-6 bg-slate-50/50 scroll-smooth\" id=\"chat-box\">\n                                    <div v-for=\"msg in chatHistory\" :key=\"msg.message_id\" :class=\"msg.direction === 'INCOMING' ? 'justify-start' : 'justify-end'\" class=\"flex\">\n                                        <div :class=\"msg.direction === 'INCOMING' ? 'bg-white text-slate-800 shadow-sm rounded-br-none' : 'bg-blue-600 text-white shadow-blue-100 shadow-lg rounded-bl-none'\"\n                                             class=\"p-4 rounded-3xl max-w-[85%] md:max-w-[70%] relative group min-w-[80px]\">\n                                            <p class=\"text-sm leading-relaxed\">{{ msg.message_text }}</p>\n                                            <span class=\"block text-[9px] mt-2 opacity-50 text-left font-bold\">{{ formatTime(msg.created_at) }}</span>\n                                        </div>\n                                    </div>\n                                </div>\n                            </template>\n                            <div v-else class=\"flex-1 flex flex-col items-center justify-center p-10 text-center\">\n                                <div class=\"w-32 h-32 bg-slate-50 rounded-full flex items-center justify-center mb-8\"><i class=\"fa-regular fa-comments text-5xl text-slate-200\"></i></div>\n                                <h3 class=\"text-xl font-black text-slate-800 mb-2\">Conversations Inbox</h3>\n                                <p class=\"text-slate-400 max-w-xs\">Select a user from the list to view the full conversation details</p>\n                            </div>\n                        </div>\n                    </div>\n\n                    <!-- Settings -->\n                    <div v-if=\"currentTab === 'settings'\" key=\"settings\" class=\"max-w-4xl mx-auto space-y-8 pb-10\">\n                        <!-- Admin Phone -->\n                        <div class=\"card p-10 border-t-4 border-blue-600\">\n                            <h3 class=\"text-xl font-black text-slate-800 mb-6\">System Settings</h3>\n                            <div class=\"space-y-6\">\n                                <div class=\"flex flex-col gap-2\">\n                                    <label class=\"font-bold text-slate-700 text-sm\">System Admin Phone Number (Admin)</label>\n                                    <div class=\"flex flex-col sm:flex-row gap-4\">\n                                        <input v-model=\"settings.admin_phone\" type=\"text\" placeholder=\"9665+1234567890\" class=\"flex-1 p-4 border border-slate-200 rounded-2xl ltr focus:ring-2 focus:ring-blue-500 outline-none\">\n                                        \n                                    <!-- Welcome Message -->\n                                    <div class=\"bg-white rounded-3xl shadow-sm p-8\">\n                                        <h3 class=\"text-2xl font-bold text-slate-800 mb-6 flex items-center gap-3\">\n                                            <i class=\"fa-solid fa-message text-green-600\"></i>\n                                            Welcome Message\n                                        </h3>\n                                        <textarea v-model=\"settings.welcome_message\" rows=\"3\" placeholder=\"Welcome to Coupons\" class=\"w-full p-4 border border-slate-200 rounded-2xl focus:ring-2 focus:ring-blue-500 outline-none resize-none\"></textarea>\n                                        <p class=\"text-sm text-slate-500 mt-2\">The message that appears on first contact with the customer</p>\n                                    </div>\n\n                                    <!-- End Message -->\n                                    <div class=\"bg-white rounded-3xl shadow-sm p-8\">\n                                        <h3 class=\"text-2xl font-bold text-slate-800 mb-6 flex items-center gap-3\">\n                                            <i class=\"fa-solid fa-envelope-open-text text-purple-600\"></i>\n                                            End Message\n                                        </h3>\n                                        <textarea v-model=\"settings.end_message\" rows=\"5\" placeholder=\"It was our pleasure serving you 💐💐💐\" class=\"w-full p-4 border border-slate-200 rounded-2xl focus:ring-2 focus:ring-blue-500 outline-none resize-none\"></textarea>\n                                        <p class=\"text-sm text-slate-500 mt-2\">The message that appears after sending the coupon to the customer</p>\n                                    </div>\n\n                                    <button @click=\"saveSettings\" class=\"px-8 py-4 bg-blue-600 text-white font-bold rounded-2xl hover:bg-blue-700 transition-all active:scale-95 shadow-lg shadow-blue-100 flex items-center justify-center gap-2\">\n                                            <i class=\"fa-solid fa-save\"></i> Save Settings\n                                        </button>\n                                    </div>\n                                    <p class=\"text-xs text-slate-400\">This number will be able to control the bot via WhatsApp/Telegram</p>\n                                </div>\n                                <transition name=\"fade\">\n                                    <p v-if=\"settingsSaved\" class=\"text-emerald-500 text-sm font-black flex items-center gap-2\"><i class=\"fa-solid fa-circle-check\"></i> Settings saved successfully</p>\n                                </transition>\n                            </div>\n                        </div>\n\n                        <!-- Operations -->\n                        <div class=\"grid grid-cols-1 md:grid-cols-2 gap-8\">\n                            <div class=\"card p-8 text-center border-t-4 border-orange-400\">\n                                <div class=\"w-16 h-16 bg-orange-50 text-orange-500 rounded-full flex items-center justify-center mx-auto mb-6\"><i class=\"fa-solid fa-screwdriver-wrench text-2xl\"></i></div>\n                                <h4 class=\"font-black text-lg mb-2\">Update Structure</h4>\n                                <p class=\"text-sm text-slate-400 mb-8\">Repair tables and add missing columns without deleting data</p>\n                                <button @click=\"repairDB\" :disabled=\"loading\" class=\"w-full py-4 bg-orange-500 text-white font-black rounded-2xl hover:bg-orange-600 active:scale-95 transition-all flex items-center justify-center gap-3\">\n                                    <i v-if=\"loading\" class=\"fa-solid fa-circle-notch fa-spin\"></i>\n                                    <i v-else class=\"fa-solid fa-magic-wand-sparkles\"></i>\n                                    Start Update\n                                </button>\n                            </div>\n                            <div class=\"card p-8 text-center border-t-4 border-red-500\">\n                                <div class=\"w-16 h-16 bg-red-50 text-red-500 rounded-full flex items-center justify-center mx-auto mb-6\"><i class=\"fa-solid fa-triangle-exclamation text-2xl\"></i></div>\n                                <h4 class=\"font-black text-lg mb-2 text-red-600\">Danger Zone</h4>\n                                <p class=\"text-sm text-slate-400 mb-8\">Delete all data and full factory reset (cannot be undone)</p>\n                                <button @click=\"initializeDB\" :disabled=\"loading\" class=\"w-full py-4 bg-red-500 text-white font-black rounded-2xl hover:bg-red-600 active:scale-95 transition-all flex items-center justify-center gap-3\">\n                                    <i v-if=\"loading\" class=\"fa-solid fa-circle-notch fa-spin\"></i>\n                                    <i v-else class=\"fa-solid fa-trash-arrow-up\"></i>\n                                    Full Initialization\n                                </button>\n                            </div>\n                        </div>\n                        <transition name=\"fade\">\n                            <div v-if=\"initResult\" :class=\"initSuccess ? 'bg-emerald-50 text-emerald-600 border-emerald-100' : 'bg-red-50 text-red-600 border-red-100'\" class=\"p-6 rounded-3xl border-2 text-center font-black\">\n                                {{ initResult }}\n                            </div>\n                        </transition>\n                    </div>\n                </transition>\n            </div>\n        </main>\n    </div>\n\n    <script>\n        const { createApp, ref, computed, onMounted, nextTick } = Vue;\n        createApp({\n            setup() {\n                const currentTab = ref('dashboard');\n                const sidebarOpen = ref(false);\n                const isMobile = ref(false);\n                const loading = ref(false);\n                const initResult = ref('');\n                const initSuccess = ref(false);\n                const settingsSaved = ref(false);\n                const advancedSettingsOpen = ref(false);\n\n                const stats = ref({ customers: 0, messages: 0, records: 0, companies: 0 });\n                const companies = ref([]);\n                const coupons = ref([]);\n                const customers = ref([]);\n                const records = ref([]);\n                const inboxMessages = ref([]);\n                const chatHistory = ref([]);\n                const selectedConversation = ref(null);\n                const settings = ref({ \n                    admin_phone: '',\n                    welcome_message: 'Welcome to Coupons',\n                    end_message: 'It was our pleasure serving you 💐💐💐\\nIf you want a new coupon type *coupon*\\nFollow us on Snapchat for the latest deals\\n'\n                });\n\n                const newCompany = ref({ name: '', info: '' });\n                const newCoupon = ref({\n                    company_id: '',\n                    coupon_text: '',\n                    coupon_value: 0,\n                    is_percentage: false,\n                    has_max: false,\n                    max_value: 0,\n                    is_max_percentage: false,\n                    has_min_purchase: false,\n                    min_purchase_value: 0,\n                    is_on_items_quantity: false,\n                    has_expiry: false,\n                    expiry_date: '',\n                    other_info: ''\n                });\n\n                const navItems = [\n                    { id: 'dashboard', label: 'Home', icon: 'fa-solid fa-chart-pie' },\n                    { id: 'inbox', label: 'Messages', icon: 'fa-solid fa-comment-dots' },\n                    { id: 'companies', label: 'Partners', icon: 'fa-solid fa-building' },\n                    { id: 'coupons', label: 'Coupons', icon: 'fa-solid fa-ticket' },\n                    { id: 'settings', label: 'Settings', icon: 'fa-solid fa-sliders' }\n                ];\n\n                const api = async (endpoint, body = {}) => {\n                    try {\n                        const res = await fetch(`/webhook/coupon-bot/api/${endpoint}`, { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(body) });\n                        return await res.json();\n                    } catch(e) { console.error('API Error:', e); return []; }\n                };\n\n                const fetchAll = () => { fetchStats(); fetchCompanies(); fetchCoupons(); fetchInbox(); fetchSettings(); };\n                const fetchStats = async () => { const data = await api('stats'); if(data.length) stats.value = data[0]; };\n                const fetchCompanies = async () => { companies.value = await api('companies', { httpMethod: 'GET' }); };\n                const fetchCoupons = async () => { coupons.value = await api('coupons', { httpMethod: 'GET' }); };\n                const fetchCustomers = async () => { customers.value = await api('customers', { httpMethod: 'GET' }); };\n                const fetchRecords = async () => { records.value = await api('records', { httpMethod: 'GET' }); };\n                const fetchInbox = async () => {\n                    const data = await api('inbox');\n                    const conversationsMap = new Map();\n                    data.forEach(msg => conversationsMap.set(msg.customer_phone, msg));\n                    inboxMessages.value = Array.from(conversationsMap.values());\n                };\n                const fetchSettings = async () => {\n                    const data = await api('settings', { httpMethod: 'GET' });\n                    if(data?.length) {\n                        const phone = data.find(r => r.key === 'admin_phone');\n                        if(phone) settings.value.admin_phone = phone.value;\n                        \n                        const welcome = data.find(r => r.key === 'welcome_message');\n                        if(welcome) settings.value.welcome_message = welcome.value;\n                        \n                        const endMsg = data.find(r => r.key === 'end_message');\n                        if(endMsg) settings.value.end_message = endMsg.value;\n                    }\n                };\n\n                const saveSettings = async () => {\n                    await api('settings', { httpMethod: 'POST', body: settings.value });\n                    settingsSaved.value = true;\n                    setTimeout(() => settingsSaved.value = false, 3000);\n                };\n\n                const initializeDB = async () => {\n                    if (!confirm('All data will be permanently deleted! Are you sure?')) return;\n                    loading.value = true; initResult.value = '';\n                    try {\n                        const response = await fetch('/webhook/coupon-bot/execute-init', { method: 'POST' });\n                        if (response.ok) { initSuccess.value = true; initResult.value = '✅ Initialization completed successfully!'; fetchAll(); }\n                    } catch (e) { initSuccess.value = false; initResult.value = '❌ Operation failed'; }\n                    loading.value = false;\n                };\n\n                const repairDB = async () => {\n                    loading.value = true; initResult.value = '';\n                    try {\n                        const response = await fetch('/webhook/coupon-bot/migrate', { method: 'POST' });\n                        if (response.ok) { initSuccess.value = true; initResult.value = '✅ Tables updated!'; fetchAll(); }\n                    } catch (e) { initSuccess.value = false; initResult.value = '❌ Update failed'; }\n                    loading.value = false;\n                };\n\n                const selectConversation = async (conv) => {\n                    selectedConversation.value = conv;\n                    chatHistory.value = await api('history', { customer_phone: conv.customer_phone });\n                    nextTick(() => {\n                        const objDiv = document.getElementById(\"chat-box\");\n                        if(objDiv) objDiv.scrollTop = objDiv.scrollHeight;\n                    });\n                };\n\n                const setTab = (id) => { currentTab.value = id; if(isMobile.value) sidebarOpen.value = false; };\n\n                onMounted(() => {\n                    fetchAll();\n                    isMobile.value = window.innerWidth <= 768;\n                    window.addEventListener('resize', () => isMobile.value = window.innerWidth <= 768);\n                });\n\n                const statsDisplay = computed(() => [\n                    { label: 'Messages', value: stats.value.messages, bgClass: 'bg-blue-50 text-blue-600', iconClass: 'fa-solid fa-comments' },\n                    { label: 'Customers', value: stats.value.customers, bgClass: 'bg-emerald-50 text-emerald-600', iconClass: 'fa-solid fa-users' },\n                    { label: 'Partners', value: stats.value.companies, bgClass: 'bg-orange-50 text-orange-600', iconClass: 'fa-solid fa-briefcase' },\n                    { label: 'Sent Coupons', value: stats.value.records, bgClass: 'bg-indigo-50 text-indigo-600', iconClass: 'fa-solid fa-ticket' }\n                ]);\n\n                const pageTitle = computed(() => ({ 'dashboard': 'Home', 'inbox': 'Messages', 'companies': 'Partner Management', 'coupons': 'Coupon Management', 'settings': 'System Settings' }[currentTab.value]));\n                const currentDate = new Date().toLocaleDateString('ar-EG', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' });\n\n                const formatTime = (dateStr) => {\n                     if(!dateStr) return '';\n                     const date = new Date(dateStr);\n                     return date.toLocaleTimeString('ar-EG', { hour: '2-digit', minute: '2-digit' });\n                };\n                const formatDate = (dateStr) => {\n                     if(!dateStr) return '';\n                     return new Date(dateStr).toLocaleDateString('ar-EG');\n                };\n\n                return { advancedSettingsOpen, currentTab, sidebarOpen, isMobile, pageTitle, currentDate, navItems, statsDisplay, companies, coupons, inboxMessages, chatHistory, selectedConversation, selectConversation, formatTime, formatDate, newCompany, newCoupon, addCompany: () => api('companies', { httpMethod: 'POST', body: newCompany.value }).then(fetchAll), addCoupon: () => api('coupons', { httpMethod: 'POST', body: newCoupon.value }).then(fetchAll), deleteCompany: (id) => confirm('Delete company?') && api('companies', { httpMethod: 'DELETE', body: { company_id: id } }).then(fetchAll), deleteCoupon: (id) => confirm('Delete coupon?') && api('coupons', { httpMethod: 'DELETE', body: { coupon_id: id } }).then(fetchAll), setTab, fetchInbox, fetchAll, settings, saveSettings, settingsSaved, loading, initResult, initSuccess, initializeDB, repairDB };\n            }\n        }).mount('#app');\n    </script>\n</body>\n</html>"},"typeVersion":1},{"id":"28e3f6ad-f0a5-4213-ae18-cae5dd7f9f54","name":"Execute SQL Webhook","type":"n8n-nodes-base.webhook","position":[-2704,6656],"webhookId":"939cf48a-7ccd-4c66-8344-596c5adb70c4","parameters":{"path":"coupon-bot/execute-init","options":{},"httpMethod":"POST","responseMode":"responseNode"},"typeVersion":1},{"id":"b3958756-d816-45e3-b73c-53894cc9a870","name":"Postgres Init","type":"n8n-nodes-base.postgres","position":[-2496,6656],"parameters":{"query":"DROP TABLE IF EXISTS coupons CASCADE;\nDROP TABLE IF EXISTS companies CASCADE;\nDROP TABLE IF EXISTS messages CASCADE;\nDROP TABLE IF EXISTS records CASCADE;\nDROP TABLE IF EXISTS customers CASCADE;\nDROP TABLE IF EXISTS settings CASCADE;\nDROP TABLE IF EXISTS sessions CASCADE;\n\nCREATE TABLE customers (\n    customer_id SERIAL PRIMARY KEY,\n    name VARCHAR(255),\n    phone VARCHAR(50) UNIQUE NOT NULL,\n    selected_sites TEXT,\n    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP\n);\n\nCREATE TABLE companies (\n    company_id SERIAL PRIMARY KEY,\n    name VARCHAR(255) NOT NULL,\n    info TEXT\n);\n\nCREATE TABLE coupons (\n    coupon_id SERIAL PRIMARY KEY,\n    company_id INT NOT NULL REFERENCES companies(company_id) ON DELETE CASCADE,\n    coupon_text TEXT NOT NULL,\n    coupon_value NUMERIC(10, 2),\n    is_percentage BOOLEAN DEFAULT FALSE,\n    has_max BOOLEAN DEFAULT FALSE,\n    max_value NUMERIC(10, 2),\n    is_max_percentage BOOLEAN DEFAULT FALSE,\n    has_min_purchase BOOLEAN DEFAULT FALSE,\n    min_purchase_value NUMERIC(10, 2),\n    is_on_items_quantity BOOLEAN DEFAULT FALSE,\n    registration_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n    has_expiry BOOLEAN DEFAULT FALSE,\n    expiry_date TIMESTAMP,\n    other_info TEXT\n);\n\nCREATE TABLE records (\n    record_id SERIAL PRIMARY KEY,\n    customer_id INT NOT NULL REFERENCES customers(customer_id) ON DELETE CASCADE,\n    company_id INT NOT NULL REFERENCES companies(company_id) ON DELETE CASCADE,\n    coupon_id INT NOT NULL REFERENCES coupons(coupon_id) ON DELETE CASCADE,\n    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP\n);\n\nCREATE TABLE messages (\n    message_id SERIAL PRIMARY KEY,\n    customer_id INT REFERENCES customers(customer_id) ON DELETE SET NULL,\n    sender_phone VARCHAR(50),\n    sender_name VARCHAR(255),\n    external_message_id VARCHAR(255),\n    message_text TEXT NOT NULL,\n    message_type VARCHAR(50) DEFAULT 'text',\n    direction VARCHAR(20) DEFAULT 'INCOMING',\n    is_from_customer BOOLEAN DEFAULT TRUE,\n    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP\n);\n\nCREATE TABLE settings (\n    key VARCHAR(50) PRIMARY KEY,\n    value TEXT\n);\n\nCREATE TABLE sessions (\n    phone VARCHAR(50) PRIMARY KEY,\n    state VARCHAR(50) NOT NULL,\n    data JSONB DEFAULT '{}',\n    last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP\n);","operation":"executeQuery","additionalFields":{}},"credentials":{"postgres":{"id":"credential-id","name":"Postgres account 2"}},"typeVersion":1},{"id":"b111ea27-9fd3-4887-87b5-591e3c288ca0","name":"Respond Success","type":"n8n-nodes-base.respondToWebhook","position":[-2304,6656],"parameters":{"options":{},"respondWith":"json","responseBody":"{\n  \"status\": \"success\",\n  \"message\": \"Database initialized fully with correct schema.\"\n}"},"typeVersion":1},{"id":"aebbe1a5-3f73-4564-865d-db1a0ab2d2ba","name":"Stats API Webhook","type":"n8n-nodes-base.webhook","position":[-2704,6848],"webhookId":"2bb99f4a-a93d-4eb7-9972-79d95d6b1c25","parameters":{"path":"coupon-bot/api/stats","options":{},"httpMethod":"POST","responseMode":"responseNode"},"typeVersion":1},{"id":"2b873782-fea5-48db-b93f-bc2020f08cc4","name":"Get Stats Query","type":"n8n-nodes-base.postgres","position":[-2496,6848],"parameters":{"query":"SELECT \n    (SELECT COUNT(*) FROM customers) as customers,\n    (SELECT COUNT(*) FROM messages) as messages,\n    (SELECT COUNT(*) FROM records) as records,\n    (SELECT COUNT(*) FROM companies) as companies;","operation":"executeQuery","additionalFields":{}},"credentials":{"postgres":{"id":"credential-id","name":"Postgres account 2"}},"typeVersion":1},{"id":"b16d4d0c-979e-4d9f-a51e-4eead9039a50","name":"Respond Stats","type":"n8n-nodes-base.respondToWebhook","position":[-2304,6848],"parameters":{"options":{},"respondWith":"json","responseBody":"={{ JSON.stringify($items().map(i => i.json)) }}"},"typeVersion":1},{"id":"a5f931f9-627c-4ade-902e-6da8cbf10aeb","name":"Companies API","type":"n8n-nodes-base.webhook","position":[-2720,7104],"webhookId":"8143eee4-b2c0-40c9-a829-41eb3c9c5506","parameters":{"path":"coupon-bot/api/companies","options":{},"httpMethod":"POST","responseMode":"responseNode"},"typeVersion":1},{"id":"e3202c5a-a165-46c8-befc-44db2757f336","name":"Switch Company Method","type":"n8n-nodes-base.if","position":[-2512,7104],"parameters":{"conditions":{"string":[{"value1":"={{ $json.body.httpMethod }}","value2":"GET","operation":"notEqual"}]}},"typeVersion":1},{"id":"d786ad0d-9857-411f-8db8-707efbd62238","name":"Get Companies","type":"n8n-nodes-base.postgres","position":[-2288,7200],"parameters":{"query":"SELECT * FROM companies ORDER BY company_id DESC;","operation":"executeQuery","additionalFields":{}},"credentials":{"postgres":{"id":"credential-id","name":"Postgres account 2"}},"typeVersion":1},{"id":"1e4d03a9-6631-4452-9bb0-994fd298cb05","name":"Modify Company","type":"n8n-nodes-base.postgres","position":[-2288,7024],"parameters":{"query":"={{ $json.body.httpMethod === 'POST' ? \"INSERT INTO companies (name, info) VALUES ('\" + $json.body.body.name + \"', '\" + ($json.body.body.info || \"\") + \"') RETURNING *;\" : ($json.body.httpMethod === 'PUT' ? \"UPDATE companies SET name = '\" + $json.body.body.name + \"', info = '\" + ($json.body.body.info || \"\") + \"' WHERE company_id = \" + $json.body.body.company_id + \" RETURNING *;\" : \"DELETE FROM companies WHERE company_id = \" + $json.body.body.company_id + \" RETURNING *;\") }}","operation":"executeQuery","additionalFields":{}},"credentials":{"postgres":{"id":"credential-id","name":"Postgres account 2"}},"typeVersion":1},{"id":"659962ba-cbf2-49f1-9540-b43d93e7b166","name":"Respond Companies","type":"n8n-nodes-base.respondToWebhook","position":[-2080,7200],"parameters":{"options":{},"respondWith":"json","responseBody":"={{ $items().length > 0 && $items()[0].json.company_id ? JSON.stringify($items().map(i => i.json)) : \"[]\" }}"},"typeVersion":1},{"id":"2db4a6ae-b6d4-4030-a1e5-3f3af75e9178","name":"Respond Company Modified","type":"n8n-nodes-base.respondToWebhook","position":[-2080,7024],"parameters":{"options":{},"respondWith":"json","responseBody":"={{ JSON.stringify($json) }}"},"typeVersion":1},{"id":"72a180db-38b5-483e-be1b-d59bdb44bb43","name":"Coupons API","type":"n8n-nodes-base.webhook","position":[-2720,7472],"webhookId":"c6479e69-d77f-4bac-9ff0-984165ae34b6","parameters":{"path":"coupon-bot/api/coupons","options":{},"httpMethod":"POST","responseMode":"responseNode"},"typeVersion":1},{"id":"0ecaf993-6e3e-4e34-ab43-da126ea86e59","name":"Switch Coupon Method","type":"n8n-nodes-base.if","position":[-2512,7472],"parameters":{"conditions":{"string":[{"value1":"={{ $json.body.httpMethod }}","value2":"GET","operation":"notEqual"}]}},"typeVersion":1},{"id":"15427b71-5784-419c-917f-9367831e545a","name":"Get Coupons","type":"n8n-nodes-base.postgres","position":[-2288,7600],"parameters":{"query":"SELECT c.*, co.name as company_name FROM coupons c JOIN companies co ON c.company_id = co.company_id ORDER BY c.coupon_id DESC;","operation":"executeQuery","additionalFields":{}},"credentials":{"postgres":{"id":"credential-id","name":"Postgres account 2"}},"typeVersion":1},{"id":"e1a011d8-2a00-4369-90ca-3408b2c5e875","name":"Modify Coupon","type":"n8n-nodes-base.postgres","position":[-2288,7424],"parameters":{"query":"={{ $json.body.httpMethod === 'POST' ? \n  \"INSERT INTO coupons (company_id, coupon_text, coupon_value, is_percentage, has_max, max_value, is_max_percentage, has_min_purchase, min_purchase_value, is_on_items_quantity, has_expiry, expiry_date, other_info) VALUES (\" + \n  $json.body.body.company_id + \", '\" + \n  $json.body.body.coupon_text + \"', \" + \n  ($json.body.body.coupon_value ? $json.body.body.coupon_value : \"NULL\") + \", \" + \n  ($json.body.body.is_percentage || false) + \", \" + \n  ($json.body.body.has_max || false) + \", \" + \n  ($json.body.body.max_value || 0) + \", \" + \n  ($json.body.body.is_max_percentage || false) + \", \" + \n  ($json.body.body.has_min_purchase || false) + \", \" + \n  ($json.body.body.min_purchase_value || 0) + \", \" + \n  ($json.body.body.is_on_items_quantity || false) + \", \" + \n  ($json.body.body.has_expiry || false) + \", \" + \n  ($json.body.body.expiry_date ? \"'\" + $json.body.body.expiry_date + \"'\" : \"NULL\") + \", '\" + \n  ($json.body.body.other_info || \"\") + \"') RETURNING *;\" \n  : \n  \"DELETE FROM coupons WHERE coupon_id = \" + $json.body.body.coupon_id + \" RETURNING *;\" \n}}","operation":"executeQuery","additionalFields":{}},"credentials":{"postgres":{"id":"credential-id","name":"Postgres account 2"}},"typeVersion":1},{"id":"f74bce24-e60a-47e6-97e3-1d7de6440952","name":"Respond Coupons","type":"n8n-nodes-base.respondToWebhook","position":[-2080,7600],"parameters":{"options":{},"respondWith":"json","responseBody":"={{ $items().length > 0 && $items()[0].json.coupon_id ? JSON.stringify($items().map(i => i.json)) : \"[]\" }}"},"typeVersion":1},{"id":"236f9929-e9e3-4756-81ed-7fe7d53db39b","name":"Respond Coupon Modified","type":"n8n-nodes-base.respondToWebhook","position":[-2080,7424],"parameters":{"options":{},"respondWith":"json","responseBody":"={{ JSON.stringify($json) }}"},"typeVersion":1},{"id":"ac1317d7-d0ac-4624-abb9-edc2d4e6dc2a","name":"Inbox API","type":"n8n-nodes-base.webhook","position":[-2736,7792],"webhookId":"38f5f249-61d7-45da-937f-d5629e595646","parameters":{"path":"coupon-bot/api/inbox","options":{},"httpMethod":"POST","responseMode":"responseNode"},"typeVersion":1},{"id":"d327ca3e-0776-4f36-b937-8854a742245c","name":"Get Messages","type":"n8n-nodes-base.postgres","position":[-2528,7792],"parameters":{"query":"SELECT DISTINCT ON (COALESCE(c.phone, m.sender_phone))\n  m.message_id,\n  m.message_text,\n  m.created_at,\n  m.direction,\n  m.customer_id,\n  COALESCE(NULLIF(c.name, ''), NULLIF(m.sender_name, ''), 'Unknown Customer') as customer_name,\n  COALESCE(NULLIF(c.phone, ''), NULLIF(m.sender_phone, ''), 'Without Number') as customer_phone\nFROM messages m \nLEFT JOIN customers c ON m.customer_id = c.customer_id \nORDER BY COALESCE(c.phone, m.sender_phone), m.created_at DESC;","operation":"executeQuery","additionalFields":{}},"credentials":{"postgres":{"id":"credential-id","name":"Postgres account 2"}},"typeVersion":1},{"id":"96c8f8d4-6b1b-49f0-b980-2d5561c81acf","name":"Respond Messages","type":"n8n-nodes-base.respondToWebhook","position":[-2336,7792],"parameters":{"options":{},"respondWith":"json","responseBody":"={{ JSON.stringify($items().map(i => i.json)) }}"},"typeVersion":1},{"id":"2531fda1-9fb6-42f2-a20f-5b687cd41b88","name":"Settings API","type":"n8n-nodes-base.webhook","position":[-2736,8000],"webhookId":"c8d88746-ccda-4441-b5a7-05d12c286884","parameters":{"path":"coupon-bot/api/settings","options":{},"httpMethod":"POST","responseMode":"responseNode"},"typeVersion":1},{"id":"eca6405d-7592-4e19-979f-368d5aa4a594","name":"Switch Settings Method","type":"n8n-nodes-base.if","position":[-2528,8000],"parameters":{"conditions":{"string":[{"value1":"={{ $json.body.httpMethod }}","value2":"GET","operation":"notEqual"}]}},"typeVersion":1},{"id":"842d901a-4f17-48c1-b2ac-35cf6367bb68","name":"Save Settings","type":"n8n-nodes-base.postgres","position":[-2336,8000],"parameters":{"query":"={{ \n  \"INSERT INTO settings (key, value) VALUES ('admin_phone', '\" + ($json.body.body.admin_phone || '') + \"') ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value;\" +\n  \"INSERT INTO settings (key, value) VALUES ('welcome_message', '\" + ($json.body.body.welcome_message || 'Welcome to Coupons').replace(/'/g, \"''\") + \"') ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value;\" +\n  \"INSERT INTO settings (key, value) VALUES ('end_message', '\" + ($json.body.body.end_message || 'It was our pleasure serving you 💐💐💐\\nIf you want a new coupon type *coupon*\\nFollow us on Snapchat for the latest deals\\n').replace(/'/g, \"''\") + \"') ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value;\"\n}}","operation":"executeQuery","additionalFields":{}},"credentials":{"postgres":{"id":"credential-id","name":"Postgres account 2"}},"typeVersion":1,"alwaysOutputData":true},{"id":"464f3547-b90b-4e54-891b-56a5d276dfc8","name":"Get Settings","type":"n8n-nodes-base.postgres","position":[-2336,8144],"parameters":{"query":"SELECT * FROM settings;","operation":"executeQuery","additionalFields":{}},"credentials":{"postgres":{"id":"credential-id","name":"Postgres account 2"}},"typeVersion":1,"alwaysOutputData":true},{"id":"2af699ee-5b0e-454f-9085-17b2443373d9","name":"Respond Settings Saved","type":"n8n-nodes-base.respondToWebhook","position":[-2128,8000],"parameters":{"options":{},"respondWith":"json","responseBody":"={{ JSON.stringify($json) }}"},"typeVersion":1},{"id":"69b608f7-3843-4751-864c-3a3bc6e75875","name":"Respond Settings","type":"n8n-nodes-base.respondToWebhook","position":[-2128,8144],"parameters":{"options":{},"respondWith":"json","responseBody":"={{ $items().length > 0 ? JSON.stringify($items().map(i => i.json)) : \"[]\" }}"},"typeVersion":1},{"id":"82475bcd-8dda-4439-a6b7-2208e37dcf6a","name":"Migrate API Webhook","type":"n8n-nodes-base.webhook","position":[-2736,8352],"webhookId":"f4376f23-61e1-49df-990e-e1b8db0d96f9","parameters":{"path":"coupon-bot/migrate","options":{},"httpMethod":"POST","responseMode":"responseNode"},"typeVersion":1},{"id":"776706d3-8ce0-447f-849e-0282ca3dbfd9","name":"Repair Tables Schema","type":"n8n-nodes-base.postgres","position":[-2528,8352],"parameters":{"query":"-- Add phone column to customers if missing\nDO $$ \nBEGIN\n    IF NOT EXISTS (\n        SELECT 1 FROM information_schema.columns \n        WHERE table_name = 'customers' AND column_name = 'phone'\n    ) THEN\n        ALTER TABLE customers ADD COLUMN phone VARCHAR(50);\n    END IF;\nEND $$;\n\n-- Fix other messages table columns\nALTER TABLE messages ADD COLUMN IF NOT EXISTS sender_name VARCHAR(255);\nALTER TABLE messages ADD COLUMN IF NOT EXISTS message_type VARCHAR(50) DEFAULT 'text';\nALTER TABLE messages ADD COLUMN IF NOT EXISTS direction VARCHAR(20) DEFAULT 'INCOMING';\n\n-- Fix customers table maintenance columns\nALTER TABLE customers ADD COLUMN IF NOT EXISTS created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP;\nALTER TABLE customers ADD COLUMN IF NOT EXISTS updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP;\n\n-- Update NULL values\nUPDATE customers SET created_at = CURRENT_TIMESTAMP WHERE created_at IS NULL;\nUPDATE customers SET updated_at = CURRENT_TIMESTAMP WHERE updated_at IS NULL;\n\n-- Add UNIQUE constraint if not exists\nDO $$ \nBEGIN\n    IF NOT EXISTS (\n        SELECT 1 FROM pg_constraint \n        WHERE conname = 'customers_phone_unique'\n    ) THEN\n        ALTER TABLE customers ADD CONSTRAINT customers_phone_unique UNIQUE (phone);\n    END IF;\nEND $$;\n\n-- Fix phone field length\nALTER TABLE customers ALTER COLUMN phone TYPE VARCHAR(50);\nALTER TABLE messages ALTER COLUMN sender_phone TYPE VARCHAR(50);\n\n-- Ensure sessions table exists for state management\nCREATE TABLE IF NOT EXISTS sessions (\n    phone VARCHAR(50) PRIMARY KEY,\n    state VARCHAR(50) NOT NULL,\n    data JSONB DEFAULT '{}',\n    last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP\n);\n\n-- One-time cleanup of corrupted settings values\nDELETE FROM settings WHERE value LIKE '{{%';","operation":"executeQuery","additionalFields":{}},"credentials":{"postgres":{"id":"credential-id","name":"Postgres account 2"}},"typeVersion":1},{"id":"f7b7598d-f70f-4d34-9e9a-7b22dfc0b578","name":"Respond Migration Success","type":"n8n-nodes-base.respondToWebhook","position":[-2336,8352],"parameters":{"options":{},"respondWith":"json","responseBody":"{\"status\": \"success\", \"message\": \"Schema updated successfully\"}"},"typeVersion":1},{"id":"6fdb986b-15a2-4ed1-a099-4d97ad00cb2e","name":"Chat History API","type":"n8n-nodes-base.webhook","position":[-3168,8160],"webhookId":"06954201-9c6a-4d9f-96a1-968605c2a417","parameters":{"path":"coupon-bot/api/history","options":{},"httpMethod":"POST","responseMode":"responseNode"},"typeVersion":1},{"id":"9a7b3ecc-3016-4be7-9b26-db0c051669cd","name":"Get Chat History","type":"n8n-nodes-base.postgres","position":[-2960,8160],"parameters":{"query":"={{ \"SELECT \\n  m.*,\\n  COALESCE(NULLIF(c.name, ''), NULLIF(m.sender_name, ''), 'Unknown Customer') as customer_name,\\n  COALESCE(NULLIF(c.phone, ''), NULLIF(m.sender_phone, ''), 'Without Number') as customer_phone\\nFROM messages m \\nLEFT JOIN customers c ON m.customer_id = c.customer_id \\nWHERE REPLACE(COALESCE(c.phone, m.sender_phone), '+', '') = '\" + ($json.body.customer_phone ? $json.body.customer_phone.toString().replace('+', '') : '') + \"'\\nORDER BY m.created_at ASC \\nLIMIT 100;\" }}","operation":"executeQuery","additionalFields":{}},"credentials":{"postgres":{"id":"credential-id","name":"Postgres account 2"}},"typeVersion":1},{"id":"bccd1835-1420-4aeb-bdfa-84fa11599c1d","name":"Respond History","type":"n8n-nodes-base.respondToWebhook","position":[-2768,8160],"parameters":{"options":{},"respondWith":"json","responseBody":"={{ JSON.stringify($items().map(i => i.json)) }}"},"typeVersion":1},{"id":"826667ad-d445-4278-9849-1dabfb0c6e7c","name":"Customers API","type":"n8n-nodes-base.webhook","position":[-2736,8528],"webhookId":"customers-api-webhook","parameters":{"path":"coupon-bot/api/customers","options":{},"httpMethod":"POST","responseMode":"responseNode"},"typeVersion":1},{"id":"26269f3f-c9ec-48a5-beb8-33d72d5bc9a0","name":"Get Customers","type":"n8n-nodes-base.postgres","position":[-2528,8528],"parameters":{"query":"SELECT * FROM customers ORDER BY created_at DESC;","operation":"executeQuery","additionalFields":{}},"credentials":{"postgres":{"id":"credential-id","name":"Postgres account 2"}},"typeVersion":1},{"id":"ce8c2d87-ee65-4715-a124-11dcb86fce4a","name":"Respond Customers","type":"n8n-nodes-base.respondToWebhook","position":[-2336,8528],"parameters":{"options":{},"respondWith":"json","responseBody":"={{ JSON.stringify($items().map(i => i.json)) }}"},"typeVersion":1},{"id":"94ab2f14-ec87-45f2-b857-c8538661790d","name":"Records API","type":"n8n-nodes-base.webhook","position":[-2736,8736],"webhookId":"records-api-webhook","parameters":{"path":"coupon-bot/api/records","options":{},"httpMethod":"POST","responseMode":"responseNode"},"typeVersion":1},{"id":"212732b3-8f67-4f15-a538-db6a5b2c7ab0","name":"Get Records","type":"n8n-nodes-base.postgres","position":[-2528,8736],"parameters":{"query":"SELECT r.*, c.name as customer_name, co.name as company_name, cp.coupon_text \nFROM records r \nJOIN customers c ON r.customer_id = c.customer_id \nJOIN companies co ON r.company_id = co.company_id \nJOIN coupons cp ON r.coupon_id = cp.coupon_id \nORDER BY r.created_at DESC;","operation":"executeQuery","additionalFields":{}},"credentials":{"postgres":{"id":"credential-id","name":"Postgres account 2"}},"typeVersion":1},{"id":"f7193791-adf7-4fde-abad-4868b272273b","name":"Respond Records","type":"n8n-nodes-base.respondToWebhook","position":[-2336,8736],"parameters":{"options":{},"respondWith":"json","responseBody":"={{ JSON.stringify($items().map(i => i.json)) }}"},"typeVersion":1},{"id":"0a6a50dc-9f21-4c59-b5ae-b9cdf13d654d","name":"WhatsApp Webhook","type":"n8n-nodes-base.webhook","position":[-480,8528],"webhookId":"e1ee73fd-f3fb-401b-baf5-e4a871f4cc3d","parameters":{"path":"whatsapp","options":{},"httpMethod":"POST","responseMode":"responseNode"},"typeVersion":1},{"id":"53916c9b-f215-4809-b3a0-5b57f806c77a","name":"Is Message?","type":"n8n-nodes-base.if","position":[-240,8528],"parameters":{"conditions":{"boolean":[{"value1":"={{ $('WhatsApp Webhook').item.json.body.entry[0].changes[0].value.messages ? true : false }}","value2":true}]}},"typeVersion":1},{"id":"a6034fab-a9c5-464d-a03d-8c4ed0e7911a","name":"Get Admin Phone","type":"n8n-nodes-base.postgres","position":[16,8512],"parameters":{"query":"SELECT * FROM settings WHERE key = 'admin_phone';","operation":"executeQuery","additionalFields":{}},"credentials":{"postgres":{"id":"credential-id","name":"Postgres account 2"}},"typeVersion":1,"alwaysOutputData":true},{"id":"32150657-cc95-450e-b9fe-e95bea90916a","name":"Set Basic Data","type":"n8n-nodes-base.set","position":[240,8512],"parameters":{"values":{"string":[{"name":"sender_phone","value":"={{ $('WhatsApp Webhook').item.json.body.entry[0].changes[0].value.messages[0].from }}"},{"name":"sender_name","value":"={{ $('WhatsApp Webhook').item.json.body.entry[0].changes[0].value.contacts[0].profile.name || 'Unknown' }}"},{"name":"admin_phone","value":"={{ $json.value }}"},{"name":"phone_number_id","value":"={{ $('WhatsApp Webhook').item.json.body.entry[0].changes[0].value.metadata.phone_number_id }}"},{"name":"message_text","value":"={{ $('WhatsApp Webhook').item.json.body.entry[0].changes[0].value.messages[0].text?.body || $('WhatsApp Webhook').item.json.body.entry[0].changes[0].value.messages[0].button?.text || $('WhatsApp Webhook').item.json.body.entry[0].changes[0].value.messages[0].interactive?.button_reply?.title || $('WhatsApp Webhook').item.json.body.entry[0].changes[0].value.messages[0].interactive?.list_reply?.title || '' }}"},{"name":"message_type","value":"={{ $('WhatsApp Webhook').item.json.body.entry[0].changes[0].value.messages[0].type }}"},{"name":"button_id","value":"={{ $('WhatsApp Webhook').item.json.body.entry[0].changes[0].value.messages[0].interactive?.button_reply?.id || $('WhatsApp Webhook').item.json.body.entry[0].changes[0].value.messages[0].interactive?.list_reply?.id || '' }}"}],"boolean":[{"name":"is_admin","value":"={{ $('WhatsApp Webhook').item.json.body.entry[0].changes[0].value.messages[0].from == $json.value }}"}]},"options":{}},"typeVersion":1},{"id":"cab7469c-8d42-4072-bc3c-34bd8682c132","name":"Upsert Customer","type":"n8n-nodes-base.postgres","position":[544,8528],"parameters":{"query":"={{ \"INSERT INTO customers (name, phone, updated_at) VALUES ('\" + ($('Set Basic Data').item.json.sender_name || 'Unknown').replace(/'/g, \"''\") + \"', '\" + ($('Set Basic Data').item.json.sender_phone || '') + \"', NOW()) ON CONFLICT (phone) DO UPDATE SET name = EXCLUDED.name, updated_at = NOW() RETURNING customer_id;\" }}","operation":"executeQuery","additionalFields":{}},"credentials":{"postgres":{"id":"credential-id","name":"Postgres account 2"}},"typeVersion":1},{"id":"5c910ffa-1f6f-4a35-930f-f8b9b4baa5ad","name":"Save Incoming Message","type":"n8n-nodes-base.postgres","position":[800,8528],"parameters":{"query":"={{ \"INSERT INTO messages (customer_id, sender_phone, sender_name, message_text, message_type, direction, created_at) VALUES (\" + ($('Upsert Customer').item.json.customer_id || 'NULL') + \", '\" + ($('Set Basic Data').item.json.sender_phone || '') + \"', '\" + ($('Set Basic Data').item.json.sender_name || 'Unknown').replace(/'/g, \"''\") + \"', '\" + ($('Set Basic Data').item.json.message_text || '').replace(/'/g, \"''\") + \"', '\" + ($('Set Basic Data').item.json.message_type || 'text') + \"', 'INCOMING', NOW());\" }}","operation":"executeQuery","additionalFields":{}},"credentials":{"postgres":{"id":"credential-id","name":"Postgres account 2"}},"typeVersion":1,"alwaysOutputData":true},{"id":"4d4f4279-7019-45e3-a026-c08dfaabff46","name":"Get User Session","type":"n8n-nodes-base.postgres","position":[992,8528],"parameters":{"query":"={{ \"SELECT * FROM sessions WHERE phone = '\" + $('Set Basic Data').item.json.sender_phone + \"';\" }}","operation":"executeQuery","additionalFields":{}},"credentials":{"postgres":{"id":"credential-id","name":"Postgres account 2"}},"typeVersion":1,"alwaysOutputData":true},{"id":"93f5a166-3d01-4d67-9161-004d2a618fa4","name":"Is Admin?","type":"n8n-nodes-base.if","position":[1200,8528],"parameters":{"conditions":{"boolean":[{"value1":"={{ $('Set Basic Data').item.json.is_admin }}","value2":true}]}},"typeVersion":1},{"id":"6d61ca8b-39aa-4adb-a2e5-712a4633fe51","name":"Switch User Action","type":"n8n-nodes-base.if","position":[1872,8560],"parameters":{"conditions":{"string":[{"value1":"={{ $('Set Basic Data').item.json.message_type }}","value2":"button_reply"},{"value1":"={{ $('Set Basic Data').item.json.message_type }}","value2":"interactive"}]},"combineOperation":"any"},"typeVersion":1},{"id":"fec7b14c-d577-431a-bfe5-67c24542f93e","name":"Get Companies List","type":"n8n-nodes-base.postgres","position":[3856,7792],"parameters":{"query":"SELECT * FROM companies WHERE name != 'Add Company' ORDER BY company_id DESC LIMIT 10;","operation":"executeQuery","additionalFields":{}},"credentials":{"postgres":{"id":"credential-id","name":"Postgres account 2"}},"typeVersion":1,"alwaysOutputData":true},{"id":"1df6c23a-d069-4f29-9874-a03854e3a078","name":"Send Companies List","type":"n8n-nodes-base.httpRequest","position":[4144,7520],"parameters":{"url":"=https://graph.facebook.com/v22.0/{{ $('Set Basic Data').first().json.phone_number_id }}/messages","method":"POST","options":{},"jsonBody":"={{ (function() {\n  const companies = $items();\n  if (!companies || companies.length === 0 || (companies.length === 1 && !companies[0].json.company_id)) {\n    return JSON.stringify({\n      \"messaging_product\": \"whatsapp\",\n      \"to\": $('Set Basic Data').first().json.sender_phone,\n      \"type\": \"text\",\n      \"text\": { \"body\": \"Sorry, no companies are currently available.\" }\n    });\n  }\n  \n  // Get Welcome Message\n  const welcomeMsg = $('Get Welcome & End Messages').all().find(item => item.json.key === 'welcome_message');\n  const welcomeText = welcomeMsg ? welcomeMsg.json.value : 'Welcome to Coupons';\n  \n  const rows = companies.filter(c => c.json.company_id).slice(0, 10).map(function(c, index) {\n    const companyName = c.json.name || 'Company ' + (index + 1);\n    return {\n      \"id\": 'company_' + c.json.company_id,\n      \"title\": companyName.substring(0, 24)\n    };\n  });\n  \n  return JSON.stringify({\n    \"messaging_product\": \"whatsapp\",\n    \"to\": $('Set Basic Data').first().json.sender_phone,\n    \"type\": \"interactive\",\n    \"interactive\": {\n      \"type\": \"list\",\n      \"body\": { \"text\": welcomeText + \"\\n\\nChoose a company to view coupons:\" },\n      \"action\": {\n        \"button\": \"Companies\",\n        \"sections\": [{\n          \"title\": \"Companies\",\n          \"rows\": rows\n        }]\n      }\n    }\n  });\n})() }}","sendBody":true,"specifyBody":"json","authentication":"genericCredentialType","genericAuthType":"httpBearerAuth"},"credentials":{"httpBearerAuth":{"id":"credential-id","name":"whatsapp"}},"executeOnce":true,"typeVersion":4},{"id":"fe07e168-5450-4a40-89f6-116bdb4997dc","name":"Is Company Selection?","type":"n8n-nodes-base.if","position":[3888,8032],"parameters":{"conditions":{"boolean":[{"value1":"={{ $('Set Basic Data').item.json.button_id.startsWith('company_') ? true : false }}","value2":true}]}},"typeVersion":1},{"id":"f320746f-88d7-4742-a687-29c9a5f0150e","name":"Get Coupons List","type":"n8n-nodes-base.postgres","position":[4288,7760],"parameters":{"query":"={{ 'SELECT * FROM coupons WHERE company_id = ' + $('Set Basic Data').item.json.button_id.split('_')[1] + ' AND (expiry_date IS NULL OR expiry_date > NOW()) ORDER BY coupon_value DESC LIMIT 10;' }}","operation":"executeQuery","additionalFields":{}},"credentials":{"postgres":{"id":"credential-id","name":"Postgres account 2"}},"typeVersion":1,"alwaysOutputData":true},{"id":"5a563bd9-3865-456a-b228-cdf9be8da995","name":"Send Coupons List","type":"n8n-nodes-base.httpRequest","position":[4496,8016],"parameters":{"url":"=https://graph.facebook.com/v22.0/{{ $('Set Basic Data').first().json.phone_number_id }}/messages","method":"POST","options":{},"jsonBody":"={{ (function() {\n  const coupons = $items();\n  if (!coupons || coupons.length === 0 || (coupons.length === 1 && !coupons[0].json.coupon_id)) {\n    return JSON.stringify({\n      \"messaging_product\": \"whatsapp\",\n      \"to\": $('Set Basic Data').first().json.sender_phone,\n      \"type\": \"text\",\n      \"text\": { \"body\": \"Sorry, no coupons are available for this company right now.\" }\n    });\n  }\n\n  const rows = coupons.filter(c => c.json.coupon_id).slice(0, 10).map(function(c, index) {\n    const couponText = c.json.coupon_text || 'Coupon ' + (index + 1);\n    return {\n      \"id\": 'coupon_' + c.json.coupon_id,\n      \"title\": couponText.substring(0, 24)\n    };\n  });\n  \n  return JSON.stringify({\n    \"messaging_product\": \"whatsapp\",\n    \"to\": $('Set Basic Data').first().json.sender_phone,\n    \"type\": \"interactive\",\n    \"interactive\": {\n      \"type\": \"list\",\n      \"body\": { \"text\": \"Choose the coupon code:\" },\n      \"action\": {\n        \"button\": \"Coupons\",\n        \"sections\": [{\n          \"title\": \"Coupons\",\n          \"rows\": rows\n        }]\n      }\n    }\n  });\n})() }}","sendBody":true,"specifyBody":"json","authentication":"genericCredentialType","genericAuthType":"httpBearerAuth"},"credentials":{"httpBearerAuth":{"id":"credential-id","name":"whatsapp"}},"executeOnce":true,"typeVersion":4},{"id":"3a2eb1a9-2d17-46f9-b388-52633fa7363b","name":"Get Coupon Details","type":"n8n-nodes-base.postgres","position":[4288,8144],"parameters":{"query":"={{ 'SELECT * FROM coupons WHERE coupon_id = ' + $('Set Basic Data').item.json.button_id.split('_')[1] + ';' }}","operation":"executeQuery","additionalFields":{}},"credentials":{"postgres":{"id":"credential-id","name":"Postgres account 2"}},"typeVersion":1,"alwaysOutputData":true},{"id":"2156a012-0d3f-4e07-8121-efde69421a20","name":"Log Coupon Redemption","type":"n8n-nodes-base.postgres","position":[4704,8080],"parameters":{"query":"={{ \"INSERT INTO records (customer_id, company_id, coupon_id, created_at) SELECT (SELECT customer_id FROM customers WHERE phone = '\" + $('Set Basic Data').item.json.sender_phone + \"' LIMIT 1), \" + ($json.company_id || 0) + \", \" + ($json.coupon_id || 0) + \", NOW() WHERE EXISTS (SELECT 1 FROM customers WHERE phone = '\" + $('Set Basic Data').item.json.sender_phone + \"');\" }}","operation":"executeQuery","additionalFields":{}},"credentials":{"postgres":{"id":"credential-id","name":"Postgres account 2"}},"typeVersion":1},{"id":"bd48c551-5dc9-42bd-985d-dce9edd4c99f","name":"Send Coupon Details","type":"n8n-nodes-base.httpRequest","position":[4512,8384],"parameters":{"url":"=https://graph.facebook.com/v22.0/{{ $('Set Basic Data').item.json.phone_number_id }}/messages","method":"POST","options":{},"jsonBody":"={{ JSON.stringify({\n  messaging_product: \"whatsapp\",\n  to: $('Set Basic Data').item.json.sender_phone,\n  type: \"text\",\n  text: { \n    body: \"Coupon Details:\\nCode: \" + $('Get Coupon Details').first().json.coupon_text + \n          ($('Get Coupon Details').first().json.coupon_value ? \"\\nValue: \" + $('Get Coupon Details').first().json.coupon_value + ($('Get Coupon Details').first().json.is_percentage ? '%' : '') : '') +\n          ($('Get Coupon Details').first().json.other_info ? '\\nNotes: ' + $('Get Coupon Details').first().json.other_info : '') + \n          \"\\n\\n\" + ($json.value || \"It was our pleasure serving you 💐💐💐\\nIf you want a new coupon type *coupon*\\nFollow us on Snapchat for the latest deals\\n \")\n  }\n}) }}","sendBody":true,"specifyBody":"json","authentication":"genericCredentialType","genericAuthType":"httpBearerAuth"},"credentials":{"httpBearerAuth":{"id":"credential-id","name":"whatsapp"}},"typeVersion":4},{"id":"5d4818b9-0455-401c-b720-f42d64185f05","name":"Check Admin State","type":"n8n-nodes-base.if","position":[1472,8400],"parameters":{"conditions":{"boolean":[{"value1":"={{ ['WAITING_COMPANY_NAME', 'WAITING_COUPON_DETAILS', 'WAITING_EDIT_COMPANY_SELECT', 'WAITING_EDIT_COMPANY_NAME', 'WAITING_DELETE_COMPANY_SELECT', 'WAITING_ADD_COUPON_COMPANY', 'WAITING_ADD_COUPON_TEXT', 'WAITING_ADD_COUPON_VALUE', 'WAITING_EDIT_COUPON_SELECT', 'WAITING_EDIT_COUPON_TEXT', 'WAITING_DELETE_COUPON_SELECT'].includes($('Get User Session').item.json.state) && !($('Set Basic Data').item.json.button_id || '').startsWith('admin_') }}","value2":true}]}},"typeVersion":1},{"id":"6b51c752-a60b-4984-a78d-9e5a004b5779","name":"Add Company (Admin)","type":"n8n-nodes-base.postgres","position":[3504,5568],"parameters":{"query":"={{ \"INSERT INTO companies (name) VALUES ('\" + $('Set Basic Data').item.json.message_text.replace(/'/g, \"''\") + \"'); UPDATE sessions SET state = 'IDLE' WHERE phone = '\" + $('Set Basic Data').item.json.sender_phone + \"';\" }}","operation":"executeQuery","additionalFields":{}},"credentials":{"postgres":{"id":"credential-id","name":"discounts DB"}},"typeVersion":1,"alwaysOutputData":true},{"id":"bac44789-fc03-409a-88cd-9f4fa7a9d2a7","name":"Send Admin Success","type":"n8n-nodes-base.httpRequest","position":[3776,5632],"parameters":{"url":"=https://graph.facebook.com/v22.0/{{ $('Set Basic Data').item.json.phone_number_id }}/messages","method":"POST","options":{},"jsonBody":"={{ JSON.stringify({\n  messaging_product: \"whatsapp\",\n  to: $('Set Basic Data').item.json.sender_phone,\n  type: \"text\",\n  text: { body: \"Company added successfully!\" }\n}) }}","sendBody":true,"specifyBody":"json","authentication":"genericCredentialType","genericAuthType":"httpBearerAuth"},"credentials":{"httpBearerAuth":{"id":"credential-id","name":"whatsapp"}},"typeVersion":4},{"id":"790c1575-9dd9-41f7-9560-efe003612470","name":"Admin Choice","type":"n8n-nodes-base.switch","position":[2496,7888],"parameters":{"rules":{"values":[{"conditions":{"options":{"leftValue":"","typeFilter":"string","caseSensitive":true},"combinator":"and","conditions":[{"operator":{"type":"string","operation":"equals"},"leftValue":"={{ ($('Set Basic Data').item.json.button_id || '') }}","rightValue":"admin_add_company"}]}},{"conditions":{"options":{"leftValue":"","typeFilter":"string","caseSensitive":true},"combinator":"and","conditions":[{"operator":{"type":"string","operation":"equals"},"leftValue":"={{ ($('Set Basic Data').item.json.button_id || '') }}","rightValue":"admin_browse"}]}},{"conditions":{"options":{"leftValue":"","typeFilter":"string","caseSensitive":false},"combinator":"and","conditions":[{"operator":{"type":"boolean","operation":"equals"},"leftValue":"={{ ($('Set Basic Data').item.json.button_id || '').startsWith('company_') || ($('Set Basic Data').item.json.button_id || '').startsWith('coupon_') }}","rightValue":true}]}},{"conditions":{"options":{"leftValue":"","typeFilter":"string","caseSensitive":true},"combinator":"and","conditions":[{"operator":{"type":"string","operation":"equals"},"leftValue":"={{ ($('Set Basic Data').item.json.button_id || '') }}","rightValue":"admin_edit_company"}]}},{"conditions":{"options":{"leftValue":"","typeFilter":"string","caseSensitive":true},"combinator":"and","conditions":[{"operator":{"type":"string","operation":"equals"},"leftValue":"={{ ($('Set Basic Data').item.json.button_id || '') }}","rightValue":"admin_delete_company"}]}},{"conditions":{"options":{"leftValue":"","typeFilter":"string","caseSensitive":true},"combinator":"and","conditions":[{"operator":{"type":"string","operation":"equals"},"leftValue":"={{ ($('Set Basic Data').item.json.button_id || '') }}","rightValue":"admin_add_coupon"}]}},{"conditions":{"options":{"leftValue":"","typeFilter":"string","caseSensitive":true},"combinator":"and","conditions":[{"operator":{"type":"string","operation":"equals"},"leftValue":"={{ ($('Set Basic Data').item.json.button_id || '') }}","rightValue":"admin_edit_coupon"}]}},{"conditions":{"options":{"leftValue":"","typeFilter":"string","caseSensitive":true},"combinator":"and","conditions":[{"operator":{"type":"string","operation":"equals"},"leftValue":"={{ ($('Set Basic Data').item.json.button_id || '') }}","rightValue":"admin_delete_coupon"}]}},{"conditions":{"options":{"leftValue":"","typeFilter":"boolean","caseSensitive":true},"combinator":"and","conditions":[{"operator":{"type":"boolean","operation":"equals"},"leftValue":"={{ true }}","rightValue":true}]}},{"conditions":{"options":{"leftValue":"","typeFilter":"string","caseSensitive":true},"combinator":"and","conditions":[{"operator":{"type":"string","operation":"equals"},"leftValue":"={{ ($('Set Basic Data').item.json.button_id || '') }}","rightValue":"admin_edit_welcome"}]}},{"conditions":{"options":{"leftValue":"","typeFilter":"string","caseSensitive":true},"combinator":"and","conditions":[{"operator":{"type":"string","operation":"equals"},"leftValue":"={{ ($('Set Basic Data').item.json.button_id || '') }}","rightValue":"admin_edit_end"}]}}]},"options":{"fallbackOutput":false}},"typeVersion":3},{"id":"9c9eac2d-5220-43dc-912f-4cece6e97054","name":"Set Admin State (Add Co)","type":"n8n-nodes-base.postgres","position":[3168,7712],"parameters":{"query":"={{ \"INSERT INTO sessions (phone, state) VALUES ('\" + $('Set Basic Data').item.json.sender_phone + \"', 'WAITING_COMPANY_NAME') ON CONFLICT (phone) DO UPDATE SET state = 'WAITING_COMPANY_NAME';\" }}","operation":"executeQuery","additionalFields":{}},"credentials":{"postgres":{"id":"credential-id","name":"Postgres account 2"}},"typeVersion":1,"alwaysOutputData":true},{"id":"a6934aa6-3aab-4b51-bae7-5d5ddc9aa28d","name":"Ask Admin Input","type":"n8n-nodes-base.httpRequest","position":[3520,7776],"parameters":{"url":"=https://graph.facebook.com/v22.0/{{ $('Set Basic Data').item.json.phone_number_id }}/messages","method":"POST","options":{},"jsonBody":"={{ JSON.stringify({\n  messaging_product: \"whatsapp\",\n  to: $('Set Basic Data').item.json.sender_phone,\n  type: \"text\",\n  text: { body: \"Please enter the new company name:\" }\n}) }}","sendBody":true,"specifyBody":"json","authentication":"genericCredentialType","genericAuthType":"httpBearerAuth"},"credentials":{"httpBearerAuth":{"id":"credential-id","name":"whatsapp"}},"typeVersion":4},{"id":"3d6735d6-e004-41eb-90ff-2242ae129536","name":"Show Admin Menu","type":"n8n-nodes-base.httpRequest","position":[3424,8880],"parameters":{"url":"=https://graph.facebook.com/v22.0/{{ $('Set Basic Data').item.json.phone_number_id }}/messages","method":"POST","options":{},"jsonBody":"={{ JSON.stringify({\n  messaging_product: \"whatsapp\",\n  to: $('Set Basic Data').item.json.sender_phone,\n  type: \"interactive\",\n  interactive: {\n    type: \"list\",\n    body: { text: \"Welcome Admin. Choose from the menu:\" },\n    action: {\n      button: \"Menu\",\n      sections: [{\n        title: \"Company Management\",\n        rows: [\n          { id: \"admin_add_company\", title: \"Add Company\", description: \"Add a new company\" },\n          { id: \"admin_edit_company\", title: \"Edit Company\", description: \"Edit a company name\" },\n          { id: \"admin_delete_company\", title: \"Delete Company\", description: \"Delete a company from the system\" }\n        ]\n      },\n      {\n        title: \"Coupon Management\",\n        rows: [\n          { id: \"admin_add_coupon\", title: \"Add Coupon\", description: \"Add a coupon for a company\" },\n          { id: \"admin_edit_coupon\", title: \"Edit Coupon\", description: \"Edit an existing coupon\" },\n          { id: \"admin_delete_coupon\", title: \"Delete Coupon\", description: \"Delete a coupon from the system\" }\n        ]\n      },\n      {\n        title: \"Settings\",\n        rows: [\n          { id: \"admin_edit_welcome\", title: \"Edit Welcome Message\", description: \"Change start message\" },\n          { id: \"admin_edit_end\", title: \"Edit End Message\", description: \"Change end of service message\" }\n        ]\n      },\n      {\n        title: \"Other\",\n        rows: [\n          { id: \"admin_browse\", title: \"Browse as Member\", description: \"View coupons as a regular user\" }\n        ]\n      }]\n    }\n  }\n}) }}","sendBody":true,"specifyBody":"json","authentication":"genericCredentialType","genericAuthType":"httpBearerAuth"},"credentials":{"httpBearerAuth":{"id":"credential-id","name":"whatsapp"}},"typeVersion":4},{"id":"e155a6c9-6a37-40bc-8e3a-9a0f150fc7a8","name":"Log Sent Companies","type":"n8n-nodes-base.postgres","position":[4096,7840],"parameters":{"query":"={{ \"INSERT INTO messages (customer_id, sender_phone, sender_name, message_text, message_type, direction, created_at) VALUES (\" + ($('Upsert Customer').item.json.customer_id || 'NULL') + \", '\" + ($('Set Basic Data').item.json.sender_phone || '') + \"', '\" + ($('Set Basic Data').item.json.sender_name || 'Unknown').replace(/'/g, \"''\") + \"', '\" + ($items(\"Get Companies List\").length === 0 || ($items(\"Get Companies List\").length === 1 && !$items(\"Get Companies List\")[0].json.company_id) ? \"Sorry, no companies are currently available.\" : \"Welcome! Choose a company to view coupons:\").replace(/'/g, \"''\") + \"', 'text', 'OUTGOING', NOW());\" }}","operation":"executeQuery","additionalFields":{}},"credentials":{"postgres":{"id":"credential-id","name":"Postgres account 2"}},"typeVersion":1},{"id":"4dba4fc4-7d37-4dc2-be86-c81ed38cff15","name":"Log Sent Coupons","type":"n8n-nodes-base.postgres","position":[4560,7616],"parameters":{"query":"={{ \"INSERT INTO messages (customer_id, sender_phone, sender_name, message_text, message_type, direction, created_at) VALUES (\" + ($('Upsert Customer').item.json.customer_id || 'NULL') + \", '\" + ($('Set Basic Data').item.json.sender_phone || '') + \"', '\" + ($('Set Basic Data').item.json.sender_name || 'Unknown').replace(/'/g, \"''\") + \"', '\" + ($items(\"Get Coupons List\").length === 0 || ($items(\"Get Coupons List\").length === 1 && !$items(\"Get Coupons List\")[0].json.coupon_id) ? \"Sorry, no coupons are available for this company right now.\" : \"Choose a coupon:\").replace(/'/g, \"''\") + \"', 'text', 'OUTGOING', NOW());\" }}","operation":"executeQuery","additionalFields":{}},"credentials":{"postgres":{"id":"credential-id","name":"Postgres account 2"}},"typeVersion":1},{"id":"a6b6c5b7-52f4-49c7-95fb-549de69b0eb6","name":"Log Sent Details","type":"n8n-nodes-base.postgres","position":[4512,8208],"parameters":{"query":"={{ \"INSERT INTO messages (customer_id, sender_phone, sender_name, message_text, message_type, direction, created_at) VALUES (\" + ($('Upsert Customer').item.json.customer_id || 'NULL') + \", '\" + ($('Set Basic Data').item.json.sender_phone || '') + \"', '\" + ($('Set Basic Data').item.json.sender_name || 'Unknown').replace(/'/g, \"''\") + \"', '\" + (\"Coupon Details:\\nCode: \" + ($('Get Coupon Details').item.json.coupon_text || '') + \"\\nValue: \" + ($('Get Coupon Details').item.json.coupon_value || '') + ($('Get Coupon Details').item.json.is_percentage ? '%' : '') + \"\\n\" + ($('Get Coupon Details').item.json.other_info ? 'Notes: ' + $('Get Coupon Details').item.json.other_info : '') + \"\\n\\nThank you for choosing us!\").replace(/'/g, \"''\") + \"', 'text', 'OUTGOING', NOW());\" }}","operation":"executeQuery","additionalFields":{}},"credentials":{"postgres":{"id":"credential-id","name":"Postgres account 2"}},"typeVersion":1},{"id":"edda2b09-4919-45e4-8cfe-2fb2280a2eb1","name":"Log Admin Success","type":"n8n-nodes-base.postgres","position":[3776,5488],"parameters":{"query":"={{ \"INSERT INTO messages (customer_id, sender_phone, sender_name, message_text, message_type, direction, created_at) VALUES (\" + ($('Upsert Customer').item.json.customer_id || 'NULL') + \", '\" + ($('Set Basic Data').item.json.sender_phone || '') + \"', '\" + ($('Set Basic Data').item.json.sender_name || 'Unknown').replace(/'/g, \"''\") + \"', 'Company added successfully!', 'text', 'OUTGOING', NOW());\" }}","operation":"executeQuery","additionalFields":{}},"credentials":{"postgres":{"id":"credential-id","name":"discounts DB"}},"typeVersion":1},{"id":"51413e15-5780-412a-aaa0-21040abbdf44","name":"Log Admin Input","type":"n8n-nodes-base.postgres","position":[3520,7568],"parameters":{"query":"={{ \"INSERT INTO messages (customer_id, sender_phone, sender_name, message_text, message_type, direction, created_at) VALUES (\" + ($('Upsert Customer').item.json.customer_id || 'NULL') + \", '\" + ($('Set Basic Data').item.json.sender_phone || '') + \"', '\" + ($('Set Basic Data').item.json.sender_name || 'Unknown').replace(/'/g, \"''\") + \"', 'Please enter the new company name:', 'text', 'OUTGOING', NOW());\" }}","operation":"executeQuery","additionalFields":{}},"credentials":{"postgres":{"id":"credential-id","name":"Postgres account 2"}},"typeVersion":1},{"id":"f79743b0-18b9-464c-b2a0-1861863788b4","name":"Log Admin Menu","type":"n8n-nodes-base.postgres","position":[3440,8768],"parameters":{"query":"={{ \"INSERT INTO messages (customer_id, sender_phone, sender_name, message_text, message_type, direction, created_at) VALUES (\" + ($('Upsert Customer').item.json.customer_id || 'NULL') + \", '\" + ($('Set Basic Data').item.json.sender_phone || '') + \"', '\" + ($('Set Basic Data').item.json.sender_name || 'Unknown').replace(/'/g, \"''\") + \"', 'Welcome Admin. Menu:', 'text', 'OUTGOING', NOW());\" }}","operation":"executeQuery","additionalFields":{}},"credentials":{"postgres":{"id":"credential-id","name":"Postgres account 2"}},"typeVersion":1,"alwaysOutputData":true},{"id":"5535e0fc-51da-42c0-be5a-b2c32ada37ef","name":"Admin State Router","type":"n8n-nodes-base.switch","position":[2240,6992],"parameters":{"rules":{"values":[{"conditions":{"options":{"leftValue":"","typeFilter":"string","caseSensitive":true},"combinator":"and","conditions":[{"operator":{"type":"string","operation":"equals"},"leftValue":"={{ $('Get User Session').item.json.state }}","rightValue":"WAITING_COMPANY_NAME"}]}},{"conditions":{"options":{"leftValue":"","typeFilter":"string","caseSensitive":true},"combinator":"and","conditions":[{"operator":{"type":"string","operation":"equals"},"leftValue":"={{ $('Get User Session').item.json.state }}","rightValue":"WAITING_EDIT_COMPANY_SELECT"}]}},{"conditions":{"options":{"leftValue":"","typeFilter":"string","caseSensitive":true},"combinator":"and","conditions":[{"operator":{"type":"string","operation":"equals"},"leftValue":"={{ $('Get User Session').item.json.state }}","rightValue":"WAITING_EDIT_COMPANY_NAME"}]}},{"conditions":{"options":{"leftValue":"","typeFilter":"string","caseSensitive":true},"combinator":"and","conditions":[{"operator":{"type":"string","operation":"equals"},"leftValue":"={{ $('Get User Session').item.json.state }}","rightValue":"WAITING_DELETE_COMPANY_SELECT"}]}},{"conditions":{"options":{"leftValue":"","typeFilter":"string","caseSensitive":true},"combinator":"and","conditions":[{"operator":{"type":"string","operation":"equals"},"leftValue":"={{ $('Get User Session').item.json.state }}","rightValue":"WAITING_ADD_COUPON_COMPANY"}]}},{"conditions":{"options":{"leftValue":"","typeFilter":"string","caseSensitive":true},"combinator":"and","conditions":[{"operator":{"type":"string","operation":"equals"},"leftValue":"={{ $('Get User Session').item.json.state }}","rightValue":"WAITING_ADD_COUPON_TEXT"}]}},{"conditions":{"options":{"leftValue":"","typeFilter":"string","caseSensitive":true},"combinator":"and","conditions":[{"operator":{"type":"string","operation":"equals"},"leftValue":"={{ $('Get User Session').item.json.state }}","rightValue":"WAITING_ADD_COUPON_VALUE"}]}},{"conditions":{"options":{"leftValue":"","typeFilter":"string","caseSensitive":true},"combinator":"and","conditions":[{"operator":{"type":"string","operation":"equals"},"leftValue":"={{ $('Get User Session').item.json.state }}","rightValue":"WAITING_EDIT_COUPON_SELECT"}]}},{"conditions":{"options":{"leftValue":"","typeFilter":"string","caseSensitive":true},"combinator":"and","conditions":[{"operator":{"type":"string","operation":"equals"},"leftValue":"={{ $('Get User Session').item.json.state }}","rightValue":"WAITING_EDIT_COUPON_TEXT"}]}},{"conditions":{"options":{"leftValue":"","typeFilter":"string","caseSensitive":true},"combinator":"and","conditions":[{"operator":{"type":"string","operation":"equals"},"leftValue":"={{ $('Get User Session').item.json.state }}","rightValue":"WAITING_DELETE_COUPON_SELECT"}]}},{"conditions":{"options":{"leftValue":"","typeFilter":"string","caseSensitive":true},"combinator":"and","conditions":[{"operator":{"type":"string","operation":"equals"},"leftValue":"={{ $json.state }}","rightValue":"EDIT_WELCOME"}]}},{"conditions":{"options":{"leftValue":"","typeFilter":"string","caseSensitive":true},"combinator":"and","conditions":[{"operator":{"type":"string","operation":"equals"},"leftValue":"={{ $json.state }}","rightValue":"EDIT_END"}]}}]},"options":{"fallbackOutput":false}},"typeVersion":3},{"id":"8bad6268-c50f-4823-bb2b-b858cabd07ca","name":"Save Edited Company","type":"n8n-nodes-base.postgres","position":[3552,5952],"parameters":{"query":"={{ \"UPDATE companies SET name = '\" + $('Set Basic Data').item.json.message_text.replace(/'/g, \"''\") + \"' WHERE company_id = \" + ($('Get User Session').item.json.data?.company_id || 0) + \"; UPDATE sessions SET state = 'IDLE', data = '{}' WHERE phone = '\" + $('Set Basic Data').item.json.sender_phone + \"';\" }}","operation":"executeQuery","additionalFields":{}},"credentials":{"postgres":{"id":"credential-id","name":"discounts DB"}},"typeVersion":1,"alwaysOutputData":true},{"id":"a013cd2f-4a63-483c-8880-e4163ffe43fb","name":"Send Edit Company Success","type":"n8n-nodes-base.httpRequest","position":[3808,5952],"parameters":{"url":"=https://graph.facebook.com/v22.0/{{ $('Set Basic Data').item.json.phone_number_id }}/messages","method":"POST","options":{},"jsonBody":"={{ JSON.stringify({ messaging_product: \"whatsapp\", to: $('Set Basic Data').item.json.sender_phone, type: \"text\", text: { body: \"Company edited successfully!\" } }) }}","sendBody":true,"specifyBody":"json","authentication":"genericCredentialType","genericAuthType":"httpBearerAuth"},"credentials":{"httpBearerAuth":{"id":"credential-id","name":"whatsapp"}},"typeVersion":4},{"id":"fd7ca772-0ae2-4233-bee3-fc1b0e61fa59","name":"Delete Company Exec","type":"n8n-nodes-base.postgres","position":[3552,6144],"parameters":{"query":"={{ \"DELETE FROM companies WHERE company_id = \" + $('Set Basic Data').item.json.button_id.split('_')[1] + \"; UPDATE sessions SET state = 'IDLE', data = '{}' WHERE phone = '\" + $('Set Basic Data').item.json.sender_phone + \"';\" }}","operation":"executeQuery","additionalFields":{}},"credentials":{"postgres":{"id":"credential-id","name":"discounts DB"}},"typeVersion":1,"alwaysOutputData":true},{"id":"7c6f7509-dddc-4df0-b85f-b89ec8e28210","name":"Send Delete Company Success","type":"n8n-nodes-base.httpRequest","position":[3808,6128],"parameters":{"url":"=https://graph.facebook.com/v22.0/{{ $('Set Basic Data').item.json.phone_number_id }}/messages","method":"POST","options":{},"jsonBody":"={{ JSON.stringify({ messaging_product: \"whatsapp\", to: $('Set Basic Data').item.json.sender_phone, type: \"text\", text: { body: \"Company deleted successfully!\" } }) }}","sendBody":true,"specifyBody":"json","authentication":"genericCredentialType","genericAuthType":"httpBearerAuth"},"credentials":{"httpBearerAuth":{"id":"credential-id","name":"whatsapp"}},"typeVersion":4},{"id":"13bc5f77-8c4f-432c-b423-c781fd83b342","name":"Set Coupon Company","type":"n8n-nodes-base.postgres","position":[3568,6304],"parameters":{"query":"={{ \"UPDATE sessions SET state = 'WAITING_ADD_COUPON_TEXT', data = '{\\\"company_id\\\": \" + $('Set Basic Data').item.json.button_id.split('_')[1] + \"}' WHERE phone = '\" + $('Set Basic Data').item.json.sender_phone + \"';\" }}","operation":"executeQuery","additionalFields":{}},"credentials":{"postgres":{"id":"credential-id","name":"discounts DB"}},"typeVersion":1,"alwaysOutputData":true},{"id":"e2b6cb94-6041-43b4-bfa5-4a44fc719cf5","name":"Ask Coupon Text","type":"n8n-nodes-base.httpRequest","position":[3824,6304],"parameters":{"url":"=https://graph.facebook.com/v22.0/{{ $('Set Basic Data').item.json.phone_number_id }}/messages","method":"POST","options":{},"jsonBody":"={{ JSON.stringify({ messaging_product: \"whatsapp\", to: $('Set Basic Data').item.json.sender_phone, type: \"text\", text: { body: \"Enter the coupon code:\" } }) }}","sendBody":true,"specifyBody":"json","authentication":"genericCredentialType","genericAuthType":"httpBearerAuth"},"credentials":{"httpBearerAuth":{"id":"credential-id","name":"whatsapp"}},"typeVersion":4},{"id":"253aae5d-d688-478c-9b1e-7a5df2d5103e","name":"Save Coupon Text","type":"n8n-nodes-base.postgres","position":[3568,6464],"parameters":{"query":"={{ \"UPDATE sessions SET state = 'WAITING_ADD_COUPON_VALUE', data = data || '{\\\"coupon_text\\\": \\\"\" + $('Set Basic Data').item.json.message_text.replace(/\"/g, '\\\\\"').replace(/'/g, \"''\") + \"\\\"}' WHERE phone = '\" + $('Set Basic Data').item.json.sender_phone + \"';\" }}","operation":"executeQuery","additionalFields":{}},"credentials":{"postgres":{"id":"credential-id","name":"discounts DB"}},"typeVersion":1,"alwaysOutputData":true},{"id":"07495e0e-bc6e-45c1-a2aa-4147ac1febe3","name":"Ask Coupon Value","type":"n8n-nodes-base.httpRequest","position":[3840,6464],"parameters":{"url":"=https://graph.facebook.com/v22.0/{{ $('Set Basic Data').item.json.phone_number_id }}/messages","method":"POST","options":{},"jsonBody":"={{ JSON.stringify({ messaging_product: \"whatsapp\", to: $('Set Basic Data').item.json.sender_phone, type: \"text\", text: { body: \"Enter the discount value (number only):\\n\\nOr type \\\"skip\\\" if you don't know the value\" } }) }}","sendBody":true,"specifyBody":"json","authentication":"genericCredentialType","genericAuthType":"httpBearerAuth"},"credentials":{"httpBearerAuth":{"id":"credential-id","name":"whatsapp"}},"typeVersion":4},{"id":"986ca793-bf95-49a0-87a2-d791559893cd","name":"Insert New Coupon","type":"n8n-nodes-base.postgres","position":[3568,6640],"parameters":{"query":"={{ (function() { \n  var data = $('Get User Session').item.json.data || {}; \n  var companyId = data.company_id || 0; \n  var couponText = (data.coupon_text || '').replace(/'/g, \"''\"); \n  var messageText = $('Set Basic Data').item.json.message_text.trim();\n  var couponValue = null;\n  \n  // If the message is not \"skip\" or empty\n  if (messageText && messageText !== 'skip' && messageText !== 'skip') {\n    couponValue = parseInt(messageText) || null;\n  }\n  \n  var valueSQL = couponValue !== null ? couponValue : 'NULL';\n  \n  return \"INSERT INTO coupons (company_id, coupon_text, coupon_value) VALUES (\" + companyId + \", '\" + couponText + \"', \" + valueSQL + \"); UPDATE sessions SET state = 'IDLE', data = '{}' WHERE phone = '\" + $('Set Basic Data').item.json.sender_phone + \"';\"; \n})() }}","operation":"executeQuery","additionalFields":{}},"credentials":{"postgres":{"id":"credential-id","name":"discounts DB"}},"typeVersion":1,"alwaysOutputData":true},{"id":"d56037ad-c3c0-4a01-b1f6-772a14bd18a1","name":"Send Add Coupon Success","type":"n8n-nodes-base.httpRequest","position":[3840,6624],"parameters":{"url":"=https://graph.facebook.com/v22.0/{{ $('Set Basic Data').item.json.phone_number_id }}/messages","method":"POST","options":{},"jsonBody":"={{ JSON.stringify({ messaging_product: \"whatsapp\", to: $('Set Basic Data').item.json.sender_phone, type: \"text\", text: { body: \"Coupon added successfully!\" } }) }}","sendBody":true,"specifyBody":"json","authentication":"genericCredentialType","genericAuthType":"httpBearerAuth"},"credentials":{"httpBearerAuth":{"id":"credential-id","name":"whatsapp"}},"typeVersion":4},{"id":"6c4863de-5a72-4922-9e26-8ef4b24da7ba","name":"Set Edit Coupon Select","type":"n8n-nodes-base.postgres","position":[3584,6800],"parameters":{"query":"={{ \"UPDATE sessions SET state = 'WAITING_EDIT_COUPON_TEXT', data = '{\\\"coupon_id\\\": \" + $('Set Basic Data').item.json.button_id.split('_')[1] + \"}' WHERE phone = '\" + $('Set Basic Data').item.json.sender_phone + \"';\" }}","operation":"executeQuery","additionalFields":{}},"credentials":{"postgres":{"id":"credential-id","name":"discounts DB"}},"typeVersion":1,"alwaysOutputData":true},{"id":"d9197059-c085-4591-83e2-8eff20686d8f","name":"Ask Edit Coupon Text","type":"n8n-nodes-base.httpRequest","position":[3856,6816],"parameters":{"url":"=https://graph.facebook.com/v22.0/{{ $('Set Basic Data').item.json.phone_number_id }}/messages","method":"POST","options":{},"jsonBody":"={{ JSON.stringify({ messaging_product: \"whatsapp\", to: $('Set Basic Data').item.json.sender_phone, type: \"text\", text: { body: \"Enter the new coupon code:\" } }) }}","sendBody":true,"specifyBody":"json","authentication":"genericCredentialType","genericAuthType":"httpBearerAuth"},"credentials":{"httpBearerAuth":{"id":"credential-id","name":"whatsapp"}},"typeVersion":4},{"id":"bab41a96-b679-4b5c-853c-316e80b6dde0","name":"Save Edited Coupon","type":"n8n-nodes-base.postgres","position":[3584,6960],"parameters":{"query":"={{ \"UPDATE coupons SET coupon_text = '\" + $('Set Basic Data').item.json.message_text.replace(/'/g, \"''\") + \"' WHERE coupon_id = \" + ($('Get User Session').item.json.data?.coupon_id || 0) + \"; UPDATE sessions SET state = 'IDLE', data = '{}' WHERE phone = '\" + $('Set Basic Data').item.json.sender_phone + \"';\" }}","operation":"executeQuery","additionalFields":{}},"credentials":{"postgres":{"id":"credential-id","name":"discounts DB"}},"typeVersion":1,"alwaysOutputData":true},{"id":"651ee444-bd34-4fa0-a1f7-0d76e6ce6725","name":"Send Edit Coupon Success","type":"n8n-nodes-base.httpRequest","position":[3856,6976],"parameters":{"url":"=https://graph.facebook.com/v22.0/{{ $('Set Basic Data').item.json.phone_number_id }}/messages","method":"POST","options":{},"jsonBody":"={{ JSON.stringify({ messaging_product: \"whatsapp\", to: $('Set Basic Data').item.json.sender_phone, type: \"text\", text: { body: \"Coupon edited successfully!\" } }) }}","sendBody":true,"specifyBody":"json","authentication":"genericCredentialType","genericAuthType":"httpBearerAuth"},"credentials":{"httpBearerAuth":{"id":"credential-id","name":"whatsapp"}},"typeVersion":4},{"id":"13a27e1e-307b-4f55-9068-5069329d5b9d","name":"Delete Coupon Exec","type":"n8n-nodes-base.postgres","position":[3584,7136],"parameters":{"query":"={{ \"DELETE FROM coupons WHERE coupon_id = \" + $('Set Basic Data').item.json.button_id.split('_')[1] + \"; UPDATE sessions SET state = 'IDLE', data = '{}' WHERE phone = '\" + $('Set Basic Data').item.json.sender_phone + \"';\" }}","operation":"executeQuery","additionalFields":{}},"credentials":{"postgres":{"id":"credential-id","name":"discounts DB"}},"typeVersion":1,"alwaysOutputData":true},{"id":"9a4114c7-33fb-4fd0-85f0-b71ad347121d","name":"Send Delete Coupon Success","type":"n8n-nodes-base.httpRequest","position":[3872,7120],"parameters":{"url":"=https://graph.facebook.com/v22.0/{{ $('Set Basic Data').item.json.phone_number_id }}/messages","method":"POST","options":{},"jsonBody":"={{ JSON.stringify({ messaging_product: \"whatsapp\", to: $('Set Basic Data').item.json.sender_phone, type: \"text\", text: { body: \"Coupon deleted successfully!\" } }) }}","sendBody":true,"specifyBody":"json","authentication":"genericCredentialType","genericAuthType":"httpBearerAuth"},"credentials":{"httpBearerAuth":{"id":"credential-id","name":"whatsapp"}},"typeVersion":4},{"id":"aa88c7aa-ddd3-4b78-86d7-77e477dc84ba","name":"Set Edit Company State","type":"n8n-nodes-base.postgres","position":[3552,5776],"parameters":{"query":"={{ \"UPDATE sessions SET state = 'WAITING_EDIT_COMPANY_NAME', data = '{\\\"company_id\\\": \" + $('Set Basic Data').item.json.button_id.split('_')[1] + \"}' WHERE phone = '\" + $('Set Basic Data').item.json.sender_phone + \"';\" }}","operation":"executeQuery","additionalFields":{}},"credentials":{"postgres":{"id":"credential-id","name":"discounts DB"}},"typeVersion":1,"alwaysOutputData":true},{"id":"c3cd639f-ebda-40c1-831e-359e68fdb00d","name":"Ask Edit Company Name","type":"n8n-nodes-base.httpRequest","position":[3792,5776],"parameters":{"url":"=https://graph.facebook.com/v22.0/{{ $('Set Basic Data').item.json.phone_number_id }}/messages","method":"POST","options":{},"jsonBody":"={{ JSON.stringify({ messaging_product: \"whatsapp\", to: $('Set Basic Data').item.json.sender_phone, type: \"text\", text: { body: \"Enter the new company name:\" } }) }}","sendBody":true,"specifyBody":"json","authentication":"genericCredentialType","genericAuthType":"httpBearerAuth"},"credentials":{"httpBearerAuth":{"id":"credential-id","name":"whatsapp"}},"typeVersion":4},{"id":"d5a1c9da-9d6e-4c33-9203-2e52d14115af","name":"Get Companies For Edit","type":"n8n-nodes-base.postgres","position":[3376,8128],"parameters":{"query":"SELECT * FROM companies ORDER BY company_id DESC LIMIT 10;","operation":"executeQuery","additionalFields":{}},"credentials":{"postgres":{"id":"credential-id","name":"Postgres account 2"}},"typeVersion":1,"alwaysOutputData":true},{"id":"b526455b-92e3-4bd0-b2b6-38f26857a622","name":"Send Edit Companies List","type":"n8n-nodes-base.httpRequest","position":[3616,8128],"parameters":{"url":"=https://graph.facebook.com/v22.0/{{ $('Set Basic Data').first().json.phone_number_id }}/messages","method":"POST","options":{},"jsonBody":"={{ (function() {\n  const companies = $items();\n  if (!companies || companies.length === 0 || !companies[0].json.company_id) {\n    return JSON.stringify({ messaging_product: \"whatsapp\", to: $('Set Basic Data').first().json.sender_phone, type: \"text\", text: { body: \"No companies to edit.\" } });\n  }\n  const rows = companies.filter(c => c.json.company_id).slice(0, 10).map(c => ({\n    id: 'editco_' + c.json.company_id,\n    title: (c.json.name || 'Company').substring(0, 24)\n  }));\n  return JSON.stringify({\n    messaging_product: \"whatsapp\",\n    to: $('Set Basic Data').first().json.sender_phone,\n    type: \"interactive\",\n    interactive: {\n      type: \"list\",\n      body: { text: \"Choose the company to edit:\" },\n      action: { button: \"Companies\", sections: [{ title: \"Companies\", rows: rows }] }\n    }\n  });\n})() }}","sendBody":true,"specifyBody":"json","authentication":"genericCredentialType","genericAuthType":"httpBearerAuth"},"credentials":{"httpBearerAuth":{"id":"credential-id","name":"whatsapp"}},"executeOnce":true,"typeVersion":4},{"id":"62d62b65-94bd-4a9c-a33c-0a5e88de8483","name":"Get Companies For Delete","type":"n8n-nodes-base.postgres","position":[3360,8288],"parameters":{"query":"SELECT * FROM companies ORDER BY company_id DESC LIMIT 10;","operation":"executeQuery","additionalFields":{}},"credentials":{"postgres":{"id":"credential-id","name":"Postgres account 2"}},"typeVersion":1,"alwaysOutputData":true},{"id":"c1f3a323-efab-41cb-9287-d3b9aa752222","name":"Send Delete Companies List","type":"n8n-nodes-base.httpRequest","position":[3600,8288],"parameters":{"url":"=https://graph.facebook.com/v22.0/{{ $('Set Basic Data').first().json.phone_number_id }}/messages","method":"POST","options":{},"jsonBody":"={{ (function() {\n  const companies = $items();\n  if (!companies || companies.length === 0 || !companies[0].json.company_id) {\n    return JSON.stringify({ messaging_product: \"whatsapp\", to: $('Set Basic Data').first().json.sender_phone, type: \"text\", text: { body: \"No companies to delete.\" } });\n  }\n  const rows = companies.filter(c => c.json.company_id).slice(0, 10).map(c => ({\n    id: 'delco_' + c.json.company_id,\n    title: (c.json.name || 'Company').substring(0, 24)\n  }));\n  return JSON.stringify({\n    messaging_product: \"whatsapp\",\n    to: $('Set Basic Data').first().json.sender_phone,\n    type: \"interactive\",\n    interactive: {\n      type: \"list\",\n      body: { text: \"Choose the company to delete:\" },\n      action: { button: \"Companies\", sections: [{ title: \"Companies\", rows: rows }] }\n    }\n  });\n})() }}","sendBody":true,"specifyBody":"json","authentication":"genericCredentialType","genericAuthType":"httpBearerAuth"},"credentials":{"httpBearerAuth":{"id":"credential-id","name":"whatsapp"}},"executeOnce":true,"typeVersion":4},{"id":"4cd30ff8-03f5-4bd0-be5b-3e8175b0d925","name":"Get Companies For Add Coupon","type":"n8n-nodes-base.postgres","position":[3392,7920],"parameters":{"query":"SELECT * FROM companies ORDER BY company_id DESC LIMIT 10;","operation":"executeQuery","additionalFields":{}},"credentials":{"postgres":{"id":"credential-id","name":"Postgres account 2"}},"typeVersion":1,"alwaysOutputData":true},{"id":"f13a5376-87f2-4c58-880b-1f44c3bce843","name":"Send Add Coupon Companies","type":"n8n-nodes-base.httpRequest","position":[3632,7904],"parameters":{"url":"=https://graph.facebook.com/v22.0/{{ $('Set Basic Data').first().json.phone_number_id }}/messages","method":"POST","options":{},"jsonBody":"={{ (function() {\n  const companies = $items();\n  if (!companies || companies.length === 0 || !companies[0].json.company_id) {\n    return JSON.stringify({ messaging_product: \"whatsapp\", to: $('Set Basic Data').first().json.sender_phone, type: \"text\", text: { body: \"No companies. Add a company first.\" } });\n  }\n  const rows = companies.filter(c => c.json.company_id).slice(0, 10).map(c => ({\n    id: 'addcpn_' + c.json.company_id,\n    title: (c.json.name || 'Company').substring(0, 24)\n  }));\n  return JSON.stringify({\n    messaging_product: \"whatsapp\",\n    to: $('Set Basic Data').first().json.sender_phone,\n    type: \"interactive\",\n    interactive: {\n      type: \"list\",\n      body: { text: \"Choose the company to add a coupon:\" },\n      action: { button: \"Companies\", sections: [{ title: \"Companies\", rows: rows }] }\n    }\n  });\n})() }}","sendBody":true,"specifyBody":"json","authentication":"genericCredentialType","genericAuthType":"httpBearerAuth"},"credentials":{"httpBearerAuth":{"id":"credential-id","name":"whatsapp"}},"executeOnce":true,"typeVersion":4},{"id":"34d58e84-b928-4c6d-a76d-b86d8daaa75c","name":"Get Coupons For Edit","type":"n8n-nodes-base.postgres","position":[3344,8448],"parameters":{"query":"SELECT c.*, co.name as company_name FROM coupons c LEFT JOIN companies co ON c.company_id = co.company_id ORDER BY c.coupon_id DESC LIMIT 10;","operation":"executeQuery","additionalFields":{}},"credentials":{"postgres":{"id":"credential-id","name":"Postgres account 2"}},"typeVersion":1,"alwaysOutputData":true},{"id":"ba5c3fc0-9e4a-4726-a5fc-01a3efd33a48","name":"Send Edit Coupons List","type":"n8n-nodes-base.httpRequest","position":[3584,8448],"parameters":{"url":"=https://graph.facebook.com/v22.0/{{ $('Set Basic Data').first().json.phone_number_id }}/messages","method":"POST","options":{},"jsonBody":"={{ (function() {\n  const coupons = $items();\n  if (!coupons || coupons.length === 0 || !coupons[0].json.coupon_id) {\n    return JSON.stringify({ messaging_product: \"whatsapp\", to: $('Set Basic Data').first().json.sender_phone, type: \"text\", text: { body: \"No coupons to edit.\" } });\n  }\n  const rows = coupons.filter(c => c.json.coupon_id).slice(0, 10).map(c => ({\n    id: 'editcpn_' + c.json.coupon_id,\n    title: (c.json.coupon_text || 'Coupon').substring(0, 24),\n    description: (c.json.company_name || '').substring(0, 72)\n  }));\n  return JSON.stringify({\n    messaging_product: \"whatsapp\",\n    to: $('Set Basic Data').first().json.sender_phone,\n    type: \"interactive\",\n    interactive: {\n      type: \"list\",\n      body: { text: \"Choose the coupon to edit:\" },\n      action: { button: \"Coupons\", sections: [{ title: \"Coupons\", rows: rows }] }\n    }\n  });\n})() }}","sendBody":true,"specifyBody":"json","authentication":"genericCredentialType","genericAuthType":"httpBearerAuth"},"credentials":{"httpBearerAuth":{"id":"credential-id","name":"whatsapp"}},"executeOnce":true,"typeVersion":4},{"id":"6e41f752-9d19-4404-bfad-6cfbd941c330","name":"Get Coupons For Delete","type":"n8n-nodes-base.postgres","position":[3360,8592],"parameters":{"query":"SELECT c.*, co.name as company_name FROM coupons c LEFT JOIN companies co ON c.company_id = co.company_id ORDER BY c.coupon_id DESC LIMIT 10;","operation":"executeQuery","additionalFields":{}},"credentials":{"postgres":{"id":"credential-id","name":"Postgres account 2"}},"typeVersion":1,"alwaysOutputData":true},{"id":"a1e8290d-b6dd-4ab9-a2b0-a1e9ccb0f4d4","name":"Send Delete Coupons List","type":"n8n-nodes-base.httpRequest","position":[3600,8592],"parameters":{"url":"=https://graph.facebook.com/v22.0/{{ $('Set Basic Data').first().json.phone_number_id }}/messages","method":"POST","options":{},"jsonBody":"={{ (function() {\n  const coupons = $items();\n  if (!coupons || coupons.length === 0 || !coupons[0].json.coupon_id) {\n    return JSON.stringify({ messaging_product: \"whatsapp\", to: $('Set Basic Data').first().json.sender_phone, type: \"text\", text: { body: \"No coupons to delete.\" } });\n  }\n  const rows = coupons.filter(c => c.json.coupon_id).slice(0, 10).map(c => ({\n    id: 'delcpn_' + c.json.coupon_id,\n    title: (c.json.coupon_text || 'Coupon').substring(0, 24),\n    description: (c.json.company_name || '').substring(0, 72)\n  }));\n  return JSON.stringify({\n    messaging_product: \"whatsapp\",\n    to: $('Set Basic Data').first().json.sender_phone,\n    type: \"interactive\",\n    interactive: {\n      type: \"list\",\n      body: { text: \"Choose the coupon to delete:\" },\n      action: { button: \"Coupons\", sections: [{ title: \"Coupons\", rows: rows }] }\n    }\n  });\n})() }}","sendBody":true,"specifyBody":"json","authentication":"genericCredentialType","genericAuthType":"httpBearerAuth"},"credentials":{"httpBearerAuth":{"id":"credential-id","name":"whatsapp"}},"executeOnce":true,"typeVersion":4},{"id":"183d0b63-f521-49ba-9e7e-60bb9d3afbfa","name":"Set Edit Company List State","type":"n8n-nodes-base.postgres","position":[3168,8112],"parameters":{"query":"={{ \"INSERT INTO sessions (phone, state) VALUES ('\" + $('Set Basic Data').item.json.sender_phone + \"', 'WAITING_EDIT_COMPANY_SELECT') ON CONFLICT (phone) DO UPDATE SET state = 'WAITING_EDIT_COMPANY_SELECT';\" }}","operation":"executeQuery","additionalFields":{}},"credentials":{"postgres":{"id":"credential-id","name":"Postgres account 2"}},"typeVersion":1,"alwaysOutputData":true},{"id":"d798fa98-626c-4741-b340-69bc3d746ba9","name":"Set Delete Company List State","type":"n8n-nodes-base.postgres","position":[3168,8288],"parameters":{"query":"={{ \"INSERT INTO sessions (phone, state) VALUES ('\" + $('Set Basic Data').item.json.sender_phone + \"', 'WAITING_DELETE_COMPANY_SELECT') ON CONFLICT (phone) DO UPDATE SET state = 'WAITING_DELETE_COMPANY_SELECT';\" }}","operation":"executeQuery","additionalFields":{}},"credentials":{"postgres":{"id":"credential-id","name":"Postgres account 2"}},"typeVersion":1,"alwaysOutputData":true},{"id":"d8a73ae6-8b73-40ab-b69a-d12d076894ec","name":"Set Add Coupon State","type":"n8n-nodes-base.postgres","position":[3168,7920],"parameters":{"query":"={{ \"INSERT INTO sessions (phone, state) VALUES ('\" + $('Set Basic Data').item.json.sender_phone + \"', 'WAITING_ADD_COUPON_COMPANY') ON CONFLICT (phone) DO UPDATE SET state = 'WAITING_ADD_COUPON_COMPANY';\" }}","operation":"executeQuery","additionalFields":{}},"credentials":{"postgres":{"id":"credential-id","name":"Postgres account 2"}},"typeVersion":1,"alwaysOutputData":true},{"id":"bd6ea451-c4c1-447a-a355-3b913e9bfb45","name":"Set Edit Coupon State","type":"n8n-nodes-base.postgres","position":[3152,8448],"parameters":{"query":"={{ \"INSERT INTO sessions (phone, state) VALUES ('\" + $('Set Basic Data').item.json.sender_phone + \"', 'WAITING_EDIT_COUPON_SELECT') ON CONFLICT (phone) DO UPDATE SET state = 'WAITING_EDIT_COUPON_SELECT';\" }}","operation":"executeQuery","additionalFields":{}},"credentials":{"postgres":{"id":"credential-id","name":"Postgres account 2"}},"typeVersion":1,"alwaysOutputData":true},{"id":"66173b93-1f48-492c-8281-029e65ec4648","name":"Set Delete Coupon State","type":"n8n-nodes-base.postgres","position":[3168,8592],"parameters":{"query":"={{ \"INSERT INTO sessions (phone, state) VALUES ('\" + $('Set Basic Data').item.json.sender_phone + \"', 'WAITING_DELETE_COUPON_SELECT') ON CONFLICT (phone) DO UPDATE SET state = 'WAITING_DELETE_COUPON_SELECT';\" }}","operation":"executeQuery","additionalFields":{}},"credentials":{"postgres":{"id":"credential-id","name":"Postgres account 2"}},"typeVersion":1,"alwaysOutputData":true},{"id":"b54e0af6-913f-43f0-acea-5a48b2810a85","name":"Check If Single Coupon","type":"n8n-nodes-base.if","position":[4448,7824],"parameters":{"conditions":{"number":[{"value1":"={{ $items().length }}","value2":1,"operation":"equal"}]}},"typeVersion":1,"alwaysOutputData":false},{"id":"006b3867-aa1b-4af2-a1c8-bb0a3021b6b0","name":"Get Welcome & End Messages","type":"n8n-nodes-base.postgres","position":[3888,7424],"parameters":{"query":"SELECT key, value FROM settings WHERE key IN ('welcome_message', 'end_message');","operation":"executeQuery","additionalFields":{}},"credentials":{"postgres":{"id":"credential-id","name":"Postgres account 2"}},"typeVersion":1,"alwaysOutputData":true},{"id":"3b2d74a1-deb4-45a5-bf18-d847e1d1218b","name":"Get End Message For Coupon","type":"n8n-nodes-base.postgres","position":[4800,8304],"parameters":{"query":"SELECT value FROM settings WHERE key = 'end_message';","operation":"executeQuery","additionalFields":{}},"credentials":{"postgres":{"id":"credential-id","name":"Postgres account 2"}},"typeVersion":1,"alwaysOutputData":true},{"id":"d1bd3d06-e1f4-4415-a699-12dd9901d061","name":"Set Edit Welcome State","type":"n8n-nodes-base.postgres","position":[2752,6160],"parameters":{"query":"={{ \"UPDATE sessions SET state = 'EDIT_WELCOME', data = '{}' WHERE phone = '\" + $('Set Basic Data').item.json.sender_phone + \"';\" }}","operation":"executeQuery","additionalFields":{}},"credentials":{"postgres":{"id":"credential-id","name":"discounts DB"}},"typeVersion":1,"alwaysOutputData":true},{"id":"117bca63-609b-465e-bc1a-ed43f1e5cd6d","name":"Ask Welcome Message","type":"n8n-nodes-base.httpRequest","position":[2928,6160],"parameters":{"url":"=https://graph.facebook.com/v22.0/{{ $('Set Basic Data').first().json.phone_number_id }}/messages","method":"POST","options":{},"jsonBody":"={{ JSON.stringify({ \n  messaging_product: \"whatsapp\", \n  to: $('Set Basic Data').item.json.sender_phone, \n  type: \"text\", \n  text: { body: \"Enter the new welcome message:\" } \n}) }}","sendBody":true,"specifyBody":"json","authentication":"genericCredentialType","genericAuthType":"httpBearerAuth"},"credentials":{"httpBearerAuth":{"id":"credential-id","name":"whatsapp"}},"executeOnce":true,"typeVersion":4},{"id":"0d373d30-a5c9-41ca-b64d-4346ca83d00c","name":"Save Welcome Message","type":"n8n-nodes-base.postgres","position":[3104,6400],"parameters":{"query":"={{ \"INSERT INTO settings (key, value) VALUES ('welcome_message', '\" + $('Set Basic Data').item.json.message_text.replace(/'/g, \"''\") + \"') ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value; UPDATE sessions SET state = 'IDLE', data = '{}' WHERE phone = '\" + $('Set Basic Data').item.json.sender_phone + \"';\" }}","operation":"executeQuery","additionalFields":{}},"credentials":{"postgres":{"id":"credential-id","name":"discounts DB"}},"typeVersion":1,"alwaysOutputData":true},{"id":"e3a62d9f-b200-4639-872c-eb4a75364624","name":"Send Welcome Success","type":"n8n-nodes-base.httpRequest","position":[3280,6400],"parameters":{"url":"=https://graph.facebook.com/v22.0/{{ $('Set Basic Data').first().json.phone_number_id }}/messages","method":"POST","options":{},"jsonBody":"={{ JSON.stringify({ \n  messaging_product: \"whatsapp\", \n  to: $('Set Basic Data').item.json.sender_phone, \n  type: \"text\", \n  text: { body: \"✅ Welcome message updated successfully!\" } \n}) }}","sendBody":true,"specifyBody":"json","authentication":"genericCredentialType","genericAuthType":"httpBearerAuth"},"credentials":{"httpBearerAuth":{"id":"credential-id","name":"whatsapp"}},"executeOnce":true,"typeVersion":4},{"id":"b39d9982-fa9c-4c18-9282-143cbf696232","name":"Set Edit End State","type":"n8n-nodes-base.postgres","position":[2752,6496],"parameters":{"query":"={{ \"UPDATE sessions SET state = 'EDIT_END', data = '{}' WHERE phone = '\" + $('Set Basic Data').item.json.sender_phone + \"';\" }}","operation":"executeQuery","additionalFields":{}},"credentials":{"postgres":{"id":"credential-id","name":"discounts DB"}},"typeVersion":1,"alwaysOutputData":true},{"id":"a53a7a88-2c03-4ba4-a3b9-cf5eab8d8273","name":"Ask End Message","type":"n8n-nodes-base.httpRequest","position":[2928,6496],"parameters":{"url":"=https://graph.facebook.com/v22.0/{{ $('Set Basic Data').first().json.phone_number_id }}/messages","method":"POST","options":{},"jsonBody":"={{ JSON.stringify({ \n  messaging_product: \"whatsapp\", \n  to: $('Set Basic Data').item.json.sender_phone, \n  type: \"text\", \n  text: { body: \"Enter the new end message:\" } \n}) }}","sendBody":true,"specifyBody":"json","authentication":"genericCredentialType","genericAuthType":"httpBearerAuth"},"credentials":{"httpBearerAuth":{"id":"credential-id","name":"whatsapp"}},"executeOnce":true,"typeVersion":4},{"id":"a17c9215-6f3d-4a04-a081-12e1bedf909c","name":"Save End Message","type":"n8n-nodes-base.postgres","position":[3104,6736],"parameters":{"query":"={{ \"INSERT INTO settings (key, value) VALUES ('end_message', '\" + $('Set Basic Data').item.json.message_text.replace(/'/g, \"''\") + \"') ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value; UPDATE sessions SET state = 'IDLE', data = '{}' WHERE phone = '\" + $('Set Basic Data').item.json.sender_phone + \"';\" }}","operation":"executeQuery","additionalFields":{}},"credentials":{"postgres":{"id":"credential-id","name":"discounts DB"}},"typeVersion":1,"alwaysOutputData":true},{"id":"94a4a03b-2aab-465d-bfe0-b8ad2e36da12","name":"Send End Success","type":"n8n-nodes-base.httpRequest","position":[3280,6736],"parameters":{"url":"=https://graph.facebook.com/v22.0/{{ $('Set Basic Data').first().json.phone_number_id }}/messages","method":"POST","options":{},"jsonBody":"={{ JSON.stringify({ \n  messaging_product: \"whatsapp\", \n  to: $('Set Basic Data').item.json.sender_phone, \n  type: \"text\", \n  text: { body: \"✅ End message updated successfully!\" } \n}) }}","sendBody":true,"specifyBody":"json","authentication":"genericCredentialType","genericAuthType":"httpBearerAuth"},"credentials":{"httpBearerAuth":{"id":"credential-id","name":"whatsapp"}},"executeOnce":true,"typeVersion":4},{"id":"4e1269af-a96f-45f7-a3fa-b4aba7f833f1","name":"Sticky Note","type":"n8n-nodes-base.stickyNote","position":[-3824,6112],"parameters":{"width":544,"height":512,"content":"# 🎯 Coupon Bot Dashboard\n\nA complete admin dashboard for managing discount coupons, \ncompanies, and customer conversations via WhatsApp/Telegram.\n\n### How it works:\n1. Dashboard UI served via webhook endpoint\n2. REST API for CRUD operations (Companies, Coupons, Settings)\n3. PostgreSQL database for data persistence\n4. Real-time inbox for customer chat history\n\n### Setup steps:\n1. Configure PostgreSQL credentials (discounts DB)\n2. Run /execute-init to create database schema\n3. Access dashboard at /coupon-bot/dashboard\n4. Set admin phone in Settings tab\n\n⚠️ Run /migrate first if upgrading existing database\n\n"},"typeVersion":1},{"id":"1c6d239c-567d-49cc-950e-9644bc5932df","name":"Sticky Note1","type":"n8n-nodes-base.stickyNote","position":[-3136,6352],"parameters":{"color":7,"width":1008,"height":272,"content":"📊 Dashboard UI\n- Vue.js 3 + Tailwind CSS interface\n- English LTR layout\n- Responsive mobile design\n- Served via RespondToWebhook node"},"typeVersion":1},{"id":"02e7c3a8-031a-42cd-a9bf-e371a2889f19","name":"Sticky Note2","type":"n8n-nodes-base.stickyNote","position":[-3056,6640],"parameters":{"color":5,"width":1008,"height":320,"content":"🗄️ Database Schema\n- Customers, Companies, Coupons tables\n- Messages & Records tracking\n- Settings & Sessions management\n- Migration support for updates"},"typeVersion":1},{"id":"cf0b90f2-aae6-45e2-a23a-75250e57248a","name":"Sticky Note3","type":"n8n-nodes-base.stickyNote","position":[-3024,6992],"parameters":{"color":7,"width":1072,"height":384,"content":"🔌 REST API Routes\n- GET/POST /api/companies\n- GET/POST /api/coupons  \n- GET/POST /api/settings\n- GET /api/stats, /api/inbox, /api/history"},"typeVersion":1},{"id":"f2835cb2-7ca4-4a5d-aecd-c0c42db05578","name":"Sticky Note4","type":"n8n-nodes-base.stickyNote","position":[-3008,7392],"parameters":{"color":5,"width":1056,"height":384,"content":"⚙️ System Tools\n- Database initialization (/execute-init)\n- Schema migration & repair (/migrate)\n- Full reset capability\n- Settings management"},"typeVersion":1},{"id":"b9fee30c-5dd9-4d25-beb7-94ef5d5e199a","name":"Sticky Note5","type":"n8n-nodes-base.stickyNote","position":[-3264,7792],"parameters":{"color":7,"width":1328,"height":544,"content":"💬 Customer Inbox System\n- Real-time chat interface\n- Message history tracking\n- Customer conversation threads\n- WhatsApp/Telegram integration ready\n- Webhook endpoints: /api/inbox, /api/history"},"typeVersion":1},{"id":"5fda487c-65ed-43c8-877e-46cb775da390","name":"Sticky Note6","type":"n8n-nodes-base.stickyNote","position":[-3248,8352],"parameters":{"color":5,"width":1328,"height":544,"content":"👥 Customer Management\n- Customer profiles and phone tracking\n- Coupon usage records\n- Usage analytics and history\n- API endpoints: /api/customers, /api/records"},"typeVersion":1},{"id":"39e2809e-8bc8-497e-948e-79a4421f7f30","name":"Sticky Note8","type":"n8n-nodes-base.stickyNote","position":[-1104,7120],"parameters":{"width":496,"height":1872,"content":"# 🤖 WhatsApp Coupon Bot\n\nComplete WhatsApp Business API integration for the Coupon Dashboard. Handles customer conversations and admin management via WhatsApp.\n\n---\n# 🔄 Session-Based State Management\n\nThe bot uses PostgreSQL sessions table to track conversations across multiple messages.\n\nHow it works:\n• Each phone number has one active session\n• State column tracks current operation (IDLE, WAITING_COMPANY_NAME, etc.)\n• Data column stores temporary info as JSON\n• Sessions persist until operation completes\n\nCommon states:\n• IDLE - default state, no active operation\n• WAITING_COMPANY_NAME - adding new company\n• WAITING_ADD_COUPON_TEXT - entering coupon code\n• WAITING_ADD_COUPON_VALUE - entering discount value\n• WAITING_EDIT_COMPANY_SELECT - selecting company to edit\n• EDIT_WELCOME / EDIT_END - customizing messages\n\nBest practice: Always reset to IDLE after operation completes and clear data field.\n## 📋 How It Works\n\n### 1. Webhook Reception\n- Receives messages from WhatsApp Business API\n- Validates message type (text, button, interactive)\n- Extracts sender info and message content\n\n### 2. User Authentication\n- Checks if sender is admin (matches `admin_phone` in settings)\n- **Regular users** → Customer flow\n- **Admin users** → Admin management flow\n\n## 🔧 Setup Requirements\n\n### 1. WhatsApp Business API Credentials\n- Phone Number ID\n- Access Token (Bearer Auth)\n\n### 2. PostgreSQL Database\n- Same DB as Dashboard\n- Tables: `customers`, `companies`, `coupons`, `messages`, `records`, `sessions`, `settings`\n\n### 3. Webhook Configuration\n- Endpoint: `/webhook/whatsapp`\n- Verify token configured in Meta\n\n### 4. Admin Setup\n- Set `admin_phone` in dashboard settings\n- Admin gets full management menu\n\n---\n\n## 🌐 API Endpoints Used\n- **Facebook Graph API v22.0** for sending messages\n- **Interactive messages** (lists, buttons)\n- **Text messages** with custom content\n\n"},"typeVersion":1},{"id":"68e78c25-3ff3-4146-957c-6f9251cc9392","name":"Sticky Note9","type":"n8n-nodes-base.stickyNote","position":[1136,7344],"parameters":{"color":7,"width":496,"height":656,"content":"# 🔄 Session-Based State Management\n\nThe bot uses PostgreSQL sessions table to track conversations across multiple messages.\n\nHow it works:\n• Each phone number has one active session\n• State column tracks current operation (IDLE, WAITING_COMPANY_NAME, etc.)\n• Data column stores temporary info as JSON\n• Sessions persist until operation completes\n\nCommon states:\n• IDLE - default state, no active operation\n• WAITING_COMPANY_NAME - adding new company\n• WAITING_ADD_COUPON_TEXT - entering coupon code\n• WAITING_ADD_COUPON_VALUE - entering discount value\n• WAITING_EDIT_COMPANY_SELECT - selecting company to edit\n• EDIT_WELCOME / EDIT_END - customizing messages\n\nBest practice: Always reset to IDLE after operation completes and clear data field."},"typeVersion":1},{"id":"e40ec871-1a86-4e4d-8454-7eb9b2c4019a","name":"Sticky Note10","type":"n8n-nodes-base.stickyNote","position":[2288,8656],"parameters":{"color":7,"width":496,"height":704,"content":"# 🔌 WhatsApp API Integration & User Routing\n\nMessage Types:\n• Text - simple replies and confirmations (4096 chars max)\n• Interactive List - company/coupon selection (10 items max)\n• Button Reply - quick actions (3 buttons max)\n\nUser Detection:\n• Compares sender phone with admin_phone setting\n• Routes to Admin Path or Customer Path automatically\n\nCustomer Path:\nShow welcome → List companies → Select company → View coupons → Send details\n\nAdmin Path:\nShow menu → Manage companies/coupons → Edit messages → Test as customer\n\nAPI Endpoint:\nPOST https://graph.facebook.com/v22.0/{phone_number_id}/messages\nAuthentication: Bearer Token\n\nCommon issues:\n• Message not delivered: check phone format with country code\n• List not showing: verify max 10 items and valid JSON"},"typeVersion":1},{"id":"920d663c-928b-44c2-a94f-627b1cba6438","name":"Sticky Note7","type":"n8n-nodes-base.stickyNote","position":[-2224,5088],"parameters":{"color":7,"width":1936,"height":960,"content":"\n\n![Dashboard Screenshot](https://jobotai.site/1.png)"},"typeVersion":1},{"id":"83d2df4c-20fb-4894-8419-8a6641efb654","name":"Sticky Note11","type":"n8n-nodes-base.stickyNote","position":[-1216,6096],"parameters":{"color":7,"width":1936,"height":960,"content":"\n\n![Dashboard Screenshot](https://jobotai.site/4.png)"},"typeVersion":1},{"id":"89f12ec6-2ec9-4316-a9af-327bbf813f36","name":"Sticky Note12","type":"n8n-nodes-base.stickyNote","position":[-192,5104],"parameters":{"color":7,"width":1936,"height":960,"content":"\n\n![Dashboard Screenshot](https://jobotai.site/5.png)"},"typeVersion":1},{"id":"44984253-a7d1-48c2-888a-9e9ccf3c0cea","name":"Sticky Note13","type":"n8n-nodes-base.stickyNote","position":[-560,4928],"parameters":{"color":7,"width":640,"height":96,"content":"# 🎯 Coupon Bot  +  Dashboard + Inbox"},"typeVersion":1}],"pinData":{},"connections":{"Inbox API":{"main":[[{"node":"Get Messages","type":"main","index":0}]]},"Is Admin?":{"main":[[{"node":"Check Admin State","type":"main","index":0}],[{"node":"Switch User Action","type":"main","index":0}]]},"Coupons API":{"main":[[{"node":"Switch Coupon Method","type":"main","index":0}]]},"Get Coupons":{"main":[[{"node":"Respond Coupons","type":"main","index":0}]]},"Get Records":{"main":[[{"node":"Respond Records","type":"main","index":0}]]},"Is Message?":{"main":[[{"node":"Get Admin Phone","type":"main","index":0}]]},"Records API":{"main":[[{"node":"Get Records","type":"main","index":0}]]},"Admin Choice":{"main":[[{"node":"Set Admin State (Add Co)","type":"main","index":0}],[{"node":"Get Companies List","type":"main","index":0}],[{"node":"Is Company Selection?","type":"main","index":0}],[{"node":"Set Edit Company List State","type":"main","index":0}],[{"node":"Set Delete Company List State","type":"main","index":0}],[{"node":"Set Add Coupon State","type":"main","index":0}],[{"node":"Set Edit Coupon State","type":"main","index":0}],[{"node":"Set Delete Coupon State","type":"main","index":0}],[{"node":"Show Admin Menu","type":"main","index":0},{"node":"Log Admin Menu","type":"main","index":0}],[{"node":"Set Edit Welcome State","type":"main","index":0}],[{"node":"Set Edit End State","type":"main","index":0}]]},"Get Messages":{"main":[[{"node":"Respond Messages","type":"main","index":0}]]},"Get Settings":{"main":[[{"node":"Respond Settings","type":"main","index":0}]]},"Settings API":{"main":[[{"node":"Switch Settings Method","type":"main","index":0}]]},"Companies API":{"main":[[{"node":"Switch Company Method","type":"main","index":0}]]},"Customers API":{"main":[[{"node":"Get Customers","type":"main","index":0}]]},"Get Companies":{"main":[[{"node":"Respond Companies","type":"main","index":0}]]},"Get Customers":{"main":[[{"node":"Respond Customers","type":"main","index":0}]]},"Modify Coupon":{"main":[[{"node":"Respond Coupon Modified","type":"main","index":0}]]},"Postgres Init":{"main":[[{"node":"Respond Success","type":"main","index":0}]]},"Save Settings":{"main":[[{"node":"Respond Settings Saved","type":"main","index":0}]]},"Modify Company":{"main":[[{"node":"Respond Company Modified","type":"main","index":0}]]},"Set Basic Data":{"main":[[{"node":"Upsert Customer","type":"main","index":0}]]},"Get Admin Phone":{"main":[[{"node":"Set Basic Data","type":"main","index":0}]]},"Get Stats Query":{"main":[[{"node":"Respond Stats","type":"main","index":0}]]},"Upsert Customer":{"main":[[{"node":"Save Incoming Message","type":"main","index":0}]]},"Chat History API":{"main":[[{"node":"Get Chat History","type":"main","index":0}]]},"Get Chat History":{"main":[[{"node":"Respond History","type":"main","index":0}]]},"Get Coupons List":{"main":[[{"node":"Check If Single Coupon","type":"main","index":0},{"node":"Log Sent Coupons","type":"main","index":0}]]},"Get User Session":{"main":[[{"node":"Is Admin?","type":"main","index":0}]]},"Save Coupon Text":{"main":[[{"node":"Ask Coupon Value","type":"main","index":0}]]},"Save End Message":{"main":[[{"node":"Send End Success","type":"main","index":0}]]},"WhatsApp Webhook":{"main":[[{"node":"Is Message?","type":"main","index":0}]]},"Check Admin State":{"main":[[{"node":"Admin State Router","type":"main","index":0}],[{"node":"Admin Choice","type":"main","index":0}]]},"Dashboard Webhook":{"main":[[{"node":"Respond with Dashboard HTML","type":"main","index":0}]]},"Insert New Coupon":{"main":[[{"node":"Send Add Coupon Success","type":"main","index":0}]]},"Stats API Webhook":{"main":[[{"node":"Get Stats Query","type":"main","index":0}]]},"Admin State Router":{"main":[[{"node":"Add Company (Admin)","type":"main","index":0}],[{"node":"Set Edit Company State","type":"main","index":0}],[{"node":"Save Edited Company","type":"main","index":0}],[{"node":"Delete Company Exec","type":"main","index":0}],[{"node":"Set Coupon Company","type":"main","index":0}],[{"node":"Save Coupon Text","type":"main","index":0}],[{"node":"Insert New Coupon","type":"main","index":0}],[{"node":"Set Edit Coupon Select","type":"main","index":0}],[{"node":"Save Edited Coupon","type":"main","index":0}],[{"node":"Delete Coupon Exec","type":"main","index":0}],[{"node":"Save Welcome Message","type":"main","index":0}],[{"node":"Save End Message","type":"main","index":0}]]},"Delete Coupon Exec":{"main":[[{"node":"Send Delete Coupon Success","type":"main","index":0}]]},"Get Companies List":{"main":[[{"node":"Send Companies List","type":"main","index":0},{"node":"Log Sent Companies","type":"main","index":0}]]},"Get Coupon Details":{"main":[[{"node":"Log Coupon Redemption","type":"main","index":0},{"node":"Get End Message For Coupon","type":"main","index":0},{"node":"Log Sent Details","type":"main","index":0}]]},"Save Edited Coupon":{"main":[[{"node":"Send Edit Coupon Success","type":"main","index":0}]]},"Set Coupon Company":{"main":[[{"node":"Ask Coupon Text","type":"main","index":0}]]},"Set Edit End State":{"main":[[{"node":"Ask End Message","type":"main","index":0}]]},"Switch User Action":{"main":[[{"node":"Is Company Selection?","type":"main","index":0}],[{"node":"Get Companies List","type":"main","index":0}]]},"Add Company (Admin)":{"main":[[{"node":"Send Admin Success","type":"main","index":0},{"node":"Log Admin Success","type":"main","index":0}]]},"Delete Company Exec":{"main":[[{"node":"Send Delete Company Success","type":"main","index":0}]]},"Execute SQL Webhook":{"main":[[{"node":"Postgres Init","type":"main","index":0}]]},"Migrate API Webhook":{"main":[[{"node":"Repair Tables Schema","type":"main","index":0}]]},"Save Edited Company":{"main":[[{"node":"Send Edit Company Success","type":"main","index":0}]]},"Get Coupons For Edit":{"main":[[{"node":"Send Edit Coupons List","type":"main","index":0}]]},"Repair Tables Schema":{"main":[[{"node":"Respond Migration Success","type":"main","index":0}]]},"Save Welcome Message":{"main":[[{"node":"Send Welcome Success","type":"main","index":0}]]},"Set Add Coupon State":{"main":[[{"node":"Get Companies For Add Coupon","type":"main","index":0}]]},"Switch Coupon Method":{"main":[[{"node":"Modify Coupon","type":"main","index":0}],[{"node":"Get Coupons","type":"main","index":0}]]},"Is Company Selection?":{"main":[[{"node":"Get Coupons List","type":"main","index":0}],[{"node":"Get Coupon Details","type":"main","index":0}]]},"Save Incoming Message":{"main":[[{"node":"Get User Session","type":"main","index":0}]]},"Set Edit Coupon State":{"main":[[{"node":"Get Coupons For Edit","type":"main","index":0}]]},"Switch Company Method":{"main":[[{"node":"Modify Company","type":"main","index":0}],[{"node":"Get Companies","type":"main","index":0}]]},"Check If Single Coupon":{"main":[[{"node":"Get Coupon Details","type":"main","index":0}],[{"node":"Send Coupons List","type":"main","index":0}]]},"Get Companies For Edit":{"main":[[{"node":"Send Edit Companies List","type":"main","index":0}]]},"Get Coupons For Delete":{"main":[[{"node":"Send Delete Coupons List","type":"main","index":0}]]},"Set Edit Company State":{"main":[[{"node":"Ask Edit Company Name","type":"main","index":0}]]},"Set Edit Coupon Select":{"main":[[{"node":"Ask Edit Coupon Text","type":"main","index":0}]]},"Set Edit Welcome State":{"main":[[{"node":"Ask Welcome Message","type":"main","index":0}]]},"Switch Settings Method":{"main":[[{"node":"Save Settings","type":"main","index":0}],[{"node":"Get Settings","type":"main","index":0}]]},"Set Delete Coupon State":{"main":[[{"node":"Get Coupons For Delete","type":"main","index":0}]]},"Get Companies For Delete":{"main":[[{"node":"Send Delete Companies List","type":"main","index":0}]]},"Set Admin State (Add Co)":{"main":[[{"node":"Ask Admin Input","type":"main","index":0},{"node":"Log Admin Input","type":"main","index":0}]]},"Get End Message For Coupon":{"main":[[{"node":"Send Coupon Details","type":"main","index":0}]]},"Get Welcome & End Messages":{"main":[[{"node":"Get Companies List","type":"main","index":0}]]},"Set Edit Company List State":{"main":[[{"node":"Get Companies For Edit","type":"main","index":0}]]},"Get Companies For Add Coupon":{"main":[[{"node":"Send Add Coupon Companies","type":"main","index":0}]]},"Set Delete Company List State":{"main":[[{"node":"Get Companies For Delete","type":"main","index":0}]]}}},"lastUpdatedBy":1,"workflowInfo":{"nodeCount":130,"nodeTypes":{"n8n-nodes-base.if":{"count":9},"n8n-nodes-base.set":{"count":1},"n8n-nodes-base.switch":{"count":2},"n8n-nodes-base.webhook":{"count":12},"n8n-nodes-base.postgres":{"count":54},"n8n-nodes-base.stickyNote":{"count":14},"n8n-nodes-base.httpRequest":{"count":24},"n8n-nodes-base.respondToWebhook":{"count":14}}},"status":"published","readyToDemo":null,"user":{"name":"Mira Melhem","username":"melhem","bio":"","verified":true,"links":["https://jobotai.site/"],"avatar":"https://gravatar.com/avatar/314fc0a056c4374a2b7e71f0ecd16feedea47f6cb173dbf75f18b549316e091c?r=pg&d=retro&size=200"},"nodes":[{"id":19,"icon":"file:httprequest.svg","name":"n8n-nodes-base.httpRequest","codex":{"data":{"alias":["API","Request","URL","Build","cURL"],"resources":{"generic":[{"url":"https://n8n.io/blog/2021-the-year-to-automate-the-new-you-with-n8n/","icon":"☀️","label":"2021: The Year to Automate the New You with n8n"},{"url":"https://n8n.io/blog/why-business-process-automation-with-n8n-can-change-your-daily-life/","icon":"🧬","label":"Why business process automation with n8n can change your daily life"},{"url":"https://n8n.io/blog/automatically-pulling-and-visualizing-data-with-n8n/","icon":"📈","label":"Automatically pulling and visualizing data with n8n"},{"url":"https://n8n.io/blog/learn-how-to-automatically-cross-post-your-content-with-n8n/","icon":"✍️","label":"Learn how to automatically cross-post your content with n8n"},{"url":"https://n8n.io/blog/automatically-adding-expense-receipts-to-google-sheets-with-telegram-mindee-twilio-and-n8n/","icon":"🧾","label":"Automatically Adding Expense Receipts to Google Sheets with Telegram, Mindee, Twilio, and n8n"},{"url":"https://n8n.io/blog/running-n8n-on-ships-an-interview-with-maranics/","icon":"🛳","label":"Running n8n on ships: An interview with Maranics"},{"url":"https://n8n.io/blog/what-are-apis-how-to-use-them-with-no-code/","icon":" 🪢","label":"What are APIs and how to use them with no code"},{"url":"https://n8n.io/blog/5-tasks-you-can-automate-with-notion-api/","icon":"⚡️","label":"5 tasks you can automate with the new Notion API "},{"url":"https://n8n.io/blog/world-poetry-day-workflow/","icon":"📜","label":"Celebrating World Poetry Day with a daily poem in Telegram"},{"url":"https://n8n.io/blog/automate-google-apps-for-productivity/","icon":"💡","label":"15 Google apps you can combine and automate to increase productivity"},{"url":"https://n8n.io/blog/automate-designs-with-bannerbear-and-n8n/","icon":"🎨","label":"Automate Designs with Bannerbear and n8n"},{"url":"https://n8n.io/blog/how-uproc-scraped-a-multi-page-website-with-a-low-code-workflow/","icon":" 🕸️","label":"How uProc scraped a multi-page website with a low-code workflow"},{"url":"https://n8n.io/blog/building-an-expense-tracking-app-in-10-minutes/","icon":"📱","label":"Building an expense tracking app in 10 minutes"},{"url":"https://n8n.io/blog/5-workflow-automations-for-mattermost-that-we-love-at-n8n/","icon":"🤖","label":"5 workflow automations for Mattermost that we love at n8n"},{"url":"https://n8n.io/blog/how-to-use-the-http-request-node-the-swiss-army-knife-for-workflow-automation/","icon":"🧰","label":"How to use the HTTP Request Node - The Swiss Army Knife for Workflow Automation"},{"url":"https://n8n.io/blog/learn-how-to-use-webhooks-with-mattermost-slash-commands/","icon":"🦄","label":"Learn how to use webhooks with Mattermost slash commands"},{"url":"https://n8n.io/blog/how-a-membership-development-manager-automates-his-work-and-investments/","icon":"📈","label":"How a Membership Development Manager automates his work and investments"},{"url":"https://n8n.io/blog/a-low-code-bitcoin-ticker-built-with-questdb-and-n8n-io/","icon":"📈","label":"A low-code bitcoin ticker built with QuestDB and n8n.io"},{"url":"https://n8n.io/blog/how-to-set-up-a-ci-cd-pipeline-with-no-code/","icon":"🎡","label":"How to set up a no-code CI/CD pipeline with GitHub and TravisCI"},{"url":"https://n8n.io/blog/automations-for-activists/","icon":"✨","label":"How Common Knowledge use workflow automation for activism"},{"url":"https://n8n.io/blog/creating-scheduled-text-affirmations-with-n8n/","icon":"🤟","label":"Creating scheduled text affirmations with n8n"},{"url":"https://n8n.io/blog/how-goomer-automated-their-operations-with-over-200-n8n-workflows/","icon":"🛵","label":"How Goomer automated their operations with over 200 n8n workflows"},{"url":"https://n8n.io/blog/aws-workflow-automation/","label":"7 no-code workflow automations for Amazon Web Services"}],"primaryDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.httprequest/"}]},"categories":["Development","Core Nodes"],"nodeVersion":"1.0","codexVersion":"1.0","subcategories":{"Core Nodes":["Helpers"]}}},"group":"[\"output\"]","defaults":{"name":"HTTP Request","color":"#0004F5"},"iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHZpZXdCb3g9IjAgMCA0MCA0MCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik00MCAyMEM0MCA4Ljk1MzE0IDMxLjA0NjkgMCAyMCAwQzguOTUzMTQgMCAwIDguOTUzMTQgMCAyMEMwIDMxLjA0NjkgOC45NTMxNCA0MCAyMCA0MEMzMS4wNDY5IDQwIDQwIDMxLjA0NjkgNDAgMjBaTTIwIDM2Ljk0NThDMTguODg1MiAzNi45NDU4IDE3LjEzNzggMzUuOTY3IDE1LjQ5OTggMzIuNjk4NUMxNC43OTY0IDMxLjI5MTggMTQuMTk2MSAyOS41NDMxIDEzLjc1MjYgMjcuNjg0N0gyNi4xODk4QzI1LjgwNDUgMjkuNTQwMyAyNS4yMDQ0IDMxLjI5MDEgMjQuNTAwMiAzMi42OTg1QzIyLjg2MjIgMzUuOTY3IDIxLjExNDggMzYuOTQ1OCAyMCAzNi45NDU4Wk0xMi45MDY0IDIwQzEyLjkwNjQgMjEuNjA5NyAxMy4wMDg3IDIzLjE2NCAxMy4yMDAzIDI0LjYzMDVIMjYuNzk5N0MyNi45OTEzIDIzLjE2NCAyNy4wOTM2IDIxLjYwOTcgMjcuMDkzNiAyMEMyNy4wOTM2IDE4LjM5MDMgMjYuOTkxMyAxNi44MzYgMjYuNzk5NyAxNS4zNjk1SDEzLjIwMDNDMTMuMDA4NyAxNi44MzYgMTIuOTA2NCAxOC4zOTAzIDEyLjkwNjQgMjBaTTIwIDMuMDU0MTlDMjEuMTE0OSAzLjA1NDE5IDIyLjg2MjIgNC4wMzA3OCAyNC41MDAxIDcuMzAwMzlDMjUuMjA2NiA4LjcxNDA4IDI1LjgwNzIgMTAuNDA2NyAyNi4xOTIgMTIuMzE1M0gxMy43NTAxQzE0LjE5MzMgMTAuNDA0NyAxNC43OTQyIDguNzEyNTQgMTUuNDk5OCA3LjMwMDY0QzE3LjEzNzcgNC4wMzA4MyAxOC44ODUxIDMuMDU0MTkgMjAgMy4wNTQxOVpNMzAuMTQ3OCAyMEMzMC4xNDc4IDE4LjQwOTkgMzAuMDU0MyAxNi44NjE3IDI5LjgyMjcgMTUuMzY5NUgzNi4zMDQyQzM2LjcyNTIgMTYuODQyIDM2Ljk0NTggMTguMzk2NCAzNi45NDU4IDIwQzM2Ljk0NTggMjEuNjAzNiAzNi43MjUyIDIzLjE1OCAzNi4zMDQyIDI0LjYzMDVIMjkuODIyN0MzMC4wNTQzIDIzLjEzODMgMzAuMTQ3OCAyMS41OTAxIDMwLjE0NzggMjBaTTI2LjI3NjcgNC4yNTUxMkMyNy42MzY1IDYuMzYwMTkgMjguNzExIDkuMTMyIDI5LjM3NzQgMTIuMzE1M0gzNS4xMDQ2QzMzLjI1MTEgOC42NjggMzAuMTA3IDUuNzgzNDYgMjYuMjc2NyA0LjI1NTEyWk0xMC42MjI2IDEyLjMxNTNINC44OTI5M0M2Ljc1MTQ3IDguNjY3ODQgOS44OTM1MSA1Ljc4MzQxIDEzLjcyMzIgNC4yNTUxM0MxMi4zNjM1IDYuMzYwMjEgMTEuMjg5IDkuMTMyMDEgMTAuNjIyNiAxMi4zMTUzWk0zLjA1NDE5IDIwQzMuMDU0MTkgMjEuNjAzIDMuMjc3NDMgMjMuMTU3NSAzLjY5NDg0IDI0LjYzMDVIMTAuMTIxN0M5Ljk0NjE5IDIzLjE0MiA5Ljg1MjIyIDIxLjU5NDMgOS44NTIyMiAyMEM5Ljg1MjIyIDE4LjQwNTcgOS45NDYxOSAxNi44NTggMTAuMTIxNyAxNS4zNjk1SDMuNjk0ODRDMy4yNzc0MyAxNi44NDI1IDMuMDU0MTkgMTguMzk3IDMuMDU0MTkgMjBaTTI2LjI3NjYgMzUuNzQyN0MyNy42MzY1IDMzLjYzOTMgMjguNzExIDMwLjg2OCAyOS4zNzc0IDI3LjY4NDdIMzUuMTA0NkMzMy4yNTEgMzEuMzMyMiAzMC4xMDY4IDM0LjIxNzkgMjYuMjc2NiAzNS43NDI3Wk0xMy43MjM0IDM1Ljc0MjdDOS44OTM2OSAzNC4yMTc5IDYuNzUxNTUgMzEuMzMyNCA0Ljg5MjkzIDI3LjY4NDdIMTAuNjIyNkMxMS4yODkgMzAuODY4IDEyLjM2MzUgMzMuNjM5MyAxMy43MjM0IDM1Ljc0MjdaIiBmaWxsPSIjM0E0MkU5Ii8+Cjwvc3ZnPgo="},"displayName":"HTTP Request","typeVersion":4,"nodeCategories":[{"id":5,"name":"Development"},{"id":9,"name":"Core Nodes"}]},{"id":20,"icon":"fa:map-signs","name":"n8n-nodes-base.if","codex":{"data":{"alias":["Router","Filter","Condition","Logic","Boolean","Branch"],"details":"The IF node can be used to implement binary conditional logic in your workflow. You can set up one-to-many conditions to evaluate each item of data being inputted into the node. That data will either evaluate to TRUE or FALSE and route out of the node accordingly.\n\nThis node has multiple types of conditions: Bool, String, Number, and Date & Time.","resources":{"generic":[{"url":"https://n8n.io/blog/learn-to-automate-your-factorys-incident-reporting-a-step-by-step-guide/","icon":"🏭","label":"Learn to Automate Your Factory's Incident Reporting: A Step by Step Guide"},{"url":"https://n8n.io/blog/2021-the-year-to-automate-the-new-you-with-n8n/","icon":"☀️","label":"2021: The Year to Automate the New You with n8n"},{"url":"https://n8n.io/blog/why-business-process-automation-with-n8n-can-change-your-daily-life/","icon":"🧬","label":"Why business process automation with n8n can change your daily life"},{"url":"https://n8n.io/blog/create-a-toxic-language-detector-for-telegram/","icon":"🤬","label":"Create a toxic language detector for Telegram in 4 step"},{"url":"https://n8n.io/blog/no-code-ecommerce-workflow-automations/","icon":"store","label":"6 e-commerce workflows to power up your Shopify s"},{"url":"https://n8n.io/blog/how-to-build-a-low-code-self-hosted-url-shortener/","icon":"🔗","label":"How to build a low-code, self-hosted URL shortener in 3 steps"},{"url":"https://n8n.io/blog/automate-your-data-processing-pipeline-in-9-steps-with-n8n/","icon":"⚙️","label":"Automate your data processing pipeline in 9 steps"},{"url":"https://n8n.io/blog/how-to-get-started-with-crm-automation-and-no-code-workflow-ideas/","icon":"👥","label":"How to get started with CRM automation (with 3 no-code workflow ideas"},{"url":"https://n8n.io/blog/5-tasks-you-can-automate-with-notion-api/","icon":"⚡️","label":"5 tasks you can automate with the new Notion API "},{"url":"https://n8n.io/blog/automate-google-apps-for-productivity/","icon":"💡","label":"15 Google apps you can combine and automate to increase productivity"},{"url":"https://n8n.io/blog/automation-for-maintainers-of-open-source-projects/","icon":"🏷️","label":"How to automatically manage contributions to open-source projects"},{"url":"https://n8n.io/blog/how-uproc-scraped-a-multi-page-website-with-a-low-code-workflow/","icon":" 🕸️","label":"How uProc scraped a multi-page website with a low-code workflow"},{"url":"https://n8n.io/blog/5-workflow-automations-for-mattermost-that-we-love-at-n8n/","icon":"🤖","label":"5 workflow automations for Mattermost that we love at n8n"},{"url":"https://n8n.io/blog/why-this-product-manager-loves-workflow-automation-with-n8n/","icon":"🧠","label":"Why this Product Manager loves workflow automation with n8n"},{"url":"https://n8n.io/blog/sending-automated-congratulations-with-google-sheets-twilio-and-n8n/","icon":"🙌","label":"Sending Automated Congratulations with Google Sheets, Twilio, and n8n "},{"url":"https://n8n.io/blog/how-to-set-up-a-ci-cd-pipeline-with-no-code/","icon":"🎡","label":"How to set up a no-code CI/CD pipeline with GitHub and TravisCI"},{"url":"https://n8n.io/blog/benefits-of-automation-and-n8n-an-interview-with-hubspots-hugh-durkin/","icon":"🎖","label":"Benefits of automation and n8n: An interview with HubSpot's Hugh Durkin"},{"url":"https://n8n.io/blog/aws-workflow-automation/","label":"7 no-code workflow automations for Amazon Web Services"}],"primaryDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.if/"}]},"categories":["Core Nodes"],"nodeVersion":"1.0","codexVersion":"1.0","subcategories":{"Core Nodes":["Flow"]}}},"group":"[\"transform\"]","defaults":{"name":"If","color":"#408000"},"iconData":{"icon":"map-signs","type":"icon"},"displayName":"If","typeVersion":2,"nodeCategories":[{"id":9,"name":"Core Nodes"}]},{"id":30,"icon":"file:postgres.svg","name":"n8n-nodes-base.postgres","codex":{"data":{"resources":{"generic":[{"url":"https://n8n.io/blog/love-at-first-sight-ricardos-n8n-journey/","icon":"❤️","label":"Love at first sight: Ricardo’s n8n journey"},{"url":"https://n8n.io/blog/why-i-chose-n8n-over-zapier-in-2020/","icon":"😍","label":"Why I chose n8n over Zapier in 2020"},{"url":"https://n8n.io/blog/database-monitoring-and-alerting-with-n8n/","icon":"📡","label":"Database Monitoring and Alerting with n8n"},{"url":"https://n8n.io/blog/running-n8n-on-ships-an-interview-with-maranics/","icon":"🛳","label":"Running n8n on ships: An interview with Maranics"},{"url":"https://n8n.io/blog/automate-your-data-processing-pipeline-in-9-steps-with-n8n/","icon":"⚙️","label":"Automate your data processing pipeline in 9 steps"},{"url":"https://n8n.io/blog/how-honest-burgers-use-automation-to-save-100k-per-year/","icon":"🍔","label":"How Honest Burgers Use Automation to Save $100k per year"}],"primaryDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.postgres/"}],"credentialDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/credentials/postgres/"}]},"categories":["Development","Data & Storage"],"nodeVersion":"1.0","codexVersion":"1.0"}},"group":"[\"input\"]","defaults":{"name":"Postgres"},"iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiBmaWxsPSIjZmZmIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiB2aWV3Qm94PSIwIDAgNzkgODEiPjx1c2UgeGxpbms6aHJlZj0iI2EiIHg9Ii41IiB5PSIuNSIvPjxzeW1ib2wgaWQ9ImEiIG92ZXJmbG93PSJ2aXNpYmxlIj48ZyBmaWxsLXJ1bGU9Im5vbnplcm8iIHN0cm9rZT0ibm9uZSI+PHBhdGggZmlsbD0iIzAwMCIgZD0iTTc3LjM5MSA0Ny45MjJjLS40NjYtMS40MTItMS42ODgtMi4zOTYtMy4yNjgtMi42MzItLjc0NS0uMTExLTEuNTk4LS4wNjQtMi42MDguMTQ0LTEuNzYuMzYzLTMuMDY1LjUwMS00LjAxOC41MjggMy41OTYtNi4wNzIgNi41MjEtMTIuOTk3IDguMjA0LTE5LjUxNSAyLjcyMi0xMC41NCAxLjI2OC0xNS4zNDEtLjQzMi0xNy41MTNDNzAuNzcgMy4xODUgNjQuMjA2LjA5NyA1Ni4yODcuMDAyYy00LjIyNC0uMDUyLTcuOTMzLjc4Mi05Ljg2NyAxLjM4MmEzNyAzNyAwIDAgMC01Ljc3LS41MjhjLTMuODA5LS4wNjEtNy4xNzQuNzctMTAuMDUgMi40NzZhNDYgNDYgMCAwIDAtNy4wOTgtMS43ODJDMTYuNTYxLjQxMSAxMC45NjggMS4yOTkgNi44NzYgNC4xOSAxLjkyMiA3LjY4OS0uMzc1IDEzLjc3LjA1IDIyLjI2MmMuMTM1IDIuNjk2IDEuNjQzIDEwLjkgNC4wMTggMTguNjggMS4zNjUgNC40NzIgMi44MiA4LjE4NSA0LjMyNiAxMS4wMzggMi4xMzUgNC4wNDYgNC40MTkgNi40MjggNi45ODQgNy4yODQgMS40MzguNDc5IDQuMDQ5LjgxNCA2Ljc5Ny0xLjQ3M2E2IDYgMCAwIDAgMS40MjkgMS4yM2MuNzgzLjQ5NCAxLjc0Ljg5NyAyLjY5NiAxLjEzNiAzLjQ0Ni44NjIgNi42NzQuNjQ2IDkuNDI3LS41NjFsLjA0MSAxLjM2Mi4wNiAxLjg5OWMuMTYzIDQuMDY0LjQ0IDcuMjIzIDEuMjU5IDkuNDM0LjA0NS4xMjIuMTA1LjMwNy4xNjkuNTAzLjQwOSAxLjI1MSAxLjA5MiAzLjM0NiAyLjgzIDQuOTg3IDEuOCAxLjY5OSAzLjk3OCAyLjIyIDUuOTcyIDIuMjIgMSAwIDEuOTU1LS4xMzEgMi43OTItLjMxMSAyLjk4NC0uNjM5IDYuMzczLTEuNjE0IDguODI0LTUuMTA0IDIuMzE4LTMuMyAzLjQ0NC04LjI3IDMuNjQ4LTE2LjEwMWwuMDc0LS42MzQuMDQ4LS40MTQuNTQ2LjA0OC4xNDEuMDFjMy4wMzkuMTM4IDYuNzU1LS41MDYgOS4wMzctMS41NjYgMS44MDMtLjgzNyA3LjU4Mi0zLjg4OCA2LjIyMS04LjAwNyIvPjxwYXRoIGZpbGw9IiMzMzY3OTEiIGQ9Ik03Mi4xOTUgNDguNzIzYy05LjAzNiAxLjg2NC05LjY1Ny0xLjE5NS05LjY1Ny0xLjE5NSA5LjU0MS0xNC4xNTcgMTMuNTI5LTMyLjEyNyAxMC4wODctMzYuNTI1QzYzLjIzNS0uOTk0IDQ2Ljk4MSA0LjY4IDQ2LjcxIDQuODI3bC0uMDg3LjAxNmMtMS43ODUtLjM3MS0zLjc4My0uNTkxLTYuMDI5LS42MjgtNC4wODktLjA2Ny03LjE5IDEuMDcyLTkuNTQ0IDIuODU3IDAgMC0yOC45OTUtMTEuOTQ1LTI3LjY0NyAxNS4wMjMuMjg3IDUuNzM3IDguMjIzIDQzLjQxIDE3LjY4OSAzMi4wMzEgMy40Ni00LjE2MSA2LjgwMy03LjY3OSA2LjgwMy03LjY3OSAxLjY2IDEuMTAzIDMuNjQ4IDEuNjY2IDUuNzMyIDEuNDYzbC4xNjItLjEzN2E2LjMgNi4zIDAgMCAwIC4wNjUgMS42MmMtMi40MzkgMi43MjUtMS43MjIgMy4yMDMtNi41OTcgNC4yMDYtNC45MzMgMS4wMTctMi4wMzUgMi44MjYtLjE0MyAzLjI5OSAyLjI5NC41NzQgNy42IDEuMzg2IDExLjE4NS0zLjYzM2wtLjE0My41NzNjLjk1Ni43NjUgMS42MjYgNC45NzggMS41MTQgOC43OTdzLS4xODggNi40NDEuNTY1IDguNDg5IDEuNTAzIDYuNjU2IDcuOTEyIDUuMjgyYzUuMzU1LTEuMTQ4IDguMTMtNC4xMjEgOC41MTYtOS4wODEuMjc0LTMuNTI2Ljg5NC0zLjAwNS45MzMtNi4xNThsLjQ5Ny0xLjQ5M2MuNTczLTQuNzguMDkxLTYuMzIyIDMuMzktNS42MDVsLjgwMi4wN2MyLjQyOC4xMSA1LjYwNi0uMzkxIDcuNDcxLTEuMjU3IDQuMDE2LTEuODY0IDYuMzk4LTQuOTc2IDIuNDM4LTQuMTU4Ii8+PHBhdGggZD0iTTMyLjc0NyAyNC42NmMtLjgxNC0uMTEzLTEuNTUyLS4wMDgtMS45MjUuMjc0YS43LjcgMCAwIDAtLjI5Mi40N2MtLjA0Ny4zMzYuMTg4LjcwNy4zMzMuODk4LjQwOS41NDIgMS4wMDYuOTE1IDEuNTk4Ljk5N2EyIDIgMCAwIDAgLjI1Ni4wMThjLjk4NiAwIDEuODgzLS43NjggMS45NjItMS4zMzUuMDk5LS43MS0uOTMyLTEuMTgzLTEuOTMxLTEuMzIybTI2Ljk3NS4wMjJjLS4wNzgtLjU1Ni0xLjA2OC0uNzE1LTIuMDA3LS41ODRzLTEuODQ4LjU1NC0xLjc3MiAxLjExMmMuMDYxLjQzNC44NDQgMS4xNzQgMS43NzEgMS4xNzRxLjExNyAwIC4yMzctLjAxNmMuNjE5LS4wODYgMS4wNzMtLjQ3OSAxLjI4OC0uNzA1LjMyOS0uMzQ1LjUxOC0uNzMuNDg0LS45OG0xNS40NzcgMjMuODI4Yy0uMzQ1LTEuMDQyLTEuNDUzLTEuMzc3LTMuMjk2LS45OTctNS40NzEgMS4xMjktNy40My4zNDctOC4wNzMtLjEyNyA0LjI1Mi02LjQ3OCA3Ljc1LTE0LjMwOCA5LjYzNy0yMS42MTQuODk0LTMuNDYxIDEuMzg4LTYuNjc1IDEuNDI4LTkuMjk0LjA0NS0yLjg3Ni0uNDQ1LTQuOTg4LTEuNDU1LTYuMjc5LTQuMDcyLTUuMjAzLTEwLjA0OC03Ljk5NC0xNy4yODMtOC4wNy00Ljk3My0uMDU2LTkuMTc1IDEuMjE3LTkuOTkgMS41NzVhMjUgMjUgMCAwIDAtNS42MjItLjcyMmMtMy43MzQtLjA2LTYuOTYxLjgzNC05LjYzMyAyLjY1NWE0MyA0MyAwIDAgMC03LjgyOC0yLjA1MmMtNi4zNDItMS4wMjEtMTEuMzgxLS4yNDgtMTQuOTc4IDIuMy00LjI5MSAzLjA0LTYuMjcyIDguNDc1LTUuODg4IDE2LjE1Mi4xMjkgMi41ODMgMS42MDEgMTAuNTI5IDMuOTIzIDE4LjEzOSAzLjA1NyAxMC4wMTYgNi4zOCAxNS42ODYgOS44NzcgMTYuODUyYTQuNCA0LjQgMCAwIDAgMS40MDIuMjMyYzEuMjc2IDAgMi44MzktLjU3NSA0LjQ2Ni0yLjUzMWExNjEgMTYxIDAgMCAxIDYuMTU2LTYuOTY2IDkuOSA5LjkgMCAwIDAgNC40MjkgMS4xOTFsLjAxLjEyMWMtLjMxLjM2OC0uNTY0LjY5LS43ODEuOTY1LTEuMDcgMS4zNTgtMS4yOTMgMS42NDEtNC43MzggMi4zNTEtLjk4LjIwMi0zLjU4Mi43MzgtMy42MiAyLjU2My0uMDQxIDEuOTkzIDMuMDc2IDIuODMgMy40MzEgMi45MTkgMS4yMzguMzEgMi40My40NjMgMy41NjguNDYzIDIuNzY2IDAgNS4yLS45MDkgNy4xNDUtMi42NjgtLjA2IDcuMTA2LjIzNiAxNC4xMDcgMS4wODkgMTYuMjQxLjY5OSAxLjc0NiAyLjQwNiA2LjAxNCA3Ljc5OCA2LjAxNC43OTEgMCAxLjY2Mi0uMDkyIDIuNjItLjI5NyA1LjYyNy0xLjIwNyA4LjA3MS0zLjY5NCA5LjAxNi05LjE3Ny41MDYtMi45MyAxLjM3NC05LjkyOCAxLjc4Mi0xMy42ODIuODYyLjI2OSAxLjk3MS4zOTIgMy4xNy4zOTIgMi41MDEgMCA1LjM4Ny0uNTMxIDcuMTk3LTEuMzcyIDIuMDMzLS45NDQgNS43MDItMy4yNjEgNS4wMzctNS4yNzR6TTYxLjggMjMuMTQ3Yy0uMDE5IDEuMTA4LS4xNzEgMi4xMTQtLjMzMyAzLjE2NC0uMTc0IDEuMTI5LS4zNTQgMi4yOTctLjM5OSAzLjcxNS0uMDQ1IDEuMzc5LjEyOCAyLjgxNC4yOTQgNC4yLjMzNyAyLjgwMS42ODIgNS42ODUtLjY1NSA4LjUzMWExMSAxMSAwIDAgMS0uNTkyLTEuMjE4Yy0uMTY2LS40MDMtLjUyNy0xLjA1LTEuMDI3LTEuOTQ2LTEuOTQ0LTMuNDg3LTYuNDk3LTExLjY1Mi00LjE2Ny0xNC45ODQuNjk0LS45OTIgMi40NTYtMi4wMTEgNi44NzktMS40NjN6TTU2LjQzOSA0LjM3NGM2LjQ4Mi4xNDMgMTEuNjA5IDIuNTY4IDE1LjI0IDcuMjA3IDIuNzg0IDMuNTU4LS4yODIgMTkuNzQ5LTkuMTU4IDMzLjcxNmwtLjI2OS0uMzM5LS4xMTItLjE0YzIuMjk0LTMuNzg4IDEuODQ1LTcuNTM2IDEuNDQ2LTEwLjg1OS0uMTY0LTEuMzY0LS4zMTktMi42NTItLjI4LTMuODYxLjA0MS0xLjI4My4yMS0yLjM4Mi4zNzQtMy40NDYuMjAyLTEuMzExLjQwNy0yLjY2Ny4zNS00LjI2NWExLjggMS44IDAgMCAwIC4wMzctLjYwMWMtLjE0NC0xLjUzMy0xLjg5NC02LjEyLTUuNDYyLTEwLjI3My0xLjk1MS0yLjI3MS00Ljc5Ny00LjgxMy04LjY4Mi02LjUyN2EyOS4zIDI5LjMgMCAwIDEgNi41MTUtLjYxMnpNMjAuMTY3IDUzLjI5OGMtMS43OTMgMi4xNTUtMy4wMzEgMS43NDItMy40MzggMS42MDctMi42NTMtLjg4NS01LjczLTYuNDkxLTguNDQ0LTE1LjM4Mi0yLjM0OC03LjY5My0zLjcyLTE1LjQyOC0zLjgyOS0xNy41OTctLjM0My02Ljg2IDEuMzItMTEuNjQxIDQuOTQzLTE0LjIxIDUuODk2LTQuMTgxIDE1LjU4OS0xLjY3OSAxOS40ODQtLjQwOWwtLjE3LjE2M2MtNi4zOTEgNi40NTUtNi4yNCAxNy40ODMtNi4yMjQgMTguMTU3YTIyIDIyIDAgMCAwIC4wNTEgMS4xMzVjLjExIDEuODU1LjMxNSA1LjMwNy0uMjMyIDkuMjE3LS41MDggMy42MzMuNjEyIDcuMTg5IDMuMDcyIDkuNzU2cS4zODMuMzk4Ljc5NS43NWExNjQgMTY0IDAgMCAwLTYuMDA4IDYuODE0em02LjgzLTkuMTEzYy0xLjk4My0yLjA2OS0yLjg4NC00Ljk0Ny0yLjQ3MS03Ljg5Ni41NzctNC4xMy4zNjQtNy43MjcuMjUtOS42NTlsLS4wMzktLjY5NGMuOTM0LS44MjggNS4yNjEtMy4xNDYgOC4zNDYtMi40MzkgMS40MDguMzIzIDIuMjY2IDEuMjgxIDIuNjIzIDIuOTMxIDEuODQ2IDguNTM5LjI0NCAxMi4wOTgtMS4wNDMgMTQuOTU3LS4yNjUuNTg5LS41MTYgMS4xNDYtLjczIDEuNzIybC0uMTY2LjQ0NWMtLjQyIDEuMTI2LS44MTEgMi4xNzMtMS4wNTMgMy4xNjctMi4xMDgtLjAwNi00LjE1OS0uOTA3LTUuNzE4LTIuNTM0em0uMzI0IDExLjUxNmE1IDUgMCAwIDEtMS40OTQtLjY0MmMuMjcxLS4xMjguNzU0LS4zMDEgMS41OTEtLjQ3NCA0LjA1Mi0uODM0IDQuNjc4LTEuNDIzIDYuMDQ1LTMuMTU4LjMxMy0uMzk4LjY2OS0uODQ5IDEuMTYtMS4zOTguNzMzLS44MjEgMS4wNjgtLjY4MiAxLjY3Ni0uNDMuNDkzLjIwNC45NzIuODIxIDEuMTY3IDEuNTAxLjA5Mi4zMjEuMTk1LjkzLS4xNDMgMS40MDQtMi44NTUgMy45OTctNy4wMTUgMy45NDYtMTAuMDAzIDMuMTk4em0yMS4yMDcgMTkuNzM1Yy00Ljk1NyAxLjA2Mi02LjcxMy0xLjQ2Ny03Ljg2OS00LjM1OS0uNzQ3LTEuODY3LTEuMTEzLTEwLjI4NS0uODUzLTE5LjU4MmExLjEgMS4xIDAgMCAwLS4wNDgtLjM1NiA1IDUgMCAwIDAtLjEzOS0uNjU3Yy0uMzg3LTEuMzUzLTEuMzMxLTIuNDg0LTIuNDYyLTIuOTUzLS40NS0uMTg2LTEuMjc1LS41MjgtMi4yNjctLjI3NC4yMTItLjg3MS41NzgtMS44NTUuOTc2LTIuOTIxbC4xNjctLjQ0OGMuMTg4LS41MDUuNDIzLTEuMDI5LjY3My0xLjU4MyAxLjM0Ny0yLjk5MiAzLjE5Mi03LjA5MSAxLjE5LTE2LjM1LS43NS0zLjQ2OC0zLjI1NC01LjE2MS03LjA1LTQuNzY4LTIuMjc2LjIzNS00LjM1OCAxLjE1NC01LjM5NiAxLjY4cS0uMzM0LjE2OS0uNjE4LjMyOWMuMjktMy40OTQgMS4zODUtMTAuMDI0IDUuNDgxLTE0LjE1NiAyLjU3OS0yLjYwMSA2LjAxNC0zLjg4NiAxMC4xOTktMy44MTcgOC4yNDYuMTM1IDEzLjUzNCA0LjM2NyAxNi41MTggNy44OTMgMi41NzEgMy4wMzkgMy45NjQgNi4xIDQuNTIgNy43NTEtNC4xNzktLjQyNS03LjAyMi40LTguNDYzIDIuNDYtMy4xMzUgNC40ODEgMS43MTUgMTMuMTc4IDQuMDQ2IDE3LjM1OC40MjcuNzY2Ljc5NiAxLjQyOC45MTIgMS43MDkuNzU5IDEuODM5IDEuNzQyIDMuMDY3IDIuNDU5IDMuOTY0LjIyLjI3NS40MzMuNTQxLjU5Ni43NzQtMS4yNjYuMzY1LTMuNTM5IDEuMjA4LTMuMzMyIDUuNDIyLS4xNjcgMi4xMTUtMS4zNTYgMTIuMDE2LTEuOTU5IDE1LjUxNC0uNzk3IDQuNjIxLTIuNDk3IDYuMzQzLTcuMjc5IDcuMzY4em0yMC42OTMtMjMuNjhjLTEuMjk0LjYwMS0zLjQ2IDEuMDUyLTUuNTE4IDEuMTQ4LTIuMjczLjEwNy0zLjQzLS4yNTUtMy43MDItLjQ3Ny0uMTI4LTIuNjI2Ljg1LTIuOTAxIDEuODg0LTMuMTkxLjE2My0uMDQ2LjMyMS0uMDkuNDc0LS4xNDRhNCA0IDAgMCAwIC4zMTMuMjNjMS44MjcgMS4yMDYgNS4wODUgMS4zMzYgOS42ODUuMzg2bC4wNS0uMDFjLS42Mi41OC0xLjY4MiAxLjM1OS0zLjE4NyAyLjA1OHoiLz48L2c+PC9zeW1ib2w+PC9zdmc+"},"displayName":"Postgres","typeVersion":3,"nodeCategories":[{"id":3,"name":"Data & Storage"},{"id":5,"name":"Development"}]},{"id":38,"icon":"fa:pen","name":"n8n-nodes-base.set","codex":{"data":{"alias":["Set","JS","JSON","Filter","Transform","Map"],"resources":{"generic":[{"url":"https://n8n.io/blog/learn-to-automate-your-factorys-incident-reporting-a-step-by-step-guide/","icon":"🏭","label":"Learn to Automate Your Factory's Incident Reporting: A Step by Step Guide"},{"url":"https://n8n.io/blog/2021-the-year-to-automate-the-new-you-with-n8n/","icon":"☀️","label":"2021: The Year to Automate the New You with n8n"},{"url":"https://n8n.io/blog/automatically-pulling-and-visualizing-data-with-n8n/","icon":"📈","label":"Automatically pulling and visualizing data with n8n"},{"url":"https://n8n.io/blog/database-monitoring-and-alerting-with-n8n/","icon":"📡","label":"Database Monitoring and Alerting with n8n"},{"url":"https://n8n.io/blog/automatically-adding-expense-receipts-to-google-sheets-with-telegram-mindee-twilio-and-n8n/","icon":"🧾","label":"Automatically Adding Expense Receipts to Google Sheets with Telegram, Mindee, Twilio, and n8n"},{"url":"https://n8n.io/blog/no-code-ecommerce-workflow-automations/","icon":"store","label":"6 e-commerce workflows to power up your Shopify s"},{"url":"https://n8n.io/blog/how-to-build-a-low-code-self-hosted-url-shortener/","icon":"🔗","label":"How to build a low-code, self-hosted URL shortener in 3 steps"},{"url":"https://n8n.io/blog/automate-your-data-processing-pipeline-in-9-steps-with-n8n/","icon":"⚙️","label":"Automate your data processing pipeline in 9 steps"},{"url":"https://n8n.io/blog/how-to-get-started-with-crm-automation-and-no-code-workflow-ideas/","icon":"👥","label":"How to get started with CRM automation (with 3 no-code workflow ideas"},{"url":"https://n8n.io/blog/5-tasks-you-can-automate-with-notion-api/","icon":"⚡️","label":"5 tasks you can automate with the new Notion API "},{"url":"https://n8n.io/blog/automate-google-apps-for-productivity/","icon":"💡","label":"15 Google apps you can combine and automate to increase productivity"},{"url":"https://n8n.io/blog/how-uproc-scraped-a-multi-page-website-with-a-low-code-workflow/","icon":" 🕸️","label":"How uProc scraped a multi-page website with a low-code workflow"},{"url":"https://n8n.io/blog/building-an-expense-tracking-app-in-10-minutes/","icon":"📱","label":"Building an expense tracking app in 10 minutes"},{"url":"https://n8n.io/blog/the-ultimate-guide-to-automate-your-video-collaboration-with-whereby-mattermost-and-n8n/","icon":"📹","label":"The ultimate guide to automate your video collaboration with Whereby, Mattermost, and n8n"},{"url":"https://n8n.io/blog/5-workflow-automations-for-mattermost-that-we-love-at-n8n/","icon":"🤖","label":"5 workflow automations for Mattermost that we love at n8n"},{"url":"https://n8n.io/blog/learn-to-build-powerful-api-endpoints-using-webhooks/","icon":"🧰","label":"Learn to Build Powerful API Endpoints Using Webhooks"},{"url":"https://n8n.io/blog/how-a-membership-development-manager-automates-his-work-and-investments/","icon":"📈","label":"How a Membership Development Manager automates his work and investments"},{"url":"https://n8n.io/blog/a-low-code-bitcoin-ticker-built-with-questdb-and-n8n-io/","icon":"📈","label":"A low-code bitcoin ticker built with QuestDB and n8n.io"},{"url":"https://n8n.io/blog/how-to-set-up-a-ci-cd-pipeline-with-no-code/","icon":"🎡","label":"How to set up a no-code CI/CD pipeline with GitHub and TravisCI"},{"url":"https://n8n.io/blog/benefits-of-automation-and-n8n-an-interview-with-hubspots-hugh-durkin/","icon":"🎖","label":"Benefits of automation and n8n: An interview with HubSpot's Hugh Durkin"},{"url":"https://n8n.io/blog/how-goomer-automated-their-operations-with-over-200-n8n-workflows/","icon":"🛵","label":"How Goomer automated their operations with over 200 n8n workflows"},{"url":"https://n8n.io/blog/aws-workflow-automation/","label":"7 no-code workflow automations for Amazon Web Services"}],"primaryDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.set/"}]},"categories":["Core Nodes"],"nodeVersion":"1.0","codexVersion":"1.0","subcategories":{"Core Nodes":["Data Transformation"]}}},"group":"[\"input\"]","defaults":{"name":"Edit Fields"},"iconData":{"icon":"pen","type":"icon"},"displayName":"Edit Fields (Set)","typeVersion":3,"nodeCategories":[{"id":9,"name":"Core Nodes"}]},{"id":47,"icon":"file:webhook.svg","name":"n8n-nodes-base.webhook","codex":{"data":{"alias":["HTTP","API","Build","WH"],"resources":{"generic":[{"url":"https://n8n.io/blog/learn-how-to-automatically-cross-post-your-content-with-n8n/","icon":"✍️","label":"Learn how to automatically cross-post your content with n8n"},{"url":"https://n8n.io/blog/running-n8n-on-ships-an-interview-with-maranics/","icon":"🛳","label":"Running n8n on ships: An interview with Maranics"},{"url":"https://n8n.io/blog/how-to-build-a-low-code-self-hosted-url-shortener/","icon":"🔗","label":"How to build a low-code, self-hosted URL shortener in 3 steps"},{"url":"https://n8n.io/blog/what-are-apis-how-to-use-them-with-no-code/","icon":" 🪢","label":"What are APIs and how to use them with no code"},{"url":"https://n8n.io/blog/5-tasks-you-can-automate-with-notion-api/","icon":"⚡️","label":"5 tasks you can automate with the new Notion API "},{"url":"https://n8n.io/blog/how-a-digital-strategist-uses-n8n-for-online-marketing/","icon":"💻","label":"How a digital strategist uses n8n for online marketing"},{"url":"https://n8n.io/blog/the-ultimate-guide-to-automate-your-video-collaboration-with-whereby-mattermost-and-n8n/","icon":"📹","label":"The ultimate guide to automate your video collaboration with Whereby, Mattermost, and n8n"},{"url":"https://n8n.io/blog/how-to-automatically-give-kudos-to-contributors-with-github-slack-and-n8n/","icon":"👏","label":"How to automatically give kudos to contributors with GitHub, Slack, and n8n"},{"url":"https://n8n.io/blog/5-workflow-automations-for-mattermost-that-we-love-at-n8n/","icon":"🤖","label":"5 workflow automations for Mattermost that we love at n8n"},{"url":"https://n8n.io/blog/why-this-product-manager-loves-workflow-automation-with-n8n/","icon":"🧠","label":"Why this Product Manager loves workflow automation with n8n"},{"url":"https://n8n.io/blog/creating-custom-incident-response-workflows-with-n8n/","label":"How to automate every step of an incident response workflow"},{"url":"https://n8n.io/blog/learn-to-build-powerful-api-endpoints-using-webhooks/","icon":"🧰","label":"Learn to Build Powerful API Endpoints Using Webhooks"},{"url":"https://n8n.io/blog/learn-how-to-use-webhooks-with-mattermost-slash-commands/","icon":"🦄","label":"Learn how to use webhooks with Mattermost slash commands"},{"url":"https://n8n.io/blog/how-goomer-automated-their-operations-with-over-200-n8n-workflows/","icon":"🛵","label":"How Goomer automated their operations with over 200 n8n workflows"}],"primaryDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.webhook/"}]},"categories":["Development","Core Nodes"],"nodeVersion":"1.0","codexVersion":"1.0","subcategories":{"Core Nodes":["Helpers"]}}},"group":"[\"trigger\"]","defaults":{"name":"Webhook"},"iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCI+PHBhdGggZmlsbD0iIzM3NDc0ZiIgZD0iTTM1IDM3Yy0yLjIgMC00LTEuOC00LTRzMS44LTQgNC00IDQgMS44IDQgNC0xLjggNC00IDQiLz48cGF0aCBmaWxsPSIjMzc0NzRmIiBkPSJNMzUgNDNjLTMgMC01LjktMS40LTcuOC0zLjdsMy4xLTIuNWMxLjEgMS40IDIuOSAyLjMgNC43IDIuMyAzLjMgMCA2LTIuNyA2LTZzLTIuNy02LTYtNmMtMSAwLTIgLjMtMi45LjdsLTEuNyAxTDIzLjMgMTZsMy41LTEuOSA1LjMgOS40YzEtLjMgMi0uNSAzLS41IDUuNSAwIDEwIDQuNSAxMCAxMFM0MC41IDQzIDM1IDQzIi8+PHBhdGggZmlsbD0iIzM3NDc0ZiIgZD0iTTE0IDQzQzguNSA0MyA0IDM4LjUgNCAzM2MwLTQuNiAzLjEtOC41IDcuNS05LjdsMSAzLjlDOS45IDI3LjkgOCAzMC4zIDggMzNjMCAzLjMgMi43IDYgNiA2czYtMi43IDYtNnYtMmgxNXY0SDIzLjhjLS45IDQuNi01IDgtOS44IDgiLz48cGF0aCBmaWxsPSIjZTkxZTYzIiBkPSJNMTQgMzdjLTIuMiAwLTQtMS44LTQtNHMxLjgtNCA0LTQgNCAxLjggNCA0LTEuOCA0LTQgNCIvPjxwYXRoIGZpbGw9IiMzNzQ3NGYiIGQ9Ik0yNSAxOWMtMi4yIDAtNC0xLjgtNC00czEuOC00IDQtNCA0IDEuOCA0IDQtMS44IDQtNCA0Ii8+PHBhdGggZmlsbD0iI2U5MWU2MyIgZD0ibTE1LjcgMzQtMy40LTIgNS45LTkuN2MtMi0xLjktMy4yLTQuNS0zLjItNy4zIDAtNS41IDQuNS0xMCAxMC0xMHMxMCA0LjUgMTAgMTBjMCAuOS0uMSAxLjctLjMgMi41bC0zLjktMWMuMS0uNS4yLTEgLjItMS41IDAtMy4zLTIuNy02LTYtNnMtNiAyLjctNiA2YzAgMi4xIDEuMSA0IDIuOSA1LjFsMS43IDF6Ii8+PC9zdmc+"},"displayName":"Webhook","typeVersion":2,"nodeCategories":[{"id":5,"name":"Development"},{"id":9,"name":"Core Nodes"}]},{"id":112,"icon":"fa:map-signs","name":"n8n-nodes-base.switch","codex":{"data":{"alias":["Router","If","Path","Filter","Condition","Logic","Branch","Case"],"resources":{"generic":[{"url":"https://n8n.io/blog/2021-the-year-to-automate-the-new-you-with-n8n/","icon":"☀️","label":"2021: The Year to Automate the New You with n8n"},{"url":"https://n8n.io/blog/how-to-get-started-with-crm-automation-and-no-code-workflow-ideas/","icon":"👥","label":"How to get started with CRM automation (with 3 no-code workflow ideas"},{"url":"https://n8n.io/blog/build-your-own-virtual-assistant-with-n8n-a-step-by-step-guide/","icon":"👦","label":"Build your own virtual assistant with n8n: A step by step guide"},{"url":"https://n8n.io/blog/automation-for-maintainers-of-open-source-projects/","icon":"🏷️","label":"How to automatically manage contributions to open-source projects"}],"primaryDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.switch/"}]},"categories":["Core Nodes"],"nodeVersion":"1.0","codexVersion":"1.0","subcategories":{"Core Nodes":["Flow"]}}},"group":"[\"transform\"]","defaults":{"name":"Switch","color":"#506000"},"iconData":{"icon":"map-signs","type":"icon"},"displayName":"Switch","typeVersion":3,"nodeCategories":[{"id":9,"name":"Core Nodes"}]},{"id":535,"icon":"file:webhook.svg","name":"n8n-nodes-base.respondToWebhook","codex":{"data":{"resources":{"primaryDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.respondtowebhook/"}]},"categories":["Core Nodes","Utility"],"nodeVersion":"1.0","codexVersion":"1.0","subcategories":{"Core Nodes":["Helpers"]}}},"group":"[\"transform\"]","defaults":{"name":"Respond to Webhook"},"iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCI+PHBhdGggZmlsbD0iIzM3NDc0ZiIgZD0iTTM1IDM3Yy0yLjIgMC00LTEuOC00LTRzMS44LTQgNC00IDQgMS44IDQgNC0xLjggNC00IDQiLz48cGF0aCBmaWxsPSIjMzc0NzRmIiBkPSJNMzUgNDNjLTMgMC01LjktMS40LTcuOC0zLjdsMy4xLTIuNWMxLjEgMS40IDIuOSAyLjMgNC43IDIuMyAzLjMgMCA2LTIuNyA2LTZzLTIuNy02LTYtNmMtMSAwLTIgLjMtMi45LjdsLTEuNyAxTDIzLjMgMTZsMy41LTEuOSA1LjMgOS40YzEtLjMgMi0uNSAzLS41IDUuNSAwIDEwIDQuNSAxMCAxMFM0MC41IDQzIDM1IDQzIi8+PHBhdGggZmlsbD0iIzM3NDc0ZiIgZD0iTTE0IDQzQzguNSA0MyA0IDM4LjUgNCAzM2MwLTQuNiAzLjEtOC41IDcuNS05LjdsMSAzLjlDOS45IDI3LjkgOCAzMC4zIDggMzNjMCAzLjMgMi43IDYgNiA2czYtMi43IDYtNnYtMmgxNXY0SDIzLjhjLS45IDQuNi01IDgtOS44IDgiLz48cGF0aCBmaWxsPSIjZTkxZTYzIiBkPSJNMTQgMzdjLTIuMiAwLTQtMS44LTQtNHMxLjgtNCA0LTQgNCAxLjggNCA0LTEuOCA0LTQgNCIvPjxwYXRoIGZpbGw9IiMzNzQ3NGYiIGQ9Ik0yNSAxOWMtMi4yIDAtNC0xLjgtNC00czEuOC00IDQtNCA0IDEuOCA0IDQtMS44IDQtNCA0Ii8+PHBhdGggZmlsbD0iI2U5MWU2MyIgZD0ibTE1LjcgMzQtMy40LTIgNS45LTkuN2MtMi0xLjktMy4yLTQuNS0zLjItNy4zIDAtNS41IDQuNS0xMCAxMC0xMHMxMCA0LjUgMTAgMTBjMCAuOS0uMSAxLjctLjMgMi41bC0zLjktMWMuMS0uNS4yLTEgLjItMS41IDAtMy4zLTIuNy02LTYtNnMtNiAyLjctNiA2YzAgMi4xIDEuMSA0IDIuOSA1LjFsMS43IDF6Ii8+PC9zdmc+"},"displayName":"Respond to Webhook","typeVersion":2,"nodeCategories":[{"id":7,"name":"Utility"},{"id":9,"name":"Core Nodes"}]},{"id":565,"icon":"fa:sticky-note","name":"n8n-nodes-base.stickyNote","codex":{"data":{"alias":["Comments","Notes","Sticky"],"categories":["Core Nodes"],"nodeVersion":"1.0","codexVersion":"1.0","subcategories":{"Core Nodes":["Helpers"]}}},"group":"[\"input\"]","defaults":{"name":"Sticky Note","color":"#FFD233"},"iconData":{"icon":"sticky-note","type":"icon"},"displayName":"Sticky Note","typeVersion":1,"nodeCategories":[{"id":9,"name":"Core Nodes"}]}],"categories":[{"id":39,"name":"CRM"},{"id":47,"name":"AI Chatbot"}],"image":[]}}