nova-ai / supabase /schema.sql
shubhjn's picture
Add admin dashboard, database schema, payments, and security middleware
0eed197
-- Nova AI - Supabase Database Schema
-- Run this in Supabase SQL Editor
-- Enable UUID extension
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-- Profiles table (extends Supabase auth.users)
CREATE TABLE IF NOT EXISTS profiles (
id UUID REFERENCES auth.users ON DELETE CASCADE PRIMARY KEY,
email TEXT UNIQUE,
display_name TEXT,
avatar_url TEXT,
-- Subscription
subscription_tier TEXT DEFAULT 'free' CHECK (subscription_tier IN ('free', 'pro')),
tokens_used INTEGER DEFAULT 0,
tokens_limit INTEGER DEFAULT 20,
tokens_reset_at TIMESTAMPTZ DEFAULT (NOW() + INTERVAL '30 days'),
-- Consent
consent_given BOOLEAN DEFAULT FALSE,
consent_given_at TIMESTAMPTZ,
data_collection_consent BOOLEAN DEFAULT FALSE,
terms_accepted_at TIMESTAMPTZ,
-- Preferences
theme TEXT DEFAULT 'system',
notifications_enabled BOOLEAN DEFAULT TRUE,
-- Admin
is_admin BOOLEAN DEFAULT FALSE,
-- Timestamps
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- Chat history
CREATE TABLE IF NOT EXISTS chat_history (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID REFERENCES profiles(id) ON DELETE CASCADE,
title TEXT DEFAULT 'New Chat',
messages JSONB DEFAULT '[]'::jsonb,
model_id TEXT,
is_local BOOLEAN DEFAULT FALSE,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- Blocked IPs (security)
CREATE TABLE IF NOT EXISTS blocked_ips (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
ip_address TEXT NOT NULL,
device_info JSONB DEFAULT '{}'::jsonb,
user_agent TEXT,
reason TEXT,
attempt_count INTEGER DEFAULT 1,
blocked_at TIMESTAMPTZ DEFAULT NOW(),
blocked_by UUID REFERENCES profiles(id),
is_active BOOLEAN DEFAULT TRUE
);
-- Subscriptions
CREATE TABLE IF NOT EXISTS subscriptions (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID REFERENCES profiles(id) ON DELETE CASCADE,
tier TEXT NOT NULL CHECK (tier IN ('free', 'pro', 'addon')),
razorpay_subscription_id TEXT,
razorpay_order_id TEXT,
amount INTEGER, -- in paise
currency TEXT DEFAULT 'INR',
status TEXT DEFAULT 'active' CHECK (status IN ('active', 'cancelled', 'expired', 'pending')),
started_at TIMESTAMPTZ DEFAULT NOW(),
expires_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Token purchases (add-ons)
CREATE TABLE IF NOT EXISTS token_purchases (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID REFERENCES profiles(id) ON DELETE CASCADE,
tokens_purchased INTEGER NOT NULL,
amount INTEGER NOT NULL, -- in paise
razorpay_payment_id TEXT,
status TEXT DEFAULT 'pending' CHECK (status IN ('pending', 'completed', 'failed')),
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Admin notifications
CREATE TABLE IF NOT EXISTS admin_notifications (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
type TEXT NOT NULL, -- 'security', 'system', 'user', 'payment'
title TEXT NOT NULL,
message TEXT,
metadata JSONB DEFAULT '{}'::jsonb,
is_read BOOLEAN DEFAULT FALSE,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Task queue (for background processing tracking)
CREATE TABLE IF NOT EXISTS task_queue (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID REFERENCES profiles(id) ON DELETE CASCADE,
task_type TEXT NOT NULL, -- 'chat', 'image', 'analysis'
task_data JSONB NOT NULL,
result JSONB,
status TEXT DEFAULT 'pending' CHECK (status IN ('pending', 'processing', 'completed', 'failed')),
error_message TEXT,
created_at TIMESTAMPTZ DEFAULT NOW(),
completed_at TIMESTAMPTZ
);
-- API usage logs
CREATE TABLE IF NOT EXISTS api_usage (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID REFERENCES profiles(id) ON DELETE SET NULL,
endpoint TEXT NOT NULL,
method TEXT,
ip_address TEXT,
tokens_used INTEGER DEFAULT 0,
response_time_ms INTEGER,
status_code INTEGER,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Create indexes for performance
CREATE INDEX IF NOT EXISTS idx_profiles_email ON profiles(email);
CREATE INDEX IF NOT EXISTS idx_chat_history_user ON chat_history(user_id);
CREATE INDEX IF NOT EXISTS idx_chat_history_updated ON chat_history(updated_at DESC);
CREATE INDEX IF NOT EXISTS idx_blocked_ips_address ON blocked_ips(ip_address);
CREATE INDEX IF NOT EXISTS idx_blocked_ips_active ON blocked_ips(is_active);
CREATE INDEX IF NOT EXISTS idx_subscriptions_user ON subscriptions(user_id);
CREATE INDEX IF NOT EXISTS idx_task_queue_user ON task_queue(user_id);
CREATE INDEX IF NOT EXISTS idx_task_queue_status ON task_queue(status);
CREATE INDEX IF NOT EXISTS idx_api_usage_user ON api_usage(user_id);
CREATE INDEX IF NOT EXISTS idx_api_usage_created ON api_usage(created_at DESC);
CREATE INDEX IF NOT EXISTS idx_admin_notifications_unread ON admin_notifications(is_read) WHERE is_read = FALSE;
-- Row Level Security (RLS)
ALTER TABLE profiles ENABLE ROW LEVEL SECURITY;
ALTER TABLE chat_history ENABLE ROW LEVEL SECURITY;
ALTER TABLE subscriptions ENABLE ROW LEVEL SECURITY;
ALTER TABLE token_purchases ENABLE ROW LEVEL SECURITY;
ALTER TABLE task_queue ENABLE ROW LEVEL SECURITY;
-- Profiles: Users can read/update their own profile
CREATE POLICY "Users can view own profile" ON profiles
FOR SELECT USING (auth.uid() = id);
CREATE POLICY "Users can update own profile" ON profiles
FOR UPDATE USING (auth.uid() = id);
-- Chat history: Users can manage their own chats
CREATE POLICY "Users can view own chats" ON chat_history
FOR SELECT USING (auth.uid() = user_id);
CREATE POLICY "Users can insert own chats" ON chat_history
FOR INSERT WITH CHECK (auth.uid() = user_id);
CREATE POLICY "Users can update own chats" ON chat_history
FOR UPDATE USING (auth.uid() = user_id);
CREATE POLICY "Users can delete own chats" ON chat_history
FOR DELETE USING (auth.uid() = user_id);
-- Subscriptions: Users can view their own
CREATE POLICY "Users can view own subscriptions" ON subscriptions
FOR SELECT USING (auth.uid() = user_id);
-- Token purchases: Users can view their own
CREATE POLICY "Users can view own purchases" ON token_purchases
FOR SELECT USING (auth.uid() = user_id);
-- Task queue: Users can manage their own tasks
CREATE POLICY "Users can view own tasks" ON task_queue
FOR SELECT USING (auth.uid() = user_id);
CREATE POLICY "Users can insert own tasks" ON task_queue
FOR INSERT WITH CHECK (auth.uid() = user_id);
-- Function to handle new user signup
CREATE OR REPLACE FUNCTION handle_new_user()
RETURNS TRIGGER AS $$
BEGIN
INSERT INTO profiles (id, email, display_name)
VALUES (
NEW.id,
NEW.email,
COALESCE(NEW.raw_user_meta_data->>'full_name', split_part(NEW.email, '@', 1))
);
RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- Trigger for new user signup
DROP TRIGGER IF EXISTS on_auth_user_created ON auth.users;
CREATE TRIGGER on_auth_user_created
AFTER INSERT ON auth.users
FOR EACH ROW EXECUTE FUNCTION handle_new_user();
-- Function to update tokens_used
CREATE OR REPLACE FUNCTION use_tokens(p_user_id UUID, p_tokens INTEGER)
RETURNS BOOLEAN AS $$
DECLARE
v_tokens_used INTEGER;
v_tokens_limit INTEGER;
BEGIN
SELECT tokens_used, tokens_limit INTO v_tokens_used, v_tokens_limit
FROM profiles WHERE id = p_user_id;
IF v_tokens_used + p_tokens > v_tokens_limit THEN
RETURN FALSE;
END IF;
UPDATE profiles
SET tokens_used = tokens_used + p_tokens, updated_at = NOW()
WHERE id = p_user_id;
RETURN TRUE;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- Function to reset monthly tokens
CREATE OR REPLACE FUNCTION reset_monthly_tokens()
RETURNS void AS $$
BEGIN
UPDATE profiles
SET tokens_used = 0, tokens_reset_at = NOW() + INTERVAL '30 days'
WHERE tokens_reset_at < NOW();
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- Admin check function
CREATE OR REPLACE FUNCTION is_admin(p_user_id UUID)
RETURNS BOOLEAN AS $$
BEGIN
RETURN EXISTS (SELECT 1 FROM profiles WHERE id = p_user_id AND is_admin = TRUE);
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;