mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-02-14 22:48:10 +01:00
* feat: replace unsupported MongoDB aggregation operators for FerretDB compatibility Replace $lookup, $unwind, $sample, $replaceRoot, and $addFields aggregation stages which are unsupported on FerretDB v2.x (postgres-documentdb backend). - Prompt.js: Replace $lookup/$unwind/$project pipelines with find().select().lean() + attachProductionPrompts() batch helper. Replace $group/$replaceRoot/$sample in getRandomPromptGroups with distinct() + Fisher-Yates shuffle. - Agent/Prompt migration scripts: Replace $lookup anti-join pattern with distinct() + $nin two-step queries for finding un-migrated resources. All replacement patterns verified against FerretDB v2.7.0. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: use $pullAll for simple array removals, fix memberIds type mismatches Replace $pull with $pullAll for exact-value scalar array removals. Both operators work on MongoDB and FerretDB, but $pullAll is more explicit for exact matching (no condition expressions). Fix critical type mismatch bugs where ObjectId values were used against String[] memberIds arrays in Group queries: - config/delete-user.js: use string uid instead of ObjectId user._id - e2e/setup/cleanupUser.ts: convert userId.toString() before query Harden PermissionService.bulkUpdateResourcePermissions abort handling to prevent crash when abortTransaction is called after commitTransaction. All changes verified against FerretDB v2.7.0 and MongoDB Memory Server. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: harden transaction support probe for FerretDB compatibility Commit the transaction before aborting in supportsTransactions probe, and wrap abortTransaction in try-catch to prevent crashes when abort is called after a successful commit (observed behavior on FerretDB). Co-authored-by: Cursor <cursoragent@cursor.com> * feat: add FerretDB compatibility test suite, retry utilities, and CI config Add comprehensive FerretDB integration test suite covering: - $pullAll scalar array operations - $pull with subdocument conditions - $lookup replacement (find + manual join) - $sample replacement (distinct + Fisher-Yates) - $bit and $bitsAllSet operations - Migration anti-join pattern - Multi-tenancy (useDb, scaling, write amplification) - Sharding proof-of-concept - Production operations (backup/restore, schema migration, deadlock retry) Add production retryWithBackoff utility for deadlock recovery during concurrent index creation on FerretDB/DocumentDB backends. Add UserController.spec.js tests for deleteUserController (runs in CI). Configure jest and eslint to isolate FerretDB tests from CI pipelines: - packages/data-schemas/jest.config.mjs: ignore misc/ directory - eslint.config.mjs: ignore packages/data-schemas/misc/ Include Docker Compose config for local FerretDB v2.7 + postgres-documentdb, dedicated jest/tsconfig for the test files, and multi-tenancy findings doc. Co-authored-by: Cursor <cursoragent@cursor.com> * style: brace formatting in aclEntry.ts modifyPermissionBits Co-authored-by: Cursor <cursoragent@cursor.com> * refactor: reorganize retry utilities and update imports - Moved retryWithBackoff utility to a new file `retry.ts` for better structure. - Updated imports in `orgOperations.ferretdb.spec.ts` to reflect the new location of retry utilities. - Removed old import statement for retryWithBackoff from index.ts to streamline exports. * test: add $pullAll coverage for ConversationTag and PermissionService Add integration tests for deleteConversationTag verifying $pullAll removes tags from conversations correctly, and for syncUserEntraGroupMemberships verifying $pullAll removes user from non-matching Entra groups while preserving local group membership. --------- Co-authored-by: Cursor <cursoragent@cursor.com>
133 lines
4.5 KiB
JavaScript
133 lines
4.5 KiB
JavaScript
const { GLOBAL_PROJECT_NAME } = require('librechat-data-provider').Constants;
|
|
const { Project } = require('~/db/models');
|
|
|
|
/**
|
|
* Retrieve a project by ID and convert the found project document to a plain object.
|
|
*
|
|
* @param {string} projectId - The ID of the project to find and return as a plain object.
|
|
* @param {string|string[]} [fieldsToSelect] - The fields to include or exclude in the returned document.
|
|
* @returns {Promise<IMongoProject>} A plain object representing the project document, or `null` if no project is found.
|
|
*/
|
|
const getProjectById = async function (projectId, fieldsToSelect = null) {
|
|
const query = Project.findById(projectId);
|
|
|
|
if (fieldsToSelect) {
|
|
query.select(fieldsToSelect);
|
|
}
|
|
|
|
return await query.lean();
|
|
};
|
|
|
|
/**
|
|
* Retrieve a project by name and convert the found project document to a plain object.
|
|
* If the project with the given name doesn't exist and the name is "instance", create it and return the lean version.
|
|
*
|
|
* @param {string} projectName - The name of the project to find or create.
|
|
* @param {string|string[]} [fieldsToSelect] - The fields to include or exclude in the returned document.
|
|
* @returns {Promise<IMongoProject>} A plain object representing the project document.
|
|
*/
|
|
const getProjectByName = async function (projectName, fieldsToSelect = null) {
|
|
const query = { name: projectName };
|
|
const update = { $setOnInsert: { name: projectName } };
|
|
const options = {
|
|
new: true,
|
|
upsert: projectName === GLOBAL_PROJECT_NAME,
|
|
lean: true,
|
|
select: fieldsToSelect,
|
|
};
|
|
|
|
return await Project.findOneAndUpdate(query, update, options);
|
|
};
|
|
|
|
/**
|
|
* Add an array of prompt group IDs to a project's promptGroupIds array, ensuring uniqueness.
|
|
*
|
|
* @param {string} projectId - The ID of the project to update.
|
|
* @param {string[]} promptGroupIds - The array of prompt group IDs to add to the project.
|
|
* @returns {Promise<IMongoProject>} The updated project document.
|
|
*/
|
|
const addGroupIdsToProject = async function (projectId, promptGroupIds) {
|
|
return await Project.findByIdAndUpdate(
|
|
projectId,
|
|
{ $addToSet: { promptGroupIds: { $each: promptGroupIds } } },
|
|
{ new: true },
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Remove an array of prompt group IDs from a project's promptGroupIds array.
|
|
*
|
|
* @param {string} projectId - The ID of the project to update.
|
|
* @param {string[]} promptGroupIds - The array of prompt group IDs to remove from the project.
|
|
* @returns {Promise<IMongoProject>} The updated project document.
|
|
*/
|
|
const removeGroupIdsFromProject = async function (projectId, promptGroupIds) {
|
|
return await Project.findByIdAndUpdate(
|
|
projectId,
|
|
{ $pullAll: { promptGroupIds: promptGroupIds } },
|
|
{ new: true },
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Remove a prompt group ID from all projects.
|
|
*
|
|
* @param {string} promptGroupId - The ID of the prompt group to remove from projects.
|
|
* @returns {Promise<void>}
|
|
*/
|
|
const removeGroupFromAllProjects = async (promptGroupId) => {
|
|
await Project.updateMany({}, { $pullAll: { promptGroupIds: [promptGroupId] } });
|
|
};
|
|
|
|
/**
|
|
* Add an array of agent IDs to a project's agentIds array, ensuring uniqueness.
|
|
*
|
|
* @param {string} projectId - The ID of the project to update.
|
|
* @param {string[]} agentIds - The array of agent IDs to add to the project.
|
|
* @returns {Promise<IMongoProject>} The updated project document.
|
|
*/
|
|
const addAgentIdsToProject = async function (projectId, agentIds) {
|
|
return await Project.findByIdAndUpdate(
|
|
projectId,
|
|
{ $addToSet: { agentIds: { $each: agentIds } } },
|
|
{ new: true },
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Remove an array of agent IDs from a project's agentIds array.
|
|
*
|
|
* @param {string} projectId - The ID of the project to update.
|
|
* @param {string[]} agentIds - The array of agent IDs to remove from the project.
|
|
* @returns {Promise<IMongoProject>} The updated project document.
|
|
*/
|
|
const removeAgentIdsFromProject = async function (projectId, agentIds) {
|
|
return await Project.findByIdAndUpdate(
|
|
projectId,
|
|
{ $pullAll: { agentIds: agentIds } },
|
|
{ new: true },
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Remove an agent ID from all projects.
|
|
*
|
|
* @param {string} agentId - The ID of the agent to remove from projects.
|
|
* @returns {Promise<void>}
|
|
*/
|
|
const removeAgentFromAllProjects = async (agentId) => {
|
|
await Project.updateMany({}, { $pullAll: { agentIds: [agentId] } });
|
|
};
|
|
|
|
module.exports = {
|
|
getProjectById,
|
|
getProjectByName,
|
|
/* prompts */
|
|
addGroupIdsToProject,
|
|
removeGroupIdsFromProject,
|
|
removeGroupFromAllProjects,
|
|
/* agents */
|
|
addAgentIdsToProject,
|
|
removeAgentIdsFromProject,
|
|
removeAgentFromAllProjects,
|
|
};
|