File size: 8,417 Bytes
8de5b21 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 |
-- Supabase Database Schema for SanadCheck LLM API
-- Execute these scripts in your Supabase SQL Editor
-- Enable necessary extensions
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-- Create custom user roles enum
CREATE TYPE user_role AS ENUM ('user', 'admin', 'researcher');
-- Create users table (extends Supabase auth.users)
CREATE TABLE IF NOT EXISTS public.users (
id UUID PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE,
email TEXT UNIQUE NOT NULL,
username TEXT UNIQUE,
full_name TEXT,
role user_role DEFAULT 'user'::user_role,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
last_login TIMESTAMP WITH TIME ZONE,
-- Constraints
CONSTRAINT valid_email CHECK (email ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$'),
CONSTRAINT username_length CHECK (length(username) >= 3 AND length(username) <= 50),
CONSTRAINT full_name_length CHECK (length(full_name) <= 100)
);
-- Create user_sessions table for JWT session management
CREATE TABLE IF NOT EXISTS public.user_sessions (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE,
access_token TEXT NOT NULL,
refresh_token TEXT,
expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
last_used TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
ended_at TIMESTAMP WITH TIME ZONE,
user_agent TEXT,
ip_address INET,
-- Indexes for performance
CONSTRAINT unique_access_token UNIQUE (access_token)
);
-- Create narrator_extractions table for storing extraction results
CREATE TABLE IF NOT EXISTS public.narrator_extractions (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE,
session_id UUID REFERENCES public.user_sessions(id) ON DELETE SET NULL,
hadith_text TEXT NOT NULL,
extracted_narrators TEXT[] NOT NULL DEFAULT '{}',
sanad_chain TEXT NOT NULL,
success BOOLEAN NOT NULL DEFAULT FALSE,
error_message TEXT,
processing_time_ms INTEGER,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
ip_address INET,
-- Constraints
CONSTRAINT hadith_text_not_empty CHECK (length(trim(hadith_text)) > 0),
CONSTRAINT processing_time_positive CHECK (processing_time_ms IS NULL OR processing_time_ms >= 0)
);
-- Create narrator_analyses table for storing individual narrator analysis results
CREATE TABLE IF NOT EXISTS public.narrator_analyses (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE,
extraction_id UUID REFERENCES public.narrator_extractions(id) ON DELETE SET NULL,
narrator_name TEXT NOT NULL,
reliability_grade TEXT NOT NULL,
confidence_level TEXT NOT NULL,
reasoning TEXT NOT NULL,
scholarly_consensus TEXT NOT NULL,
known_issues TEXT,
biographical_info TEXT NOT NULL,
recommendation TEXT NOT NULL,
success BOOLEAN NOT NULL DEFAULT FALSE,
error_message TEXT,
processing_time_ms INTEGER,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
-- Constraints
CONSTRAINT narrator_name_not_empty CHECK (length(trim(narrator_name)) > 0),
CONSTRAINT reliability_grade_valid CHECK (reliability_grade IN ('Thiqah', 'Saduq', 'Da''if', 'Matruk', 'Majhul')),
CONSTRAINT confidence_level_valid CHECK (confidence_level IN ('High', 'Medium', 'Low')),
CONSTRAINT processing_time_positive CHECK (processing_time_ms IS NULL OR processing_time_ms >= 0)
);
-- Create indexes for better performance
CREATE INDEX IF NOT EXISTS idx_user_sessions_user_id ON public.user_sessions(user_id);
CREATE INDEX IF NOT EXISTS idx_user_sessions_access_token ON public.user_sessions(access_token);
CREATE INDEX IF NOT EXISTS idx_user_sessions_expires_at ON public.user_sessions(expires_at);
CREATE INDEX IF NOT EXISTS idx_user_sessions_active ON public.user_sessions(user_id) WHERE ended_at IS NULL;
CREATE INDEX IF NOT EXISTS idx_narrator_extractions_user_id ON public.narrator_extractions(user_id);
CREATE INDEX IF NOT EXISTS idx_narrator_extractions_created_at ON public.narrator_extractions(created_at);
CREATE INDEX IF NOT EXISTS idx_narrator_extractions_success ON public.narrator_extractions(success);
CREATE INDEX IF NOT EXISTS idx_narrator_analyses_user_id ON public.narrator_analyses(user_id);
CREATE INDEX IF NOT EXISTS idx_narrator_analyses_extraction_id ON public.narrator_analyses(extraction_id);
CREATE INDEX IF NOT EXISTS idx_narrator_analyses_narrator_name ON public.narrator_analyses(narrator_name);
CREATE INDEX IF NOT EXISTS idx_narrator_analyses_created_at ON public.narrator_analyses(created_at);
-- Create updated_at trigger function
CREATE OR REPLACE FUNCTION public.update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ language 'plpgsql';
-- Add updated_at trigger to users table
CREATE TRIGGER update_users_updated_at
BEFORE UPDATE ON public.users
FOR EACH ROW
EXECUTE FUNCTION public.update_updated_at_column();
-- Row Level Security (RLS) Policies
-- Enable RLS on all tables
ALTER TABLE public.users ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.user_sessions ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.narrator_extractions ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.narrator_analyses ENABLE ROW LEVEL SECURITY;
-- Users table policies
CREATE POLICY "Users can view own profile" ON public.users
FOR SELECT USING (auth.uid() = id);
CREATE POLICY "Users can update own profile" ON public.users
FOR UPDATE USING (auth.uid() = id);
CREATE POLICY "Service role can manage all users" ON public.users
FOR ALL USING (auth.role() = 'service_role');
-- User sessions policies
CREATE POLICY "Users can view own sessions" ON public.user_sessions
FOR SELECT USING (auth.uid() = user_id);
CREATE POLICY "Service role can manage all sessions" ON public.user_sessions
FOR ALL USING (auth.role() = 'service_role');
-- Narrator extractions policies
CREATE POLICY "Users can view own extractions" ON public.narrator_extractions
FOR SELECT USING (auth.uid() = user_id);
CREATE POLICY "Users can insert own extractions" ON public.narrator_extractions
FOR INSERT WITH CHECK (auth.uid() = user_id);
CREATE POLICY "Service role can manage all extractions" ON public.narrator_extractions
FOR ALL USING (auth.role() = 'service_role');
-- Narrator analyses policies
CREATE POLICY "Users can view own analyses" ON public.narrator_analyses
FOR SELECT USING (auth.uid() = user_id);
CREATE POLICY "Users can insert own analyses" ON public.narrator_analyses
FOR INSERT WITH CHECK (auth.uid() = user_id);
CREATE POLICY "Service role can manage all analyses" ON public.narrator_analyses
FOR ALL USING (auth.role() = 'service_role');
-- Grant necessary permissions
GRANT USAGE ON SCHEMA public TO anon, authenticated;
GRANT ALL ON ALL TABLES IN SCHEMA public TO anon, authenticated;
GRANT ALL ON ALL SEQUENCES IN SCHEMA public TO anon, authenticated;
-- Create a function to automatically create user profile on signup
CREATE OR REPLACE FUNCTION public.handle_new_user()
RETURNS TRIGGER AS $$
BEGIN
INSERT INTO public.users (id, email, username, full_name)
VALUES (
NEW.id,
NEW.email,
COALESCE(NEW.raw_user_meta_data->>'username', NULL),
COALESCE(NEW.raw_user_meta_data->>'full_name', NULL)
);
RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- Create trigger to run the function on new user signup
CREATE TRIGGER on_auth_user_created
AFTER INSERT ON auth.users
FOR EACH ROW EXECUTE FUNCTION public.handle_new_user();
-- Create a view for user analytics
CREATE VIEW public.user_analytics AS
SELECT
u.id,
u.email,
u.role,
u.created_at,
u.last_login,
COUNT(ne.id) as total_extractions,
COUNT(na.id) as total_analyses,
COUNT(us.id) as total_sessions
FROM public.users u
LEFT JOIN public.narrator_extractions ne ON u.id = ne.user_id
LEFT JOIN public.narrator_analyses na ON u.id = na.user_id
LEFT JOIN public.user_sessions us ON u.id = us.user_id
GROUP BY u.id, u.email, u.role, u.created_at, u.last_login;
-- Grant select on the view
GRANT SELECT ON public.user_analytics TO authenticated;
|