Spaces:
Sleeping
Sleeping
File size: 9,741 Bytes
f89de6b | 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 209 210 211 212 213 | -- ClauseGuard β Full Database Schema v3.1
-- FIX v4.1: Removed hardcoded admin email (was committed to public repo)
-- Tables ordered by dependency (no forward references)
-- βββ 1. Teams (no dependencies) βββ
CREATE TABLE IF NOT EXISTS public.teams (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL,
owner_id UUID REFERENCES auth.users NOT NULL,
plan TEXT DEFAULT 'team',
max_seats INT DEFAULT 5,
razorpay_subscription_id TEXT,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- βββ 2. Profiles (depends on teams) βββ
CREATE TABLE IF NOT EXISTS public.profiles (
id UUID REFERENCES auth.users ON DELETE CASCADE PRIMARY KEY,
email TEXT,
full_name TEXT,
avatar_url TEXT,
razorpay_subscription_id TEXT,
plan TEXT DEFAULT 'free' CHECK (plan IN ('free', 'pro', 'team')),
role TEXT DEFAULT 'user' CHECK (role IN ('user', 'admin')),
is_banned BOOLEAN DEFAULT false,
team_id UUID REFERENCES public.teams(id) ON DELETE SET NULL,
analyses_this_month INT DEFAULT 0,
monthly_reset_at TIMESTAMPTZ DEFAULT date_trunc('month', NOW()),
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- βββ 3. Team Invites (depends on teams) βββ
CREATE TABLE IF NOT EXISTS public.team_invites (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
team_id UUID REFERENCES public.teams ON DELETE CASCADE NOT NULL,
email TEXT NOT NULL,
role TEXT DEFAULT 'member' CHECK (role IN ('admin', 'member')),
status TEXT DEFAULT 'pending' CHECK (status IN ('pending', 'accepted', 'expired')),
invited_by UUID REFERENCES auth.users NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW(),
expires_at TIMESTAMPTZ DEFAULT NOW() + INTERVAL '7 days',
UNIQUE(team_id, email)
);
-- βββ 4. Analyses (depends on profiles, teams) βββ
CREATE TABLE IF NOT EXISTS public.analyses (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES public.profiles ON DELETE CASCADE NOT NULL,
team_id UUID REFERENCES public.teams ON DELETE SET NULL,
source_url TEXT,
source_type TEXT DEFAULT 'tos' CHECK (source_type IN ('tos', 'contract', 'rental', 'other')),
total_clauses INT NOT NULL,
flagged_count INT NOT NULL,
risk_score INT NOT NULL CHECK (risk_score >= 0 AND risk_score <= 100),
grade CHAR(1) NOT NULL CHECK (grade IN ('A', 'B', 'C', 'D', 'F')),
clauses JSONB NOT NULL DEFAULT '[]',
entities JSONB DEFAULT '[]',
contradictions JSONB DEFAULT '[]',
obligations JSONB DEFAULT '[]',
compliance JSONB DEFAULT '{}',
raw_text TEXT,
model TEXT DEFAULT 'regex',
latency_ms INT DEFAULT 0,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- βββ 5. API Keys (depends on profiles, teams) βββ
CREATE TABLE IF NOT EXISTS public.api_keys (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES public.profiles ON DELETE CASCADE NOT NULL,
team_id UUID REFERENCES public.teams ON DELETE CASCADE,
name TEXT NOT NULL,
key_hash TEXT NOT NULL UNIQUE,
key_prefix TEXT NOT NULL,
calls_this_month INT DEFAULT 0,
calls_limit INT DEFAULT 1000,
last_used_at TIMESTAMPTZ,
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- βββ 6. Custom Clause Rules (depends on profiles, teams) βββ
CREATE TABLE IF NOT EXISTS public.custom_rules (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES public.profiles ON DELETE CASCADE,
team_id UUID REFERENCES public.teams ON DELETE CASCADE,
name TEXT NOT NULL,
description TEXT,
pattern TEXT NOT NULL,
severity TEXT DEFAULT 'MEDIUM' CHECK (severity IN ('HIGH', 'MEDIUM', 'LOW')),
category TEXT NOT NULL,
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- βββ 7. Admin Logs βββ
CREATE TABLE IF NOT EXISTS public.admin_logs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
admin_id UUID REFERENCES auth.users NOT NULL,
action TEXT NOT NULL,
target_type TEXT,
target_id TEXT,
details JSONB,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- βββ Indexes βββ
CREATE INDEX IF NOT EXISTS idx_analyses_user_id ON public.analyses(user_id);
CREATE INDEX IF NOT EXISTS idx_analyses_team_id ON public.analyses(team_id);
CREATE INDEX IF NOT EXISTS idx_analyses_created_at ON public.analyses(created_at DESC);
CREATE INDEX IF NOT EXISTS idx_api_keys_key_hash ON public.api_keys(key_hash);
CREATE INDEX IF NOT EXISTS idx_api_keys_user_id ON public.api_keys(user_id);
CREATE INDEX IF NOT EXISTS idx_team_invites_email ON public.team_invites(email);
CREATE INDEX IF NOT EXISTS idx_custom_rules_user_id ON public.custom_rules(user_id);
CREATE INDEX IF NOT EXISTS idx_custom_rules_team_id ON public.custom_rules(team_id);
CREATE INDEX IF NOT EXISTS idx_admin_logs_created ON public.admin_logs(created_at DESC);
CREATE INDEX IF NOT EXISTS idx_profiles_role ON public.profiles(role);
CREATE INDEX IF NOT EXISTS idx_profiles_email ON public.profiles(email);
-- βββ Row Level Security βββ
ALTER TABLE public.profiles ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.analyses ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.teams ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.team_invites ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.api_keys ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.custom_rules ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.admin_logs ENABLE ROW LEVEL SECURITY;
-- Profiles
CREATE POLICY "Users see own profile" ON public.profiles FOR SELECT USING (auth.uid() = id);
CREATE POLICY "Users update own profile" ON public.profiles FOR UPDATE USING (auth.uid() = id);
CREATE POLICY "Admins read all profiles" ON public.profiles FOR SELECT USING (auth.uid() IN (SELECT id FROM public.profiles WHERE role = 'admin'));
CREATE POLICY "Admins update all profiles" ON public.profiles FOR UPDATE USING (auth.uid() IN (SELECT id FROM public.profiles WHERE role = 'admin'));
-- Analyses
CREATE POLICY "Users see own analyses" ON public.analyses FOR SELECT
USING (auth.uid() = user_id OR team_id IN (SELECT team_id FROM public.profiles WHERE id = auth.uid()));
CREATE POLICY "Users insert analyses" ON public.analyses FOR INSERT WITH CHECK (auth.uid() = user_id);
CREATE POLICY "Users delete own analyses" ON public.analyses FOR DELETE USING (auth.uid() = user_id);
CREATE POLICY "Admins read all analyses" ON public.analyses FOR SELECT USING (auth.uid() IN (SELECT id FROM public.profiles WHERE role = 'admin'));
-- Teams
CREATE POLICY "Team members can view" ON public.teams FOR SELECT
USING (id IN (SELECT team_id FROM public.profiles WHERE id = auth.uid()) OR owner_id = auth.uid());
CREATE POLICY "Owner can update team" ON public.teams FOR UPDATE USING (owner_id = auth.uid());
CREATE POLICY "Admins read all teams" ON public.teams FOR SELECT USING (auth.uid() IN (SELECT id FROM public.profiles WHERE role = 'admin'));
-- Team invites
CREATE POLICY "Members see team invites" ON public.team_invites FOR SELECT
USING (team_id IN (SELECT team_id FROM public.profiles WHERE id = auth.uid()));
CREATE POLICY "Users can invite" ON public.team_invites FOR INSERT WITH CHECK (invited_by = auth.uid());
-- API Keys
CREATE POLICY "Users see own API keys" ON public.api_keys FOR SELECT
USING (user_id = auth.uid() OR team_id IN (SELECT team_id FROM public.profiles WHERE id = auth.uid()));
CREATE POLICY "Users manage own API keys" ON public.api_keys FOR ALL USING (user_id = auth.uid());
CREATE POLICY "Admins read all api_keys" ON public.api_keys FOR SELECT USING (auth.uid() IN (SELECT id FROM public.profiles WHERE role = 'admin'));
-- Custom Rules
CREATE POLICY "Users see own rules" ON public.custom_rules FOR SELECT
USING (user_id = auth.uid() OR team_id IN (SELECT team_id FROM public.profiles WHERE id = auth.uid()));
CREATE POLICY "Users manage own rules" ON public.custom_rules FOR ALL USING (user_id = auth.uid());
CREATE POLICY "Admins read all rules" ON public.custom_rules FOR SELECT USING (auth.uid() IN (SELECT id FROM public.profiles WHERE role = 'admin'));
-- Admin Logs
CREATE POLICY "Admins manage logs" ON public.admin_logs FOR ALL
USING (auth.uid() IN (SELECT id FROM public.profiles WHERE role = 'admin'));
-- βββ Auto-create profile on signup βββ
CREATE OR REPLACE FUNCTION public.handle_new_user()
RETURNS TRIGGER AS $$
BEGIN
INSERT INTO public.profiles (id, email, full_name, avatar_url)
VALUES (
NEW.id, NEW.email,
COALESCE(NEW.raw_user_meta_data ->> 'full_name', NEW.raw_user_meta_data ->> 'name', ''),
COALESCE(NEW.raw_user_meta_data ->> 'avatar_url', NEW.raw_user_meta_data ->> 'picture', '')
);
RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
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 public.handle_new_user();
-- βββ FIX v4.1: Admin setup via environment variable βββ
-- DO NOT hardcode admin emails in source code committed to public repos.
-- Instead, run this manually after your first signup:
--
-- UPDATE public.profiles
-- SET role = 'admin', plan = 'pro'
-- WHERE email = '<YOUR_EMAIL>';
--
-- Or set ADMIN_EMAIL env var and run:
-- DO $$ BEGIN
-- UPDATE public.profiles SET role = 'admin', plan = 'pro'
-- WHERE email = current_setting('app.admin_email', true);
-- END $$;
-- βββ Monthly reset function βββ
CREATE OR REPLACE FUNCTION public.reset_monthly_usage()
RETURNS void AS $$
BEGIN
UPDATE public.profiles SET analyses_this_month = 0, monthly_reset_at = date_trunc('month', NOW())
WHERE monthly_reset_at < date_trunc('month', NOW());
UPDATE public.api_keys SET calls_this_month = 0;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
|