mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-02-14 14:38:11 +01:00
🗑️ chore: Remove Deprecated Project Model and Associated Fields (#11773)
* 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.
This commit is contained in:
parent
3398f6a17a
commit
37cc5faff5
41 changed files with 94 additions and 821 deletions
|
|
@ -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<MongoAgent>} 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,
|
||||
|
|
|
|||
|
|
@ -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] },
|
||||
},
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<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,
|
||||
};
|
||||
|
|
@ -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, {
|
||||
|
|
|
|||
|
|
@ -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', () => {
|
||||
|
|
|
|||
|
|
@ -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 });
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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 || '');
|
||||
|
|
|
|||
|
|
@ -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
|
|||
</div>
|
||||
|
||||
<div className="flex h-full items-center gap-2">
|
||||
{isGlobalGroup && (
|
||||
{isPublicGroup && (
|
||||
<EarthIcon
|
||||
className="icon-md text-green-500"
|
||||
aria-label={localize('com_ui_global_group')}
|
||||
|
|
|
|||
|
|
@ -2,10 +2,9 @@ import { FileText, Plus } from 'lucide-react';
|
|||
import { Link } from 'react-router-dom';
|
||||
import { Button, Skeleton } from '@librechat/client';
|
||||
import { PermissionTypes, Permissions } from 'librechat-data-provider';
|
||||
import type { TPromptGroup, TStartupConfig } from 'librechat-data-provider';
|
||||
import type { TPromptGroup } from 'librechat-data-provider';
|
||||
import DashGroupItem from '~/components/Prompts/Groups/DashGroupItem';
|
||||
import ChatGroupItem from '~/components/Prompts/Groups/ChatGroupItem';
|
||||
import { useGetStartupConfig } from '~/data-provider';
|
||||
import { useLocalize, useHasAccess } from '~/hooks';
|
||||
import { cn } from '~/utils';
|
||||
|
||||
|
|
@ -19,8 +18,6 @@ export default function List({
|
|||
isLoading: boolean;
|
||||
}) {
|
||||
const localize = useLocalize();
|
||||
const { data: startupConfig = {} as Partial<TStartupConfig> } = 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 (
|
||||
<ChatGroupItem
|
||||
key={group._id}
|
||||
group={group}
|
||||
instanceProjectId={instanceProjectId}
|
||||
/>
|
||||
);
|
||||
return <ChatGroupItem key={group._id} group={group} />;
|
||||
}
|
||||
return (
|
||||
<DashGroupItem key={group._id} group={group} instanceProjectId={instanceProjectId} />
|
||||
);
|
||||
return <DashGroupItem key={group._id} group={group} />;
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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') {
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<string>();
|
||||
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,
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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<Permissions, string[]>,
|
||||
bodyProps: {} as Record<Permissions, string[]>,
|
||||
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<Permissions, string[]>,
|
||||
bodyProps: {} as Record<Permissions, string[]>,
|
||||
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<Permissions, string[]>,
|
||||
bodyProps: {} as Record<Permissions, string[]>,
|
||||
getRoleByName: mockGetRoleByName,
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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<string>();
|
||||
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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -539,7 +539,6 @@ export type TPromptGroup = {
|
|||
command?: string;
|
||||
oneliner?: string;
|
||||
category?: string;
|
||||
projectIds?: string[];
|
||||
productionId?: string | null;
|
||||
productionPrompt?: Pick<TPrompt, 'prompt'> | null;
|
||||
author: string;
|
||||
|
|
@ -592,9 +591,7 @@ export type TCreatePromptResponse = {
|
|||
group?: TPromptGroup;
|
||||
};
|
||||
|
||||
export type TUpdatePromptGroupPayload = Partial<TPromptGroup> & {
|
||||
removeProjectIds?: string[];
|
||||
};
|
||||
export type TUpdatePromptGroupPayload = Partial<TPromptGroup>;
|
||||
|
||||
export type TUpdatePromptGroupVariables = {
|
||||
id: string;
|
||||
|
|
|
|||
|
|
@ -252,15 +252,12 @@ export type Agent = {
|
|||
instructions?: string | null;
|
||||
additional_instructions?: string | null;
|
||||
tools?: string[];
|
||||
projectIds?: string[];
|
||||
tool_kwargs?: Record<string, unknown>;
|
||||
metadata?: Record<string, unknown>;
|
||||
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'
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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<string, unknown>;
|
||||
expect(doc.projectIds).toEqual(['p2']);
|
||||
});
|
||||
|
||||
it('should handle removing from nested dynamic paths (tool_resources)', async () => {
|
||||
await Agent.create({
|
||||
name: 'Resource Agent',
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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<IMongoProject>('Project', projectSchema);
|
||||
}
|
||||
|
|
@ -76,10 +76,6 @@ const agentSchema = new Schema<IAgent>(
|
|||
type: [{ type: Schema.Types.Mixed }],
|
||||
default: [],
|
||||
},
|
||||
isCollaborative: {
|
||||
type: Boolean,
|
||||
default: undefined,
|
||||
},
|
||||
conversation_starters: {
|
||||
type: [String],
|
||||
default: [],
|
||||
|
|
@ -88,11 +84,6 @@ const agentSchema = new Schema<IAgent>(
|
|||
type: Schema.Types.Mixed,
|
||||
default: {},
|
||||
},
|
||||
projectIds: {
|
||||
type: [Schema.Types.ObjectId],
|
||||
ref: 'Project',
|
||||
index: true,
|
||||
},
|
||||
versions: {
|
||||
type: [Schema.Types.Mixed],
|
||||
default: [],
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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<IMongoProject>(
|
||||
{
|
||||
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;
|
||||
|
|
@ -22,12 +22,6 @@ const promptGroupSchema = new Schema<IPromptGroupDocument>(
|
|||
default: '',
|
||||
index: true,
|
||||
},
|
||||
projectIds: {
|
||||
type: [Schema.Types.ObjectId],
|
||||
ref: 'Project',
|
||||
index: true,
|
||||
default: [],
|
||||
},
|
||||
productionId: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'Prompt',
|
||||
|
|
|
|||
|
|
@ -31,11 +31,8 @@ export interface IAgent extends Omit<Document, 'model'> {
|
|||
/** @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<IAgent, 'versions'>[];
|
||||
category: string;
|
||||
support_contact?: ISupportContact;
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ export interface IPromptGroup {
|
|||
numberOfGenerations: number;
|
||||
oneliner: string;
|
||||
category: string;
|
||||
projectIds: Types.ObjectId[];
|
||||
productionId: Types.ObjectId;
|
||||
author: Types.ObjectId;
|
||||
authorName: string;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue