UNPKG

@lobehub/chat

Version:

Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.

132 lines (114 loc) 3.98 kB
import { and, desc, eq } from 'drizzle-orm/expressions'; import { LobeChatDatabase } from '@/database/type'; import { FileService } from '@/server/services/file'; import { GenerationAsset, ImageGenerationTopic } from '@/types/generation'; import { GenerationTopicItem, generationTopics } from '../schemas/generation'; export class GenerationTopicModel { private userId: string; private db: LobeChatDatabase; private fileService: FileService; constructor(db: LobeChatDatabase, userId: string) { this.userId = userId; this.db = db; this.fileService = new FileService(db, userId); } queryAll = async () => { const topics = await this.db .select() .from(generationTopics) .orderBy(desc(generationTopics.updatedAt)) .where(eq(generationTopics.userId, this.userId)); return Promise.all( topics.map(async (topic) => { if (topic.coverUrl) { return { ...topic, coverUrl: await this.fileService.getFullFileUrl(topic.coverUrl), }; } return topic; }), ); }; create = async (title: string) => { const [newGenerationTopic] = await this.db .insert(generationTopics) .values({ title, userId: this.userId, }) .returning(); return newGenerationTopic; }; update = async ( id: string, data: Partial<ImageGenerationTopic>, ): Promise<GenerationTopicItem | undefined> => { const [updatedTopic] = await this.db .update(generationTopics) .set({ ...data, updatedAt: new Date() }) .where(and(eq(generationTopics.id, id), eq(generationTopics.userId, this.userId))) .returning(); return updatedTopic; }; /** * Delete a generation topic and return associated file URLs for cleanup * * This method follows the "database first, files second" deletion principle: * 1. First queries the topic with all its batches and generations to collect file URLs * 2. Then deletes the database record (cascade delete handles related batches and generations) * 3. Returns the deleted topic data and file URLs for cleanup * * @param id - The topic ID to delete * @returns Object containing deleted topic data and file URLs to clean, or undefined if topic not found or access denied */ delete = async ( id: string, ): Promise<{ deletedTopic: GenerationTopicItem; filesToDelete: string[] } | undefined> => { // 1. First, get the topic with all its batches and generations to collect file URLs const topicWithBatches = await this.db.query.generationTopics.findFirst({ where: and(eq(generationTopics.id, id), eq(generationTopics.userId, this.userId)), with: { batches: { with: { generations: { columns: { asset: true, }, }, }, }, }, }); // If topic doesn't exist or doesn't belong to user, return undefined if (!topicWithBatches) { return undefined; } // 2. Collect all file URLs that need to be deleted const filesToDelete: string[] = []; // Add cover image URL if exists if (topicWithBatches.coverUrl) { filesToDelete.push(topicWithBatches.coverUrl); } // Add thumbnail URLs from all generations if (topicWithBatches.batches) { for (const batch of topicWithBatches.batches) { for (const gen of batch.generations) { const asset = gen.asset as GenerationAsset; if (asset?.thumbnailUrl) { filesToDelete.push(asset.thumbnailUrl); } } } } // 3. Delete the topic record (this will cascade delete all batches and generations) const [deletedTopic] = await this.db .delete(generationTopics) .where(and(eq(generationTopics.id, id), eq(generationTopics.userId, this.userId))) .returning(); return { deletedTopic, filesToDelete, }; }; }