🗑️ 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:
Danny Avila 2026-02-13 03:04:15 -05:00
parent 3398f6a17a
commit 37cc5faff5
No known key found for this signature in database
GPG key ID: BF31EEB2C5CA0956
41 changed files with 94 additions and 821 deletions

View file

@ -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,

View file

@ -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 {

View file

@ -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,
});

View file

@ -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,

View file

@ -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);

View file

@ -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()