mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-08-27 12:13:30 +00:00
🕰️ fix: Stop Pinning and Archiving From Counting as Chat Activity (#14861)
* fix: stop pinning and archiving from counting as chat activity Both routes went through saveConvo, which lets mongoose stamp updatedAt. The sidebar orders chats by that field, so pinning hoisted an untouched chat to the top of Today, and unarchiving a year-old chat dropped it there too instead of back into its own date group. saveConvo now takes preserveUpdatedAt, and both routes pass it. They also pass noUpsert: with timestamps suppressed an upsert would insert a conversation carrying neither createdAt nor updatedAt, so an unknown conversation id is now a 404 rather than a silently created stub. * test: pin a project's activity pointer against metadata-only saves Review raised that preserving updatedAt could drag a project's lastConversationAt back to the pinned chat's older timestamp, since the incremental path $sets it outright. That path is not reachable here: a pin carries no chatProjectId, so previousChatProjectId stays null while the conversation has a real one, projectMembershipChanged is therefore true, and saveConvo takes the full recompute branch instead. This test holds that in place, with a newer sibling conversation in the project so a regression to the incremental path would fail it. * fix: keep updatedAt through the retention backfill Under RetentionMode.ALL a legacy chat with no stored isTemporary gets a second write after the main update, and that one still had mongoose timestamps enabled. The first archive of such a chat therefore bumped updatedAt anyway and landed in Today, defeating preserveUpdatedAt on exactly the old conversations it was meant to protect.
This commit is contained in:
parent
fb8ae881cf
commit
5fc05ac037
4 changed files with 227 additions and 8 deletions
|
|
@ -543,7 +543,11 @@ describe('Convos Routes', () => {
|
|||
expect(saveConvo).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ userId: 'test-user-123' }),
|
||||
{ conversationId: mockConversationId, isArchived: true },
|
||||
{ context: `POST /api/convos/archive ${mockConversationId}` },
|
||||
{
|
||||
context: `POST /api/convos/archive ${mockConversationId}`,
|
||||
preserveUpdatedAt: true,
|
||||
noUpsert: true,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -572,7 +576,11 @@ describe('Convos Routes', () => {
|
|||
expect(saveConvo).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ userId: 'test-user-123' }),
|
||||
{ conversationId: mockConversationId, isArchived: false },
|
||||
{ context: `POST /api/convos/archive ${mockConversationId}` },
|
||||
{
|
||||
context: `POST /api/convos/archive ${mockConversationId}`,
|
||||
preserveUpdatedAt: true,
|
||||
noUpsert: true,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -663,7 +671,11 @@ describe('Convos Routes', () => {
|
|||
expect(saveConvo).toHaveBeenCalledWith(
|
||||
{ userId: 'test-user-123' },
|
||||
{ conversationId: mockConversationId, pinned: true },
|
||||
{ context: `POST /api/convos/pin ${mockConversationId}` },
|
||||
{
|
||||
context: `POST /api/convos/pin ${mockConversationId}`,
|
||||
preserveUpdatedAt: true,
|
||||
noUpsert: true,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -678,10 +690,27 @@ describe('Convos Routes', () => {
|
|||
expect(saveConvo).toHaveBeenCalledWith(
|
||||
{ userId: 'test-user-123' },
|
||||
{ conversationId: mockConversationId, pinned: false },
|
||||
{ context: `POST /api/convos/pin ${mockConversationId}` },
|
||||
{
|
||||
context: `POST /api/convos/pin ${mockConversationId}`,
|
||||
preserveUpdatedAt: true,
|
||||
noUpsert: true,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
/** With timestamps suppressed, an upsert would insert a conversation carrying
|
||||
* neither createdAt nor updatedAt, so an unknown id has to be rejected instead. */
|
||||
it('should return 404 when the conversation does not exist', async () => {
|
||||
saveConvo.mockResolvedValue(null);
|
||||
|
||||
const response = await request(app)
|
||||
.post('/api/convos/pin')
|
||||
.send({ arg: { conversationId: 'missing-convo', pinned: true } });
|
||||
|
||||
expect(response.status).toBe(404);
|
||||
expect(response.body).toEqual({ error: 'Conversation not found' });
|
||||
});
|
||||
|
||||
it('should return 400 when conversationId is missing', async () => {
|
||||
const response = await request(app)
|
||||
.post('/api/convos/pin')
|
||||
|
|
|
|||
|
|
@ -208,8 +208,20 @@ router.post('/archive', validateConvoAccess, async (req, res) => {
|
|||
interfaceConfig: req?.config?.interfaceConfig,
|
||||
},
|
||||
{ conversationId, isArchived },
|
||||
{ context: `POST /api/convos/archive ${conversationId}` },
|
||||
{
|
||||
context: `POST /api/convos/archive ${conversationId}`,
|
||||
/** Filing a chat away is not activity: `updatedAt` stays the chat's own last
|
||||
* activity so unarchiving restores it to its real place in the date groups. */
|
||||
preserveUpdatedAt: true,
|
||||
/** Without timestamps, an upsert would insert a conversation that has none. */
|
||||
noUpsert: true,
|
||||
},
|
||||
);
|
||||
|
||||
if (!dbResponse) {
|
||||
return res.status(404).json({ error: 'Conversation not found' });
|
||||
}
|
||||
|
||||
res.status(200).json(dbResponse);
|
||||
} catch (error) {
|
||||
logger.error('Error archiving conversation', error);
|
||||
|
|
@ -236,8 +248,19 @@ router.post('/pin', validateConvoAccess, async (req, res) => {
|
|||
const dbResponse = await db.saveConvo(
|
||||
{ userId: req.user.id },
|
||||
{ conversationId, pinned },
|
||||
{ context: `POST /api/convos/pin ${conversationId}` },
|
||||
{
|
||||
context: `POST /api/convos/pin ${conversationId}`,
|
||||
/** Pinning is a bookmark, not activity, so the sidebar's `updatedAt` order stands. */
|
||||
preserveUpdatedAt: true,
|
||||
/** Without timestamps, an upsert would insert a conversation that has none. */
|
||||
noUpsert: true,
|
||||
},
|
||||
);
|
||||
|
||||
if (!dbResponse) {
|
||||
return res.status(404).json({ error: 'Conversation not found' });
|
||||
}
|
||||
|
||||
res.status(200).json(dbResponse);
|
||||
} catch (error) {
|
||||
logger.error('Error pinning conversation', error);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue