auto-claude: subtask-4-3 - Create Supabase chat database operations module

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-25 00:51:23 +01:00
co-authored by Claude Opus 4.5
parent 798d7e8f5b
commit 32717916ea
+334
View File
@@ -0,0 +1,334 @@
import { createClient as createServerClient } from '@supabase/supabase-js';
// Type definitions for chat tables
export type ChatRole = 'user' | 'assistant' | 'system';
export interface ChatSession {
id: string;
created_at: string;
visitor_id: string;
metadata: Record<string, unknown>;
}
export interface ChatMessage {
id: string;
session_id: string;
role: ChatRole;
content: string;
created_at: string;
}
// Input types for creating records
export interface CreateChatSessionInput {
visitor_id: string;
metadata?: Record<string, unknown>;
}
export interface CreateChatMessageInput {
session_id: string;
role: ChatRole;
content: string;
}
// Create a Supabase client with service role key for server-side operations
// This bypasses RLS for administrative operations
function createServiceClient() {
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
const serviceRoleKey = process.env.SUPABASE_SERVICE_ROLE_KEY;
if (!supabaseUrl || !serviceRoleKey) {
throw new Error(
'Missing Supabase environment variables: NEXT_PUBLIC_SUPABASE_URL or SUPABASE_SERVICE_ROLE_KEY'
);
}
return createServerClient(supabaseUrl, serviceRoleKey, {
auth: {
autoRefreshToken: false,
persistSession: false,
},
});
}
// ============================================
// Chat Session Operations
// ============================================
/**
* Create a new chat session for a visitor
*/
export async function createChatSession(
input: CreateChatSessionInput
): Promise<ChatSession> {
const supabase = createServiceClient();
const { data, error } = await supabase
.from('chat_sessions')
.insert({
visitor_id: input.visitor_id,
metadata: input.metadata ?? {},
})
.select()
.single();
if (error) {
throw new Error(`Failed to create chat session: ${error.message}`);
}
return data as ChatSession;
}
/**
* Get a chat session by ID
*/
export async function getChatSession(
sessionId: string
): Promise<ChatSession | null> {
const supabase = createServiceClient();
const { data, error } = await supabase
.from('chat_sessions')
.select()
.eq('id', sessionId)
.single();
if (error) {
if (error.code === 'PGRST116') {
// No rows found
return null;
}
throw new Error(`Failed to get chat session: ${error.message}`);
}
return data as ChatSession;
}
/**
* Get or create a chat session for a visitor
* Useful for maintaining session continuity
*/
export async function getOrCreateChatSession(
visitorId: string,
metadata?: Record<string, unknown>
): Promise<ChatSession> {
const supabase = createServiceClient();
// First, try to find an existing active session for this visitor
// Get the most recent session from the last 24 hours
const twentyFourHoursAgo = new Date(
Date.now() - 24 * 60 * 60 * 1000
).toISOString();
const { data: existingSession, error: findError } = await supabase
.from('chat_sessions')
.select()
.eq('visitor_id', visitorId)
.gte('created_at', twentyFourHoursAgo)
.order('created_at', { ascending: false })
.limit(1)
.single();
if (existingSession && !findError) {
return existingSession as ChatSession;
}
// No existing session found, create a new one
return createChatSession({
visitor_id: visitorId,
metadata,
});
}
/**
* Update session metadata
*/
export async function updateSessionMetadata(
sessionId: string,
metadata: Record<string, unknown>
): Promise<ChatSession> {
const supabase = createServiceClient();
const { data, error } = await supabase
.from('chat_sessions')
.update({ metadata })
.eq('id', sessionId)
.select()
.single();
if (error) {
throw new Error(`Failed to update session metadata: ${error.message}`);
}
return data as ChatSession;
}
// ============================================
// Chat Message Operations
// ============================================
/**
* Add a message to a chat session
*/
export async function addChatMessage(
input: CreateChatMessageInput
): Promise<ChatMessage> {
const supabase = createServiceClient();
const { data, error } = await supabase
.from('chat_messages')
.insert({
session_id: input.session_id,
role: input.role,
content: input.content,
})
.select()
.single();
if (error) {
throw new Error(`Failed to add chat message: ${error.message}`);
}
return data as ChatMessage;
}
/**
* Get all messages for a chat session
* Returns messages in chronological order
*/
export async function getChatMessages(
sessionId: string
): Promise<ChatMessage[]> {
const supabase = createServiceClient();
const { data, error } = await supabase
.from('chat_messages')
.select()
.eq('session_id', sessionId)
.order('created_at', { ascending: true });
if (error) {
throw new Error(`Failed to get chat messages: ${error.message}`);
}
return (data ?? []) as ChatMessage[];
}
/**
* Get the last N messages for a session
* Useful for maintaining context window limits
*/
export async function getRecentMessages(
sessionId: string,
limit: number = 20
): Promise<ChatMessage[]> {
const supabase = createServiceClient();
const { data, error } = await supabase
.from('chat_messages')
.select()
.eq('session_id', sessionId)
.order('created_at', { ascending: false })
.limit(limit);
if (error) {
throw new Error(`Failed to get recent messages: ${error.message}`);
}
// Reverse to get chronological order
return ((data ?? []) as ChatMessage[]).reverse();
}
/**
* Delete all messages for a session (clear history)
*/
export async function clearChatMessages(sessionId: string): Promise<void> {
const supabase = createServiceClient();
const { error } = await supabase
.from('chat_messages')
.delete()
.eq('session_id', sessionId);
if (error) {
throw new Error(`Failed to clear chat messages: ${error.message}`);
}
}
/**
* Delete a chat session and all its messages
* Messages are deleted automatically via ON DELETE CASCADE
*/
export async function deleteChatSession(sessionId: string): Promise<void> {
const supabase = createServiceClient();
const { error } = await supabase
.from('chat_sessions')
.delete()
.eq('id', sessionId);
if (error) {
throw new Error(`Failed to delete chat session: ${error.message}`);
}
}
// ============================================
// Utility Functions
// ============================================
/**
* Get conversation history formatted for AI context
* Converts database messages to the format expected by chat completion APIs
*/
export async function getConversationHistory(
sessionId: string,
maxMessages: number = 20
): Promise<Array<{ role: ChatRole; content: string }>> {
const messages = await getRecentMessages(sessionId, maxMessages);
return messages.map((msg) => ({
role: msg.role,
content: msg.content,
}));
}
/**
* Save a conversation turn (user message + assistant response)
* Convenience function for typical chat flow
*/
export async function saveConversationTurn(
sessionId: string,
userMessage: string,
assistantResponse: string
): Promise<{ userMsg: ChatMessage; assistantMsg: ChatMessage }> {
const userMsg = await addChatMessage({
session_id: sessionId,
role: 'user',
content: userMessage,
});
const assistantMsg = await addChatMessage({
session_id: sessionId,
role: 'assistant',
content: assistantResponse,
});
return { userMsg, assistantMsg };
}
// Default export for convenience
export default {
// Session operations
createChatSession,
getChatSession,
getOrCreateChatSession,
updateSessionMetadata,
deleteChatSession,
// Message operations
addChatMessage,
getChatMessages,
getRecentMessages,
clearChatMessages,
// Utility functions
getConversationHistory,
saveConversationTurn,
};