From 37cc5faff5b609d43914d4cb7f03bd555c09194c Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Fri, 13 Feb 2026 03:04:15 -0500 Subject: [PATCH] =?UTF-8?q?=F0=9F=97=91=EF=B8=8F=20chore:=20Remove=20Depre?= =?UTF-8?q?cated=20Project=20Model=20and=20Associated=20Fields=20(#11773)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: remove projects and projectIds usage * chore: empty line linting * chore: remove isCollaborative property across agent models and related tests - Removed the isCollaborative property from agent models, controllers, and tests, as it is deprecated in favor of ACL permissions. - Updated related validation schemas and data provider types to reflect this change. - Ensured all references to isCollaborative were stripped from the codebase to maintain consistency and clarity. --- api/models/Agent.js | 87 +-------- api/models/Agent.spec.js | 177 +----------------- api/models/Project.js | 133 ------------- api/models/Prompt.js | 66 +------ api/models/Prompt.spec.js | 9 +- api/models/PromptGroupMigration.spec.js | 29 ++- api/server/controllers/agents/v1.js | 6 - api/server/controllers/agents/v1.spec.js | 29 --- api/server/routes/agents/v1.js | 13 +- api/server/routes/config.js | 6 +- api/server/routes/prompts.js | 11 +- api/server/services/start/migration.js | 3 - .../Prompts/Groups/ChatGroupItem.tsx | 13 +- .../Prompts/Groups/DashGroupItem.tsx | 10 +- client/src/components/Prompts/Groups/List.tsx | 17 +- .../SidePanel/Agents/AgentPanel.test.tsx | 3 +- .../Agents/__tests__/AgentFooter.spec.tsx | 8 - client/src/data-provider/prompts.ts | 7 +- config/migrate-agent-permissions.js | 31 ++- config/migrate-prompt-permissions.js | 27 ++- packages/api/src/agents/migration.ts | 27 +-- packages/api/src/agents/validation.ts | 3 - packages/api/src/middleware/access.spec.ts | 27 +-- packages/api/src/prompts/migration.ts | 32 ++-- packages/api/src/prompts/schemas.spec.ts | 20 -- packages/api/src/prompts/schemas.ts | 4 - packages/data-provider/src/config.ts | 3 - packages/data-provider/src/schemas.ts | 3 - packages/data-provider/src/types.ts | 5 +- .../data-provider/src/types/assistants.ts | 6 - .../migrationAntiJoin.ferretdb.spec.ts | 7 +- .../ferretdb/promptLookup.ferretdb.spec.ts | 11 +- .../misc/ferretdb/pullAll.ferretdb.spec.ts | 18 -- packages/data-schemas/src/models/index.ts | 2 - packages/data-schemas/src/models/project.ts | 8 - packages/data-schemas/src/schema/agent.ts | 9 - packages/data-schemas/src/schema/index.ts | 1 - packages/data-schemas/src/schema/project.ts | 34 ---- .../data-schemas/src/schema/promptGroup.ts | 6 - packages/data-schemas/src/types/agent.ts | 3 - packages/data-schemas/src/types/prompts.ts | 1 - 41 files changed, 94 insertions(+), 821 deletions(-) delete mode 100644 api/models/Project.js delete mode 100644 packages/data-schemas/src/models/project.ts delete mode 100644 packages/data-schemas/src/schema/project.ts diff --git a/api/models/Agent.js b/api/models/Agent.js index 2467af25ac..0e3b1332c5 100644 --- a/api/models/Agent.js +++ b/api/models/Agent.js @@ -4,7 +4,6 @@ const { logger } = require('@librechat/data-schemas'); const { getCustomEndpointConfig } = require('@librechat/api'); const { Tools, - SystemRoles, ResourceType, actionDelimiter, isAgentsEndpoint, @@ -12,11 +11,6 @@ const { encodeEphemeralAgentId, } = require('librechat-data-provider'); const { mcp_all, mcp_delimiter } = require('librechat-data-provider').Constants; -const { - removeAgentFromAllProjects, - removeAgentIdsFromProject, - addAgentIdsToProject, -} = require('./Project'); const { removeAllPermissions } = require('~/server/services/PermissionService'); const { getMCPServerTools } = require('~/server/services/Config'); const { Agent, AclEntry, User } = require('~/db/models'); @@ -291,22 +285,8 @@ const isDuplicateVersion = (updateData, currentData, versions, actionsHash = nul break; } - // Special handling for projectIds (MongoDB ObjectIds) - if (field === 'projectIds') { - const wouldBeIds = wouldBeArr.map((id) => id.toString()).sort(); - const versionIds = lastVersionArr.map((id) => id.toString()).sort(); - - if (!wouldBeIds.every((id, i) => id === versionIds[i])) { - isMatch = false; - break; - } - } // Handle arrays of objects - else if ( - wouldBeArr.length > 0 && - typeof wouldBeArr[0] === 'object' && - wouldBeArr[0] !== null - ) { + if (wouldBeArr.length > 0 && typeof wouldBeArr[0] === 'object' && wouldBeArr[0] !== null) { const sortedWouldBe = [...wouldBeArr].map((item) => JSON.stringify(item)).sort(); const sortedVersion = [...lastVersionArr].map((item) => JSON.stringify(item)).sort(); @@ -587,7 +567,6 @@ const removeAgentResourceFiles = async ({ agent_id, files }) => { const deleteAgent = async (searchParameter) => { const agent = await Agent.findOneAndDelete(searchParameter); if (agent) { - await removeAgentFromAllProjects(agent.id); await Promise.all([ removeAllPermissions({ resourceType: ResourceType.AGENT, @@ -631,10 +610,6 @@ const deleteUserAgents = async (userId) => { const agentIds = userAgents.map((agent) => agent.id); const agentObjectIds = userAgents.map((agent) => agent._id); - for (const agentId of agentIds) { - await removeAgentFromAllProjects(agentId); - } - await AclEntry.deleteMany({ resourceType: { $in: [ResourceType.AGENT, ResourceType.REMOTE_AGENT] }, resourceId: { $in: agentObjectIds }, @@ -710,7 +685,6 @@ const getListAgentsByAccess = async ({ name: 1, avatar: 1, author: 1, - projectIds: 1, description: 1, updatedAt: 1, category: 1, @@ -755,64 +729,6 @@ const getListAgentsByAccess = async ({ }; }; -/** - * Updates the projects associated with an agent, adding and removing project IDs as specified. - * This function also updates the corresponding projects to include or exclude the agent ID. - * - * @param {Object} params - Parameters for updating the agent's projects. - * @param {IUser} params.user - Parameters for updating the agent's projects. - * @param {string} params.agentId - The ID of the agent to update. - * @param {string[]} [params.projectIds] - Array of project IDs to add to the agent. - * @param {string[]} [params.removeProjectIds] - Array of project IDs to remove from the agent. - * @returns {Promise} The updated agent document. - * @throws {Error} If there's an error updating the agent or projects. - */ -const updateAgentProjects = async ({ user, agentId, projectIds, removeProjectIds }) => { - const updateOps = {}; - - if (removeProjectIds && removeProjectIds.length > 0) { - for (const projectId of removeProjectIds) { - await removeAgentIdsFromProject(projectId, [agentId]); - } - updateOps.$pullAll = { projectIds: removeProjectIds }; - } - - if (projectIds && projectIds.length > 0) { - for (const projectId of projectIds) { - await addAgentIdsToProject(projectId, [agentId]); - } - updateOps.$addToSet = { projectIds: { $each: projectIds } }; - } - - if (Object.keys(updateOps).length === 0) { - return await getAgent({ id: agentId }); - } - - const updateQuery = { id: agentId, author: user.id }; - if (user.role === SystemRoles.ADMIN) { - delete updateQuery.author; - } - - const updatedAgent = await updateAgent(updateQuery, updateOps, { - updatingUserId: user.id, - skipVersioning: true, - }); - if (updatedAgent) { - return updatedAgent; - } - if (updateOps.$addToSet) { - for (const projectId of projectIds) { - await removeAgentIdsFromProject(projectId, [agentId]); - } - } else if (updateOps.$pull) { - for (const projectId of removeProjectIds) { - await addAgentIdsToProject(projectId, [agentId]); - } - } - - return await getAgent({ id: agentId }); -}; - /** * Reverts an agent to a specific version in its version history. * @param {Object} searchParameter - The search parameters to find the agent to revert. @@ -921,7 +837,6 @@ module.exports = { deleteAgent, deleteUserAgents, revertAgentVersion, - updateAgentProjects, countPromotedAgents, addAgentResourceFile, getListAgentsByAccess, diff --git a/api/models/Agent.spec.js b/api/models/Agent.spec.js index baceb3e8f3..1e242efb07 100644 --- a/api/models/Agent.spec.js +++ b/api/models/Agent.spec.js @@ -24,7 +24,6 @@ const { deleteAgent, deleteUserAgents, revertAgentVersion, - updateAgentProjects, addAgentResourceFile, getListAgentsByAccess, removeAgentResourceFiles, @@ -1060,53 +1059,6 @@ describe('models/Agent', () => { expect(userAfter.favorites.some((f) => f.model === 'gpt-4')).toBe(true); }); - test('should update agent projects', async () => { - const agentId = `agent_${uuidv4()}`; - const authorId = new mongoose.Types.ObjectId(); - const projectId1 = new mongoose.Types.ObjectId(); - const projectId2 = new mongoose.Types.ObjectId(); - const projectId3 = new mongoose.Types.ObjectId(); - - await createAgent({ - id: agentId, - name: 'Project Test Agent', - provider: 'test', - model: 'test-model', - author: authorId, - projectIds: [projectId1], - }); - - await updateAgent( - { id: agentId }, - { $addToSet: { projectIds: { $each: [projectId2, projectId3] } } }, - ); - - await updateAgent({ id: agentId }, { $pull: { projectIds: projectId1 } }); - - await updateAgent({ id: agentId }, { projectIds: [projectId2, projectId3] }); - - const updatedAgent = await getAgent({ id: agentId }); - expect(updatedAgent.projectIds).toHaveLength(2); - expect(updatedAgent.projectIds.map((id) => id.toString())).toContain(projectId2.toString()); - expect(updatedAgent.projectIds.map((id) => id.toString())).toContain(projectId3.toString()); - expect(updatedAgent.projectIds.map((id) => id.toString())).not.toContain( - projectId1.toString(), - ); - - await updateAgent({ id: agentId }, { projectIds: [] }); - - const emptyProjectsAgent = await getAgent({ id: agentId }); - expect(emptyProjectsAgent.projectIds).toHaveLength(0); - - const nonExistentId = `agent_${uuidv4()}`; - await expect( - updateAgentProjects({ - id: nonExistentId, - projectIds: [projectId1], - }), - ).rejects.toThrow(); - }); - test('should handle ephemeral agent loading', async () => { const agentId = 'ephemeral_test'; const endpoint = 'openai'; @@ -1178,20 +1130,6 @@ describe('models/Agent', () => { const result = await fn(); expect(result).toBe(expected); }); - - test('should handle updateAgentProjects with non-existent agent', async () => { - const nonExistentId = `agent_${uuidv4()}`; - const userId = new mongoose.Types.ObjectId(); - const projectId = new mongoose.Types.ObjectId(); - - const result = await updateAgentProjects({ - user: { id: userId.toString() }, - agentId: nonExistentId, - projectIds: [projectId.toString()], - }); - - expect(result).toBeNull(); - }); }); }); @@ -1315,7 +1253,6 @@ describe('models/Agent', () => { test('should handle MongoDB operators and field updates correctly', async () => { const agentId = `agent_${uuidv4()}`; const authorId = new mongoose.Types.ObjectId(); - const projectId = new mongoose.Types.ObjectId(); await createAgent({ id: agentId, @@ -1331,7 +1268,6 @@ describe('models/Agent', () => { { description: 'Updated description', $push: { tools: 'tool2' }, - $addToSet: { projectIds: projectId }, }, ); @@ -1339,7 +1275,6 @@ describe('models/Agent', () => { expect(firstUpdate.description).toBe('Updated description'); expect(firstUpdate.tools).toContain('tool1'); expect(firstUpdate.tools).toContain('tool2'); - expect(firstUpdate.projectIds.map((id) => id.toString())).toContain(projectId.toString()); expect(firstUpdate.versions).toHaveLength(2); await updateAgent( @@ -1744,7 +1679,6 @@ describe('models/Agent', () => { test('should handle version comparison with special field types', async () => { const agentId = `agent_${uuidv4()}`; const authorId = new mongoose.Types.ObjectId(); - const projectId = new mongoose.Types.ObjectId(); await createAgent({ id: agentId, @@ -1752,7 +1686,6 @@ describe('models/Agent', () => { provider: 'test', model: 'test-model', author: authorId, - projectIds: [projectId], model_parameters: { temperature: 0.7 }, }); @@ -2630,7 +2563,6 @@ describe('models/Agent', () => { const authorId = new mongoose.Types.ObjectId(); const userId = new mongoose.Types.ObjectId(); const agentId = `agent_${uuidv4()}`; - const projectId = new mongoose.Types.ObjectId(); await createAgent({ id: agentId, @@ -2638,7 +2570,6 @@ describe('models/Agent', () => { provider: 'openai', model: 'gpt-4', author: authorId, - projectIds: [projectId], }); const mockReq = { user: { id: userId.toString() } }; @@ -2698,7 +2629,6 @@ describe('models/Agent', () => { test('should handle agent creation with all optional fields', async () => { const agentId = `agent_${uuidv4()}`; const authorId = new mongoose.Types.ObjectId(); - const projectId = new mongoose.Types.ObjectId(); const agent = await createAgent({ id: agentId, @@ -2711,9 +2641,7 @@ describe('models/Agent', () => { tools: ['tool1', 'tool2'], actions: ['action1', 'action2'], model_parameters: { temperature: 0.8, max_tokens: 1000 }, - projectIds: [projectId], avatar: 'https://example.com/avatar.png', - isCollaborative: true, tool_resources: { file_search: { file_ids: ['file1', 'file2'] }, }, @@ -2727,9 +2655,7 @@ describe('models/Agent', () => { expect(agent.actions).toEqual(['action1', 'action2']); expect(agent.model_parameters.temperature).toBe(0.8); expect(agent.model_parameters.max_tokens).toBe(1000); - expect(agent.projectIds.map((id) => id.toString())).toContain(projectId.toString()); expect(agent.avatar).toBe('https://example.com/avatar.png'); - expect(agent.isCollaborative).toBe(true); expect(agent.tool_resources.file_search.file_ids).toEqual(['file1', 'file2']); }); @@ -2935,21 +2861,6 @@ describe('models/Agent', () => { expect(finalAgent.name).toBe('Version 4'); }); - test('should handle updateAgentProjects error scenarios', async () => { - const nonExistentId = `agent_${uuidv4()}`; - const userId = new mongoose.Types.ObjectId(); - const projectId = new mongoose.Types.ObjectId(); - - // Test with non-existent agent - const result = await updateAgentProjects({ - user: { id: userId.toString() }, - agentId: nonExistentId, - projectIds: [projectId.toString()], - }); - - expect(result).toBeNull(); - }); - test('should handle revertAgentVersion properly', async () => { const agentId = `agent_${uuidv4()}`; const authorId = new mongoose.Types.ObjectId(); @@ -3003,8 +2914,6 @@ describe('models/Agent', () => { test('should handle updateAgent with combined MongoDB operators', async () => { const agentId = `agent_${uuidv4()}`; const authorId = new mongoose.Types.ObjectId(); - const projectId1 = new mongoose.Types.ObjectId(); - const projectId2 = new mongoose.Types.ObjectId(); await createAgent({ id: agentId, @@ -3013,7 +2922,6 @@ describe('models/Agent', () => { model: 'test-model', author: authorId, tools: ['tool1'], - projectIds: [projectId1], }); // Use multiple operators in single update - but avoid conflicting operations on same field @@ -3022,14 +2930,6 @@ describe('models/Agent', () => { { name: 'Updated Name', $push: { tools: 'tool2' }, - $addToSet: { projectIds: projectId2 }, - }, - ); - - const finalAgent = await updateAgent( - { id: agentId }, - { - $pull: { projectIds: projectId1 }, }, ); @@ -3037,11 +2937,7 @@ describe('models/Agent', () => { expect(updatedAgent.name).toBe('Updated Name'); expect(updatedAgent.tools).toContain('tool1'); expect(updatedAgent.tools).toContain('tool2'); - expect(updatedAgent.projectIds.map((id) => id.toString())).toContain(projectId2.toString()); - - expect(finalAgent).toBeDefined(); - expect(finalAgent.projectIds.map((id) => id.toString())).not.toContain(projectId1.toString()); - expect(finalAgent.versions).toHaveLength(3); + expect(updatedAgent.versions).toHaveLength(2); }); test('should handle updateAgent when agent does not exist', async () => { @@ -3315,65 +3211,6 @@ describe('models/Agent', () => { expect(updated2.description).toBe('Another description'); }); - test('should skip version creation when skipVersioning option is used', async () => { - const agentId = `agent_${uuidv4()}`; - const authorId = new mongoose.Types.ObjectId(); - const projectId1 = new mongoose.Types.ObjectId(); - const projectId2 = new mongoose.Types.ObjectId(); - - // Create agent with initial projectIds - await createAgent({ - id: agentId, - name: 'Test Agent', - provider: 'test', - model: 'test-model', - author: authorId, - projectIds: [projectId1], - }); - - // Share agent using updateAgentProjects (which uses skipVersioning) - const shared = await updateAgentProjects({ - user: { id: authorId.toString() }, // Use the same author ID - agentId: agentId, - projectIds: [projectId2.toString()], - }); - - // Should NOT create a new version due to skipVersioning - expect(shared.versions).toHaveLength(1); - expect(shared.projectIds.map((id) => id.toString())).toContain(projectId1.toString()); - expect(shared.projectIds.map((id) => id.toString())).toContain(projectId2.toString()); - - // Unshare agent using updateAgentProjects - const unshared = await updateAgentProjects({ - user: { id: authorId.toString() }, - agentId: agentId, - removeProjectIds: [projectId1.toString()], - }); - - // Still should NOT create a new version - expect(unshared.versions).toHaveLength(1); - expect(unshared.projectIds.map((id) => id.toString())).not.toContain(projectId1.toString()); - expect(unshared.projectIds.map((id) => id.toString())).toContain(projectId2.toString()); - - // Regular update without skipVersioning should create a version - const regularUpdate = await updateAgent( - { id: agentId }, - { description: 'Updated description' }, - ); - - expect(regularUpdate.versions).toHaveLength(2); - expect(regularUpdate.description).toBe('Updated description'); - - // Direct updateAgent with MongoDB operators should still create versions - const directUpdate = await updateAgent( - { id: agentId }, - { $addToSet: { projectIds: { $each: [projectId1] } } }, - ); - - expect(directUpdate.versions).toHaveLength(3); - expect(directUpdate.projectIds.length).toBe(2); - }); - test('should preserve agent_ids in version history', async () => { const agentId = `agent_${uuidv4()}`; const authorId = new mongoose.Types.ObjectId(); @@ -3754,7 +3591,6 @@ function createTestIds() { return { agentId: `agent_${uuidv4()}`, authorId: new mongoose.Types.ObjectId(), - projectId: new mongoose.Types.ObjectId(), fileId: uuidv4(), }; } @@ -3788,9 +3624,6 @@ function mockFindOneAndUpdateError(errorOnCall = 1) { } function generateVersionTestCases() { - const projectId1 = new mongoose.Types.ObjectId(); - const projectId2 = new mongoose.Types.ObjectId(); - return [ { name: 'simple field update', @@ -3817,13 +3650,5 @@ function generateVersionTestCases() { update: { tools: ['tool2', 'tool3'] }, duplicate: { tools: ['tool2', 'tool3'] }, }, - { - name: 'projectIds update', - initial: { - projectIds: [projectId1], - }, - update: { projectIds: [projectId1, projectId2] }, - duplicate: { projectIds: [projectId2, projectId1] }, - }, ]; } diff --git a/api/models/Project.js b/api/models/Project.js deleted file mode 100644 index dc92348b54..0000000000 --- a/api/models/Project.js +++ /dev/null @@ -1,133 +0,0 @@ -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} 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} 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} 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} 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} - */ -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} 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} 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} - */ -const removeAgentFromAllProjects = async (agentId) => { - await Project.updateMany({}, { $pullAll: { agentIds: [agentId] } }); -}; - -module.exports = { - getProjectById, - getProjectByName, - /* prompts */ - addGroupIdsToProject, - removeGroupIdsFromProject, - removeGroupFromAllProjects, - /* agents */ - addAgentIdsToProject, - removeAgentIdsFromProject, - removeAgentFromAllProjects, -}; diff --git a/api/models/Prompt.js b/api/models/Prompt.js index 83e82c5be6..dc6b19682e 100644 --- a/api/models/Prompt.js +++ b/api/models/Prompt.js @@ -1,18 +1,7 @@ const { ObjectId } = require('mongodb'); const { escapeRegExp } = require('@librechat/api'); const { logger } = require('@librechat/data-schemas'); -const { - Constants, - SystemRoles, - ResourceType, - SystemCategories, -} = require('librechat-data-provider'); -const { - removeGroupFromAllProjects, - removeGroupIdsFromProject, - addGroupIdsToProject, - getProjectByName, -} = require('./Project'); +const { SystemRoles, ResourceType, SystemCategories } = require('librechat-data-provider'); const { removeAllPermissions } = require('~/server/services/PermissionService'); const { PromptGroup, Prompt, AclEntry } = require('~/db/models'); @@ -48,34 +37,21 @@ const getAllPromptGroups = async (req, filter) => { try { const { name, ...query } = filter; - let searchShared = true; - let searchSharedOnly = false; if (name) { query.name = new RegExp(escapeRegExp(name), 'i'); } if (!query.category) { delete query.category; } else if (query.category === SystemCategories.MY_PROMPTS) { - searchShared = false; delete query.category; } else if (query.category === SystemCategories.NO_CATEGORY) { query.category = ''; } else if (query.category === SystemCategories.SHARED_PROMPTS) { - searchSharedOnly = true; delete query.category; } let combinedQuery = query; - if (searchShared) { - const project = await getProjectByName(Constants.GLOBAL_PROJECT_NAME, 'promptGroupIds'); - if (project && project.promptGroupIds && project.promptGroupIds.length > 0) { - const projectQuery = { _id: { $in: project.promptGroupIds }, ...query }; - delete projectQuery.author; - combinedQuery = searchSharedOnly ? projectQuery : { $or: [projectQuery, query] }; - } - } - const groups = await PromptGroup.find(combinedQuery) .sort({ createdAt: -1 }) .select('name oneliner category author authorName createdAt updatedAt command productionId') @@ -100,34 +76,21 @@ const getPromptGroups = async (req, filter) => { const validatedPageNumber = Math.max(parseInt(pageNumber, 10), 1); const validatedPageSize = Math.max(parseInt(pageSize, 10), 1); - let searchShared = true; - let searchSharedOnly = false; if (name) { query.name = new RegExp(escapeRegExp(name), 'i'); } if (!query.category) { delete query.category; } else if (query.category === SystemCategories.MY_PROMPTS) { - searchShared = false; delete query.category; } else if (query.category === SystemCategories.NO_CATEGORY) { query.category = ''; } else if (query.category === SystemCategories.SHARED_PROMPTS) { - searchSharedOnly = true; delete query.category; } let combinedQuery = query; - if (searchShared) { - const project = await getProjectByName(Constants.GLOBAL_PROJECT_NAME, 'promptGroupIds'); - if (project && project.promptGroupIds && project.promptGroupIds.length > 0) { - const projectQuery = { _id: { $in: project.promptGroupIds }, ...query }; - delete projectQuery.author; - combinedQuery = searchSharedOnly ? projectQuery : { $or: [projectQuery, query] }; - } - } - const skip = (validatedPageNumber - 1) * validatedPageSize; const limit = validatedPageSize; @@ -137,7 +100,7 @@ const getPromptGroups = async (req, filter) => { .skip(skip) .limit(limit) .select( - 'name numberOfGenerations oneliner category projectIds productionId author authorName createdAt updatedAt', + 'name numberOfGenerations oneliner category productionId author authorName createdAt updatedAt', ) .lean(), PromptGroup.countDocuments(combinedQuery), @@ -182,7 +145,6 @@ const deletePromptGroup = async ({ _id, author, role }) => { } await Prompt.deleteMany(groupQuery); - await removeGroupFromAllProjects(_id); try { await removeAllPermissions({ resourceType: ResourceType.PROMPTGROUP, resourceId: _id }); @@ -241,7 +203,7 @@ async function getListPromptGroupsByAccess({ const findQuery = PromptGroup.find(baseQuery) .sort({ updatedAt: -1, _id: 1 }) .select( - 'name numberOfGenerations oneliner category projectIds productionId author authorName createdAt updatedAt', + 'name numberOfGenerations oneliner category productionId author authorName createdAt updatedAt', ); if (isPaginated) { @@ -487,7 +449,6 @@ module.exports = { } await PromptGroup.deleteOne({ _id: groupId }); - await removeGroupFromAllProjects(groupId); return { prompt: 'Prompt deleted successfully', @@ -523,10 +484,6 @@ module.exports = { const groupIds = promptGroups.map((group) => group._id); - for (const groupId of groupIds) { - await removeGroupFromAllProjects(groupId); - } - await AclEntry.deleteMany({ resourceType: ResourceType.PROMPTGROUP, resourceId: { $in: groupIds }, @@ -547,23 +504,6 @@ module.exports = { updatePromptGroup: async (filter, data) => { try { const updateOps = {}; - if (data.removeProjectIds) { - for (const projectId of data.removeProjectIds) { - await removeGroupIdsFromProject(projectId, [filter._id]); - } - - updateOps.$pullAll = { projectIds: data.removeProjectIds }; - delete data.removeProjectIds; - } - - if (data.projectIds) { - for (const projectId of data.projectIds) { - await addGroupIdsToProject(projectId, [filter._id]); - } - - updateOps.$addToSet = { projectIds: { $each: data.projectIds } }; - delete data.projectIds; - } const updateData = { ...data, ...updateOps }; const updatedDoc = await PromptGroup.findOneAndUpdate(filter, updateData, { diff --git a/api/models/Prompt.spec.js b/api/models/Prompt.spec.js index e00a1a518c..d749173e81 100644 --- a/api/models/Prompt.spec.js +++ b/api/models/Prompt.spec.js @@ -19,7 +19,7 @@ const dbModels = require('~/db/models'); logger.silent = true; let mongoServer; -let Prompt, PromptGroup, AclEntry, AccessRole, User, Group, Project; +let Prompt, PromptGroup, AclEntry, AccessRole, User, Group; let promptFns, permissionService; let testUsers, testGroups, testRoles; @@ -36,7 +36,6 @@ beforeAll(async () => { AccessRole = dbModels.AccessRole; User = dbModels.User; Group = dbModels.Group; - Project = dbModels.Project; promptFns = require('~/models/Prompt'); permissionService = require('~/server/services/PermissionService'); @@ -118,12 +117,6 @@ async function setupTestData() { description: 'Group with viewer access', }), }; - - await Project.create({ - name: 'Global', - description: 'Global project', - promptGroupIds: [], - }); } describe('Prompt ACL Permissions', () => { diff --git a/api/models/PromptGroupMigration.spec.js b/api/models/PromptGroupMigration.spec.js index f568012cb3..04ff612e7d 100644 --- a/api/models/PromptGroupMigration.spec.js +++ b/api/models/PromptGroupMigration.spec.js @@ -3,7 +3,6 @@ const { ObjectId } = require('mongodb'); const { logger } = require('@librechat/data-schemas'); const { MongoMemoryServer } = require('mongodb-memory-server'); const { - Constants, ResourceType, AccessRoleIds, PrincipalType, @@ -19,9 +18,9 @@ logger.silent = true; describe('PromptGroup Migration Script', () => { let mongoServer; - let Prompt, PromptGroup, AclEntry, AccessRole, User, Project; + let Prompt, PromptGroup, AclEntry, AccessRole, User; let migrateToPromptGroupPermissions; - let testOwner, testProject; + let testOwner; let ownerRole, viewerRole; beforeAll(async () => { @@ -37,7 +36,6 @@ describe('PromptGroup Migration Script', () => { AclEntry = dbModels.AclEntry; AccessRole = dbModels.AccessRole; User = dbModels.User; - Project = dbModels.Project; // Create test user testOwner = await User.create({ @@ -46,11 +44,10 @@ describe('PromptGroup Migration Script', () => { role: 'USER', }); - // Create test project with the proper name - const projectName = Constants.GLOBAL_PROJECT_NAME || 'instance'; - testProject = await Project.create({ + // Create test project document in the raw `projects` collection + const projectName = 'instance'; + await mongoose.connection.db.collection('projects').insertOne({ name: projectName, - description: 'Global project', promptGroupIds: [], }); @@ -95,9 +92,9 @@ describe('PromptGroup Migration Script', () => { await Prompt.deleteMany({}); await PromptGroup.deleteMany({}); await AclEntry.deleteMany({}); - // Reset the project's promptGroupIds array - testProject.promptGroupIds = []; - await testProject.save(); + await mongoose.connection.db + .collection('projects') + .updateOne({ name: 'instance' }, { $set: { promptGroupIds: [] } }); }); it('should categorize promptGroups correctly in dry run', async () => { @@ -118,8 +115,9 @@ describe('PromptGroup Migration Script', () => { }); // Add global group to project's promptGroupIds array - testProject.promptGroupIds = [globalPromptGroup._id]; - await testProject.save(); + await mongoose.connection.db + .collection('projects') + .updateOne({ name: 'instance' }, { $set: { promptGroupIds: [globalPromptGroup._id] } }); const result = await migrateToPromptGroupPermissions({ dryRun: true }); @@ -146,8 +144,9 @@ describe('PromptGroup Migration Script', () => { }); // Add global group to project's promptGroupIds array - testProject.promptGroupIds = [globalPromptGroup._id]; - await testProject.save(); + await mongoose.connection.db + .collection('projects') + .updateOne({ name: 'instance' }, { $set: { promptGroupIds: [globalPromptGroup._id] } }); const result = await migrateToPromptGroupPermissions({ dryRun: false }); diff --git a/api/server/controllers/agents/v1.js b/api/server/controllers/agents/v1.js index 34078b2250..5c16ae50b3 100644 --- a/api/server/controllers/agents/v1.js +++ b/api/server/controllers/agents/v1.js @@ -173,9 +173,6 @@ const getAgentHandler = async (req, res, expandProperties = false) => { agent.author = agent.author.toString(); - // @deprecated - isCollaborative replaced by ACL permissions - agent.isCollaborative = !!agent.isCollaborative; - // Check if agent is public const isPublic = await hasPublicPermission({ resourceType: ResourceType.AGENT, @@ -199,9 +196,6 @@ const getAgentHandler = async (req, res, expandProperties = false) => { author: agent.author, provider: agent.provider, model: agent.model, - projectIds: agent.projectIds, - // @deprecated - isCollaborative replaced by ACL permissions - isCollaborative: agent.isCollaborative, isPublic: agent.isPublic, version: agent.version, // Safe metadata diff --git a/api/server/controllers/agents/v1.spec.js b/api/server/controllers/agents/v1.spec.js index e8eab369e8..b8796a9e32 100644 --- a/api/server/controllers/agents/v1.spec.js +++ b/api/server/controllers/agents/v1.spec.js @@ -14,10 +14,6 @@ jest.mock('~/server/services/Config', () => ({ }), })); -jest.mock('~/models/Project', () => ({ - getProjectByName: jest.fn().mockResolvedValue(null), -})); - jest.mock('~/server/services/Files/strategies', () => ({ getStrategyFunctions: jest.fn(), })); @@ -174,7 +170,6 @@ describe('Agent Controllers - Mass Assignment Protection', () => { // Unauthorized fields that should be stripped author: new mongoose.Types.ObjectId().toString(), // Should not be able to set author authorName: 'Hacker', // Should be stripped - isCollaborative: true, // Should be stripped on creation versions: [], // Should be stripped _id: new mongoose.Types.ObjectId(), // Should be stripped id: 'custom_agent_id', // Should be overridden @@ -193,7 +188,6 @@ describe('Agent Controllers - Mass Assignment Protection', () => { // Verify unauthorized fields were not set expect(createdAgent.author.toString()).toBe(mockReq.user.id); // Should be the request user, not the malicious value expect(createdAgent.authorName).toBeUndefined(); - expect(createdAgent.isCollaborative).toBeFalsy(); expect(createdAgent.versions).toHaveLength(1); // Should have exactly 1 version from creation expect(createdAgent.id).not.toBe('custom_agent_id'); // Should have generated ID expect(createdAgent.id).toMatch(/^agent_/); // Should have proper prefix @@ -444,7 +438,6 @@ describe('Agent Controllers - Mass Assignment Protection', () => { model: 'gpt-3.5-turbo', author: existingAgentAuthorId, description: 'Original description', - isCollaborative: false, versions: [ { name: 'Original Agent', @@ -466,7 +459,6 @@ describe('Agent Controllers - Mass Assignment Protection', () => { name: 'Updated Agent', description: 'Updated description', model: 'gpt-4', - isCollaborative: true, // This IS allowed in updates }; await updateAgentHandler(mockReq, mockRes); @@ -479,13 +471,11 @@ describe('Agent Controllers - Mass Assignment Protection', () => { expect(updatedAgent.name).toBe('Updated Agent'); expect(updatedAgent.description).toBe('Updated description'); expect(updatedAgent.model).toBe('gpt-4'); - expect(updatedAgent.isCollaborative).toBe(true); expect(updatedAgent.author).toBe(existingAgentAuthorId.toString()); // Verify in database const agentInDb = await Agent.findOne({ id: existingAgentId }); expect(agentInDb.name).toBe('Updated Agent'); - expect(agentInDb.isCollaborative).toBe(true); }); test('should reject update with unauthorized fields (mass assignment protection)', async () => { @@ -540,25 +530,6 @@ describe('Agent Controllers - Mass Assignment Protection', () => { expect(updatedAgent.name).toBe('Admin Update'); }); - test('should handle projectIds updates', async () => { - mockReq.user.id = existingAgentAuthorId.toString(); - mockReq.params.id = existingAgentId; - - const projectId1 = new mongoose.Types.ObjectId().toString(); - const projectId2 = new mongoose.Types.ObjectId().toString(); - - mockReq.body = { - projectIds: [projectId1, projectId2], - }; - - await updateAgentHandler(mockReq, mockRes); - - expect(mockRes.json).toHaveBeenCalled(); - - const updatedAgent = mockRes.json.mock.calls[0][0]; - expect(updatedAgent).toBeDefined(); - }); - test('should validate tool_resources in updates', async () => { mockReq.user.id = existingAgentAuthorId.toString(); mockReq.params.id = existingAgentId; diff --git a/api/server/routes/agents/v1.js b/api/server/routes/agents/v1.js index 682a9c795f..21f7a4914c 100644 --- a/api/server/routes/agents/v1.js +++ b/api/server/routes/agents/v1.js @@ -21,15 +21,6 @@ const checkAgentCreate = generateCheckAccess({ getRoleByName, }); -const checkGlobalAgentShare = generateCheckAccess({ - permissionType: PermissionTypes.AGENTS, - permissions: [Permissions.USE, Permissions.CREATE], - bodyProps: { - [Permissions.SHARE]: ['projectIds', 'removeProjectIds'], - }, - getRoleByName, -}); - router.use(requireJwtAuth); /** @@ -99,7 +90,7 @@ router.get( */ router.patch( '/:id', - checkGlobalAgentShare, + checkAgentCreate, canAccessAgentResource({ requiredPermission: PermissionBits.EDIT, resourceIdParam: 'id', @@ -148,7 +139,7 @@ router.delete( */ router.post( '/:id/revert', - checkGlobalAgentShare, + checkAgentCreate, canAccessAgentResource({ requiredPermission: PermissionBits.EDIT, resourceIdParam: 'id', diff --git a/api/server/routes/config.js b/api/server/routes/config.js index a2dc5b79d2..317400c1f3 100644 --- a/api/server/routes/config.js +++ b/api/server/routes/config.js @@ -1,10 +1,9 @@ const express = require('express'); const { logger } = require('@librechat/data-schemas'); const { isEnabled, getBalanceConfig } = require('@librechat/api'); -const { Constants, CacheKeys, defaultSocialLogins } = require('librechat-data-provider'); +const { CacheKeys, defaultSocialLogins } = require('librechat-data-provider'); const { getLdapConfig } = require('~/server/services/Config/ldap'); const { getAppConfig } = require('~/server/services/Config/app'); -const { getProjectByName } = require('~/models/Project'); const { getLogStores } = require('~/cache'); const router = express.Router(); @@ -37,8 +36,6 @@ router.get('/', async function (req, res) { return today.getMonth() === 1 && today.getDate() === 11; }; - const instanceProject = await getProjectByName(Constants.GLOBAL_PROJECT_NAME, '_id'); - const ldap = getLdapConfig(); try { @@ -101,7 +98,6 @@ router.get('/', async function (req, res) { sharedLinksEnabled, publicSharedLinksEnabled, analyticsGtmId: process.env.ANALYTICS_GTM_ID, - instanceProjectId: instanceProject._id.toString(), bundlerURL: process.env.SANDPACK_BUNDLER_URL, staticBundlerURL: process.env.SANDPACK_STATIC_BUNDLER_URL, sharePointFilePickerEnabled, diff --git a/api/server/routes/prompts.js b/api/server/routes/prompts.js index 037bf04813..a0fe65ffd1 100644 --- a/api/server/routes/prompts.js +++ b/api/server/routes/prompts.js @@ -56,15 +56,6 @@ const checkPromptCreate = generateCheckAccess({ getRoleByName, }); -const checkGlobalPromptShare = generateCheckAccess({ - permissionType: PermissionTypes.PROMPTS, - permissions: [Permissions.USE, Permissions.CREATE], - bodyProps: { - [Permissions.SHARE]: ['projectIds', 'removeProjectIds'], - }, - getRoleByName, -}); - router.use(requireJwtAuth); router.use(checkPromptAccess); @@ -364,7 +355,7 @@ const patchPromptGroup = async (req, res) => { router.patch( '/groups/:groupId', - checkGlobalPromptShare, + checkPromptCreate, canAccessPromptGroupResource({ requiredPermission: PermissionBits.EDIT, }), diff --git a/api/server/services/start/migration.js b/api/server/services/start/migration.js index 83b9c83e39..ab8d32b714 100644 --- a/api/server/services/start/migration.js +++ b/api/server/services/start/migration.js @@ -6,7 +6,6 @@ const { checkAgentPermissionsMigration, checkPromptPermissionsMigration, } = require('@librechat/api'); -const { getProjectByName } = require('~/models/Project'); const { Agent, PromptGroup } = require('~/db/models'); const { findRoleByIdentifier } = require('~/models'); @@ -20,7 +19,6 @@ async function checkMigrations() { mongoose, methods: { findRoleByIdentifier, - getProjectByName, }, AgentModel: Agent, }); @@ -33,7 +31,6 @@ async function checkMigrations() { mongoose, methods: { findRoleByIdentifier, - getProjectByName, }, PromptGroupModel: PromptGroup, }); diff --git a/client/src/components/Prompts/Groups/ChatGroupItem.tsx b/client/src/components/Prompts/Groups/ChatGroupItem.tsx index f6e103a78d..9c4f149e57 100644 --- a/client/src/components/Prompts/Groups/ChatGroupItem.tsx +++ b/client/src/components/Prompts/Groups/ChatGroupItem.tsx @@ -16,22 +16,13 @@ import PreviewPrompt from '~/components/Prompts/PreviewPrompt'; import ListCard from '~/components/Prompts/Groups/ListCard'; import { detectVariables } from '~/utils'; -function ChatGroupItem({ - group, - instanceProjectId, -}: { - group: TPromptGroup; - instanceProjectId?: string; -}) { +function ChatGroupItem({ group }: { group: TPromptGroup }) { const localize = useLocalize(); const { submitPrompt } = useSubmitMessage(); const [isPreviewDialogOpen, setPreviewDialogOpen] = useState(false); const [isVariableDialogOpen, setVariableDialogOpen] = useState(false); - const groupIsGlobal = useMemo( - () => instanceProjectId != null && group.projectIds?.includes(instanceProjectId), - [group, instanceProjectId], - ); + const groupIsGlobal = useMemo(() => group.isPublic === true, [group.isPublic]); // Check permissions for the promptGroup const { hasPermission } = useResourcePermissions(ResourceType.PROMPTGROUP, group._id || ''); diff --git a/client/src/components/Prompts/Groups/DashGroupItem.tsx b/client/src/components/Prompts/Groups/DashGroupItem.tsx index d5e8b1a810..ee8c9acf38 100644 --- a/client/src/components/Prompts/Groups/DashGroupItem.tsx +++ b/client/src/components/Prompts/Groups/DashGroupItem.tsx @@ -19,10 +19,9 @@ import { cn } from '~/utils'; interface DashGroupItemProps { group: TPromptGroup; - instanceProjectId?: string; } -function DashGroupItemComponent({ group, instanceProjectId }: DashGroupItemProps) { +function DashGroupItemComponent({ group }: DashGroupItemProps) { const params = useParams(); const navigate = useNavigate(); const localize = useLocalize(); @@ -35,10 +34,7 @@ function DashGroupItemComponent({ group, instanceProjectId }: DashGroupItemProps const canEdit = hasPermission(PermissionBits.EDIT); const canDelete = hasPermission(PermissionBits.DELETE); - const isGlobalGroup = useMemo( - () => instanceProjectId && group.projectIds?.includes(instanceProjectId), - [group.projectIds, instanceProjectId], - ); + const isPublicGroup = useMemo(() => group.isPublic === true, [group.isPublic]); const updateGroup = useUpdatePromptGroup({ onMutate: () => { @@ -115,7 +111,7 @@ function DashGroupItemComponent({ group, instanceProjectId }: DashGroupItemProps
- {isGlobalGroup && ( + {isPublicGroup && ( } = useGetStartupConfig(); - const { instanceProjectId } = startupConfig; const hasCreateAccess = useHasAccess({ permissionType: PermissionTypes.PROMPTS, permission: Permissions.CREATE, @@ -73,17 +70,9 @@ export default function List({ )} {groups.map((group) => { if (isChatRoute) { - return ( - - ); + return ; } - return ( - - ); + return ; })}
diff --git a/client/src/components/SidePanel/Agents/AgentPanel.test.tsx b/client/src/components/SidePanel/Agents/AgentPanel.test.tsx index dfb45ae8d4..a3df6d52c4 100644 --- a/client/src/components/SidePanel/Agents/AgentPanel.test.tsx +++ b/client/src/components/SidePanel/Agents/AgentPanel.test.tsx @@ -2,8 +2,8 @@ * @jest-environment jsdom */ import * as React from 'react'; -import { describe, it, expect, beforeEach, jest } from '@jest/globals'; import { render, waitFor, fireEvent } from '@testing-library/react'; +import { describe, it, expect, beforeEach, jest } from '@jest/globals'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import type { Agent } from 'librechat-data-provider'; @@ -255,7 +255,6 @@ const mockAgentQuery = ( data: { id: 'agent-123', author: 'user-123', - isCollaborative: false, ...agent, } as Agent, isInitialLoading: false, diff --git a/client/src/components/SidePanel/Agents/__tests__/AgentFooter.spec.tsx b/client/src/components/SidePanel/Agents/__tests__/AgentFooter.spec.tsx index 8d882bffe8..1d815f3e8d 100644 --- a/client/src/components/SidePanel/Agents/__tests__/AgentFooter.spec.tsx +++ b/client/src/components/SidePanel/Agents/__tests__/AgentFooter.spec.tsx @@ -26,8 +26,6 @@ mockUseWatch.mockImplementation(({ name }) => { _id: 'agent-db-123', name: 'Test Agent', author: 'user-123', - projectIds: ['project-1'], - isCollaborative: false, }; } if (name === 'id') { @@ -237,8 +235,6 @@ describe('AgentFooter', () => { _id: 'agent-db-123', name: 'Test Agent', author: 'user-123', - projectIds: ['project-1'], - isCollaborative: false, }; } if (name === 'id') { @@ -376,8 +372,6 @@ describe('AgentFooter', () => { _id: 'agent-db-123', name: 'Test Agent', author: 'different-user', // Different author - projectIds: ['project-1'], - isCollaborative: false, }; } if (name === 'id') { @@ -403,8 +397,6 @@ describe('AgentFooter', () => { _id: 'agent-db-123', name: 'Test Agent', author: 'user-123', // Same as current user - projectIds: ['project-1'], - isCollaborative: false, }; } if (name === 'id') { diff --git a/client/src/data-provider/prompts.ts b/client/src/data-provider/prompts.ts index 6c636e253f..be98e1f4a2 100644 --- a/client/src/data-provider/prompts.ts +++ b/client/src/data-provider/prompts.ts @@ -46,12 +46,7 @@ export const useUpdatePromptGroup = ( ]); const previousListData = groupListData ? structuredClone(groupListData) : undefined; - let update = variables.payload; - if (update.removeProjectIds && group?.projectIds) { - update = structuredClone(update); - update.projectIds = group.projectIds.filter((id) => !update.removeProjectIds?.includes(id)); - delete update.removeProjectIds; - } + const update = variables.payload; if (groupListData) { const newData = updateGroupFields( diff --git a/config/migrate-agent-permissions.js b/config/migrate-agent-permissions.js index b511fba50f..01d04c48ca 100644 --- a/config/migrate-agent-permissions.js +++ b/config/migrate-agent-permissions.js @@ -2,16 +2,24 @@ const path = require('path'); const { logger } = require('@librechat/data-schemas'); const { ensureRequiredCollectionsExist } = require('@librechat/api'); const { AccessRoleIds, ResourceType, PrincipalType } = require('librechat-data-provider'); -const { GLOBAL_PROJECT_NAME } = require('librechat-data-provider').Constants; require('module-alias')({ base: path.resolve(__dirname, '..', 'api') }); const connect = require('./connect'); const { grantPermission } = require('~/server/services/PermissionService'); -const { getProjectByName } = require('~/models/Project'); const { findRoleByIdentifier } = require('~/models'); const { Agent, AclEntry } = require('~/db/models'); +const GLOBAL_PROJECT_NAME = 'instance'; + +/** Queries the raw `projects` collection (which may still exist in the DB even though the model is removed) */ +async function getGlobalProjectAgentIds(db) { + const project = await db + .collection('projects') + .findOne({ name: GLOBAL_PROJECT_NAME }, { projection: { agentIds: 1 } }); + return new Set(project?.agentIds || []); +} + async function migrateAgentPermissionsEnhanced({ dryRun = true, batchSize = 100 } = {}) { await connect(); @@ -24,7 +32,6 @@ async function migrateAgentPermissionsEnhanced({ dryRun = true, batchSize = 100 await ensureRequiredCollectionsExist(db); } - // Verify required roles exist const ownerRole = await findRoleByIdentifier(AccessRoleIds.AGENT_OWNER); const viewerRole = await findRoleByIdentifier(AccessRoleIds.AGENT_VIEWER); const editorRole = await findRoleByIdentifier(AccessRoleIds.AGENT_EDITOR); @@ -33,9 +40,7 @@ async function migrateAgentPermissionsEnhanced({ dryRun = true, batchSize = 100 throw new Error('Required roles not found. Run role seeding first.'); } - // Get global project agent IDs (stores agent.id, not agent._id) - const globalProject = await getProjectByName(GLOBAL_PROJECT_NAME, ['agentIds']); - const globalAgentIds = new Set(globalProject?.agentIds || []); + const globalAgentIds = db ? await getGlobalProjectAgentIds(db) : new Set(); logger.info(`Found ${globalAgentIds.size} agents in global project`); @@ -52,9 +57,9 @@ async function migrateAgentPermissionsEnhanced({ dryRun = true, batchSize = 100 .lean(); const categories = { - globalEditAccess: [], // Global project + collaborative -> Public EDIT - globalViewAccess: [], // Global project + not collaborative -> Public VIEW - privateAgents: [], // Not in global project -> Private (owner only) + globalEditAccess: [], + globalViewAccess: [], + privateAgents: [], }; agentsToMigrate.forEach((agent) => { @@ -68,7 +73,6 @@ async function migrateAgentPermissionsEnhanced({ dryRun = true, batchSize = 100 } else { categories.privateAgents.push(agent); - // Log warning if private agent claims to be collaborative if (isCollab) { logger.warn( `Agent "${agent.name}" (${agent.id}) has isCollaborative=true but is not in global project`, @@ -130,7 +134,6 @@ async function migrateAgentPermissionsEnhanced({ dryRun = true, batchSize = 100 ownerGrants: 0, }; - // Process in batches for (let i = 0; i < agentsToMigrate.length; i += batchSize) { const batch = agentsToMigrate.slice(i, i + batchSize); @@ -143,7 +146,6 @@ async function migrateAgentPermissionsEnhanced({ dryRun = true, batchSize = 100 const isGlobal = globalAgentIds.has(agent.id); const isCollab = agent.isCollaborative; - // Always grant owner permission to author await grantPermission({ principalType: PrincipalType.USER, principalId: agent.author, @@ -154,24 +156,20 @@ async function migrateAgentPermissionsEnhanced({ dryRun = true, batchSize = 100 }); results.ownerGrants++; - // Determine public permissions for global project agents only let publicRoleId = null; let description = 'Private'; if (isGlobal) { if (isCollab) { - // Global project + collaborative = Public EDIT access publicRoleId = AccessRoleIds.AGENT_EDITOR; description = 'Global Edit'; results.publicEditGrants++; } else { - // Global project + not collaborative = Public VIEW access publicRoleId = AccessRoleIds.AGENT_VIEWER; description = 'Global View'; results.publicViewGrants++; } - // Grant public permission await grantPermission({ principalType: PrincipalType.PUBLIC, principalId: null, @@ -200,7 +198,6 @@ async function migrateAgentPermissionsEnhanced({ dryRun = true, batchSize = 100 } } - // Brief pause between batches await new Promise((resolve) => setTimeout(resolve, 100)); } diff --git a/config/migrate-prompt-permissions.js b/config/migrate-prompt-permissions.js index d86ee92f08..1127c44ceb 100644 --- a/config/migrate-prompt-permissions.js +++ b/config/migrate-prompt-permissions.js @@ -2,16 +2,24 @@ const path = require('path'); const { logger } = require('@librechat/data-schemas'); const { ensureRequiredCollectionsExist } = require('@librechat/api'); const { AccessRoleIds, ResourceType, PrincipalType } = require('librechat-data-provider'); -const { GLOBAL_PROJECT_NAME } = require('librechat-data-provider').Constants; require('module-alias')({ base: path.resolve(__dirname, '..', 'api') }); const connect = require('./connect'); const { grantPermission } = require('~/server/services/PermissionService'); -const { getProjectByName } = require('~/models/Project'); const { findRoleByIdentifier } = require('~/models'); const { PromptGroup, AclEntry } = require('~/db/models'); +const GLOBAL_PROJECT_NAME = 'instance'; + +/** Queries the raw `projects` collection (which may still exist in the DB even though the model is removed) */ +async function getGlobalProjectPromptGroupIds(db) { + const project = await db + .collection('projects') + .findOne({ name: GLOBAL_PROJECT_NAME }, { projection: { promptGroupIds: 1 } }); + return new Set((project?.promptGroupIds || []).map((id) => id.toString())); +} + async function migrateToPromptGroupPermissions({ dryRun = true, batchSize = 100 } = {}) { await connect(); @@ -24,7 +32,6 @@ async function migrateToPromptGroupPermissions({ dryRun = true, batchSize = 100 await ensureRequiredCollectionsExist(db); } - // Verify required roles exist const ownerRole = await findRoleByIdentifier(AccessRoleIds.PROMPTGROUP_OWNER); const viewerRole = await findRoleByIdentifier(AccessRoleIds.PROMPTGROUP_VIEWER); const editorRole = await findRoleByIdentifier(AccessRoleIds.PROMPTGROUP_EDITOR); @@ -33,11 +40,7 @@ async function migrateToPromptGroupPermissions({ dryRun = true, batchSize = 100 throw new Error('Required promptGroup roles not found. Run role seeding first.'); } - // Get global project prompt group IDs - const globalProject = await getProjectByName(GLOBAL_PROJECT_NAME, ['promptGroupIds']); - const globalPromptGroupIds = new Set( - (globalProject?.promptGroupIds || []).map((id) => id.toString()), - ); + const globalPromptGroupIds = db ? await getGlobalProjectPromptGroupIds(db) : new Set(); logger.info(`Found ${globalPromptGroupIds.size} prompt groups in global project`); @@ -54,8 +57,8 @@ async function migrateToPromptGroupPermissions({ dryRun = true, batchSize = 100 .lean(); const categories = { - globalViewAccess: [], // PromptGroup in global project -> Public VIEW - privateGroups: [], // Not in global project -> Private (owner only) + globalViewAccess: [], + privateGroups: [], }; promptGroupsToMigrate.forEach((group) => { @@ -115,7 +118,6 @@ async function migrateToPromptGroupPermissions({ dryRun = true, batchSize = 100 ownerGrants: 0, }; - // Process in batches for (let i = 0; i < promptGroupsToMigrate.length; i += batchSize) { const batch = promptGroupsToMigrate.slice(i, i + batchSize); @@ -127,7 +129,6 @@ async function migrateToPromptGroupPermissions({ dryRun = true, batchSize = 100 try { const isGlobalGroup = globalPromptGroupIds.has(group._id.toString()); - // Always grant owner permission to author await grantPermission({ principalType: PrincipalType.USER, principalId: group.author, @@ -138,7 +139,6 @@ async function migrateToPromptGroupPermissions({ dryRun = true, batchSize = 100 }); results.ownerGrants++; - // Grant public view permissions for promptGroups in global project if (isGlobalGroup) { await grantPermission({ principalType: PrincipalType.PUBLIC, @@ -170,7 +170,6 @@ async function migrateToPromptGroupPermissions({ dryRun = true, batchSize = 100 } } - // Brief pause between batches await new Promise((resolve) => setTimeout(resolve, 100)); } diff --git a/packages/api/src/agents/migration.ts b/packages/api/src/agents/migration.ts index 4da3852f82..0277249327 100644 --- a/packages/api/src/agents/migration.ts +++ b/packages/api/src/agents/migration.ts @@ -1,20 +1,13 @@ import { logger } from '@librechat/data-schemas'; -import { AccessRoleIds, ResourceType, PrincipalType, Constants } from 'librechat-data-provider'; +import { AccessRoleIds, ResourceType, PrincipalType } from 'librechat-data-provider'; import { ensureRequiredCollectionsExist } from '../db/utils'; import type { AccessRoleMethods, IAgent } from '@librechat/data-schemas'; import type { Model, Mongoose } from 'mongoose'; -const { GLOBAL_PROJECT_NAME } = Constants; +const GLOBAL_PROJECT_NAME = 'instance'; export interface MigrationCheckDbMethods { findRoleByIdentifier: AccessRoleMethods['findRoleByIdentifier']; - getProjectByName: ( - projectName: string, - fieldsToSelect?: string[] | null, - ) => Promise<{ - agentIds?: string[]; - [key: string]: unknown; - } | null>; } export interface MigrationCheckParams { @@ -60,7 +53,6 @@ export async function checkAgentPermissionsMigration({ await ensureRequiredCollectionsExist(db); } - // Verify required roles exist const ownerRole = await methods.findRoleByIdentifier(AccessRoleIds.AGENT_OWNER); const viewerRole = await methods.findRoleByIdentifier(AccessRoleIds.AGENT_VIEWER); const editorRole = await methods.findRoleByIdentifier(AccessRoleIds.AGENT_EDITOR); @@ -77,9 +69,13 @@ export async function checkAgentPermissionsMigration({ }; } - // Get global project agent IDs - const globalProject = await methods.getProjectByName(GLOBAL_PROJECT_NAME, ['agentIds']); - const globalAgentIds = new Set(globalProject?.agentIds || []); + let globalAgentIds = new Set(); + if (db) { + const project = await db + .collection('projects') + .findOne({ name: GLOBAL_PROJECT_NAME }, { projection: { agentIds: 1 } }); + globalAgentIds = new Set(project?.agentIds || []); + } const AclEntry = mongoose.model('AclEntry'); const migratedAgentIds = await AclEntry.distinct('resourceId', { @@ -124,7 +120,6 @@ export async function checkAgentPermissionsMigration({ privateAgents: categories.privateAgents.length, }; - // Add details for debugging if (agentsToMigrate.length > 0) { result.details = { globalEditAccess: categories.globalEditAccess.map((a) => ({ @@ -152,7 +147,6 @@ export async function checkAgentPermissionsMigration({ return result; } catch (error) { logger.error('Failed to check agent permissions migration', error); - // Return zero counts on error to avoid blocking startup return { totalToMigrate: 0, globalEditAccess: 0, @@ -170,7 +164,6 @@ export function logAgentMigrationWarning(result: MigrationCheckResult): void { return; } - // Create a visible warning box const border = '='.repeat(80); const warning = [ '', @@ -201,10 +194,8 @@ export function logAgentMigrationWarning(result: MigrationCheckResult): void { '', ]; - // Use console methods directly for visibility console.log('\n' + warning.join('\n') + '\n'); - // Also log with logger for consistency logger.warn('Agent permissions migration required', { totalToMigrate: result.totalToMigrate, globalEditAccess: result.globalEditAccess, diff --git a/packages/api/src/agents/validation.ts b/packages/api/src/agents/validation.ts index d427b3639e..8119c97204 100644 --- a/packages/api/src/agents/validation.ts +++ b/packages/api/src/agents/validation.ts @@ -94,9 +94,6 @@ export const agentUpdateSchema = agentBaseSchema.extend({ avatar: z.union([agentAvatarSchema, z.null()]).optional(), provider: z.string().optional(), model: z.string().nullable().optional(), - projectIds: z.array(z.string()).optional(), - removeProjectIds: z.array(z.string()).optional(), - isCollaborative: z.boolean().optional(), }); interface ValidateAgentModelParams { diff --git a/packages/api/src/middleware/access.spec.ts b/packages/api/src/middleware/access.spec.ts index d7ca690c48..c0efa9fcc1 100644 --- a/packages/api/src/middleware/access.spec.ts +++ b/packages/api/src/middleware/access.spec.ts @@ -216,17 +216,12 @@ describe('access middleware', () => { defaultParams.getRoleByName.mockResolvedValue(mockRole); - const checkObject = { - projectIds: ['project1'], - removeProjectIds: ['project2'], - }; + const checkObject = {}; const result = await checkAccess({ ...defaultParams, permissions: [Permissions.USE, Permissions.SHARE], - bodyProps: { - [Permissions.SHARE]: ['projectIds', 'removeProjectIds'], - } as Record, + bodyProps: {} as Record, checkObject, }); expect(result).toBe(true); @@ -244,17 +239,12 @@ describe('access middleware', () => { defaultParams.getRoleByName.mockResolvedValue(mockRole); - const checkObject = { - projectIds: ['project1'], - // missing removeProjectIds - }; + const checkObject = {}; const result = await checkAccess({ ...defaultParams, permissions: [Permissions.SHARE], - bodyProps: { - [Permissions.SHARE]: ['projectIds', 'removeProjectIds'], - } as Record, + bodyProps: {} as Record, checkObject, }); expect(result).toBe(false); @@ -343,17 +333,12 @@ describe('access middleware', () => { } as unknown as IRole; mockGetRoleByName.mockResolvedValue(mockRole); - mockReq.body = { - projectIds: ['project1'], - removeProjectIds: ['project2'], - }; + mockReq.body = {}; const middleware = generateCheckAccess({ permissionType: PermissionTypes.AGENTS, permissions: [Permissions.USE, Permissions.CREATE, Permissions.SHARE], - bodyProps: { - [Permissions.SHARE]: ['projectIds', 'removeProjectIds'], - } as Record, + bodyProps: {} as Record, getRoleByName: mockGetRoleByName, }); diff --git a/packages/api/src/prompts/migration.ts b/packages/api/src/prompts/migration.ts index a9e71d427a..be2b32e26d 100644 --- a/packages/api/src/prompts/migration.ts +++ b/packages/api/src/prompts/migration.ts @@ -1,20 +1,13 @@ import { logger } from '@librechat/data-schemas'; -import { AccessRoleIds, ResourceType, PrincipalType, Constants } from 'librechat-data-provider'; +import { AccessRoleIds, ResourceType, PrincipalType } from 'librechat-data-provider'; import { ensureRequiredCollectionsExist } from '../db/utils'; import type { AccessRoleMethods, IPromptGroupDocument } from '@librechat/data-schemas'; import type { Model, Mongoose } from 'mongoose'; -const { GLOBAL_PROJECT_NAME } = Constants; +const GLOBAL_PROJECT_NAME = 'instance'; export interface PromptMigrationCheckDbMethods { findRoleByIdentifier: AccessRoleMethods['findRoleByIdentifier']; - getProjectByName: ( - projectName: string, - fieldsToSelect?: string[] | null, - ) => Promise<{ - promptGroupIds?: string[]; - [key: string]: unknown; - } | null>; } export interface PromptMigrationCheckParams { @@ -53,13 +46,11 @@ export async function checkPromptPermissionsMigration({ logger.debug('Checking if prompt permissions migration is needed'); try { - /** Native MongoDB database instance */ const db = mongoose.connection.db; if (db) { await ensureRequiredCollectionsExist(db); } - // Verify required roles exist const ownerRole = await methods.findRoleByIdentifier(AccessRoleIds.PROMPTGROUP_OWNER); const viewerRole = await methods.findRoleByIdentifier(AccessRoleIds.PROMPTGROUP_VIEWER); const editorRole = await methods.findRoleByIdentifier(AccessRoleIds.PROMPTGROUP_EDITOR); @@ -75,11 +66,15 @@ export async function checkPromptPermissionsMigration({ }; } - /** Global project prompt group IDs */ - const globalProject = await methods.getProjectByName(GLOBAL_PROJECT_NAME, ['promptGroupIds']); - const globalPromptGroupIds = new Set( - (globalProject?.promptGroupIds || []).map((id) => id.toString()), - ); + let globalPromptGroupIds = new Set(); + if (db) { + const project = await db + .collection('projects') + .findOne({ name: GLOBAL_PROJECT_NAME }, { projection: { promptGroupIds: 1 } }); + globalPromptGroupIds = new Set( + (project?.promptGroupIds || []).map((id: { toString(): string }) => id.toString()), + ); + } const AclEntry = mongoose.model('AclEntry'); const migratedGroupIds = await AclEntry.distinct('resourceId', { @@ -118,7 +113,6 @@ export async function checkPromptPermissionsMigration({ privateGroups: categories.privateGroups.length, }; - // Add details for debugging if (promptGroupsToMigrate.length > 0) { result.details = { globalViewAccess: categories.globalViewAccess.map((g) => ({ @@ -143,7 +137,6 @@ export async function checkPromptPermissionsMigration({ return result; } catch (error) { logger.error('Failed to check prompt permissions migration', error); - // Return zero counts on error to avoid blocking startup return { totalToMigrate: 0, globalViewAccess: 0, @@ -160,7 +153,6 @@ export function logPromptMigrationWarning(result: PromptMigrationCheckResult): v return; } - // Create a visible warning box const border = '='.repeat(80); const warning = [ '', @@ -190,10 +182,8 @@ export function logPromptMigrationWarning(result: PromptMigrationCheckResult): v '', ]; - // Use console methods directly for visibility console.log('\n' + warning.join('\n') + '\n'); - // Also log with logger for consistency logger.warn('Prompt permissions migration required', { totalToMigrate: result.totalToMigrate, globalViewAccess: result.globalViewAccess, diff --git a/packages/api/src/prompts/schemas.spec.ts b/packages/api/src/prompts/schemas.spec.ts index 0008e31b51..2ba34e17f2 100644 --- a/packages/api/src/prompts/schemas.spec.ts +++ b/packages/api/src/prompts/schemas.spec.ts @@ -30,26 +30,6 @@ describe('updatePromptGroupSchema', () => { } }); - it('should accept valid projectIds array', () => { - const result = updatePromptGroupSchema.safeParse({ - projectIds: ['proj1', 'proj2'], - }); - expect(result.success).toBe(true); - if (result.success) { - expect(result.data.projectIds).toEqual(['proj1', 'proj2']); - } - }); - - it('should accept valid removeProjectIds array', () => { - const result = updatePromptGroupSchema.safeParse({ - removeProjectIds: ['proj1'], - }); - expect(result.success).toBe(true); - if (result.success) { - expect(result.data.removeProjectIds).toEqual(['proj1']); - } - }); - it('should accept valid command field', () => { const result = updatePromptGroupSchema.safeParse({ command: 'my-command-123' }); expect(result.success).toBe(true); diff --git a/packages/api/src/prompts/schemas.ts b/packages/api/src/prompts/schemas.ts index 628c07d954..43cb9d7e94 100644 --- a/packages/api/src/prompts/schemas.ts +++ b/packages/api/src/prompts/schemas.ts @@ -14,10 +14,6 @@ export const updatePromptGroupSchema = z oneliner: z.string().max(500).optional(), /** Category for organizing prompt groups */ category: z.string().max(100).optional(), - /** Project IDs to add for sharing */ - projectIds: z.array(z.string()).optional(), - /** Project IDs to remove from sharing */ - removeProjectIds: z.array(z.string()).optional(), /** Command shortcut for the prompt group */ command: z .string() diff --git a/packages/data-provider/src/config.ts b/packages/data-provider/src/config.ts index a2b47351b1..8a1da7da29 100644 --- a/packages/data-provider/src/config.ts +++ b/packages/data-provider/src/config.ts @@ -781,7 +781,6 @@ export type TStartupConfig = { sharedLinksEnabled: boolean; publicSharedLinksEnabled: boolean; analyticsGtmId?: string; - instanceProjectId: string; bundlerURL?: string; staticBundlerURL?: string; sharePointFilePickerEnabled?: boolean; @@ -1748,8 +1747,6 @@ export enum Constants { SAVED_TAG = 'Saved', /** Max number of Conversation starters for Agents/Assistants */ MAX_CONVO_STARTERS = 4, - /** Global/instance Project Name */ - GLOBAL_PROJECT_NAME = 'instance', /** Delimiter for MCP tools */ mcp_delimiter = '_mcp_', /** Prefix for MCP plugins */ diff --git a/packages/data-provider/src/schemas.ts b/packages/data-provider/src/schemas.ts index 803d970477..515646aa86 100644 --- a/packages/data-provider/src/schemas.ts +++ b/packages/data-provider/src/schemas.ts @@ -241,11 +241,8 @@ export const defaultAgentFormValues = { tools: [], tool_options: {}, provider: {}, - projectIds: [], edges: [], artifacts: '', - /** @deprecated Use ACL permissions instead */ - isCollaborative: false, recursion_limit: undefined, [Tools.execute_code]: false, [Tools.file_search]: false, diff --git a/packages/data-provider/src/types.ts b/packages/data-provider/src/types.ts index 1198f97b80..6cbd17f711 100644 --- a/packages/data-provider/src/types.ts +++ b/packages/data-provider/src/types.ts @@ -539,7 +539,6 @@ export type TPromptGroup = { command?: string; oneliner?: string; category?: string; - projectIds?: string[]; productionId?: string | null; productionPrompt?: Pick | null; author: string; @@ -592,9 +591,7 @@ export type TCreatePromptResponse = { group?: TPromptGroup; }; -export type TUpdatePromptGroupPayload = Partial & { - removeProjectIds?: string[]; -}; +export type TUpdatePromptGroupPayload = Partial; export type TUpdatePromptGroupVariables = { id: string; diff --git a/packages/data-provider/src/types/assistants.ts b/packages/data-provider/src/types/assistants.ts index 8865767240..22072403d3 100644 --- a/packages/data-provider/src/types/assistants.ts +++ b/packages/data-provider/src/types/assistants.ts @@ -252,15 +252,12 @@ export type Agent = { instructions?: string | null; additional_instructions?: string | null; tools?: string[]; - projectIds?: string[]; tool_kwargs?: Record; metadata?: Record; provider: AgentProvider; model: string | null; model_parameters: AgentModelParameters; conversation_starters?: string[]; - /** @deprecated Use ACL permissions instead */ - isCollaborative?: boolean; tool_resources?: AgentToolResources; /** @deprecated Use edges instead */ agent_ids?: string[]; @@ -313,9 +310,6 @@ export type AgentUpdateParams = { provider?: AgentProvider; model?: string | null; model_parameters?: AgentModelParameters; - projectIds?: string[]; - removeProjectIds?: string[]; - isCollaborative?: boolean; } & Pick< Agent, | 'agent_ids' diff --git a/packages/data-schemas/misc/ferretdb/migrationAntiJoin.ferretdb.spec.ts b/packages/data-schemas/misc/ferretdb/migrationAntiJoin.ferretdb.spec.ts index f2561137b7..3c4ce62337 100644 --- a/packages/data-schemas/misc/ferretdb/migrationAntiJoin.ferretdb.spec.ts +++ b/packages/data-schemas/misc/ferretdb/migrationAntiJoin.ferretdb.spec.ts @@ -24,7 +24,6 @@ const agentSchema = new Schema({ id: { type: String, required: true }, name: { type: String, required: true }, author: { type: String }, - isCollaborative: { type: Boolean, default: false }, }); const promptGroupSchema = new Schema({ @@ -107,7 +106,7 @@ describeIfFerretDB('Migration anti-join → $nin - FerretDB compatibility', () = _id: { $nin: migratedIds }, author: { $exists: true, $ne: null }, }) - .select('_id id name author isCollaborative') + .select('_id id name author') .lean(); expect(toMigrate).toHaveLength(2); @@ -197,7 +196,6 @@ describeIfFerretDB('Migration anti-join → $nin - FerretDB compatibility', () = id: 'proj_agent', name: 'Field Test', author: 'user1', - isCollaborative: true, }); const migratedIds = await AclEntry.distinct('resourceId', { @@ -209,7 +207,7 @@ describeIfFerretDB('Migration anti-join → $nin - FerretDB compatibility', () = _id: { $nin: migratedIds }, author: { $exists: true, $ne: null }, }) - .select('_id id name author isCollaborative') + .select('_id id name author') .lean(); expect(toMigrate).toHaveLength(1); @@ -218,7 +216,6 @@ describeIfFerretDB('Migration anti-join → $nin - FerretDB compatibility', () = expect(agent).toHaveProperty('id', 'proj_agent'); expect(agent).toHaveProperty('name', 'Field Test'); expect(agent).toHaveProperty('author', 'user1'); - expect(agent).toHaveProperty('isCollaborative', true); }); }); diff --git a/packages/data-schemas/misc/ferretdb/promptLookup.ferretdb.spec.ts b/packages/data-schemas/misc/ferretdb/promptLookup.ferretdb.spec.ts index 7e6c8ad1b0..255dfeda8f 100644 --- a/packages/data-schemas/misc/ferretdb/promptLookup.ferretdb.spec.ts +++ b/packages/data-schemas/misc/ferretdb/promptLookup.ferretdb.spec.ts @@ -27,7 +27,6 @@ const promptGroupSchema = new Schema( author: { type: Schema.Types.ObjectId, required: true, index: true }, authorName: { type: String, required: true }, command: { type: String }, - projectIds: { type: [Schema.Types.ObjectId], default: [] }, }, { timestamps: true }, ); @@ -51,7 +50,6 @@ type PromptGroupDoc = mongoose.Document & { oneliner: string; numberOfGenerations: number; command?: string; - projectIds: Types.ObjectId[]; createdAt: Date; updatedAt: Date; }; @@ -226,7 +224,7 @@ describeIfFerretDB('Prompt $lookup replacement - FerretDB compatibility', () => .skip(skip) .limit(limit) .select( - 'name numberOfGenerations oneliner category projectIds productionId author authorName createdAt updatedAt', + 'name numberOfGenerations oneliner category productionId author authorName createdAt updatedAt', ) .lean(), PromptGroup.countDocuments(query), @@ -273,7 +271,7 @@ describeIfFerretDB('Prompt $lookup replacement - FerretDB compatibility', () => .sort({ updatedAt: -1, _id: 1 }) .limit(normalizedLimit + 1) .select( - 'name numberOfGenerations oneliner category projectIds productionId author authorName createdAt updatedAt', + 'name numberOfGenerations oneliner category productionId author authorName createdAt updatedAt', ) .lean(); @@ -303,7 +301,7 @@ describeIfFerretDB('Prompt $lookup replacement - FerretDB compatibility', () => const groups = await PromptGroup.find({ _id: { $in: accessibleIds } }) .sort({ updatedAt: -1, _id: 1 }) .select( - 'name numberOfGenerations oneliner category projectIds productionId author authorName createdAt updatedAt', + 'name numberOfGenerations oneliner category productionId author authorName createdAt updatedAt', ) .lean(); @@ -326,7 +324,7 @@ describeIfFerretDB('Prompt $lookup replacement - FerretDB compatibility', () => const groups = await PromptGroup.find({}) .select( - 'name numberOfGenerations oneliner category projectIds productionId author authorName createdAt updatedAt', + 'name numberOfGenerations oneliner category productionId author authorName createdAt updatedAt', ) .lean(); const result = await attachProductionPrompts( @@ -339,7 +337,6 @@ describeIfFerretDB('Prompt $lookup replacement - FerretDB compatibility', () => expect(item.numberOfGenerations).toBe(5); expect(item.oneliner).toBe('A test prompt'); expect(item.category).toBe('testing'); - expect(item.projectIds).toEqual([]); expect(item.productionId).toBeDefined(); expect(item.author).toBeDefined(); expect(item.authorName).toBe('Test User'); diff --git a/packages/data-schemas/misc/ferretdb/pullAll.ferretdb.spec.ts b/packages/data-schemas/misc/ferretdb/pullAll.ferretdb.spec.ts index 446cb701d1..0e2e273609 100644 --- a/packages/data-schemas/misc/ferretdb/pullAll.ferretdb.spec.ts +++ b/packages/data-schemas/misc/ferretdb/pullAll.ferretdb.spec.ts @@ -37,7 +37,6 @@ const projectSchema = new Schema({ const agentSchema = new Schema({ name: { type: String, required: true }, - projectIds: { type: [String], default: [] }, tool_resources: { type: Schema.Types.Mixed, default: {} }, }); @@ -197,23 +196,6 @@ describeIfFerretDB('$pullAll FerretDB compatibility', () => { expect(doc.agentIds).toEqual(['a2', 'a4']); }); - it('should remove projectIds from an agent', async () => { - await Agent.create({ - name: 'Test Agent', - projectIds: ['p1', 'p2', 'p3'], - }); - - await Agent.findOneAndUpdate( - { name: 'Test Agent' }, - { $pullAll: { projectIds: ['p1', 'p3'] } }, - { new: true }, - ); - - const updated = await Agent.findOne({ name: 'Test Agent' }).lean(); - const doc = updated as Record; - expect(doc.projectIds).toEqual(['p2']); - }); - it('should handle removing from nested dynamic paths (tool_resources)', async () => { await Agent.create({ name: 'Resource Agent', diff --git a/packages/data-schemas/src/models/index.ts b/packages/data-schemas/src/models/index.ts index ca1a6259be..068aba69ed 100644 --- a/packages/data-schemas/src/models/index.ts +++ b/packages/data-schemas/src/models/index.ts @@ -13,7 +13,6 @@ import { createActionModel } from './action'; import { createAssistantModel } from './assistant'; import { createFileModel } from './file'; import { createBannerModel } from './banner'; -import { createProjectModel } from './project'; import { createKeyModel } from './key'; import { createPluginAuthModel } from './pluginAuth'; import { createTransactionModel } from './transaction'; @@ -48,7 +47,6 @@ export function createModels(mongoose: typeof import('mongoose')) { Assistant: createAssistantModel(mongoose), File: createFileModel(mongoose), Banner: createBannerModel(mongoose), - Project: createProjectModel(mongoose), Key: createKeyModel(mongoose), PluginAuth: createPluginAuthModel(mongoose), Transaction: createTransactionModel(mongoose), diff --git a/packages/data-schemas/src/models/project.ts b/packages/data-schemas/src/models/project.ts deleted file mode 100644 index c68f532bc3..0000000000 --- a/packages/data-schemas/src/models/project.ts +++ /dev/null @@ -1,8 +0,0 @@ -import projectSchema, { IMongoProject } from '~/schema/project'; - -/** - * Creates or returns the Project model using the provided mongoose instance and schema - */ -export function createProjectModel(mongoose: typeof import('mongoose')) { - return mongoose.models.Project || mongoose.model('Project', projectSchema); -} diff --git a/packages/data-schemas/src/schema/agent.ts b/packages/data-schemas/src/schema/agent.ts index 32bba8bef8..eff4b8e675 100644 --- a/packages/data-schemas/src/schema/agent.ts +++ b/packages/data-schemas/src/schema/agent.ts @@ -76,10 +76,6 @@ const agentSchema = new Schema( type: [{ type: Schema.Types.Mixed }], default: [], }, - isCollaborative: { - type: Boolean, - default: undefined, - }, conversation_starters: { type: [String], default: [], @@ -88,11 +84,6 @@ const agentSchema = new Schema( type: Schema.Types.Mixed, default: {}, }, - projectIds: { - type: [Schema.Types.ObjectId], - ref: 'Project', - index: true, - }, versions: { type: [Schema.Types.Mixed], default: [], diff --git a/packages/data-schemas/src/schema/index.ts b/packages/data-schemas/src/schema/index.ts index 454780b8bf..2a58f7c3cc 100644 --- a/packages/data-schemas/src/schema/index.ts +++ b/packages/data-schemas/src/schema/index.ts @@ -13,7 +13,6 @@ export { default as keySchema } from './key'; export { default as messageSchema } from './message'; export { default as pluginAuthSchema } from './pluginAuth'; export { default as presetSchema } from './preset'; -export { default as projectSchema } from './project'; export { default as promptSchema } from './prompt'; export { default as promptGroupSchema } from './promptGroup'; export { default as roleSchema } from './role'; diff --git a/packages/data-schemas/src/schema/project.ts b/packages/data-schemas/src/schema/project.ts deleted file mode 100644 index 05c2ddc6f2..0000000000 --- a/packages/data-schemas/src/schema/project.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { Schema, Document, Types } from 'mongoose'; - -export interface IMongoProject extends Document { - name: string; - promptGroupIds: Types.ObjectId[]; - agentIds: string[]; - createdAt?: Date; - updatedAt?: Date; -} - -const projectSchema = new Schema( - { - name: { - type: String, - required: true, - index: true, - }, - promptGroupIds: { - type: [Schema.Types.ObjectId], - ref: 'PromptGroup', - default: [], - }, - agentIds: { - type: [String], - ref: 'Agent', - default: [], - }, - }, - { - timestamps: true, - }, -); - -export default projectSchema; diff --git a/packages/data-schemas/src/schema/promptGroup.ts b/packages/data-schemas/src/schema/promptGroup.ts index 2e8bb4ef82..d751c67557 100644 --- a/packages/data-schemas/src/schema/promptGroup.ts +++ b/packages/data-schemas/src/schema/promptGroup.ts @@ -22,12 +22,6 @@ const promptGroupSchema = new Schema( default: '', index: true, }, - projectIds: { - type: [Schema.Types.ObjectId], - ref: 'Project', - index: true, - default: [], - }, productionId: { type: Schema.Types.ObjectId, ref: 'Prompt', diff --git a/packages/data-schemas/src/types/agent.ts b/packages/data-schemas/src/types/agent.ts index 3549e88b4a..f163ab63bd 100644 --- a/packages/data-schemas/src/types/agent.ts +++ b/packages/data-schemas/src/types/agent.ts @@ -31,11 +31,8 @@ export interface IAgent extends Omit { /** @deprecated Use edges instead */ agent_ids?: string[]; edges?: GraphEdge[]; - /** @deprecated Use ACL permissions instead */ - isCollaborative?: boolean; conversation_starters?: string[]; tool_resources?: unknown; - projectIds?: Types.ObjectId[]; versions?: Omit[]; category: string; support_contact?: ISupportContact; diff --git a/packages/data-schemas/src/types/prompts.ts b/packages/data-schemas/src/types/prompts.ts index d99d36eb73..53f09dcd49 100644 --- a/packages/data-schemas/src/types/prompts.ts +++ b/packages/data-schemas/src/types/prompts.ts @@ -14,7 +14,6 @@ export interface IPromptGroup { numberOfGenerations: number; oneliner: string; category: string; - projectIds: Types.ObjectId[]; productionId: Types.ObjectId; author: Types.ObjectId; authorName: string;