From f8fbac8ac61c065527d97b789bf9ceea9cd20e92 Mon Sep 17 00:00:00 2001 From: Damjan Savic Date: Sun, 25 Jan 2026 00:53:55 +0100 Subject: [PATCH] auto-claude: subtask-4-4 - Create chat_sessions and chat_messages tables in Supabase Create SQL migration for chatbot database tables: - chat_sessions: Stores visitor session data with visitor_id, metadata, timestamps - chat_messages: Stores chat messages with role (user/assistant/system), content - Proper indexes for visitor lookup and message ordering - Row Level Security (RLS) enabled with policies for: - Service role full access (for server-side API) - Visitor-based access via x-visitor-id header (for potential future client-side) - Trigger for automatic updated_at column on chat_sessions Also updated ChatSession interface in chat.ts to include updated_at field. Co-Authored-By: Claude Opus 4.5 --- src/lib/supabase/chat.ts | 1 + .../migrations/001_create_chat_tables.sql | 155 ++++++++++++++++++ 2 files changed, 156 insertions(+) create mode 100644 supabase/migrations/001_create_chat_tables.sql diff --git a/src/lib/supabase/chat.ts b/src/lib/supabase/chat.ts index 1b10dc8..603345a 100644 --- a/src/lib/supabase/chat.ts +++ b/src/lib/supabase/chat.ts @@ -6,6 +6,7 @@ export type ChatRole = 'user' | 'assistant' | 'system'; export interface ChatSession { id: string; created_at: string; + updated_at: string; visitor_id: string; metadata: Record; } diff --git a/supabase/migrations/001_create_chat_tables.sql b/supabase/migrations/001_create_chat_tables.sql new file mode 100644 index 0000000..420def0 --- /dev/null +++ b/supabase/migrations/001_create_chat_tables.sql @@ -0,0 +1,155 @@ +-- Chat Tables Migration +-- Creates tables for chatbot functionality with visitor sessions and message history +-- Version: 001 +-- Date: 2026-01-25 + +-- ============================================================================ +-- Table: chat_sessions +-- Stores chatbot session information for visitors +-- ============================================================================ +CREATE TABLE IF NOT EXISTS chat_sessions ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL, + visitor_id TEXT NOT NULL, + metadata JSONB DEFAULT '{}'::jsonb, + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL +); + +-- Index for quick lookup by visitor_id (used for session continuity) +CREATE INDEX IF NOT EXISTS idx_chat_sessions_visitor_id ON chat_sessions(visitor_id); + +-- Index for ordering sessions by creation time +CREATE INDEX IF NOT EXISTS idx_chat_sessions_created_at ON chat_sessions(created_at DESC); + +-- Comment on table +COMMENT ON TABLE chat_sessions IS 'Stores chatbot sessions for website visitors'; +COMMENT ON COLUMN chat_sessions.visitor_id IS 'Unique identifier for the visitor (generated client-side or from fingerprint)'; +COMMENT ON COLUMN chat_sessions.metadata IS 'Additional session data (locale, user_agent, referrer, etc.)'; + +-- ============================================================================ +-- Table: chat_messages +-- Stores individual chat messages within a session +-- ============================================================================ +CREATE TABLE IF NOT EXISTS chat_messages ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + session_id UUID NOT NULL REFERENCES chat_sessions(id) ON DELETE CASCADE, + role TEXT NOT NULL CHECK (role IN ('user', 'assistant', 'system')), + content TEXT NOT NULL, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL +); + +-- Index for fetching messages by session (primary query pattern) +CREATE INDEX IF NOT EXISTS idx_chat_messages_session_id ON chat_messages(session_id); + +-- Composite index for fetching messages in order within a session +CREATE INDEX IF NOT EXISTS idx_chat_messages_session_created ON chat_messages(session_id, created_at ASC); + +-- Comment on table +COMMENT ON TABLE chat_messages IS 'Stores individual messages in chatbot conversations'; +COMMENT ON COLUMN chat_messages.role IS 'Message sender role: user, assistant (AI), or system (instructions)'; +COMMENT ON COLUMN chat_messages.content IS 'The message text content'; + +-- ============================================================================ +-- Updated At Trigger for chat_sessions +-- Automatically updates the updated_at column when a session is modified +-- ============================================================================ +CREATE OR REPLACE FUNCTION update_updated_at_column() +RETURNS TRIGGER AS $$ +BEGIN + NEW.updated_at = NOW(); + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER update_chat_sessions_updated_at + BEFORE UPDATE ON chat_sessions + FOR EACH ROW + EXECUTE FUNCTION update_updated_at_column(); + +-- ============================================================================ +-- Row Level Security (RLS) +-- Enabled for production security - server uses service role key to bypass +-- ============================================================================ + +-- Enable RLS on chat_sessions +ALTER TABLE chat_sessions ENABLE ROW LEVEL SECURITY; + +-- Enable RLS on chat_messages +ALTER TABLE chat_messages ENABLE ROW LEVEL SECURITY; + +-- ============================================================================ +-- RLS Policies for chat_sessions +-- ============================================================================ + +-- Policy: Allow service role full access (for server-side API routes) +-- Note: Service role key automatically bypasses RLS, but this is explicit +CREATE POLICY "Service role has full access to chat_sessions" + ON chat_sessions + FOR ALL + TO service_role + USING (true) + WITH CHECK (true); + +-- Policy: Allow visitors to read their own sessions (for potential future client-side access) +CREATE POLICY "Visitors can read their own sessions" + ON chat_sessions + FOR SELECT + TO anon, authenticated + USING (visitor_id = current_setting('request.headers')::json->>'x-visitor-id'); + +-- Policy: Allow visitors to create sessions (for potential future client-side access) +CREATE POLICY "Visitors can create sessions" + ON chat_sessions + FOR INSERT + TO anon, authenticated + WITH CHECK (visitor_id = current_setting('request.headers')::json->>'x-visitor-id'); + +-- ============================================================================ +-- RLS Policies for chat_messages +-- ============================================================================ + +-- Policy: Allow service role full access (for server-side API routes) +CREATE POLICY "Service role has full access to chat_messages" + ON chat_messages + FOR ALL + TO service_role + USING (true) + WITH CHECK (true); + +-- Policy: Allow visitors to read messages from their sessions +CREATE POLICY "Visitors can read messages from their sessions" + ON chat_messages + FOR SELECT + TO anon, authenticated + USING ( + EXISTS ( + SELECT 1 FROM chat_sessions + WHERE chat_sessions.id = chat_messages.session_id + AND chat_sessions.visitor_id = current_setting('request.headers')::json->>'x-visitor-id' + ) + ); + +-- Policy: Allow visitors to insert messages into their sessions +CREATE POLICY "Visitors can insert messages into their sessions" + ON chat_messages + FOR INSERT + TO anon, authenticated + WITH CHECK ( + EXISTS ( + SELECT 1 FROM chat_sessions + WHERE chat_sessions.id = chat_messages.session_id + AND chat_sessions.visitor_id = current_setting('request.headers')::json->>'x-visitor-id' + ) + ); + +-- ============================================================================ +-- Grants +-- ============================================================================ + +-- Grant usage on tables to authenticated and anon roles +GRANT SELECT, INSERT ON chat_sessions TO anon, authenticated; +GRANT SELECT, INSERT ON chat_messages TO anon, authenticated; + +-- Grant all to service role (for server-side operations) +GRANT ALL ON chat_sessions TO service_role; +GRANT ALL ON chat_messages TO service_role;