Spaces:
Running
Running
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<Message[]>; | |
createMessage(message: InsertMessage): Promise<Message>; | |
deleteMessages(conversationId: string): Promise<void>; | |
// Conversation operations | |
getConversation(id: string): Promise<Conversation | undefined>; | |
getConversations(): Promise<Conversation[]>; | |
getUserConversations(userId: number): Promise<Conversation[]>; | |
createConversation(conversation: InsertConversation): Promise<Conversation>; | |
deleteConversation(id: string): Promise<boolean>; | |
updateConversationPersonality(id: string, personality: PersonalityType): Promise<Conversation | undefined>; | |
updateConversationTitle(id: string, title: string): Promise<Conversation | undefined>; | |
// User profile operations | |
getUserProfile(id: number): Promise<User | undefined>; | |
getUserByUsername(username: string): Promise<User | undefined>; | |
createUser(userData: any): Promise<User>; | |
updateUserProfile(id: number, profile: Partial<User>): Promise<User | undefined>; | |
// 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<Message[]> { | |
return db | |
.select() | |
.from(messages) | |
.where(eq(messages.conversationId, conversationId)) | |
.orderBy(asc(messages.createdAt)); | |
} | |
async createMessage(insertMessage: InsertMessage): Promise<Message> { | |
const [newMessage] = await db | |
.insert(messages) | |
.values({ | |
...insertMessage, | |
createdAt: new Date() | |
}) | |
.returning(); | |
return newMessage; | |
} | |
async deleteMessages(conversationId: string): Promise<void> { | |
await db | |
.delete(messages) | |
.where(eq(messages.conversationId, conversationId)); | |
} | |
// Conversation operations | |
async getConversation(id: string): Promise<Conversation | undefined> { | |
const [conversation] = await db | |
.select() | |
.from(conversations) | |
.where(eq(conversations.id, id)); | |
return conversation; | |
} | |
async getConversations(): Promise<Conversation[]> { | |
return db | |
.select() | |
.from(conversations) | |
.orderBy(desc(conversations.createdAt)); | |
} | |
async createConversation(conversation: InsertConversation): Promise<Conversation> { | |
// 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<boolean> { | |
// 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<Conversation | undefined> { | |
const [updatedConversation] = await db | |
.update(conversations) | |
.set({ personality }) | |
.where(eq(conversations.id, id)) | |
.returning(); | |
return updatedConversation; | |
} | |
async updateConversationTitle(id: string, title: string): Promise<Conversation | undefined> { | |
const [updatedConversation] = await db | |
.update(conversations) | |
.set({ title }) | |
.where(eq(conversations.id, id)) | |
.returning(); | |
return updatedConversation; | |
} | |
// User operations | |
async getUserProfile(id: number): Promise<User | undefined> { | |
const [user] = await db | |
.select() | |
.from(users) | |
.where(eq(users.id, id)); | |
return user; | |
} | |
async getUserByUsername(username: string): Promise<User | undefined> { | |
const [user] = await db | |
.select() | |
.from(users) | |
.where(eq(users.username, username)); | |
return user; | |
} | |
async createUser(userData: any): Promise<User> { | |
const [user] = await db | |
.insert(users) | |
.values(userData) | |
.returning(); | |
return user; | |
} | |
async updateUserProfile(id: number, profile: Partial<User>): Promise<User | undefined> { | |
// 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<Conversation[]> { | |
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(); | |