medical-platform / database /migrations /create_documents_tables.sql
Ndg07's picture
Updated Documents feature | Added Rate Limtis to the Admin Panel | RAG Backend is yet to be fixed
49287af
-- Migration: Create document upload and RAG tables
-- Date: 2026-01-13
-- This script safely creates tables and columns, checking for existence first
-- Enable UUID extension if not already enabled
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-- Create documents table with minimal schema
CREATE TABLE IF NOT EXISTS documents (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID NOT NULL,
filename TEXT NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Add all columns one by one, checking if they exist
DO $$
BEGIN
-- file_type
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'documents' AND column_name = 'file_type') THEN
ALTER TABLE documents ADD COLUMN file_type TEXT;
END IF;
-- file_size
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'documents' AND column_name = 'file_size') THEN
ALTER TABLE documents ADD COLUMN file_size INTEGER;
END IF;
-- storage_path
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'documents' AND column_name = 'storage_path') THEN
ALTER TABLE documents ADD COLUMN storage_path TEXT;
END IF;
-- processing_status
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'documents' AND column_name = 'processing_status') THEN
ALTER TABLE documents ADD COLUMN processing_status TEXT DEFAULT 'pending';
END IF;
-- error_message
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'documents' AND column_name = 'error_message') THEN
ALTER TABLE documents ADD COLUMN error_message TEXT;
END IF;
-- expires_at
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'documents' AND column_name = 'expires_at') THEN
ALTER TABLE documents ADD COLUMN expires_at TIMESTAMP WITH TIME ZONE;
END IF;
-- processed_at
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'documents' AND column_name = 'processed_at') THEN
ALTER TABLE documents ADD COLUMN processed_at TIMESTAMP WITH TIME ZONE;
END IF;
-- updated_at
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'documents' AND column_name = 'updated_at') THEN
ALTER TABLE documents ADD COLUMN updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW();
END IF;
-- feature
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'documents' AND column_name = 'feature') THEN
ALTER TABLE documents ADD COLUMN feature TEXT;
END IF;
-- file_hash
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'documents' AND column_name = 'file_hash') THEN
ALTER TABLE documents ADD COLUMN file_hash TEXT;
END IF;
END $$;
-- Add constraints only if they don't exist
DO $$
BEGIN
-- Add processing_status check constraint
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'documents_processing_status_check') THEN
ALTER TABLE documents ADD CONSTRAINT documents_processing_status_check
CHECK (processing_status IN ('pending', 'processing', 'completed', 'failed'));
END IF;
-- Add feature check constraint
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'documents_feature_check') THEN
ALTER TABLE documents ADD CONSTRAINT documents_feature_check
CHECK (feature IN ('chat', 'mcq', 'flashcard', 'explain', 'highyield'));
END IF;
END $$;
-- Create document_chunks table for RAG
CREATE TABLE IF NOT EXISTS document_chunks (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
document_id UUID NOT NULL,
chunk_index INTEGER NOT NULL,
content TEXT NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Create indexes for performance (only if they don't exist)
CREATE INDEX IF NOT EXISTS idx_documents_user_id ON documents(user_id);
CREATE INDEX IF NOT EXISTS idx_documents_status ON documents(processing_status);
CREATE INDEX IF NOT EXISTS idx_document_chunks_document_id ON document_chunks(document_id);
-- Create indexes that depend on columns existing
DO $$
BEGIN
-- Index on feature column
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'documents' AND column_name = 'feature') THEN
CREATE INDEX IF NOT EXISTS idx_documents_feature ON documents(feature);
END IF;
-- Index on expires_at column
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'documents' AND column_name = 'expires_at') THEN
CREATE INDEX IF NOT EXISTS idx_documents_expires_at ON documents(expires_at);
END IF;
-- Index on file_hash column
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'documents' AND column_name = 'file_hash') THEN
CREATE INDEX IF NOT EXISTS idx_documents_file_hash ON documents(file_hash);
END IF;
-- Text search index on content
IF NOT EXISTS (SELECT 1 FROM pg_indexes WHERE indexname = 'idx_document_chunks_content') THEN
CREATE INDEX idx_document_chunks_content ON document_chunks USING gin(to_tsvector('english', content));
END IF;
END $$;
-- Enable RLS
ALTER TABLE documents ENABLE ROW LEVEL SECURITY;
ALTER TABLE document_chunks ENABLE ROW LEVEL SECURITY;
-- Drop existing policies if they exist (to avoid conflicts)
DROP POLICY IF EXISTS "Users can view own documents" ON documents;
DROP POLICY IF EXISTS "Users can insert own documents" ON documents;
DROP POLICY IF EXISTS "Users can update own documents" ON documents;
DROP POLICY IF EXISTS "Users can delete own documents" ON documents;
DROP POLICY IF EXISTS "Users can view chunks of own documents" ON document_chunks;
DROP POLICY IF EXISTS "System can insert chunks" ON document_chunks;
DROP POLICY IF EXISTS "System can delete chunks" ON document_chunks;
DROP POLICY IF EXISTS "Admins can view all documents" ON documents;
DROP POLICY IF EXISTS "Admins can view all chunks" ON document_chunks;
-- RLS Policies for documents
CREATE POLICY "Users can view own documents"
ON documents FOR SELECT
USING (auth.uid() = user_id);
CREATE POLICY "Users can insert own documents"
ON documents FOR INSERT
WITH CHECK (auth.uid() = user_id);
CREATE POLICY "Users can update own documents"
ON documents FOR UPDATE
USING (auth.uid() = user_id);
CREATE POLICY "Users can delete own documents"
ON documents FOR DELETE
USING (auth.uid() = user_id);
-- RLS Policies for document_chunks
CREATE POLICY "Users can view chunks of own documents"
ON document_chunks FOR SELECT
USING (
EXISTS (
SELECT 1 FROM documents
WHERE documents.id = document_chunks.document_id
AND documents.user_id = auth.uid()
)
);
CREATE POLICY "System can insert chunks"
ON document_chunks FOR INSERT
WITH CHECK (true);
CREATE POLICY "System can delete chunks"
ON document_chunks FOR DELETE
USING (true);
-- Admin policies (only if users table exists)
DO $$
BEGIN
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'users') THEN
EXECUTE '
CREATE POLICY "Admins can view all documents"
ON documents FOR SELECT
USING (
EXISTS (
SELECT 1 FROM users
WHERE users.id = auth.uid()
AND users.role IN (''super_admin'', ''admin'', ''ops'')
)
);
CREATE POLICY "Admins can view all chunks"
ON document_chunks FOR SELECT
USING (
EXISTS (
SELECT 1 FROM users
WHERE users.id = auth.uid()
AND users.role IN (''super_admin'', ''admin'', ''ops'')
)
);
';
END IF;
END $$;
-- Add comments
COMMENT ON TABLE documents IS 'Stores uploaded document metadata';
COMMENT ON TABLE document_chunks IS 'Stores text chunks from documents for RAG';