mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-21 10:50:14 +01:00
🛜 refactor: Streamline App Config Usage (#9234)
* WIP: app.locals refactoring
WIP: appConfig
fix: update memory configuration retrieval to use getAppConfig based on user role
fix: update comment for AppConfig interface to clarify purpose
🏷️ refactor: Update tests to use getAppConfig for endpoint configurations
ci: Update AppService tests to initialize app config instead of app.locals
ci: Integrate getAppConfig into remaining tests
refactor: Update multer storage destination to use promise-based getAppConfig and improve error handling in tests
refactor: Rename initializeAppConfig to setAppConfig and update related tests
ci: Mock getAppConfig in various tests to provide default configurations
refactor: Update convertMCPToolsToPlugins to use mcpManager for server configuration and adjust related tests
chore: rename `Config/getAppConfig` -> `Config/app`
fix: streamline OpenAI image tools configuration by removing direct appConfig dependency and using function parameters
chore: correct parameter documentation for imageOutputType in ToolService.js
refactor: remove `getCustomConfig` dependency in config route
refactor: update domain validation to use appConfig for allowed domains
refactor: use appConfig registration property
chore: remove app parameter from AppService invocation
refactor: update AppConfig interface to correct registration and turnstile configurations
refactor: remove getCustomConfig dependency and use getAppConfig in PluginController, multer, and MCP services
refactor: replace getCustomConfig with getAppConfig in STTService, TTSService, and related files
refactor: replace getCustomConfig with getAppConfig in Conversation and Message models, update tempChatRetention functions to use AppConfig type
refactor: update getAppConfig calls in Conversation and Message models to include user role for temporary chat expiration
ci: update related tests
refactor: update getAppConfig call in getCustomConfigSpeech to include user role
fix: update appConfig usage to access allowedDomains from actions instead of registration
refactor: enhance AppConfig to include fileStrategies and update related file strategy logic
refactor: update imports to use normalizeEndpointName from @librechat/api and remove redundant definitions
chore: remove deprecated unused RunManager
refactor: get balance config primarily from appConfig
refactor: remove customConfig dependency for appConfig and streamline loadConfigModels logic
refactor: remove getCustomConfig usage and use app config in file citations
refactor: consolidate endpoint loading logic into loadEndpoints function
refactor: update appConfig access to use endpoints structure across various services
refactor: implement custom endpoints configuration and streamline endpoint loading logic
refactor: update getAppConfig call to include user role parameter
refactor: streamline endpoint configuration and enhance appConfig usage across services
refactor: replace getMCPAuthMap with getUserMCPAuthMap and remove unused getCustomConfig file
refactor: add type annotation for loadedEndpoints in loadEndpoints function
refactor: move /services/Files/images/parse to TS API
chore: add missing FILE_CITATIONS permission to IRole interface
refactor: restructure toolkits to TS API
refactor: separate manifest logic into its own module
refactor: consolidate tool loading logic into a new tools module for startup logic
refactor: move interface config logic to TS API
refactor: migrate checkEmailConfig to TypeScript and update imports
refactor: add FunctionTool interface and availableTools to AppConfig
refactor: decouple caching and DB operations from AppService, make part of consolidated `getAppConfig`
WIP: fix tests
* fix: rebase conflicts
* refactor: remove app.locals references
* refactor: replace getBalanceConfig with getAppConfig in various strategies and middleware
* refactor: replace appConfig?.balance with getBalanceConfig in various controllers and clients
* test: add balance configuration to titleConvo method in AgentClient tests
* chore: remove unused `openai-chat-tokens` package
* chore: remove unused imports in initializeMCPs.js
* refactor: update balance configuration to use getAppConfig instead of getBalanceConfig
* refactor: integrate configMiddleware for centralized configuration handling
* refactor: optimize email domain validation by removing unnecessary async calls
* refactor: simplify multer storage configuration by removing async calls
* refactor: reorder imports for better readability in user.js
* refactor: replace getAppConfig calls with req.config for improved performance
* chore: replace getAppConfig calls with req.config in tests for centralized configuration handling
* chore: remove unused override config
* refactor: add configMiddleware to endpoint route and replace getAppConfig with req.config
* chore: remove customConfig parameter from TTSService constructor
* refactor: pass appConfig from request to processFileCitations for improved configuration handling
* refactor: remove configMiddleware from endpoint route and retrieve appConfig directly in getEndpointsConfig if not in `req.config`
* test: add mockAppConfig to processFileCitations tests for improved configuration handling
* fix: pass req.config to hasCustomUserVars and call without await after synchronous refactor
* fix: type safety in useExportConversation
* refactor: retrieve appConfig using getAppConfig in PluginController and remove configMiddleware from plugins route, to avoid always retrieving when plugins are cached
* chore: change `MongoUser` typedef to `IUser`
* fix: Add `user` and `config` fields to ServerRequest and update JSDoc type annotations from Express.Request to ServerRequest
* fix: remove unused setAppConfig mock from Server configuration tests
This commit is contained in:
parent
e1ad235f17
commit
9a210971f5
210 changed files with 4102 additions and 3465 deletions
|
|
@ -83,7 +83,11 @@ router.post(
|
|||
}
|
||||
|
||||
let metadata = await encryptMetadata(removeNullishValues(_metadata, true));
|
||||
const isDomainAllowed = await isActionDomainAllowed(metadata.domain);
|
||||
const appConfig = req.config;
|
||||
const isDomainAllowed = await isActionDomainAllowed(
|
||||
metadata.domain,
|
||||
appConfig?.actions?.allowedDomains,
|
||||
);
|
||||
if (!isDomainAllowed) {
|
||||
return res.status(400).json({ message: 'Domain not allowed' });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ const {
|
|||
checkBan,
|
||||
requireJwtAuth,
|
||||
messageIpLimiter,
|
||||
configMiddleware,
|
||||
concurrentLimiter,
|
||||
messageUserLimiter,
|
||||
} = require('~/server/middleware');
|
||||
|
|
@ -22,6 +23,8 @@ router.use(uaParser);
|
|||
router.use('/', v1);
|
||||
|
||||
const chatRouter = express.Router();
|
||||
chatRouter.use(configMiddleware);
|
||||
|
||||
if (isEnabled(LIMIT_CONCURRENT_MESSAGES)) {
|
||||
chatRouter.use(concurrentLimiter);
|
||||
}
|
||||
|
|
@ -37,6 +40,4 @@ if (isEnabled(LIMIT_MESSAGE_USER)) {
|
|||
chatRouter.use('/', chat);
|
||||
router.use('/chat', chatRouter);
|
||||
|
||||
// Add marketplace routes
|
||||
|
||||
module.exports = router;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
const express = require('express');
|
||||
const { callTool, verifyToolAuth, getToolCalls } = require('~/server/controllers/tools');
|
||||
const { getAvailableTools } = require('~/server/controllers/PluginController');
|
||||
const { toolCallLimiter } = require('~/server/middleware/limiters');
|
||||
const { toolCallLimiter } = require('~/server/middleware');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
const express = require('express');
|
||||
const { generateCheckAccess } = require('@librechat/api');
|
||||
const { PermissionTypes, Permissions, PermissionBits } = require('librechat-data-provider');
|
||||
const { requireJwtAuth, canAccessAgentResource } = require('~/server/middleware');
|
||||
const { requireJwtAuth, configMiddleware, canAccessAgentResource } = require('~/server/middleware');
|
||||
const v1 = require('~/server/controllers/agents/v1');
|
||||
const { getRoleByName } = require('~/models/Role');
|
||||
const actions = require('./actions');
|
||||
|
|
@ -36,13 +36,13 @@ router.use(requireJwtAuth);
|
|||
* Agent actions route.
|
||||
* @route GET|POST /agents/actions
|
||||
*/
|
||||
router.use('/actions', actions);
|
||||
router.use('/actions', configMiddleware, actions);
|
||||
|
||||
/**
|
||||
* Get a list of available tools for agents.
|
||||
* @route GET /agents/tools
|
||||
*/
|
||||
router.use('/tools', tools);
|
||||
router.use('/tools', configMiddleware, tools);
|
||||
|
||||
/**
|
||||
* Get all agent categories with counts
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
const express = require('express');
|
||||
const { nanoid } = require('nanoid');
|
||||
const { logger } = require('@librechat/data-schemas');
|
||||
const { actionDelimiter, EModelEndpoint, removeNullishValues } = require('librechat-data-provider');
|
||||
const { encryptMetadata, domainParser } = require('~/server/services/ActionService');
|
||||
const { getOpenAIClient } = require('~/server/controllers/assistants/helpers');
|
||||
const { updateAction, getActions, deleteAction } = require('~/models/Action');
|
||||
const { updateAssistantDoc, getAssistant } = require('~/models/Assistant');
|
||||
const { isActionDomainAllowed } = require('~/server/services/domains');
|
||||
const { logger } = require('~/config');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
|
|
@ -21,6 +21,7 @@ const router = express.Router();
|
|||
*/
|
||||
router.post('/:assistant_id', async (req, res) => {
|
||||
try {
|
||||
const appConfig = req.config;
|
||||
const { assistant_id } = req.params;
|
||||
|
||||
/** @type {{ functions: FunctionTool[], action_id: string, metadata: ActionMetadata }} */
|
||||
|
|
@ -30,7 +31,10 @@ router.post('/:assistant_id', async (req, res) => {
|
|||
}
|
||||
|
||||
let metadata = await encryptMetadata(removeNullishValues(_metadata, true));
|
||||
const isDomainAllowed = await isActionDomainAllowed(metadata.domain);
|
||||
const isDomainAllowed = await isActionDomainAllowed(
|
||||
metadata.domain,
|
||||
appConfig?.actions?.allowedDomains,
|
||||
);
|
||||
if (!isDomainAllowed) {
|
||||
return res.status(400).json({ message: 'Domain not allowed' });
|
||||
}
|
||||
|
|
@ -125,7 +129,7 @@ router.post('/:assistant_id', async (req, res) => {
|
|||
}
|
||||
|
||||
/* Map Azure OpenAI model to the assistant as defined by config */
|
||||
if (req.app.locals[EModelEndpoint.azureOpenAI]?.assistants) {
|
||||
if (appConfig.endpoints?.[EModelEndpoint.azureOpenAI]?.assistants) {
|
||||
updatedAssistant = {
|
||||
...updatedAssistant,
|
||||
model: req.body.model,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
const express = require('express');
|
||||
const { uaParser, checkBan, requireJwtAuth, configMiddleware } = require('~/server/middleware');
|
||||
const router = express.Router();
|
||||
const { uaParser, checkBan, requireJwtAuth } = require('~/server/middleware');
|
||||
|
||||
const { v1 } = require('./v1');
|
||||
const chatV1 = require('./chatV1');
|
||||
|
|
@ -10,6 +10,7 @@ const chatV2 = require('./chatV2');
|
|||
router.use(requireJwtAuth);
|
||||
router.use(checkBan);
|
||||
router.use(uaParser);
|
||||
router.use(configMiddleware);
|
||||
router.use('/v1/', v1);
|
||||
router.use('/v1/chat', chatV1);
|
||||
router.use('/v2/', v2);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
const express = require('express');
|
||||
const { configMiddleware } = require('~/server/middleware');
|
||||
const v1 = require('~/server/controllers/assistants/v1');
|
||||
const v2 = require('~/server/controllers/assistants/v2');
|
||||
const documents = require('./documents');
|
||||
|
|
@ -6,6 +7,7 @@ const actions = require('./actions');
|
|||
const tools = require('./tools');
|
||||
|
||||
const router = express.Router();
|
||||
router.use(configMiddleware);
|
||||
|
||||
/**
|
||||
* Assistant actions route.
|
||||
|
|
|
|||
|
|
@ -17,12 +17,12 @@ const {
|
|||
const { verify2FAWithTempToken } = require('~/server/controllers/auth/TwoFactorAuthController');
|
||||
const { logoutController } = require('~/server/controllers/auth/LogoutController');
|
||||
const { loginController } = require('~/server/controllers/auth/LoginController');
|
||||
const { getBalanceConfig } = require('~/server/services/Config');
|
||||
const { getAppConfig } = require('~/server/services/Config');
|
||||
const middleware = require('~/server/middleware');
|
||||
const { Balance } = require('~/db/models');
|
||||
|
||||
const setBalanceConfig = createSetBalanceConfig({
|
||||
getBalanceConfig,
|
||||
getAppConfig,
|
||||
Balance,
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,14 @@
|
|||
const express = require('express');
|
||||
const { isEnabled } = require('@librechat/api');
|
||||
const { logger } = require('@librechat/data-schemas');
|
||||
const { CacheKeys, defaultSocialLogins, Constants } = require('librechat-data-provider');
|
||||
const { getCustomConfig } = require('~/server/services/Config/getCustomConfig');
|
||||
const { isEnabled, getBalanceConfig } = require('@librechat/api');
|
||||
const {
|
||||
Constants,
|
||||
CacheKeys,
|
||||
removeNullishValues,
|
||||
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 { getMCPManager } = require('~/config');
|
||||
const { getLogStores } = require('~/cache');
|
||||
|
|
@ -43,6 +48,8 @@ router.get('/', async function (req, res) {
|
|||
const ldap = getLdapConfig();
|
||||
|
||||
try {
|
||||
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||
|
||||
const isOpenIdEnabled =
|
||||
!!process.env.OPENID_CLIENT_ID &&
|
||||
!!process.env.OPENID_CLIENT_SECRET &&
|
||||
|
|
@ -55,10 +62,12 @@ router.get('/', async function (req, res) {
|
|||
!!process.env.SAML_CERT &&
|
||||
!!process.env.SAML_SESSION_SECRET;
|
||||
|
||||
const balanceConfig = getBalanceConfig(appConfig);
|
||||
|
||||
/** @type {TStartupConfig} */
|
||||
const payload = {
|
||||
appTitle: process.env.APP_TITLE || 'LibreChat',
|
||||
socialLogins: req.app.locals.socialLogins ?? defaultSocialLogins,
|
||||
socialLogins: appConfig?.registration?.socialLogins ?? defaultSocialLogins,
|
||||
discordLoginEnabled: !!process.env.DISCORD_CLIENT_ID && !!process.env.DISCORD_CLIENT_SECRET,
|
||||
facebookLoginEnabled:
|
||||
!!process.env.FACEBOOK_CLIENT_ID && !!process.env.FACEBOOK_CLIENT_SECRET,
|
||||
|
|
@ -91,10 +100,10 @@ router.get('/', async function (req, res) {
|
|||
isEnabled(process.env.SHOW_BIRTHDAY_ICON) ||
|
||||
process.env.SHOW_BIRTHDAY_ICON === '',
|
||||
helpAndFaqURL: process.env.HELP_AND_FAQ_URL || 'https://librechat.ai',
|
||||
interface: req.app.locals.interfaceConfig,
|
||||
turnstile: req.app.locals.turnstileConfig,
|
||||
modelSpecs: req.app.locals.modelSpecs,
|
||||
balance: req.app.locals.balance,
|
||||
interface: appConfig?.interfaceConfig,
|
||||
turnstile: appConfig?.turnstileConfig,
|
||||
modelSpecs: appConfig?.modelSpecs,
|
||||
balance: balanceConfig,
|
||||
sharedLinksEnabled,
|
||||
publicSharedLinksEnabled,
|
||||
analyticsGtmId: process.env.ANALYTICS_GTM_ID,
|
||||
|
|
@ -109,27 +118,31 @@ router.get('/', async function (req, res) {
|
|||
};
|
||||
|
||||
payload.mcpServers = {};
|
||||
const config = await getCustomConfig();
|
||||
if (config?.mcpServers != null) {
|
||||
const getMCPServers = () => {
|
||||
try {
|
||||
const mcpManager = getMCPManager();
|
||||
if (!mcpManager) {
|
||||
return;
|
||||
}
|
||||
const mcpServers = mcpManager.getAllServers();
|
||||
if (!mcpServers) return;
|
||||
const oauthServers = mcpManager.getOAuthServers();
|
||||
for (const serverName in config.mcpServers) {
|
||||
const serverConfig = config.mcpServers[serverName];
|
||||
payload.mcpServers[serverName] = {
|
||||
for (const serverName in mcpServers) {
|
||||
const serverConfig = mcpServers[serverName];
|
||||
payload.mcpServers[serverName] = removeNullishValues({
|
||||
startup: serverConfig?.startup,
|
||||
chatMenu: serverConfig?.chatMenu,
|
||||
isOAuth: oauthServers?.has(serverName),
|
||||
customUserVars: serverConfig?.customUserVars || {},
|
||||
};
|
||||
customUserVars: serverConfig?.customUserVars,
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error('Error loading MCP servers', err);
|
||||
} catch (error) {
|
||||
logger.error('Error loading MCP servers', error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** @type {TCustomConfig['webSearch']} */
|
||||
const webSearchConfig = req.app.locals.webSearch;
|
||||
getMCPServers();
|
||||
const webSearchConfig = appConfig?.webSearch;
|
||||
if (
|
||||
webSearchConfig != null &&
|
||||
(webSearchConfig.searchProvider ||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const endpointController = require('~/server/controllers/EndpointController');
|
||||
const overrideController = require('~/server/controllers/OverrideController');
|
||||
|
||||
const router = express.Router();
|
||||
router.get('/', endpointController);
|
||||
router.get('/config/override', overrideController);
|
||||
|
||||
module.exports = router;
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
const fs = require('fs').promises;
|
||||
const express = require('express');
|
||||
const { logger } = require('@librechat/data-schemas');
|
||||
const { getStrategyFunctions } = require('~/server/services/Files/strategies');
|
||||
const { resizeAvatar } = require('~/server/services/Files/images/avatar');
|
||||
const { filterFile } = require('~/server/services/Files/process');
|
||||
const { getFileStrategy } = require('~/server/utils/getFileStrategy');
|
||||
const { logger } = require('~/config');
|
||||
const { filterFile } = require('~/server/services/Files/process');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.post('/', async (req, res) => {
|
||||
try {
|
||||
const appConfig = req.config;
|
||||
filterFile({ req, file: req.file, image: true, isAvatar: true });
|
||||
const userId = req.user.id;
|
||||
const { manual } = req.body;
|
||||
|
|
@ -19,8 +20,8 @@ router.post('/', async (req, res) => {
|
|||
throw new Error('User ID is undefined');
|
||||
}
|
||||
|
||||
const fileStrategy = getFileStrategy(req.app.locals, { isAvatar: true });
|
||||
const desiredFormat = req.app.locals.imageOutputType;
|
||||
const fileStrategy = getFileStrategy(appConfig, { isAvatar: true });
|
||||
const desiredFormat = appConfig.imageOutputType;
|
||||
const resizedBuffer = await resizeAvatar({
|
||||
userId,
|
||||
input,
|
||||
|
|
@ -39,7 +40,7 @@ router.post('/', async (req, res) => {
|
|||
try {
|
||||
await fs.unlink(req.file.path);
|
||||
logger.debug('[/files/images/avatar] Temp. image upload file deleted');
|
||||
} catch (error) {
|
||||
} catch {
|
||||
logger.debug('[/files/images/avatar] Temp. image upload file already deleted');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,8 +36,9 @@ const router = express.Router();
|
|||
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
const appConfig = req.config;
|
||||
const files = await getFiles({ user: req.user.id });
|
||||
if (req.app.locals.fileStrategy === FileSources.s3) {
|
||||
if (appConfig.fileStrategy === FileSources.s3) {
|
||||
try {
|
||||
const cache = getLogStores(CacheKeys.S3_EXPIRY_INTERVAL);
|
||||
const alreadyChecked = await cache.get(req.user.id);
|
||||
|
|
@ -114,7 +115,8 @@ router.get('/agent/:agent_id', async (req, res) => {
|
|||
|
||||
router.get('/config', async (req, res) => {
|
||||
try {
|
||||
res.status(200).json(req.app.locals.fileConfig);
|
||||
const appConfig = req.config;
|
||||
res.status(200).json(appConfig.fileConfig);
|
||||
} catch (error) {
|
||||
logger.error('[/files] Error getting fileConfig', error);
|
||||
res.status(400).json({ message: 'Error in request', error: error.message });
|
||||
|
|
|
|||
|
|
@ -1,18 +1,19 @@
|
|||
const path = require('path');
|
||||
const fs = require('fs').promises;
|
||||
const express = require('express');
|
||||
const { logger } = require('@librechat/data-schemas');
|
||||
const { isAgentsEndpoint } = require('librechat-data-provider');
|
||||
const {
|
||||
filterFile,
|
||||
processImageFile,
|
||||
processAgentFileUpload,
|
||||
} = require('~/server/services/Files/process');
|
||||
const { logger } = require('~/config');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.post('/', async (req, res) => {
|
||||
const metadata = req.body;
|
||||
const appConfig = req.config;
|
||||
|
||||
try {
|
||||
filterFile({ req, image: true });
|
||||
|
|
@ -30,7 +31,7 @@ router.post('/', async (req, res) => {
|
|||
logger.error('[/files/images] Error processing file:', error);
|
||||
try {
|
||||
const filepath = path.join(
|
||||
req.app.locals.paths.imageOutput,
|
||||
appConfig.paths.imageOutput,
|
||||
req.user.id,
|
||||
path.basename(req.file.filename),
|
||||
);
|
||||
|
|
@ -43,7 +44,7 @@ router.post('/', async (req, res) => {
|
|||
try {
|
||||
await fs.unlink(req.file.path);
|
||||
logger.debug('[/files/images] Temp. image upload file deleted');
|
||||
} catch (error) {
|
||||
} catch {
|
||||
logger.debug('[/files/images] Temp. image upload file already deleted');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,11 @@
|
|||
const express = require('express');
|
||||
const { uaParser, checkBan, requireJwtAuth, createFileLimiters } = require('~/server/middleware');
|
||||
const {
|
||||
createFileLimiters,
|
||||
configMiddleware,
|
||||
requireJwtAuth,
|
||||
uaParser,
|
||||
checkBan,
|
||||
} = require('~/server/middleware');
|
||||
const { avatar: asstAvatarRouter } = require('~/server/routes/assistants/v1');
|
||||
const { avatar: agentAvatarRouter } = require('~/server/routes/agents/v1');
|
||||
const { createMulterInstance } = require('./multer');
|
||||
|
|
@ -12,6 +18,7 @@ const speech = require('./speech');
|
|||
const initialize = async () => {
|
||||
const router = express.Router();
|
||||
router.use(requireJwtAuth);
|
||||
router.use(configMiddleware);
|
||||
router.use(checkBan);
|
||||
router.use(uaParser);
|
||||
|
||||
|
|
|
|||
|
|
@ -4,11 +4,12 @@ const crypto = require('crypto');
|
|||
const multer = require('multer');
|
||||
const { sanitizeFilename } = require('@librechat/api');
|
||||
const { fileConfig: defaultFileConfig, mergeFileConfig } = require('librechat-data-provider');
|
||||
const { getCustomConfig } = require('~/server/services/Config');
|
||||
const { getAppConfig } = require('~/server/services/Config');
|
||||
|
||||
const storage = multer.diskStorage({
|
||||
destination: function (req, file, cb) {
|
||||
const outputPath = path.join(req.app.locals.paths.uploads, 'temp', req.user.id);
|
||||
const appConfig = req.config;
|
||||
const outputPath = path.join(appConfig.paths.uploads, 'temp', req.user.id);
|
||||
if (!fs.existsSync(outputPath)) {
|
||||
fs.mkdirSync(outputPath, { recursive: true });
|
||||
}
|
||||
|
|
@ -68,8 +69,8 @@ const createFileFilter = (customFileConfig) => {
|
|||
};
|
||||
|
||||
const createMulterInstance = async () => {
|
||||
const customConfig = await getCustomConfig();
|
||||
const fileConfig = mergeFileConfig(customConfig?.fileConfig);
|
||||
const appConfig = await getAppConfig();
|
||||
const fileConfig = mergeFileConfig(appConfig?.fileConfig);
|
||||
const fileFilter = createFileFilter(fileConfig);
|
||||
return multer({
|
||||
storage,
|
||||
|
|
|
|||
|
|
@ -8,21 +8,7 @@ const { createMulterInstance, storage, importFileFilter } = require('./multer');
|
|||
|
||||
// Mock only the config service that requires external dependencies
|
||||
jest.mock('~/server/services/Config', () => ({
|
||||
getCustomConfig: jest.fn(() =>
|
||||
Promise.resolve({
|
||||
fileConfig: {
|
||||
endpoints: {
|
||||
openAI: {
|
||||
supportedMimeTypes: ['image/jpeg', 'image/png', 'application/pdf'],
|
||||
},
|
||||
default: {
|
||||
supportedMimeTypes: ['image/jpeg', 'image/png', 'text/plain'],
|
||||
},
|
||||
},
|
||||
serverFileSizeLimit: 10000000, // 10MB
|
||||
},
|
||||
}),
|
||||
),
|
||||
getAppConfig: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('Multer Configuration', () => {
|
||||
|
|
@ -36,15 +22,13 @@ describe('Multer Configuration', () => {
|
|||
|
||||
mockReq = {
|
||||
user: { id: 'test-user-123' },
|
||||
app: {
|
||||
locals: {
|
||||
paths: {
|
||||
uploads: tempDir,
|
||||
},
|
||||
},
|
||||
},
|
||||
body: {},
|
||||
originalUrl: '/api/files/upload',
|
||||
config: {
|
||||
paths: {
|
||||
uploads: tempDir,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
mockFile = {
|
||||
|
|
@ -79,7 +63,7 @@ describe('Multer Configuration', () => {
|
|||
|
||||
it("should create directory recursively if it doesn't exist", (done) => {
|
||||
const deepPath = path.join(tempDir, 'deep', 'nested', 'path');
|
||||
mockReq.app.locals.paths.uploads = deepPath;
|
||||
mockReq.config.paths.uploads = deepPath;
|
||||
|
||||
const cb = jest.fn((err, destination) => {
|
||||
expect(err).toBeNull();
|
||||
|
|
@ -331,11 +315,11 @@ describe('Multer Configuration', () => {
|
|||
});
|
||||
|
||||
it('should use real config merging', async () => {
|
||||
const { getCustomConfig } = require('~/server/services/Config');
|
||||
const { getAppConfig } = require('~/server/services/Config');
|
||||
|
||||
const multerInstance = await createMulterInstance();
|
||||
|
||||
expect(getCustomConfig).toHaveBeenCalled();
|
||||
expect(getAppConfig).toHaveBeenCalled();
|
||||
expect(multerInstance).toBeDefined();
|
||||
});
|
||||
|
||||
|
|
@ -462,26 +446,15 @@ describe('Multer Configuration', () => {
|
|||
}).not.toThrow();
|
||||
});
|
||||
|
||||
it('should handle file system errors when directory creation fails', (done) => {
|
||||
it('should handle file system errors when directory creation fails', () => {
|
||||
// Test with a non-existent parent directory to simulate fs issues
|
||||
const invalidPath = '/nonexistent/path/that/should/not/exist';
|
||||
mockReq.app.locals.paths.uploads = invalidPath;
|
||||
mockReq.config.paths.uploads = invalidPath;
|
||||
|
||||
try {
|
||||
// Call getDestination which should fail due to permission/path issues
|
||||
storage.getDestination(mockReq, mockFile, (err, destination) => {
|
||||
// If callback is reached, we didn't get the expected error
|
||||
done(new Error('Expected mkdirSync to throw an error but callback was called'));
|
||||
});
|
||||
// If we get here without throwing, something unexpected happened
|
||||
done(new Error('Expected mkdirSync to throw an error but no error was thrown'));
|
||||
} catch (error) {
|
||||
// This is the expected behavior - mkdirSync throws synchronously for invalid paths
|
||||
// On Linux, this typically returns EACCES (permission denied)
|
||||
// On macOS/Darwin, this returns ENOENT (no such file or directory)
|
||||
expect(['EACCES', 'ENOENT']).toContain(error.code);
|
||||
done();
|
||||
}
|
||||
// The current implementation doesn't catch errors, so they're thrown synchronously
|
||||
expect(() => {
|
||||
storage.getDestination(mockReq, mockFile, jest.fn());
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
it('should handle malformed filenames with real sanitization', (done) => {
|
||||
|
|
@ -538,10 +511,10 @@ describe('Multer Configuration', () => {
|
|||
|
||||
describe('Real Configuration Testing', () => {
|
||||
it('should handle missing custom config gracefully with real mergeFileConfig', async () => {
|
||||
const { getCustomConfig } = require('~/server/services/Config');
|
||||
const { getAppConfig } = require('~/server/services/Config');
|
||||
|
||||
// Mock getCustomConfig to return undefined
|
||||
getCustomConfig.mockResolvedValueOnce(undefined);
|
||||
// Mock getAppConfig to return undefined
|
||||
getAppConfig.mockResolvedValueOnce(undefined);
|
||||
|
||||
const multerInstance = await createMulterInstance();
|
||||
expect(multerInstance).toBeDefined();
|
||||
|
|
@ -549,25 +522,28 @@ describe('Multer Configuration', () => {
|
|||
});
|
||||
|
||||
it('should properly integrate real fileConfig with custom endpoints', async () => {
|
||||
const { getCustomConfig } = require('~/server/services/Config');
|
||||
const { getAppConfig } = require('~/server/services/Config');
|
||||
|
||||
// Mock a custom config with additional endpoints
|
||||
getCustomConfig.mockResolvedValueOnce({
|
||||
// Mock appConfig with fileConfig
|
||||
getAppConfig.mockResolvedValueOnce({
|
||||
paths: {
|
||||
uploads: tempDir,
|
||||
},
|
||||
fileConfig: {
|
||||
endpoints: {
|
||||
anthropic: {
|
||||
supportedMimeTypes: ['text/plain', 'image/png'],
|
||||
},
|
||||
},
|
||||
serverFileSizeLimit: 20, // 20 MB
|
||||
serverFileSizeLimit: 20971520, // 20 MB in bytes (mergeFileConfig converts)
|
||||
},
|
||||
});
|
||||
|
||||
const multerInstance = await createMulterInstance();
|
||||
expect(multerInstance).toBeDefined();
|
||||
|
||||
// Verify that getCustomConfig was called (we can't spy on the actual merge function easily)
|
||||
expect(getCustomConfig).toHaveBeenCalled();
|
||||
// Verify that getAppConfig was called
|
||||
expect(getAppConfig).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ const {
|
|||
deleteMemory,
|
||||
setMemory,
|
||||
} = require('~/models');
|
||||
const { requireJwtAuth } = require('~/server/middleware');
|
||||
const { requireJwtAuth, configMiddleware } = require('~/server/middleware');
|
||||
const { getRoleByName } = require('~/models/Role');
|
||||
|
||||
const router = express.Router();
|
||||
|
|
@ -48,7 +48,7 @@ router.use(requireJwtAuth);
|
|||
* Returns all memories for the authenticated user, sorted by updated_at (newest first).
|
||||
* Also includes memory usage percentage based on token limit.
|
||||
*/
|
||||
router.get('/', checkMemoryRead, async (req, res) => {
|
||||
router.get('/', checkMemoryRead, configMiddleware, async (req, res) => {
|
||||
try {
|
||||
const memories = await getAllUserMemories(req.user.id);
|
||||
|
||||
|
|
@ -60,7 +60,8 @@ router.get('/', checkMemoryRead, async (req, res) => {
|
|||
return sum + (memory.tokenCount || 0);
|
||||
}, 0);
|
||||
|
||||
const memoryConfig = req.app.locals?.memory;
|
||||
const appConfig = req.config;
|
||||
const memoryConfig = appConfig?.memory;
|
||||
const tokenLimit = memoryConfig?.tokenLimit;
|
||||
const charLimit = memoryConfig?.charLimit || 10000;
|
||||
|
||||
|
|
@ -87,7 +88,7 @@ router.get('/', checkMemoryRead, async (req, res) => {
|
|||
* Body: { key: string, value: string }
|
||||
* Returns 201 and { created: true, memory: <createdDoc> } when successful.
|
||||
*/
|
||||
router.post('/', memoryPayloadLimit, checkMemoryCreate, async (req, res) => {
|
||||
router.post('/', memoryPayloadLimit, checkMemoryCreate, configMiddleware, async (req, res) => {
|
||||
const { key, value } = req.body;
|
||||
|
||||
if (typeof key !== 'string' || key.trim() === '') {
|
||||
|
|
@ -98,7 +99,8 @@ router.post('/', memoryPayloadLimit, checkMemoryCreate, async (req, res) => {
|
|||
return res.status(400).json({ error: 'Value is required and must be a non-empty string.' });
|
||||
}
|
||||
|
||||
const memoryConfig = req.app.locals?.memory;
|
||||
const appConfig = req.config;
|
||||
const memoryConfig = appConfig?.memory;
|
||||
const charLimit = memoryConfig?.charLimit || 10000;
|
||||
|
||||
if (key.length > 1000) {
|
||||
|
|
@ -117,6 +119,9 @@ router.post('/', memoryPayloadLimit, checkMemoryCreate, async (req, res) => {
|
|||
const tokenCount = Tokenizer.getTokenCount(value, 'o200k_base');
|
||||
|
||||
const memories = await getAllUserMemories(req.user.id);
|
||||
|
||||
const appConfig = req.config;
|
||||
const memoryConfig = appConfig?.memory;
|
||||
const tokenLimit = memoryConfig?.tokenLimit;
|
||||
|
||||
if (tokenLimit) {
|
||||
|
|
@ -191,7 +196,7 @@ router.patch('/preferences', checkMemoryOptOut, async (req, res) => {
|
|||
* Body: { key?: string, value: string }
|
||||
* Returns 200 and { updated: true, memory: <updatedDoc> } when successful.
|
||||
*/
|
||||
router.patch('/:key', memoryPayloadLimit, checkMemoryUpdate, async (req, res) => {
|
||||
router.patch('/:key', memoryPayloadLimit, checkMemoryUpdate, configMiddleware, async (req, res) => {
|
||||
const { key: urlKey } = req.params;
|
||||
const { key: bodyKey, value } = req.body || {};
|
||||
|
||||
|
|
@ -200,8 +205,8 @@ router.patch('/:key', memoryPayloadLimit, checkMemoryUpdate, async (req, res) =>
|
|||
}
|
||||
|
||||
const newKey = bodyKey || urlKey;
|
||||
|
||||
const memoryConfig = req.app.locals?.memory;
|
||||
const appConfig = req.config;
|
||||
const memoryConfig = appConfig?.memory;
|
||||
const charLimit = memoryConfig?.charLimit || 10000;
|
||||
|
||||
if (newKey.length > 1000) {
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@ const { isEnabled, createSetBalanceConfig } = require('@librechat/api');
|
|||
const { checkDomainAllowed, loginLimiter, logHeaders, checkBan } = require('~/server/middleware');
|
||||
const { syncUserEntraGroupMemberships } = require('~/server/services/PermissionService');
|
||||
const { setAuthTokens, setOpenIDAuthTokens } = require('~/server/services/AuthService');
|
||||
const { getBalanceConfig } = require('~/server/services/Config');
|
||||
const { getAppConfig } = require('~/server/services/Config');
|
||||
const { Balance } = require('~/db/models');
|
||||
|
||||
const setBalanceConfig = createSetBalanceConfig({
|
||||
getBalanceConfig,
|
||||
getAppConfig,
|
||||
Balance,
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
const express = require('express');
|
||||
const { getAvailablePluginsController } = require('../controllers/PluginController');
|
||||
const requireJwtAuth = require('../middleware/requireJwtAuth');
|
||||
const { getAvailablePluginsController } = require('~/server/controllers/PluginController');
|
||||
const { requireJwtAuth } = require('~/server/middleware');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
|
|
|
|||
|
|
@ -424,7 +424,7 @@ router.get('/', async (req, res) => {
|
|||
/**
|
||||
* Deletes a prompt
|
||||
*
|
||||
* @param {Express.Request} req - The request object.
|
||||
* @param {ServerRequest} req - The request object.
|
||||
* @param {TDeletePromptVariables} req.params - The request parameters
|
||||
* @param {import('mongoose').ObjectId} req.params.promptId - The prompt ID
|
||||
* @param {Express.Response} res - The response object.
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ const {
|
|||
// Mock modules before importing
|
||||
jest.mock('~/server/services/Config', () => ({
|
||||
getCachedTools: jest.fn().mockResolvedValue({}),
|
||||
getCustomConfig: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('~/models/Role', () => ({
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
const express = require('express');
|
||||
const { requireJwtAuth, canDeleteAccount, verifyEmailLimiter } = require('~/server/middleware');
|
||||
const {
|
||||
getUserController,
|
||||
deleteUserController,
|
||||
verifyEmailController,
|
||||
updateUserPluginsController,
|
||||
resendVerificationController,
|
||||
getTermsStatusController,
|
||||
acceptTermsController,
|
||||
verifyEmailController,
|
||||
deleteUserController,
|
||||
getUserController,
|
||||
} = require('~/server/controllers/UserController');
|
||||
const { requireJwtAuth, canDeleteAccount, verifyEmailLimiter } = require('~/server/middleware');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue