mirror of
http://88.130.71.182:3000/BlitTech/contexta_be.git
synced 2026-06-12 23:23:21 +00:00
193 lines
8.7 KiB
PL/PgSQL
193 lines
8.7 KiB
PL/PgSQL
-- ============================================================
|
|
-- 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();
|