import { type Message, type InsertMessage, type Conversation, type InsertConversation, type User, PersonalityType, messageRoleSchema, messages, conversations, users } from "@shared/schema"; import { db } from "./db"; import { eq, desc, asc } from "drizzle-orm"; import { nanoid } from "nanoid"; import session from 'express-session'; import connectPgSimple from 'connect-pg-simple'; import { pool } from './db'; export interface IStorage { // Message operations getMessages(conversationId: string): Promise; createMessage(message: InsertMessage): Promise; deleteMessages(conversationId: string): Promise; // Conversation operations getConversation(id: string): Promise; getConversations(): Promise; getUserConversations(userId: number): Promise; createConversation(conversation: InsertConversation): Promise; deleteConversation(id: string): Promise; updateConversationPersonality(id: string, personality: PersonalityType): Promise; updateConversationTitle(id: string, title: string): Promise; // User profile operations getUserProfile(id: number): Promise; getUserByUsername(username: string): Promise; createUser(userData: any): Promise; updateUserProfile(id: number, profile: Partial): Promise; // Session operations sessionStore: session.Store; } export class DatabaseStorage implements IStorage { sessionStore: session.Store; constructor() { // Initialize PostgreSQL session store const PgStore = connectPgSimple(session); this.sessionStore = new PgStore({ pool, createTableIfMissing: true, }); // Initialize default conversation if it doesn't exist this.initializeDefaultConversation(); } private async initializeDefaultConversation() { try { const defaultConversation = await this.getConversation("default"); if (!defaultConversation) { await this.createConversation({ id: "default", title: "New Conversation", personality: "general" }); } } catch (error) { console.error("Error initializing default conversation:", error); } } // Message operations async getMessages(conversationId: string): Promise { return db .select() .from(messages) .where(eq(messages.conversationId, conversationId)) .orderBy(asc(messages.createdAt)); } async createMessage(insertMessage: InsertMessage): Promise { const [newMessage] = await db .insert(messages) .values({ ...insertMessage, createdAt: new Date() }) .returning(); return newMessage; } async deleteMessages(conversationId: string): Promise { await db .delete(messages) .where(eq(messages.conversationId, conversationId)); } // Conversation operations async getConversation(id: string): Promise { const [conversation] = await db .select() .from(conversations) .where(eq(conversations.id, id)); return conversation; } async getConversations(): Promise { return db .select() .from(conversations) .orderBy(desc(conversations.createdAt)); } async createConversation(conversation: InsertConversation): Promise { // If the conversation already exists, update it if (conversation.id) { const existingConversation = await this.getConversation(conversation.id); if (existingConversation) { const [updatedConversation] = await db .update(conversations) .set({ title: conversation.title, personality: conversation.personality || "general", // Only update userId if provided ...(conversation.userId && { userId: conversation.userId }) }) .where(eq(conversations.id, conversation.id)) .returning(); return updatedConversation; } } // Otherwise, create a new conversation const [newConversation] = await db .insert(conversations) .values({ id: conversation.id || nanoid(), title: conversation.title, personality: conversation.personality || "general", userId: conversation.userId, // Include the user ID (can be null for unassociated conversations) createdAt: new Date() }) .returning(); return newConversation; } async deleteConversation(id: string): Promise { // Don't allow deleting the default conversation if (id === "default") { return false; } try { // Delete associated messages first await this.deleteMessages(id); // Then delete the conversation const [deletedConversation] = await db .delete(conversations) .where(eq(conversations.id, id)) .returning(); return !!deletedConversation; } catch (error) { console.error("Error deleting conversation:", error); return false; } } async updateConversationPersonality(id: string, personality: PersonalityType): Promise { const [updatedConversation] = await db .update(conversations) .set({ personality }) .where(eq(conversations.id, id)) .returning(); return updatedConversation; } async updateConversationTitle(id: string, title: string): Promise { const [updatedConversation] = await db .update(conversations) .set({ title }) .where(eq(conversations.id, id)) .returning(); return updatedConversation; } // User operations async getUserProfile(id: number): Promise { const [user] = await db .select() .from(users) .where(eq(users.id, id)); return user; } async getUserByUsername(username: string): Promise { const [user] = await db .select() .from(users) .where(eq(users.username, username)); return user; } async createUser(userData: any): Promise { const [user] = await db .insert(users) .values(userData) .returning(); return user; } async updateUserProfile(id: number, profile: Partial): Promise { // Remove sensitive information that shouldn't be updated this way const { password, ...updateData } = profile; const [updatedUser] = await db .update(users) .set(updateData as any) .where(eq(users.id, id)) .returning(); return updatedUser; } // Filter conversations by user ID async getUserConversations(userId: number): Promise { return db .select() .from(conversations) .where(eq(conversations.userId, userId)) .orderBy(desc(conversations.createdAt)); } } // Use the database storage for production export const storage = new DatabaseStorage();