142 lines
5.9 KiB
MySQL
142 lines
5.9 KiB
MySQL
|
|
-- Migration: Core Schema for Campaigns and Leads
|
||
|
|
-- Description: Adds core tables and RLS policies for Phase 1 MVP.
|
||
|
|
-- Created: 2026-04-07 11:31:27
|
||
|
|
|
||
|
|
-- 1. Update organizations table
|
||
|
|
ALTER TABLE public.organizations
|
||
|
|
ADD COLUMN IF NOT EXISTS icp jsonb DEFAULT '{}',
|
||
|
|
ADD COLUMN IF NOT EXISTS company_profile jsonb DEFAULT '{}',
|
||
|
|
ADD COLUMN IF NOT EXISTS monthly_quota int DEFAULT 0,
|
||
|
|
ADD COLUMN IF NOT EXISTS current_leads_month int DEFAULT 0,
|
||
|
|
ADD COLUMN IF NOT EXISTS quota_reset_date timestamptz DEFAULT now();
|
||
|
|
|
||
|
|
-- 2. Create campaigns table
|
||
|
|
CREATE TABLE IF NOT EXISTS public.campaigns (
|
||
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
|
|
organization_id uuid NOT NULL REFERENCES public.organizations(id) ON DELETE CASCADE,
|
||
|
|
search_account_id uuid NOT NULL REFERENCES auth.users(id),
|
||
|
|
name text NOT NULL,
|
||
|
|
industries jsonb DEFAULT '[]'::jsonb,
|
||
|
|
locations jsonb DEFAULT '[]'::jsonb,
|
||
|
|
headcount jsonb DEFAULT '[]'::jsonb,
|
||
|
|
target_leads int NOT NULL,
|
||
|
|
current_leads int DEFAULT 0,
|
||
|
|
search_cursor text,
|
||
|
|
status text DEFAULT 'pending'::text,
|
||
|
|
created_at timestamptz DEFAULT now(),
|
||
|
|
CONSTRAINT campaigns_status_check CHECK (status IN ('pending', 'running', 'paused', 'pipeline_completed', 'quota_reached'))
|
||
|
|
);
|
||
|
|
|
||
|
|
CREATE UNIQUE INDEX IF NOT EXISTS campaigns_name_org_unique ON public.campaigns (organization_id, name);
|
||
|
|
|
||
|
|
-- 3. Create leads table (Global deduplication & Enrichment Cache)
|
||
|
|
CREATE TABLE IF NOT EXISTS public.leads (
|
||
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
|
|
linkedin_url text UNIQUE NOT NULL,
|
||
|
|
name text NOT NULL,
|
||
|
|
domain text,
|
||
|
|
enriched_data jsonb DEFAULT '{}'::jsonb,
|
||
|
|
last_enriched_at timestamptz,
|
||
|
|
created_at timestamptz DEFAULT now()
|
||
|
|
);
|
||
|
|
|
||
|
|
-- 4. Create campaign_leads junction table
|
||
|
|
CREATE TABLE IF NOT EXISTS public.campaign_leads (
|
||
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
|
|
campaign_id uuid NOT NULL REFERENCES public.campaigns(id) ON DELETE CASCADE,
|
||
|
|
lead_id uuid NOT NULL REFERENCES public.leads(id) ON DELETE CASCADE,
|
||
|
|
organization_id uuid NOT NULL REFERENCES public.organizations(id),
|
||
|
|
status text DEFAULT 'pending'::text,
|
||
|
|
qualification_data jsonb DEFAULT '{}'::jsonb,
|
||
|
|
outreach_template jsonb DEFAULT '{}'::jsonb,
|
||
|
|
created_at timestamptz DEFAULT now(),
|
||
|
|
CONSTRAINT campaign_leads_status_check CHECK (status IN ('pending', 'qualified', 'disqualified'))
|
||
|
|
);
|
||
|
|
|
||
|
|
CREATE UNIQUE INDEX IF NOT EXISTS campaign_leads_unique ON public.campaign_leads (campaign_id, lead_id);
|
||
|
|
|
||
|
|
-- 5. Create lead_contacts table
|
||
|
|
CREATE TABLE IF NOT EXISTS public.lead_contacts (
|
||
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
|
|
lead_id uuid NOT NULL REFERENCES public.leads(id) ON DELETE CASCADE,
|
||
|
|
organization_id uuid NOT NULL REFERENCES public.organizations(id),
|
||
|
|
provider_id text,
|
||
|
|
member_urn text,
|
||
|
|
public_id text,
|
||
|
|
name text NOT NULL,
|
||
|
|
first_name text,
|
||
|
|
last_name text,
|
||
|
|
headline text,
|
||
|
|
linkedin_url text NOT NULL,
|
||
|
|
connection_status text DEFAULT 'none'::text,
|
||
|
|
claimed_by_id uuid REFERENCES auth.users(id),
|
||
|
|
created_at timestamptz DEFAULT now(),
|
||
|
|
CONSTRAINT lead_contacts_connection_status_check CHECK (connection_status IN ('none', 'pending', 'retry_pending', 'accepted', 'no_contact'))
|
||
|
|
);
|
||
|
|
|
||
|
|
-- 6. Enable RLS
|
||
|
|
ALTER TABLE public.campaigns ENABLE ROW LEVEL SECURITY;
|
||
|
|
ALTER TABLE public.leads ENABLE ROW LEVEL SECURITY;
|
||
|
|
ALTER TABLE public.campaign_leads ENABLE ROW LEVEL SECURITY;
|
||
|
|
ALTER TABLE public.lead_contacts ENABLE ROW LEVEL SECURITY;
|
||
|
|
|
||
|
|
-- 7. Implement RLS Policies
|
||
|
|
|
||
|
|
-- 7.1 Organizations Policies
|
||
|
|
DO $$
|
||
|
|
BEGIN
|
||
|
|
IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE policyname = 'tenant_can_view_organization') THEN
|
||
|
|
CREATE POLICY "tenant_can_view_organization" ON public.organizations
|
||
|
|
FOR SELECT USING ((SELECT organization_id FROM public.profiles WHERE id = auth.uid()) = id);
|
||
|
|
END IF;
|
||
|
|
|
||
|
|
IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE policyname = 'tenant_owner_can_update_organization') THEN
|
||
|
|
CREATE POLICY "tenant_owner_can_update_organization" ON public.organizations
|
||
|
|
FOR UPDATE USING ((SELECT organization_id FROM public.profiles WHERE id = auth.uid() AND role = 'owner') = id);
|
||
|
|
END IF;
|
||
|
|
END $$;
|
||
|
|
|
||
|
|
-- 7.2 Campaigns Policies
|
||
|
|
DO $$
|
||
|
|
BEGIN
|
||
|
|
IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE policyname = 'tenant_can_all_campaigns') THEN
|
||
|
|
CREATE POLICY "tenant_can_all_campaigns" ON public.campaigns
|
||
|
|
FOR ALL USING ((SELECT organization_id FROM public.profiles WHERE id = auth.uid()) = organization_id)
|
||
|
|
WITH CHECK ((SELECT organization_id FROM public.profiles WHERE id = auth.uid()) = organization_id);
|
||
|
|
END IF;
|
||
|
|
END $$;
|
||
|
|
|
||
|
|
-- 7.3 Leads Policies (Global cache)
|
||
|
|
DO $$
|
||
|
|
BEGIN
|
||
|
|
IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE policyname = 'authenticated_can_view_leads') THEN
|
||
|
|
CREATE POLICY "authenticated_can_view_leads" ON public.leads
|
||
|
|
FOR SELECT TO authenticated USING (true);
|
||
|
|
END IF;
|
||
|
|
|
||
|
|
IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE policyname = 'authenticated_can_insert_leads') THEN
|
||
|
|
CREATE POLICY "authenticated_can_insert_leads" ON public.leads
|
||
|
|
FOR INSERT TO authenticated WITH CHECK (true);
|
||
|
|
END IF;
|
||
|
|
END $$;
|
||
|
|
|
||
|
|
-- 7.4 Campaign Leads Policies
|
||
|
|
DO $$
|
||
|
|
BEGIN
|
||
|
|
IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE policyname = 'tenant_can_all_campaign_leads') THEN
|
||
|
|
CREATE POLICY "tenant_can_all_campaign_leads" ON public.campaign_leads
|
||
|
|
FOR ALL USING ((SELECT organization_id FROM public.profiles WHERE id = auth.uid()) = organization_id)
|
||
|
|
WITH CHECK ((SELECT organization_id FROM public.profiles WHERE id = auth.uid()) = organization_id);
|
||
|
|
END IF;
|
||
|
|
END $$;
|
||
|
|
|
||
|
|
-- 7.5 Lead Contacts Policies
|
||
|
|
DO $$
|
||
|
|
BEGIN
|
||
|
|
IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE policyname = 'tenant_can_all_lead_contacts') THEN
|
||
|
|
CREATE POLICY "tenant_can_all_lead_contacts" ON public.lead_contacts
|
||
|
|
FOR ALL USING ((SELECT organization_id FROM public.profiles WHERE id = auth.uid()) = organization_id)
|
||
|
|
WITH CHECK ((SELECT organization_id FROM public.profiles WHERE id = auth.uid()) = organization_id);
|
||
|
|
END IF;
|
||
|
|
END $$;
|