Skip to content

Commit a2f09e9

Browse files
refactor(solid): DRY and centralize storage logic (per PR review)
1 parent b54163d commit a2f09e9

3 files changed

Lines changed: 59 additions & 132 deletions

File tree

api/models/Conversation.js

Lines changed: 25 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -114,86 +114,59 @@ module.exports = {
114114
* @returns {Promise<TConversation>} The conversation object.
115115
*/
116116
saveConvo: async (req, { conversationId, newConversationId, ...convo }, metadata) => {
117-
// Use Solid storage when user logged in via "Continue with Solid"
118-
if (isSolidUser(req)) {
119-
try {
120-
if (metadata?.context) {
121-
logger.debug(`[saveConvo] ${metadata.context}`);
122-
}
123-
124-
const convoData = {
125-
conversationId,
126-
newConversationId,
127-
...convo,
128-
};
117+
if (metadata?.context) {
118+
logger.debug(`[saveConvo] ${metadata.context}`);
119+
}
129120

130-
if (req?.body?.isTemporary) {
131-
try {
132-
const appConfig = req.config;
133-
convoData.expiredAt = createTempChatExpirationDate(appConfig?.interfaceConfig);
134-
} catch (err) {
135-
logger.error('Error creating temporary chat expiration date:', err);
136-
logger.info(`---\`saveConvo\` context: ${metadata?.context}`);
137-
convoData.expiredAt = null;
138-
}
139-
} else {
140-
convoData.expiredAt = null;
141-
}
121+
// Build shared payload: expiredAt and base convo fields
122+
let expiredAt = null;
123+
if (req?.body?.isTemporary) {
124+
try {
125+
const appConfig = req.config;
126+
expiredAt = createTempChatExpirationDate(appConfig?.interfaceConfig);
127+
} catch (err) {
128+
logger.error('Error creating temporary chat expiration date:', err);
129+
logger.info(`---\`saveConvo\` context: ${metadata?.context}`);
130+
}
131+
}
132+
const baseConvo = {
133+
conversationId,
134+
newConversationId,
135+
...convo,
136+
expiredAt,
137+
};
142138

143-
const savedConvo = await saveConvoToSolid(req, convoData, metadata);
139+
if (isSolidUser(req)) {
140+
try {
141+
const savedConvo = await saveConvoToSolid(req, baseConvo, metadata);
144142
return savedConvo;
145143
} catch (error) {
146144
logger.error('[saveConvo] Error saving conversation to Solid Pod', error);
147145
if (metadata && metadata?.context) {
148146
logger.info(`[saveConvo] ${metadata.context}`);
149147
}
150-
// Solid users: surface error so UI can show "Save failed"
151148
throw error;
152149
}
153150
}
154151

155-
// MongoDB storage
156152
try {
157-
if (metadata?.context) {
158-
logger.debug(`[saveConvo] ${metadata.context}`);
159-
}
160-
161153
const messages = await getMessages({ conversationId }, '_id');
162-
const update = { ...convo, messages, user: req.user.id };
163-
154+
const update = { ...convo, messages, user: req.user.id, expiredAt };
164155
if (newConversationId) {
165156
update.conversationId = newConversationId;
166157
}
167158

168-
if (req?.body?.isTemporary) {
169-
try {
170-
const appConfig = req.config;
171-
update.expiredAt = createTempChatExpirationDate(appConfig?.interfaceConfig);
172-
} catch (err) {
173-
logger.error('Error creating temporary chat expiration date:', err);
174-
logger.info(`---\`saveConvo\` context: ${metadata?.context}`);
175-
update.expiredAt = null;
176-
}
177-
} else {
178-
update.expiredAt = null;
179-
}
180-
181159
/** @type {{ $set: Partial<TConversation>; $unset?: Record<keyof TConversation, number> }} */
182160
const updateOperation = { $set: update };
183161
if (metadata && metadata.unsetFields && Object.keys(metadata.unsetFields).length > 0) {
184162
updateOperation.$unset = metadata.unsetFields;
185163
}
186164

187-
/** Note: the resulting Model object is necessary for Meilisearch operations */
188165
const conversation = await Conversation.findOneAndUpdate(
189166
{ conversationId, user: req.user.id },
190167
updateOperation,
191-
{
192-
new: true,
193-
upsert: true,
194-
},
168+
{ new: true, upsert: true },
195169
);
196-
197170
return conversation.toObject();
198171
} catch (error) {
199172
logger.error('[saveConvo] Error saving conversation', error);

api/models/Message.js

Lines changed: 29 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -53,36 +53,36 @@ async function saveMessage(req, params, metadata) {
5353
return;
5454
}
5555

56-
// Use Solid storage when user logged in via "Continue with Solid"
57-
if (isSolidUser(req)) {
56+
// Build shared payload once: messageId, expiredAt, tokenCount
57+
let expiredAt = null;
58+
if (req?.body?.isTemporary) {
5859
try {
59-
const messageData = {
60-
...params,
61-
messageId: params.newMessageId || params.messageId,
62-
};
63-
64-
if (req?.body?.isTemporary) {
65-
try {
66-
const appConfig = req.config;
67-
messageData.expiredAt = createTempChatExpirationDate(appConfig?.interfaceConfig);
68-
} catch (err) {
69-
logger.error('Error creating temporary chat expiration date:', err);
70-
logger.info(`---\`saveMessage\` context: ${metadata?.context}`);
71-
messageData.expiredAt = null;
72-
}
73-
} else {
74-
messageData.expiredAt = null;
75-
}
76-
77-
if (messageData.tokenCount != null && isNaN(messageData.tokenCount)) {
78-
logger.warn(
79-
`Resetting invalid \`tokenCount\` for message \`${params.messageId}\`: ${messageData.tokenCount}`,
80-
);
81-
logger.info(`---\`saveMessage\` context: ${metadata?.context}`);
82-
messageData.tokenCount = 0;
83-
}
60+
const appConfig = req.config;
61+
expiredAt = createTempChatExpirationDate(appConfig?.interfaceConfig);
62+
} catch (err) {
63+
logger.error('Error creating temporary chat expiration date:', err);
64+
logger.info(`---\`saveMessage\` context: ${metadata?.context}`);
65+
}
66+
}
67+
const messageId = params.newMessageId || params.messageId;
68+
let tokenCount = params.tokenCount;
69+
if (tokenCount != null && isNaN(tokenCount)) {
70+
logger.warn(
71+
`Resetting invalid \`tokenCount\` for message \`${params.messageId}\`: ${tokenCount}`,
72+
);
73+
logger.info(`---\`saveMessage\` context: ${metadata?.context}`);
74+
tokenCount = 0;
75+
}
76+
const baseMessage = {
77+
...params,
78+
messageId,
79+
expiredAt,
80+
tokenCount: tokenCount ?? 0,
81+
};
8482

85-
const savedMessage = await saveMessageToSolid(req, messageData, metadata);
83+
if (isSolidUser(req)) {
84+
try {
85+
const savedMessage = await saveMessageToSolid(req, baseMessage, metadata);
8686
return savedMessage;
8787
} catch (err) {
8888
logger.error('Error saving message to Solid Pod:', err);
@@ -91,35 +91,8 @@ async function saveMessage(req, params, metadata) {
9191
}
9292
}
9393

94-
95-
// MongoDB path for non-Solid users
9694
try {
97-
const update = {
98-
...params,
99-
user: req.user.id,
100-
messageId: params.newMessageId || params.messageId,
101-
};
102-
103-
if (req?.body?.isTemporary) {
104-
try {
105-
const appConfig = req.config;
106-
update.expiredAt = createTempChatExpirationDate(appConfig?.interfaceConfig);
107-
} catch (err) {
108-
logger.error('Error creating temporary chat expiration date:', err);
109-
logger.info(`---\`saveMessage\` context: ${metadata?.context}`);
110-
update.expiredAt = null;
111-
}
112-
} else {
113-
update.expiredAt = null;
114-
}
115-
116-
if (update.tokenCount != null && isNaN(update.tokenCount)) {
117-
logger.warn(
118-
`Resetting invalid \`tokenCount\` for message \`${params.messageId}\`: ${update.tokenCount}`,
119-
);
120-
logger.info(`---\`saveMessage\` context: ${metadata?.context}`);
121-
update.tokenCount = 0;
122-
}
95+
const update = { ...baseMessage, user: req.user.id };
12396
const message = await Message.findOneAndUpdate(
12497
{ messageId: params.messageId, user: req.user.id },
12598
update,

api/server/middleware/validateMessageReq.js

Lines changed: 5 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
const { logger } = require('@librechat/data-schemas');
21
const { getConvo } = require('~/models');
3-
const { getConvoFromSolid } = require('~/server/services/SolidStorage');
4-
const { isSolidUser } = require('~/server/utils/isSolidUser');
52

6-
// Middleware to validate conversationId and user relationship
3+
// Middleware to validate conversationId and user relationship.
4+
// getConvo(user, conversationId, req) handles Solid vs MongoDB internally.
75
const validateMessageReq = async (req, res, next) => {
86
let conversationId = req.params.conversationId || req.body.conversationId;
97

@@ -15,26 +13,9 @@ const validateMessageReq = async (req, res, next) => {
1513
conversationId = req.body.message.conversationId;
1614
}
1715

18-
let conversation = null;
19-
20-
// Solid users: use Solid storage only; no MongoDB fallback
21-
if (isSolidUser(req)) {
22-
try {
23-
conversation = await getConvoFromSolid(req, conversationId);
24-
} catch (error) {
25-
logger.error('[validateMessageReq] Error loading conversation from Solid Pod', {
26-
conversationId,
27-
error: error.message,
28-
});
29-
}
30-
if (!conversation) {
31-
return res.status(404).json({ error: 'Conversation not found' });
32-
}
33-
} else {
34-
conversation = await getConvo(req.user.id, conversationId);
35-
if (!conversation) {
36-
return res.status(404).json({ error: 'Conversation not found' });
37-
}
16+
const conversation = await getConvo(req.user.id, conversationId, req);
17+
if (!conversation) {
18+
return res.status(404).json({ error: 'Conversation not found' });
3819
}
3920

4021
if (conversation.user !== req.user.id) {

0 commit comments

Comments
 (0)