-- Medical AI Platform Database Schema -- This is the ONLY schema file you need to run -- Run this in Supabase SQL Editor -- ============================================================================ -- STEP 1: Enable required extensions -- ============================================================================ CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; CREATE EXTENSION IF NOT EXISTS "vector"; -- ============================================================================ -- STEP 2: Create all tables -- ============================================================================ -- Users table CREATE TABLE IF NOT EXISTS public.users ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), email TEXT UNIQUE NOT NULL, name TEXT, plan TEXT NOT NULL DEFAULT 'free' CHECK (plan IN ('free', 'student', 'pro', 'admin')), role TEXT CHECK (role IN ('super_admin', 'admin', 'ops', 'support', 'viewer')), personal_api_key TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), disabled BOOLEAN NOT NULL DEFAULT FALSE ); -- Admin allowlist table CREATE TABLE IF NOT EXISTS public.admin_allowlist ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), email TEXT UNIQUE NOT NULL, role TEXT NOT NULL CHECK (role IN ('super_admin', 'admin', 'ops', 'support', 'viewer')), added_by UUID REFERENCES public.users(id), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- Usage counters table CREATE TABLE IF NOT EXISTS public.usage_counters ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), user_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE, date DATE NOT NULL DEFAULT CURRENT_DATE, tokens_used INTEGER NOT NULL DEFAULT 0, requests_count INTEGER NOT NULL DEFAULT 0, pdf_uploads INTEGER NOT NULL DEFAULT 0, mcqs_generated INTEGER NOT NULL DEFAULT 0, images_used INTEGER NOT NULL DEFAULT 0, flashcards_generated INTEGER NOT NULL DEFAULT 0, UNIQUE(user_id, date) ); -- API keys table CREATE TABLE IF NOT EXISTS public.api_keys ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), provider TEXT NOT NULL, feature TEXT NOT NULL, key_value TEXT NOT NULL, priority INTEGER NOT NULL DEFAULT 0, status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'degraded', 'disabled')), failure_count INTEGER NOT NULL DEFAULT 0, last_used_at TIMESTAMPTZ, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- Provider health table CREATE TABLE IF NOT EXISTS public.provider_health ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), api_key_id UUID NOT NULL REFERENCES public.api_keys(id) ON DELETE CASCADE, checked_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), status TEXT NOT NULL CHECK (status IN ('healthy', 'degraded', 'failed')), response_time_ms INTEGER, error_message TEXT, quota_remaining INTEGER ); -- System flags table CREATE TABLE IF NOT EXISTS public.system_flags ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), flag_name TEXT UNIQUE NOT NULL, flag_value TEXT NOT NULL, updated_by UUID REFERENCES public.users(id), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- Documents table CREATE TABLE IF NOT EXISTS public.documents ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), user_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE, filename TEXT NOT NULL, file_type TEXT NOT NULL CHECK (file_type IN ('pdf', 'image')), file_size INTEGER NOT NULL, storage_path TEXT NOT NULL, processing_status TEXT NOT NULL DEFAULT 'pending' CHECK (processing_status IN ('pending', 'processing', 'completed', 'failed')), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- Embeddings table CREATE TABLE IF NOT EXISTS public.embeddings ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), document_id UUID NOT NULL REFERENCES public.documents(id) ON DELETE CASCADE, chunk_text TEXT NOT NULL, chunk_index INTEGER NOT NULL, embedding VECTOR(768), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- Chat sessions table CREATE TABLE IF NOT EXISTS public.chat_sessions ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), user_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE, title TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- Messages table CREATE TABLE IF NOT EXISTS public.messages ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), session_id UUID NOT NULL REFERENCES public.chat_sessions(id) ON DELETE CASCADE, role TEXT NOT NULL CHECK (role IN ('user', 'assistant', 'system')), content TEXT NOT NULL, tokens_used INTEGER, citations JSONB, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- Study sessions table CREATE TABLE IF NOT EXISTS public.study_sessions ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), user_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE, topic TEXT NOT NULL, duration INTEGER NOT NULL, scheduled_date TIMESTAMPTZ, notes TEXT, status TEXT NOT NULL DEFAULT 'planned' CHECK (status IN ('planned', 'in_progress', 'completed', 'cancelled')), completed_at TIMESTAMPTZ, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- Subscriptions table CREATE TABLE IF NOT EXISTS public.subscriptions ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), user_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE, plan TEXT NOT NULL CHECK (plan IN ('free', 'student', 'pro', 'admin')), razorpay_subscription_id TEXT UNIQUE, status TEXT NOT NULL CHECK (status IN ('active', 'cancelled', 'expired')), current_period_start TIMESTAMPTZ, current_period_end TIMESTAMPTZ, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- Payments table CREATE TABLE IF NOT EXISTS public.payments ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), user_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE, subscription_id UUID REFERENCES public.subscriptions(id), razorpay_payment_id TEXT UNIQUE, amount INTEGER NOT NULL, currency TEXT NOT NULL DEFAULT 'INR', status TEXT NOT NULL CHECK (status IN ('success', 'failed', 'pending')), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- Audit logs table CREATE TABLE IF NOT EXISTS public.audit_logs ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), admin_id UUID REFERENCES public.users(id), action_type TEXT NOT NULL, target_type TEXT, target_id TEXT, details JSONB, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- ============================================================================ -- STEP 3: Create indexes -- ============================================================================ CREATE INDEX IF NOT EXISTS idx_users_email ON public.users(email); CREATE INDEX IF NOT EXISTS idx_admin_allowlist_email ON public.admin_allowlist(email); CREATE INDEX IF NOT EXISTS idx_usage_counters_user_date ON public.usage_counters(user_id, date); CREATE INDEX IF NOT EXISTS idx_api_keys_provider_feature ON public.api_keys(provider, feature); CREATE INDEX IF NOT EXISTS idx_chat_sessions_user_id ON public.chat_sessions(user_id); CREATE INDEX IF NOT EXISTS idx_messages_session_created ON public.messages(session_id, created_at); CREATE INDEX IF NOT EXISTS idx_documents_user_id ON public.documents(user_id); CREATE INDEX IF NOT EXISTS idx_audit_logs_admin_created ON public.audit_logs(admin_id, created_at DESC); -- ============================================================================ -- STEP 4: Create trigger for updated_at -- ============================================================================ CREATE OR REPLACE FUNCTION update_updated_at_column() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at = NOW(); RETURN NEW; END; $$ LANGUAGE plpgsql; DROP TRIGGER IF EXISTS update_users_updated_at ON public.users; CREATE TRIGGER update_users_updated_at BEFORE UPDATE ON public.users FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); DROP TRIGGER IF EXISTS update_chat_sessions_updated_at ON public.chat_sessions; CREATE TRIGGER update_chat_sessions_updated_at BEFORE UPDATE ON public.chat_sessions FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); -- ============================================================================ -- DONE! -- ============================================================================ SELECT 'Schema created successfully!' as status;