-- ============================================================ -- CONTEXTA - Supabase Database Schema -- Run this in your Supabase SQL Editor -- ============================================================ -- Enable UUID extension CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; -- ─── Companies ──────────────────────────────────────────────────────────────── CREATE TABLE IF NOT EXISTS companies ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), owner_id UUID REFERENCES auth.users(id) ON DELETE CASCADE, name VARCHAR(255) NOT NULL, website VARCHAR(255), industry VARCHAR(100), logo_url TEXT, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); -- ─── Subscriptions ──────────────────────────────────────────────────────────── CREATE TABLE IF NOT EXISTS subscriptions ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE UNIQUE, plan VARCHAR(50) DEFAULT 'free', status VARCHAR(50) DEFAULT 'active', stripe_customer_id VARCHAR(255) UNIQUE, stripe_subscription_id VARCHAR(255), stripe_price_id VARCHAR(255), current_period_start TIMESTAMPTZ, current_period_end TIMESTAMPTZ, chatbots_published INT DEFAULT 0, conversations_used INT DEFAULT 0, trial_end TIMESTAMPTZ, canceled_at TIMESTAMPTZ, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); -- ─── Chatbots ───────────────────────────────────────────────────────────────── CREATE TABLE IF NOT EXISTS chatbots ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), company_id UUID REFERENCES companies(id) ON DELETE CASCADE, name VARCHAR(255) NOT NULL, description TEXT, system_prompt TEXT, model VARCHAR(200) DEFAULT 'accounts/fireworks/models/llama-v3p1-70b-instruct', temperature DECIMAL(3,2) DEFAULT 0.70, max_tokens INT DEFAULT 1000, primary_color VARCHAR(20) DEFAULT '#6366f1', welcome_message TEXT DEFAULT 'Hello! How can I help you today?', category VARCHAR(100), industry VARCHAR(100), languages JSONB DEFAULT '["en"]', visibility VARCHAR(50) DEFAULT 'preview', is_published BOOLEAN DEFAULT FALSE, qdrant_collection_name VARCHAR(255) UNIQUE, average_rating DECIMAL(3,1), total_conversations INT DEFAULT 0, is_featured BOOLEAN DEFAULT FALSE, published_at TIMESTAMPTZ, unpublished_at TIMESTAMPTZ, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); -- ─── Documents ──────────────────────────────────────────────────────────────── CREATE TABLE IF NOT EXISTS documents ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), chatbot_id UUID REFERENCES chatbots(id) ON DELETE CASCADE, file_name VARCHAR(500) NOT NULL, file_type VARCHAR(50), file_size BIGINT DEFAULT 0, file_url TEXT, chunk_count INT DEFAULT 0, status VARCHAR(50) DEFAULT 'pending', error_message TEXT, created_at TIMESTAMPTZ DEFAULT NOW() ); -- ─── Conversations ──────────────────────────────────────────────────────────── CREATE TABLE IF NOT EXISTS conversations ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), chatbot_id UUID REFERENCES chatbots(id) ON DELETE CASCADE, user_id UUID REFERENCES auth.users(id) ON DELETE SET NULL, session_id UUID, language VARCHAR(20) DEFAULT 'en', message_count INT DEFAULT 0, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); -- ─── Messages ───────────────────────────────────────────────────────────────── CREATE TABLE IF NOT EXISTS messages ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), conversation_id UUID REFERENCES conversations(id) ON DELETE CASCADE, role VARCHAR(50) NOT NULL, content TEXT NOT NULL, sources JSONB, model VARCHAR(200), tokens_used INT DEFAULT 0, created_at TIMESTAMPTZ DEFAULT NOW() ); -- ─── Indexes ────────────────────────────────────────────────────────────────── CREATE INDEX IF NOT EXISTS idx_companies_owner ON companies(owner_id); CREATE INDEX IF NOT EXISTS idx_chatbots_company ON chatbots(company_id); CREATE INDEX IF NOT EXISTS idx_chatbots_published ON chatbots(is_published, visibility); CREATE INDEX IF NOT EXISTS idx_documents_chatbot ON documents(chatbot_id); CREATE INDEX IF NOT EXISTS idx_conversations_chatbot ON conversations(chatbot_id); CREATE INDEX IF NOT EXISTS idx_conversations_session ON conversations(session_id); CREATE INDEX IF NOT EXISTS idx_messages_conversation ON messages(conversation_id); CREATE INDEX IF NOT EXISTS idx_subscriptions_user ON subscriptions(user_id); -- ─── RLS Policies ───────────────────────────────────────────────────────────── ALTER TABLE companies ENABLE ROW LEVEL SECURITY; ALTER TABLE subscriptions ENABLE ROW LEVEL SECURITY; ALTER TABLE chatbots ENABLE ROW LEVEL SECURITY; ALTER TABLE documents ENABLE ROW LEVEL SECURITY; ALTER TABLE conversations ENABLE ROW LEVEL SECURITY; ALTER TABLE messages ENABLE ROW LEVEL SECURITY; -- Companies: users can only see/edit their own CREATE POLICY "companies_own" ON companies FOR ALL USING (auth.uid() = owner_id); -- Subscriptions: users can only see their own CREATE POLICY "subscriptions_own" ON subscriptions FOR ALL USING (auth.uid() = user_id); -- Chatbots: owners can manage, public can read published CREATE POLICY "chatbots_owner" ON chatbots FOR ALL USING ( company_id IN (SELECT id FROM companies WHERE owner_id = auth.uid()) ); CREATE POLICY "chatbots_public_read" ON chatbots FOR SELECT USING (is_published = TRUE AND visibility = 'published'); -- Documents: only chatbot owners CREATE POLICY "documents_owner" ON documents FOR ALL USING ( chatbot_id IN ( SELECT c.id FROM chatbots c JOIN companies co ON c.company_id = co.id WHERE co.owner_id = auth.uid() ) ); -- Conversations & Messages: open for anonymous users to create CREATE POLICY "conversations_insert" ON conversations FOR INSERT WITH CHECK (true); CREATE POLICY "conversations_select" ON conversations FOR SELECT USING (true); CREATE POLICY "messages_insert" ON messages FOR INSERT WITH CHECK (true); CREATE POLICY "messages_select" ON messages FOR SELECT USING (true); -- ─── Marketplace View ───────────────────────────────────────────────────────── CREATE OR REPLACE VIEW marketplace_chatbots AS SELECT c.id, c.name, c.description, c.category, c.industry, c.languages, c.primary_color, c.welcome_message, c.average_rating, c.total_conversations, c.is_featured, c.published_at, c.created_at, co.name AS company_name, co.logo_url AS company_logo FROM chatbots c JOIN companies co ON c.company_id = co.id JOIN subscriptions s ON co.owner_id = s.user_id WHERE c.visibility = 'published' AND c.is_published = TRUE AND s.status = 'active' AND s.plan IN ('starter', 'pro', 'enterprise'); -- ─── Auto-unpublish on subscription cancel ──────────────────────────────────── CREATE OR REPLACE FUNCTION unpublish_on_subscription_end() RETURNS TRIGGER AS $$ BEGIN IF NEW.status IN ('canceled', 'unpaid', 'past_due') THEN UPDATE chatbots SET visibility = 'preview', is_published = FALSE WHERE company_id IN ( SELECT id FROM companies WHERE owner_id = NEW.user_id ); END IF; RETURN NEW; END; $$ LANGUAGE plpgsql SECURITY DEFINER; DROP TRIGGER IF EXISTS subscription_status_change ON subscriptions; CREATE TRIGGER subscription_status_change AFTER UPDATE ON subscriptions FOR EACH ROW WHEN (OLD.status IS DISTINCT FROM NEW.status) EXECUTE FUNCTION unpublish_on_subscription_end();