diff --git a/packages/data-schemas/src/methods/conversation.spec.ts b/packages/data-schemas/src/methods/conversation.spec.ts index ac4af0b199..ce00cc5104 100644 --- a/packages/data-schemas/src/methods/conversation.spec.ts +++ b/packages/data-schemas/src/methods/conversation.spec.ts @@ -2,7 +2,7 @@ import mongoose from 'mongoose'; import { v4 as uuidv4 } from 'uuid'; import { MongoMemoryServer } from 'mongodb-memory-server'; import { EModelEndpoint, RetentionMode } from 'librechat-data-provider'; -import type { IChatProject, IConversation, IMessage, ISharedLink } from '../types'; +import type { IChatProject, IConversation, IMessage, IMongoFile, ISharedLink } from '../types'; import { ConversationMethods, createConversationMethods } from './conversation'; import { tenantStorage, runAsSystem } from '~/config/tenantContext'; import { createModels } from '../models'; @@ -851,9 +851,11 @@ describe('Conversation Operations', () => { describe('forced retention message cascade', () => { const Message = () => mongoose.models.Message as mongoose.Model; + const File = () => mongoose.models.File as mongoose.Model; beforeEach(async () => { await Message().deleteMany({}); + await File().deleteMany({}); }); it('backfills existing messages when ephemeral converts a permanent conversation', async () => { @@ -889,6 +891,34 @@ describe('Conversation Operations', () => { } }); + it('caps existing files when ephemeral converts a permanent conversation', async () => { + const conversationId = uuidv4(); + const fileId = uuidv4(); + await Conversation.create({ + conversationId, + user: 'user123', + endpoint: EModelEndpoint.openAI, + title: 'Existing permanent chat', + }); + await File().collection.insertOne({ + file_id: fileId, + conversationId, + user: new mongoose.Types.ObjectId(), + expiredAt: null, + }); + + await saveConvo( + { + userId: 'user123', + interfaceConfig: { temporaryChatRetention: 24, retentionMode: RetentionMode.EPHEMERAL }, + }, + { conversationId, isArchived: true }, + ); + + const file = await File().findOne({ file_id: fileId }).lean(); + expect(file?.expiredAt).toBeInstanceOf(Date); + }); + it('converts an active retained (all-mode) conversation when switching to ephemeral', async () => { const conversationId = uuidv4(); const retainedUntil = new Date(Date.now() + 365 * 24 * 60 * 60 * 1000); diff --git a/packages/data-schemas/src/methods/conversation.ts b/packages/data-schemas/src/methods/conversation.ts index 481097a289..246850198f 100644 --- a/packages/data-schemas/src/methods/conversation.ts +++ b/packages/data-schemas/src/methods/conversation.ts @@ -6,11 +6,13 @@ import type { IChatProjectDocument, IConversation, IMessage, + IMongoFile, ISharedLink, } from '~/types'; import type { MessageMethods } from './message'; import { buildRetentionVisibilityFilter, + capConversationFiles, capConversationSharedLinks, capForcedRetentionExpiry, conversationNeedsForcedRetention, @@ -365,8 +367,10 @@ export function createConversationMethods( ) { const Message = mongoose.models.Message as Model; const SharedLink = mongoose.models.SharedLink as Model; + const File = mongoose.models.File as Model; await forceConversationMessagesTemporary(Message, userId, conversationId, forcedExpiredAt); await capConversationSharedLinks(SharedLink, userId, conversationId, forcedExpiredAt); + await capConversationFiles(File, conversationId, forcedExpiredAt); } if (