diff --git a/api/server/services/start/migration.js b/api/server/services/start/migration.js index 13db789df7..83b9c83e39 100644 --- a/api/server/services/start/migration.js +++ b/api/server/services/start/migration.js @@ -1,3 +1,4 @@ +const mongoose = require('mongoose'); const { logger } = require('@librechat/data-schemas'); const { logAgentMigrationWarning, @@ -16,7 +17,8 @@ const { findRoleByIdentifier } = require('~/models'); async function checkMigrations() { try { const agentMigrationResult = await checkAgentPermissionsMigration({ - db: { + mongoose, + methods: { findRoleByIdentifier, getProjectByName, }, @@ -28,7 +30,8 @@ async function checkMigrations() { } try { const promptMigrationResult = await checkPromptPermissionsMigration({ - db: { + mongoose, + methods: { findRoleByIdentifier, getProjectByName, }, diff --git a/config/migrate-agent-permissions.js b/config/migrate-agent-permissions.js index 37bc4f124c..ac7ca5b76f 100644 --- a/config/migrate-agent-permissions.js +++ b/config/migrate-agent-permissions.js @@ -16,6 +16,38 @@ async function migrateAgentPermissionsEnhanced({ dryRun = true, batchSize = 100 logger.info('Starting Enhanced Agent Permissions Migration', { dryRun, batchSize }); + /** Ensurse `aclentries` collection exists for DocumentDB compatibility + * @param {import('mongoose').mongo.Db} db + * @param {string} collectionName + */ + async function ensureCollectionExists(db, collectionName) { + try { + const collections = await db.listCollections({ name: collectionName }).toArray(); + if (collections.length === 0) { + await db.createCollection(collectionName); + logger.info(`Created collection: ${collectionName}`); + } else { + logger.info(`Collection already exists: ${collectionName}`); + } + } catch (error) { + logger.error(`'Failed to check/create "${collectionName}" collection:`, error); + // If listCollections fails, try alternative approach + try { + // Try to access the collection directly - this will create it in MongoDB if it doesn't exist + await db.collection(collectionName).findOne({}, { projection: { _id: 1 } }); + } catch (createError) { + logger.error(`Could not ensure collection ${collectionName} exists:`, createError); + } + } + } + + const mongoose = require('mongoose'); + /** @type {import('mongoose').mongo.Db | undefined} */ + const db = mongoose.connection.db; + if (db) { + await ensureCollectionExists(db, 'aclentries'); + } + // Verify required roles exist const ownerRole = await findRoleByIdentifier(AccessRoleIds.AGENT_OWNER); const viewerRole = await findRoleByIdentifier(AccessRoleIds.AGENT_VIEWER); @@ -100,12 +132,19 @@ async function migrateAgentPermissionsEnhanced({ dryRun = true, batchSize = 100 } }); - logger.info('Agent categorization:', { - globalEditAccess: categories.globalEditAccess.length, - globalViewAccess: categories.globalViewAccess.length, - privateAgents: categories.privateAgents.length, - total: agentsToMigrate.length, - }); + logger.info( + 'Agent categorization:\n' + + JSON.stringify( + { + globalEditAccess: categories.globalEditAccess.length, + globalViewAccess: categories.globalViewAccess.length, + privateAgents: categories.privateAgents.length, + total: agentsToMigrate.length, + }, + null, + 2, + ), + ); if (dryRun) { return { diff --git a/config/migrate-prompt-permissions.js b/config/migrate-prompt-permissions.js index bebc2a564a..e1df481cd0 100644 --- a/config/migrate-prompt-permissions.js +++ b/config/migrate-prompt-permissions.js @@ -16,6 +16,38 @@ async function migrateToPromptGroupPermissions({ dryRun = true, batchSize = 100 logger.info('Starting PromptGroup Permissions Migration', { dryRun, batchSize }); + /** Ensurse `aclentries` collection exists for DocumentDB compatibility + * @param {import('mongoose').mongo.Db} db + * @param {string} collectionName + */ + async function ensureCollectionExists(db, collectionName) { + try { + const collections = await db.listCollections({ name: collectionName }).toArray(); + if (collections.length === 0) { + await db.createCollection(collectionName); + logger.info(`Created collection: ${collectionName}`); + } else { + logger.info(`Collection already exists: ${collectionName}`); + } + } catch (error) { + logger.error(`'Failed to check/create "${collectionName}" collection:`, error); + // If listCollections fails, try alternative approach + try { + // Try to access the collection directly - this will create it in MongoDB if it doesn't exist + await db.collection(collectionName).findOne({}, { projection: { _id: 1 } }); + } catch (createError) { + logger.error(`Could not ensure collection ${collectionName} exists:`, createError); + } + } + } + + const mongoose = require('mongoose'); + /** @type {import('mongoose').mongo.Db | undefined} */ + const db = mongoose.connection.db; + if (db) { + await ensureCollectionExists(db, 'aclentries'); + } + // Verify required roles exist const ownerRole = await findRoleByIdentifier(AccessRoleIds.PROMPTGROUP_OWNER); const viewerRole = await findRoleByIdentifier(AccessRoleIds.PROMPTGROUP_VIEWER); @@ -91,11 +123,18 @@ async function migrateToPromptGroupPermissions({ dryRun = true, batchSize = 100 } }); - logger.info('PromptGroup categorization:', { - globalViewAccess: categories.globalViewAccess.length, - privateGroups: categories.privateGroups.length, - total: promptGroupsToMigrate.length, - }); + logger.info( + 'PromptGroup categorization:\n' + + JSON.stringify( + { + globalViewAccess: categories.globalViewAccess.length, + privateGroups: categories.privateGroups.length, + total: promptGroupsToMigrate.length, + }, + null, + 2, + ), + ); if (dryRun) { return { diff --git a/packages/api/src/agents/migration.ts b/packages/api/src/agents/migration.ts index b1e6e0558b..e9b76c3b9b 100644 --- a/packages/api/src/agents/migration.ts +++ b/packages/api/src/agents/migration.ts @@ -1,7 +1,7 @@ import { logger } from '@librechat/data-schemas'; import { AccessRoleIds, ResourceType, PrincipalType, Constants } from 'librechat-data-provider'; import type { AccessRoleMethods, IAgent } from '@librechat/data-schemas'; -import type { Model } from 'mongoose'; +import type { Model, Mongoose, mongo } from 'mongoose'; const { GLOBAL_PROJECT_NAME } = Constants; @@ -17,7 +17,8 @@ export interface MigrationCheckDbMethods { } export interface MigrationCheckParams { - db: MigrationCheckDbMethods; + mongoose: Mongoose; + methods: MigrationCheckDbMethods; AgentModel: Model; } @@ -46,16 +47,44 @@ export interface MigrationCheckResult { * This performs a dry-run check similar to the migration script */ export async function checkAgentPermissionsMigration({ - db, + methods, + mongoose, AgentModel, }: MigrationCheckParams): Promise { logger.debug('Checking if agent permissions migration is needed'); try { + /** Ensurse `aclentries` collection exists for DocumentDB compatibility */ + async function ensureCollectionExists(db: mongo.Db, collectionName: string) { + try { + const collections = await db.listCollections({ name: collectionName }).toArray(); + if (collections.length === 0) { + await db.createCollection(collectionName); + logger.info(`Created collection: ${collectionName}`); + } else { + logger.debug(`Collection already exists: ${collectionName}`); + } + } catch (error) { + logger.error(`'Failed to check/create "${collectionName}" collection:`, error); + // If listCollections fails, try alternative approach + try { + // Try to access the collection directly - this will create it in MongoDB if it doesn't exist + await db.collection(collectionName).findOne({}, { projection: { _id: 1 } }); + } catch (createError) { + logger.error(`Could not ensure collection ${collectionName} exists:`, createError); + } + } + } + + const db = mongoose.connection.db; + if (db) { + await ensureCollectionExists(db, 'aclentries'); + } + // Verify required roles exist - const ownerRole = await db.findRoleByIdentifier(AccessRoleIds.AGENT_OWNER); - const viewerRole = await db.findRoleByIdentifier(AccessRoleIds.AGENT_VIEWER); - const editorRole = await db.findRoleByIdentifier(AccessRoleIds.AGENT_EDITOR); + const ownerRole = await methods.findRoleByIdentifier(AccessRoleIds.AGENT_OWNER); + const viewerRole = await methods.findRoleByIdentifier(AccessRoleIds.AGENT_VIEWER); + const editorRole = await methods.findRoleByIdentifier(AccessRoleIds.AGENT_EDITOR); if (!ownerRole || !viewerRole || !editorRole) { logger.warn( @@ -70,7 +99,7 @@ export async function checkAgentPermissionsMigration({ } // Get global project agent IDs - const globalProject = await db.getProjectByName(GLOBAL_PROJECT_NAME, ['agentIds']); + const globalProject = await methods.getProjectByName(GLOBAL_PROJECT_NAME, ['agentIds']); const globalAgentIds = new Set(globalProject?.agentIds || []); // Find agents without ACL entries (no batching for efficiency on startup) diff --git a/packages/api/src/prompts/migration.ts b/packages/api/src/prompts/migration.ts index 3564f06010..938d8ca8f5 100644 --- a/packages/api/src/prompts/migration.ts +++ b/packages/api/src/prompts/migration.ts @@ -1,7 +1,7 @@ import { logger } from '@librechat/data-schemas'; import { AccessRoleIds, ResourceType, PrincipalType, Constants } from 'librechat-data-provider'; import type { AccessRoleMethods, IPromptGroupDocument } from '@librechat/data-schemas'; -import type { Model } from 'mongoose'; +import type { Model, Mongoose, mongo } from 'mongoose'; const { GLOBAL_PROJECT_NAME } = Constants; @@ -17,7 +17,8 @@ export interface PromptMigrationCheckDbMethods { } export interface PromptMigrationCheckParams { - db: PromptMigrationCheckDbMethods; + mongoose: Mongoose; + methods: PromptMigrationCheckDbMethods; PromptGroupModel: Model; } @@ -44,16 +45,45 @@ export interface PromptMigrationCheckResult { * This performs a dry-run check similar to the migration script */ export async function checkPromptPermissionsMigration({ - db, + methods, + mongoose, PromptGroupModel, }: PromptMigrationCheckParams): Promise { logger.debug('Checking if prompt permissions migration is needed'); try { + /** Ensurse `aclentries` collection exists for DocumentDB compatibility */ + async function ensureCollectionExists(db: mongo.Db, collectionName: string) { + try { + const collections = await db.listCollections({ name: collectionName }).toArray(); + if (collections.length === 0) { + await db.createCollection(collectionName); + logger.info(`Created collection: ${collectionName}`); + } else { + logger.debug(`Collection already exists: ${collectionName}`); + } + } catch (error) { + logger.error(`'Failed to check/create "${collectionName}" collection:`, error); + // If listCollections fails, try alternative approach + try { + // Try to access the collection directly - this will create it in MongoDB if it doesn't exist + await db.collection(collectionName).findOne({}, { projection: { _id: 1 } }); + } catch (createError) { + logger.error(`Could not ensure collection ${collectionName} exists:`, createError); + } + } + } + + /** Native MongoDB database instance */ + const db = mongoose.connection.db; + if (db) { + await ensureCollectionExists(db, 'aclentries'); + } + // Verify required roles exist - const ownerRole = await db.findRoleByIdentifier(AccessRoleIds.PROMPTGROUP_OWNER); - const viewerRole = await db.findRoleByIdentifier(AccessRoleIds.PROMPTGROUP_VIEWER); - const editorRole = await db.findRoleByIdentifier(AccessRoleIds.PROMPTGROUP_EDITOR); + const ownerRole = await methods.findRoleByIdentifier(AccessRoleIds.PROMPTGROUP_OWNER); + const viewerRole = await methods.findRoleByIdentifier(AccessRoleIds.PROMPTGROUP_VIEWER); + const editorRole = await methods.findRoleByIdentifier(AccessRoleIds.PROMPTGROUP_EDITOR); if (!ownerRole || !viewerRole || !editorRole) { logger.warn( @@ -66,8 +96,8 @@ export async function checkPromptPermissionsMigration({ }; } - // Get global project prompt group IDs - const globalProject = await db.getProjectByName(GLOBAL_PROJECT_NAME, ['promptGroupIds']); + /** Global project prompt group IDs */ + const globalProject = await methods.getProjectByName(GLOBAL_PROJECT_NAME, ['promptGroupIds']); const globalPromptGroupIds = new Set( (globalProject?.promptGroupIds || []).map((id) => id.toString()), );