mirror of
https://github.com/Luchexx/wacrm.git
synced 2026-11-02 17:29:45 +00:00
Replaces the /automations placeholder with a full feature: - Migration 006 adds automations, automation_steps, automation_logs, and automation_pending_executions with RLS matching existing tables. - List page with active/inactive switch, quick-start templates, duplicate, delete, and View Logs actions. - Full-screen vertical flow builder with Trigger + Action + Condition (Yes/No branches) + Wait cards. Inline edit forms, up/down reorder, dotted-grid canvas. - Execution engine (src/lib/automations/engine.ts) dispatched from the WhatsApp webhook for new_message_received, new_contact_created, and keyword_match. Wait steps enqueue to automation_pending_executions; /api/automations/cron drains them behind AUTOMATION_CRON_SECRET. - Logs viewer with per-step checkmarks and expandable detail. Existing files touched only in webhook/route.ts (fire-and-forget trigger dispatch) and types/index.ts (appended automation types). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
141 lines
6.2 KiB
SQL
141 lines
6.2 KiB
SQL
-- ============================================================
|
|
-- 006_automations.sql — Automations feature
|
|
--
|
|
-- Idempotent migration — safe to run multiple times.
|
|
-- Follows the same conventions as 001_initial_schema.sql:
|
|
-- IF NOT EXISTS on tables/indexes, DROP IF EXISTS before
|
|
-- re-creating policies/triggers (Postgres has no
|
|
-- CREATE POLICY IF NOT EXISTS).
|
|
-- ============================================================
|
|
|
|
-- ============================================================
|
|
-- AUTOMATIONS
|
|
-- ============================================================
|
|
CREATE TABLE IF NOT EXISTS automations (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
|
name TEXT NOT NULL,
|
|
description TEXT,
|
|
trigger_type TEXT NOT NULL,
|
|
trigger_config JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
is_active BOOLEAN NOT NULL DEFAULT FALSE,
|
|
execution_count INTEGER NOT NULL DEFAULT 0,
|
|
last_executed_at TIMESTAMPTZ,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_automations_user_id ON automations(user_id);
|
|
-- Partial index tuned for the engine's hot path: find active automations
|
|
-- whose trigger_type matches the fired event. RLS then narrows by user_id.
|
|
CREATE INDEX IF NOT EXISTS idx_automations_active_trigger
|
|
ON automations(trigger_type) WHERE is_active = TRUE;
|
|
|
|
ALTER TABLE automations ENABLE ROW LEVEL SECURITY;
|
|
DROP POLICY IF EXISTS "Users can manage own automations" ON automations;
|
|
CREATE POLICY "Users can manage own automations" ON automations FOR ALL
|
|
USING (auth.uid() = user_id);
|
|
|
|
DROP TRIGGER IF EXISTS set_updated_at ON automations;
|
|
CREATE TRIGGER set_updated_at BEFORE UPDATE ON automations
|
|
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
|
|
|
-- ============================================================
|
|
-- AUTOMATION_STEPS
|
|
--
|
|
-- `position` — order within parent scope (root scope or a branch).
|
|
-- `parent_step_id` — NULL for root-level steps; set to the Condition
|
|
-- step's id for steps that live inside one of its
|
|
-- branches.
|
|
-- `branch` — NULL for root steps. For children of a Condition,
|
|
-- 'yes' or 'no' identifying which path.
|
|
-- ============================================================
|
|
CREATE TABLE IF NOT EXISTS automation_steps (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
automation_id UUID NOT NULL REFERENCES automations(id) ON DELETE CASCADE,
|
|
parent_step_id UUID REFERENCES automation_steps(id) ON DELETE CASCADE,
|
|
branch TEXT CHECK (branch IN ('yes', 'no')),
|
|
step_type TEXT NOT NULL,
|
|
step_config JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
position INTEGER NOT NULL,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_automation_steps_automation_id
|
|
ON automation_steps(automation_id, position);
|
|
CREATE INDEX IF NOT EXISTS idx_automation_steps_parent
|
|
ON automation_steps(parent_step_id) WHERE parent_step_id IS NOT NULL;
|
|
|
|
ALTER TABLE automation_steps ENABLE ROW LEVEL SECURITY;
|
|
DROP POLICY IF EXISTS "Users can manage steps of own automations" ON automation_steps;
|
|
CREATE POLICY "Users can manage steps of own automations" ON automation_steps FOR ALL
|
|
USING (
|
|
EXISTS (
|
|
SELECT 1 FROM automations a
|
|
WHERE a.id = automation_steps.automation_id
|
|
AND a.user_id = auth.uid()
|
|
)
|
|
);
|
|
|
|
-- ============================================================
|
|
-- AUTOMATION_LOGS
|
|
--
|
|
-- user_id is denormalized for simple RLS; contact_id is nullable so
|
|
-- history survives contact deletion (mirrors migration 004's pattern
|
|
-- on broadcast_recipients / deals).
|
|
-- ============================================================
|
|
CREATE TABLE IF NOT EXISTS automation_logs (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
automation_id UUID NOT NULL REFERENCES automations(id) ON DELETE CASCADE,
|
|
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
|
contact_id UUID REFERENCES contacts(id) ON DELETE SET NULL,
|
|
trigger_event TEXT NOT NULL,
|
|
steps_executed JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
status TEXT NOT NULL CHECK (status IN ('success', 'partial', 'failed')),
|
|
error_message TEXT,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_automation_logs_automation
|
|
ON automation_logs(automation_id, created_at DESC);
|
|
CREATE INDEX IF NOT EXISTS idx_automation_logs_user ON automation_logs(user_id);
|
|
|
|
ALTER TABLE automation_logs ENABLE ROW LEVEL SECURITY;
|
|
DROP POLICY IF EXISTS "Users can view own automation logs" ON automation_logs;
|
|
CREATE POLICY "Users can view own automation logs" ON automation_logs FOR ALL
|
|
USING (auth.uid() = user_id);
|
|
|
|
-- ============================================================
|
|
-- AUTOMATION_PENDING_EXECUTIONS
|
|
--
|
|
-- Queue row created when a running automation hits a `wait` step.
|
|
-- The cron endpoint drains rows where run_at <= now() and status =
|
|
-- 'pending', flips them to 'running', and resumes the automation
|
|
-- from `next_step_position` with the saved `context` jsonb.
|
|
--
|
|
-- Service-role only — writes never originate from the browser, and
|
|
-- the engine uses the service-role client. No user policy exposed.
|
|
-- ============================================================
|
|
CREATE TABLE IF NOT EXISTS automation_pending_executions (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
automation_id UUID NOT NULL REFERENCES automations(id) ON DELETE CASCADE,
|
|
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
|
contact_id UUID REFERENCES contacts(id) ON DELETE SET NULL,
|
|
log_id UUID REFERENCES automation_logs(id) ON DELETE CASCADE,
|
|
parent_step_id UUID REFERENCES automation_steps(id) ON DELETE SET NULL,
|
|
branch TEXT CHECK (branch IN ('yes', 'no')),
|
|
next_step_position INTEGER NOT NULL,
|
|
context JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
status TEXT NOT NULL DEFAULT 'pending'
|
|
CHECK (status IN ('pending', 'running', 'done', 'failed')),
|
|
run_at TIMESTAMPTZ NOT NULL,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_automation_pending_due
|
|
ON automation_pending_executions(run_at) WHERE status = 'pending';
|
|
|
|
ALTER TABLE automation_pending_executions ENABLE ROW LEVEL SECURITY;
|
|
-- No SELECT/INSERT/UPDATE/DELETE policy for authenticated users — all
|
|
-- access is server-side via the service-role key.
|