Database Schema Guide
Postgres tables for profiles, chats, documents, vectors, billing, plus the RLS policies that keep each user’s files out of someone else’s queries.
Prerequisites
- Supabase project with PostgreSQL
- pgvector extension enabled
Tables Overview
profiles
Stores user profile information linked to Supabase Auth.
create table profiles (
id uuid references auth.users not null primary key,
email text,
tier text default 'free', -- 'free', 'pro', 'enterprise'
credits int default 10,
stripe_customer_id text,
created_at timestamptz default now(),
updated_at timestamptz default now()
);
Key Fields:
id: Links toauth.users- automatically populatedtier: Controls feature access and allowed modelscredits: Current credit balancestripe_customer_id: Links to Stripe for billing
chats
Stores chat threads/conversations.
create table chats (
id uuid default gen_random_uuid() primary key,
user_id uuid references profiles(id) on delete cascade not null,
title text not null,
document_ids uuid[] default '{}', -- Attached document IDs
created_at timestamptz default now(),
updated_at timestamptz default now()
);
Key Fields:
document_ids: Array of document UUIDs for RAG contexttitle: Auto-generated from first message
messages
Stores individual messages within chats.
create table messages (
id uuid default gen_random_uuid() primary key,
chat_id uuid references chats(id) on delete cascade not null,
role text not null, -- 'user', 'assistant', 'system'
content text not null,
model_used text, -- Which AI model generated this
tokens_used int, -- Token count for analytics
created_at timestamptz default now()
);
documents
Stores uploaded document metadata.
create table documents (
id uuid default gen_random_uuid() primary key,
user_id uuid references profiles(id) on delete cascade not null,
filename text not null,
file_path text not null, -- Path in Supabase Storage
mime_type text not null,
file_size bigint,
status text default 'processing', -- 'processing', 'completed', 'failed'
created_at timestamptz default now(),
updated_at timestamptz default now()
);
document_embeddings
Stores vector embeddings for RAG retrieval.
create table document_embeddings (
id uuid default gen_random_uuid() primary key,
document_id uuid references documents(id) on delete cascade not null,
chunk_index int not null,
content text not null,
embedding vector(1536) not null, -- OpenAI embedding dimension
created_at timestamptz default now()
);
-- Index for similarity search
create index document_embeddings_embedding_idx on document_embeddings
using ivfflat (embedding vector_cosine_ops)
with (lists = 100);
Row Level Security (RLS)
All tables have RLS enabled with the following policies:
Profile Policies
-- Users can view their own profile
create policy "Users can view own profile" on profiles
for select using (auth.uid() = id);
-- Users can create their own profile
create policy "Users can create own profile" on profiles
for insert with check (auth.uid() = id);
-- Users can update their own profile
create policy "Users can update own profile" on profiles
for update using (auth.uid() = id);
Chat & Message Policies
-- Users can view their own chats
create policy "Users can view own chats" on chats
for select using (auth.uid() = user_id);
-- Messages: Users can view messages from their own chats
create policy "Users can view own messages" on messages
for select using (
exists (
select 1 from chats
where chats.id = messages.chat_id
and chats.user_id = auth.uid()
)
);
Database Functions
match_documents
Similarity search function for RAG retrieval:
create function match_documents(
query_embedding vector(1536),
match_threshold float,
match_count int,
filter_document_ids uuid[]
)
returns table (
id uuid,
content text,
similarity float,
document_id uuid,
chunk_index int
)
Usage: Called by the RAG retriever to find relevant document chunks.
handle_new_user
Trigger function to auto-create profile on signup:
create function handle_new_user()
returns trigger as $$
begin
insert into profiles (id, email, tier, credits)
values (new.id, new.email, 'free', 10);
return new;
end;
$$ language plpgsql;
create trigger on_auth_user_created
after insert on auth.users
for each row execute procedure handle_new_user();
Storage Setup
Create a bucket named uploads in Supabase Storage with these policies:
-- Users can upload to their own folder
create policy "Users can upload files" on storage.objects
for insert to authenticated
with check (
bucket_id = 'uploads'
and (storage.foldername(name))[1] = auth.uid()::text
);
-- Users can read their own files
create policy "Users can read own files" on storage.objects
for select to authenticated
using (
bucket_id = 'uploads'
and (storage.foldername(name))[1] = auth.uid()::text
);
Running Migrations
- Copy
src/db/schema.sqlcontent - Open Supabase SQL Editor
- Paste and run the SQL
- Verify tables are created in Table Editor