SpendWise-Backend / setup.sql
VihaanShinde10's picture
Initial commit
1313d86
-- ==========================================
-- SpendWise Full Database Setup Script
-- Paste this into your Supabase SQL Editor
-- ==========================================
-- 1. Enable Extensions
CREATE EXTENSION IF NOT EXISTS vector;
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-- 2. Users Profile Table
CREATE TABLE IF NOT EXISTS public.profiles (
id UUID PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE,
email TEXT UNIQUE NOT NULL,
display_name TEXT,
currency TEXT DEFAULT 'INR',
timezone TEXT DEFAULT 'Asia/Kolkata',
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
ALTER TABLE public.profiles ENABLE ROW LEVEL SECURITY;
CREATE POLICY "users_own_profile" ON public.profiles
FOR ALL USING (auth.uid() = id);
-- 3. Categories Table
CREATE TABLE IF NOT EXISTS public.categories (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID REFERENCES public.profiles(id) ON DELETE CASCADE,
name TEXT NOT NULL,
icon TEXT,
color TEXT,
is_system BOOLEAN DEFAULT FALSE,
parent_id UUID REFERENCES public.categories(id),
created_at TIMESTAMPTZ DEFAULT NOW()
);
ALTER TABLE public.categories ENABLE ROW LEVEL SECURITY;
CREATE POLICY "users_own_categories" ON public.categories
FOR ALL USING (auth.uid() = user_id OR is_system = TRUE);
INSERT INTO public.categories (name, icon, color, is_system, user_id) VALUES
('Food & Dining', '🍽️', '#FF6B6B', TRUE, NULL),
('Transport', 'πŸš—', '#4ECDC4', TRUE, NULL),
('Shopping', 'πŸ›οΈ', '#45B7D1', TRUE, NULL),
('Entertainment', '🎬', '#96CEB4', TRUE, NULL),
('Utilities', '⚑', '#FFEAA7', TRUE, NULL),
('Health & Medical', 'πŸ₯', '#DDA0DD', TRUE, NULL),
('Education', 'πŸ“š', '#98D8C8', TRUE, NULL),
('Travel', '✈️', '#F7DC6F', TRUE, NULL),
('Financial Services', 'πŸ’³', '#82E0AA', TRUE, NULL),
('Groceries', 'πŸ›’', '#F8C471', TRUE, NULL),
('Peer Transfer', 'πŸ‘€', '#AED6F1', TRUE, NULL),
('Others', 'πŸ“¦', '#D5DBDB', TRUE, NULL)
ON CONFLICT DO NOTHING;
-- 4. Transactions Table
CREATE TABLE IF NOT EXISTS public.transactions (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID NOT NULL REFERENCES public.profiles(id) ON DELETE CASCADE,
raw_description TEXT NOT NULL,
amount NUMERIC(14, 2) NOT NULL,
direction TEXT CHECK (direction IN ('debit', 'credit')) NOT NULL,
balance NUMERIC(14, 2),
transaction_date TIMESTAMPTZ NOT NULL,
payment_method TEXT CHECK (payment_method IN ('UPI', 'IMPS', 'NEFT', 'ATM', 'OTHER')),
cleaned_description TEXT,
merchant_name TEXT,
is_low_descriptiveness BOOLEAN DEFAULT FALSE,
category_id UUID REFERENCES public.categories(id),
category_source TEXT CHECK (category_source IN ('semantic','behavioural','fused','zero_shot','manual','pending')),
confidence_score NUMERIC(4, 3),
gating_alpha NUMERIC(4, 3),
is_recurring BOOLEAN DEFAULT FALSE,
recurrence_strength NUMERIC(4, 3),
needs_review BOOLEAN DEFAULT FALSE,
user_corrected BOOLEAN DEFAULT FALSE,
user_category_id UUID REFERENCES public.categories(id),
processing_status TEXT DEFAULT 'pending' CHECK (
processing_status IN ('pending', 'processing', 'completed', 'failed')
),
processed_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_transactions_user_date ON public.transactions(user_id, transaction_date DESC);
CREATE INDEX idx_transactions_category ON public.transactions(user_id, category_id);
CREATE INDEX idx_transactions_status ON public.transactions(user_id, processing_status);
CREATE INDEX idx_transactions_merchant ON public.transactions(user_id, merchant_name);
ALTER TABLE public.transactions ENABLE ROW LEVEL SECURITY;
CREATE POLICY "users_own_transactions" ON public.transactions
FOR ALL USING (auth.uid() = user_id);
-- 5. Embeddings Table
CREATE TABLE IF NOT EXISTS public.transaction_embeddings (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
transaction_id UUID UNIQUE NOT NULL REFERENCES public.transactions(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES public.profiles(id) ON DELETE CASCADE,
embedding vector(768),
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX ON public.transaction_embeddings
USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 100);
ALTER TABLE public.transaction_embeddings ENABLE ROW LEVEL SECURITY;
CREATE POLICY "users_own_embeddings" ON public.transaction_embeddings
FOR ALL USING (auth.uid() = user_id);
-- 6. Cluster State Table
CREATE TABLE IF NOT EXISTS public.user_clusters (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID NOT NULL REFERENCES public.profiles(id) ON DELETE CASCADE,
cluster_id INTEGER NOT NULL,
category_id UUID REFERENCES public.categories(id),
label_name TEXT,
transaction_count INTEGER DEFAULT 0,
last_clustered TIMESTAMPTZ DEFAULT NOW(),
created_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE (user_id, cluster_id)
);
ALTER TABLE public.user_clusters ENABLE ROW LEVEL SECURITY;
CREATE POLICY "users_own_clusters" ON public.user_clusters
FOR ALL USING (auth.uid() = user_id);
-- 7. Budgets Table
CREATE TABLE IF NOT EXISTS public.budgets (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID NOT NULL REFERENCES public.profiles(id) ON DELETE CASCADE,
category_id UUID NOT NULL REFERENCES public.categories(id),
amount NUMERIC(14, 2) NOT NULL,
period TEXT CHECK (period IN ('monthly', 'weekly', 'annual')) DEFAULT 'monthly',
start_date DATE NOT NULL,
end_date DATE,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
ALTER TABLE public.budgets ENABLE ROW LEVEL SECURITY;
CREATE POLICY "users_own_budgets" ON public.budgets
FOR ALL USING (auth.uid() = user_id);
-- 8. Gating Model Weights Table
CREATE TABLE IF NOT EXISTS public.gating_model_state (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID UNIQUE NOT NULL REFERENCES public.profiles(id) ON DELETE CASCADE,
W1 JSONB,
b1 JSONB,
W2 JSONB,
b2 FLOAT,
training_samples INTEGER DEFAULT 0,
val_mse FLOAT,
trained_at TIMESTAMPTZ DEFAULT NOW()
);
ALTER TABLE public.gating_model_state ENABLE ROW LEVEL SECURITY;
CREATE POLICY "users_own_model" ON public.gating_model_state
FOR ALL USING (auth.uid() = user_id);
-- 9. Triggers
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER update_transactions_updated_at
BEFORE UPDATE ON public.transactions
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_budgets_updated_at
BEFORE UPDATE ON public.budgets
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- Note: Run this last manually or ensure publication exists
-- ALTER PUBLICATION supabase_realtime ADD TABLE public.transactions;