Spaces:
Sleeping
Sleeping
File size: 7,556 Bytes
0aaa5bc | 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 | -- βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
-- LabCard AI β Supabase Schema
-- Run this in Supabase SQL Editor: https://supabase.com/dashboard/project/_/sql
-- βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
-- ββ User Profiles βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
-- Extends auth.users β created automatically on first report / payment
CREATE TABLE IF NOT EXISTS public.user_profiles (
id UUID REFERENCES auth.users(id) ON DELETE CASCADE PRIMARY KEY,
tier TEXT NOT NULL DEFAULT 'free'
CHECK (tier IN ('free', 'premium')),
reports_analyzed INTEGER NOT NULL DEFAULT 0,
premium_expires_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- Auto-update updated_at on any change
CREATE OR REPLACE FUNCTION update_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER user_profiles_updated_at
BEFORE UPDATE ON public.user_profiles
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
-- ββ Reports βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
-- Full LabReport JSON stored in report_json JSONB for future trend analysis.
-- Summary columns (score, grade, etc.) allow fast SQL aggregates without
-- parsing the full JSON every time.
CREATE TABLE IF NOT EXISTS public.reports (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
user_id UUID REFERENCES auth.users(id) ON DELETE SET NULL,
-- Extracted patient fields (denormalized for fast queries)
patient_name TEXT,
patient_age TEXT,
patient_gender TEXT,
lab_name TEXT,
report_date TEXT,
-- Health metrics (all deterministic β not from AI)
health_score INTEGER CHECK (health_score >= 0 AND health_score <= 100),
health_grade TEXT,
biological_age INTEGER,
chronological_age INTEGER,
has_critical_alert BOOLEAN DEFAULT FALSE,
-- Processing metadata
tier_used TEXT DEFAULT 'free',
biomarker_count INTEGER,
abnormal_count INTEGER,
processing_time_ms INTEGER,
-- Full report blob (enables trend queries + future re-analysis)
report_json JSONB NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Indexes for common access patterns
CREATE INDEX IF NOT EXISTS idx_reports_user_id
ON public.reports(user_id);
CREATE INDEX IF NOT EXISTS idx_reports_created_at
ON public.reports(created_at DESC);
CREATE INDEX IF NOT EXISTS idx_reports_health_score
ON public.reports(health_score);
-- GIN index on JSONB for fast biomarker trend queries
CREATE INDEX IF NOT EXISTS idx_reports_json
ON public.reports USING GIN (report_json);
-- ββ Payments ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
-- Audit trail for all Razorpay transactions.
-- razorpay_order_id is UNIQUE β idempotent upsert is safe.
CREATE TABLE IF NOT EXISTS public.payments (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
user_id UUID REFERENCES auth.users(id) ON DELETE SET NULL,
razorpay_order_id TEXT UNIQUE NOT NULL,
razorpay_payment_id TEXT UNIQUE,
razorpay_signature TEXT,
plan TEXT NOT NULL
CHECK (plan IN ('per_report', 'monthly')),
amount_paise INTEGER NOT NULL,
status TEXT NOT NULL DEFAULT 'pending'
CHECK (status IN ('pending', 'success', 'failed')),
verified_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_payments_user_id
ON public.payments(user_id);
-- ββ Row Level Security ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
-- Users can only read their own data.
-- Backend uses service role key β bypasses RLS for writes.
ALTER TABLE public.user_profiles ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.reports ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.payments ENABLE ROW LEVEL SECURITY;
-- user_profiles: users see and update only their own row
CREATE POLICY "Users see own profile"
ON public.user_profiles FOR ALL
USING (auth.uid() = id);
-- reports: users can read their own reports
CREATE POLICY "Users see own reports"
ON public.reports FOR SELECT
USING (auth.uid() = user_id);
-- reports: backend service role inserts (WITH CHECK true allows service key)
CREATE POLICY "Service role inserts reports"
ON public.reports FOR INSERT
WITH CHECK (true);
-- payments: users can only see their own payment records
CREATE POLICY "Users see own payments"
ON public.payments FOR SELECT
USING (auth.uid() = user_id);
-- payments: backend service role inserts
CREATE POLICY "Service role inserts payments"
ON public.payments FOR INSERT
WITH CHECK (true);
-- ββ Helper: increment reports_analyzed counter ββββββββββββββββββββββββββββββββ
-- Called via trigger when a new report is inserted
CREATE OR REPLACE FUNCTION increment_reports_analyzed()
RETURNS TRIGGER AS $$
BEGIN
IF NEW.user_id IS NOT NULL THEN
INSERT INTO public.user_profiles (id, reports_analyzed)
VALUES (NEW.user_id, 1)
ON CONFLICT (id) DO UPDATE
SET reports_analyzed = user_profiles.reports_analyzed + 1,
updated_at = NOW();
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
CREATE TRIGGER on_report_inserted
AFTER INSERT ON public.reports
FOR EACH ROW EXECUTE FUNCTION increment_reports_analyzed();
-- ββ Trend analysis view (bonus β useful for future dashboard) βββββββββββββββββ
-- Returns latest health score per user for trend charts
CREATE OR REPLACE VIEW public.user_health_trends AS
SELECT
r.user_id,
r.id AS report_id,
r.created_at,
r.health_score,
r.health_grade,
r.biological_age,
r.chronological_age,
r.abnormal_count,
r.lab_name
FROM public.reports r
WHERE r.user_id IS NOT NULL
ORDER BY r.user_id, r.created_at DESC;
-- ββ Verify setup ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
SELECT
schemaname,
tablename,
tableowner
FROM pg_tables
WHERE schemaname = 'public'
AND tablename IN ('user_profiles', 'reports', 'payments');
|