-- CREdispo Local PostgreSQL Schema (no Supabase dependencies) -- Enable UUID extension CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; -- ============================================ -- USERS TABLE -- ============================================ CREATE TABLE public.users ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), email TEXT UNIQUE NOT NULL, password_hash TEXT, role TEXT NOT NULL DEFAULT 'agent' CHECK (role IN ('admin', 'agent', 'buyer')), name TEXT NOT NULL, company TEXT, phone TEXT, avatar_url TEXT, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); -- ============================================ -- SELLER LEADS TABLE -- ============================================ CREATE TABLE public.seller_leads ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), agent_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE, address TEXT NOT NULL, asset_type TEXT NOT NULL CHECK (asset_type IN ( 'Office', 'Industrial', 'Retail', 'Multifamily', 'Mixed-Use', 'Land', 'Hospitality', 'Other' )), asking_price NUMERIC(15, 2), cap_rate NUMERIC(5, 2), noi NUMERIC(15, 2), sqft INTEGER, units INTEGER, year_built INTEGER, market TEXT NOT NULL, city TEXT, state TEXT, notes TEXT, status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'pending', 'matched', 'closed', 'withdrawn')), created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); -- ============================================ -- SELLER DOCUMENTS TABLE -- ============================================ CREATE TABLE public.seller_documents ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), lead_id UUID NOT NULL REFERENCES public.seller_leads(id) ON DELETE CASCADE, file_url TEXT NOT NULL, file_name TEXT NOT NULL, file_type TEXT NOT NULL, file_size INTEGER, created_at TIMESTAMPTZ DEFAULT NOW() ); -- ============================================ -- BUYERS TABLE -- ============================================ CREATE TABLE public.buyers ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), agent_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE, user_id UUID REFERENCES public.users(id) ON DELETE SET NULL, name TEXT NOT NULL, company TEXT, email TEXT NOT NULL, phone TEXT, asset_types TEXT[] NOT NULL DEFAULT '{}', target_locations TEXT[] NOT NULL DEFAULT '{}', price_min NUMERIC(15, 2), price_max NUMERIC(15, 2), cap_rate_min NUMERIC(5, 2), cap_rate_max NUMERIC(5, 2), preferences TEXT, status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'unresponsive', 'high-intent', 'inactive')), engagement_score INTEGER DEFAULT 0, last_contacted_at TIMESTAMPTZ, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); -- ============================================ -- MATCHES TABLE -- ============================================ CREATE TABLE public.matches ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), lead_id UUID NOT NULL REFERENCES public.seller_leads(id) ON DELETE CASCADE, buyer_id UUID NOT NULL REFERENCES public.buyers(id) ON DELETE CASCADE, match_score NUMERIC(5, 2) NOT NULL DEFAULT 0, status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'approved', 'sent', 'interested', 'passed', 'closed')), agent_notes TEXT, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW(), UNIQUE(lead_id, buyer_id) ); -- ============================================ -- OUTREACH TABLE -- ============================================ CREATE TABLE public.outreach ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), match_id UUID NOT NULL REFERENCES public.matches(id) ON DELETE CASCADE, channel TEXT NOT NULL CHECK (channel IN ('email', 'sms')), subject TEXT, body TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'draft' CHECK (status IN ('draft', 'queued', 'sent', 'delivered', 'opened', 'clicked', 'replied', 'bounced', 'failed')), sent_at TIMESTAMPTZ, delivered_at TIMESTAMPTZ, opened_at TIMESTAMPTZ, clicked_at TIMESTAMPTZ, replied_at TIMESTAMPTZ, created_at TIMESTAMPTZ DEFAULT NOW() ); -- ============================================ -- NDAs TABLE -- ============================================ CREATE TABLE public.ndas ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), buyer_id UUID NOT NULL REFERENCES public.buyers(id) ON DELETE CASCADE, lead_id UUID NOT NULL REFERENCES public.seller_leads(id) ON DELETE CASCADE, signer_name TEXT NOT NULL, signer_email TEXT NOT NULL, signer_company TEXT, signed_at TIMESTAMPTZ DEFAULT NOW(), ip_address TEXT, user_agent TEXT, created_at TIMESTAMPTZ DEFAULT NOW(), UNIQUE(buyer_id, lead_id) ); -- ============================================ -- SUBSCRIPTIONS TABLE -- ============================================ CREATE TABLE public.subscriptions ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), user_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE, stripe_customer_id TEXT UNIQUE, stripe_subscription_id TEXT UNIQUE, status TEXT NOT NULL DEFAULT 'inactive' CHECK (status IN ('active', 'inactive', 'past_due', 'canceled', 'trialing')), plan TEXT DEFAULT 'pro', current_period_start TIMESTAMPTZ, current_period_end TIMESTAMPTZ, cancel_at_period_end BOOLEAN DEFAULT FALSE, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); -- ============================================ -- PROMO CODES TABLE -- ============================================ CREATE TABLE public.promo_codes ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), code TEXT UNIQUE NOT NULL, discount_percent INTEGER CHECK (discount_percent BETWEEN 1 AND 100), discount_amount NUMERIC(10, 2), max_uses INTEGER, current_uses INTEGER DEFAULT 0, expires_at TIMESTAMPTZ, active BOOLEAN DEFAULT TRUE, created_at TIMESTAMPTZ DEFAULT NOW() ); -- ============================================ -- INDEXES -- ============================================ CREATE INDEX idx_seller_leads_agent ON public.seller_leads(agent_id); CREATE INDEX idx_seller_leads_status ON public.seller_leads(status); CREATE INDEX idx_seller_leads_asset_type ON public.seller_leads(asset_type); CREATE INDEX idx_seller_leads_market ON public.seller_leads(market); CREATE INDEX idx_buyers_agent ON public.buyers(agent_id); CREATE INDEX idx_buyers_status ON public.buyers(status); CREATE INDEX idx_matches_lead ON public.matches(lead_id); CREATE INDEX idx_matches_buyer ON public.matches(buyer_id); CREATE INDEX idx_matches_status ON public.matches(status); CREATE INDEX idx_outreach_match ON public.outreach(match_id); CREATE INDEX idx_outreach_status ON public.outreach(status); CREATE INDEX idx_ndas_buyer ON public.ndas(buyer_id); CREATE INDEX idx_ndas_lead ON public.ndas(lead_id); CREATE INDEX idx_subscriptions_user ON public.subscriptions(user_id); CREATE INDEX idx_subscriptions_status ON public.subscriptions(status); -- ============================================ -- FUNCTIONS -- ============================================ -- Auto-update updated_at timestamp CREATE OR REPLACE FUNCTION update_updated_at() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at = NOW(); RETURN NEW; END; $$ LANGUAGE plpgsql; CREATE TRIGGER update_users_updated_at BEFORE UPDATE ON public.users FOR EACH ROW EXECUTE FUNCTION update_updated_at(); CREATE TRIGGER update_seller_leads_updated_at BEFORE UPDATE ON public.seller_leads FOR EACH ROW EXECUTE FUNCTION update_updated_at(); CREATE TRIGGER update_buyers_updated_at BEFORE UPDATE ON public.buyers FOR EACH ROW EXECUTE FUNCTION update_updated_at(); CREATE TRIGGER update_matches_updated_at BEFORE UPDATE ON public.matches FOR EACH ROW EXECUTE FUNCTION update_updated_at(); CREATE TRIGGER update_subscriptions_updated_at BEFORE UPDATE ON public.subscriptions FOR EACH ROW EXECUTE FUNCTION update_updated_at();