labcardai-backend / supabase_schema.sql
ayush712145's picture
Upload folder using huggingface_hub
0aaa5bc verified
Raw
History Blame Contribute Delete
7.56 kB
-- ─────────────────────────────────────────────────────────────────────────────
-- 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');