mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-01-30 14:25:19 +01:00
🔃 refactor: Decouple Effects from AppService, move to data-schemas (#9974)
* chore: linting for `loadCustomConfig` * refactor: decouple CDN init and variable/health checks from AppService * refactor: move AppService to packages/data-schemas * chore: update AppConfig import path to use data-schemas * chore: update JsonSchemaType import path to use data-schemas * refactor: update UserController to import webSearchKeys and redefine FunctionTool typedef * chore: remove AppService.js * refactor: update AppConfig interface to use Partial<TCustomConfig> and make paths and fileStrategies optional * refactor: update checkConfig function to accept Partial<TCustomConfig> * chore: fix types * refactor: move handleRateLimits to startup checks as is an effect * test: remove outdated rate limit tests from AppService.spec and add new handleRateLimits tests in checks.spec
This commit is contained in:
parent
9ff608e6af
commit
838fb53208
73 changed files with 1383 additions and 1326 deletions
54
packages/api/src/cdn/azure.ts
Normal file
54
packages/api/src/cdn/azure.ts
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
import { logger } from '@librechat/data-schemas';
|
||||
import { DefaultAzureCredential } from '@azure/identity';
|
||||
import type { ContainerClient, BlobServiceClient } from '@azure/storage-blob';
|
||||
|
||||
let blobServiceClient: BlobServiceClient | null = null;
|
||||
let azureWarningLogged = false;
|
||||
|
||||
/**
|
||||
* Initializes the Azure Blob Service client.
|
||||
* This function establishes a connection by checking if a connection string is provided.
|
||||
* If available, the connection string is used; otherwise, Managed Identity (via DefaultAzureCredential) is utilized.
|
||||
* Note: Container creation (and its public access settings) is handled later in the CRUD functions.
|
||||
* @returns The initialized client, or null if the required configuration is missing.
|
||||
*/
|
||||
export const initializeAzureBlobService = async (): Promise<BlobServiceClient | null> => {
|
||||
if (blobServiceClient) {
|
||||
return blobServiceClient;
|
||||
}
|
||||
const connectionString = process.env.AZURE_STORAGE_CONNECTION_STRING;
|
||||
if (connectionString) {
|
||||
const { BlobServiceClient } = await import('@azure/storage-blob');
|
||||
blobServiceClient = BlobServiceClient.fromConnectionString(connectionString);
|
||||
logger.info('Azure Blob Service initialized using connection string');
|
||||
} else {
|
||||
const accountName = process.env.AZURE_STORAGE_ACCOUNT_NAME;
|
||||
if (!accountName) {
|
||||
if (!azureWarningLogged) {
|
||||
logger.error(
|
||||
'[initializeAzureBlobService] Azure Blob Service not initialized. Connection string missing and AZURE_STORAGE_ACCOUNT_NAME not provided.',
|
||||
);
|
||||
azureWarningLogged = true;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
const url = `https://${accountName}.blob.core.windows.net`;
|
||||
const credential = new DefaultAzureCredential();
|
||||
const { BlobServiceClient } = await import('@azure/storage-blob');
|
||||
blobServiceClient = new BlobServiceClient(url, credential);
|
||||
logger.info('Azure Blob Service initialized using Managed Identity');
|
||||
}
|
||||
return blobServiceClient;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the Azure ContainerClient for the given container name.
|
||||
* @param [containerName=process.env.AZURE_CONTAINER_NAME || 'files'] - The container name.
|
||||
* @returns The Azure ContainerClient.
|
||||
*/
|
||||
export const getAzureContainerClient = async (
|
||||
containerName = process.env.AZURE_CONTAINER_NAME || 'files',
|
||||
): Promise<ContainerClient | null> => {
|
||||
const serviceClient = await initializeAzureBlobService();
|
||||
return serviceClient ? serviceClient.getContainerClient(containerName) : null;
|
||||
};
|
||||
42
packages/api/src/cdn/firebase.ts
Normal file
42
packages/api/src/cdn/firebase.ts
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import firebase from 'firebase/app';
|
||||
import { getStorage } from 'firebase/storage';
|
||||
import { logger } from '@librechat/data-schemas';
|
||||
import type { FirebaseStorage } from 'firebase/storage';
|
||||
import type { FirebaseApp } from 'firebase/app';
|
||||
|
||||
let firebaseInitCount = 0;
|
||||
let firebaseApp: FirebaseApp | null = null;
|
||||
|
||||
export const initializeFirebase = () => {
|
||||
if (firebaseApp) {
|
||||
return firebaseApp;
|
||||
}
|
||||
|
||||
const firebaseConfig = {
|
||||
apiKey: process.env.FIREBASE_API_KEY,
|
||||
authDomain: process.env.FIREBASE_AUTH_DOMAIN,
|
||||
projectId: process.env.FIREBASE_PROJECT_ID,
|
||||
storageBucket: process.env.FIREBASE_STORAGE_BUCKET,
|
||||
messagingSenderId: process.env.FIREBASE_MESSAGING_SENDER_ID,
|
||||
appId: process.env.FIREBASE_APP_ID,
|
||||
};
|
||||
|
||||
if (Object.values(firebaseConfig).some((value) => !value)) {
|
||||
if (firebaseInitCount === 0) {
|
||||
logger.info(
|
||||
'[Optional] Firebase CDN not initialized. To enable, set FIREBASE_API_KEY, FIREBASE_AUTH_DOMAIN, FIREBASE_PROJECT_ID, FIREBASE_STORAGE_BUCKET, FIREBASE_MESSAGING_SENDER_ID, and FIREBASE_APP_ID environment variables.',
|
||||
);
|
||||
}
|
||||
firebaseInitCount++;
|
||||
return null;
|
||||
}
|
||||
|
||||
firebaseApp = firebase.initializeApp(firebaseConfig);
|
||||
logger.info('Firebase CDN initialized');
|
||||
return firebaseApp;
|
||||
};
|
||||
|
||||
export const getFirebaseStorage = (): FirebaseStorage | null => {
|
||||
const app = initializeFirebase();
|
||||
return app ? getStorage(app) : null;
|
||||
};
|
||||
3
packages/api/src/cdn/index.ts
Normal file
3
packages/api/src/cdn/index.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export * from './azure';
|
||||
export * from './firebase';
|
||||
export * from './s3';
|
||||
51
packages/api/src/cdn/s3.ts
Normal file
51
packages/api/src/cdn/s3.ts
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
import { S3Client } from '@aws-sdk/client-s3';
|
||||
import { logger } from '@librechat/data-schemas';
|
||||
|
||||
let s3: S3Client | null = null;
|
||||
|
||||
/**
|
||||
* Initializes and returns an instance of the AWS S3 client.
|
||||
*
|
||||
* If AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY are provided, they will be used.
|
||||
* Otherwise, the AWS SDK's default credentials chain (including IRSA) is used.
|
||||
*
|
||||
* If AWS_ENDPOINT_URL is provided, it will be used as the endpoint.
|
||||
*
|
||||
* @returns An instance of S3Client if the region is provided; otherwise, null.
|
||||
*/
|
||||
export const initializeS3 = (): S3Client | null => {
|
||||
if (s3) {
|
||||
return s3;
|
||||
}
|
||||
|
||||
const region = process.env.AWS_REGION;
|
||||
if (!region) {
|
||||
logger.error('[initializeS3] AWS_REGION is not set. Cannot initialize S3.');
|
||||
return null;
|
||||
}
|
||||
|
||||
// Read the custom endpoint if provided.
|
||||
const endpoint = process.env.AWS_ENDPOINT_URL;
|
||||
const accessKeyId = process.env.AWS_ACCESS_KEY_ID;
|
||||
const secretAccessKey = process.env.AWS_SECRET_ACCESS_KEY;
|
||||
|
||||
const config = {
|
||||
region,
|
||||
// Conditionally add the endpoint if it is provided
|
||||
...(endpoint ? { endpoint } : {}),
|
||||
};
|
||||
|
||||
if (accessKeyId && secretAccessKey) {
|
||||
s3 = new S3Client({
|
||||
...config,
|
||||
credentials: { accessKeyId, secretAccessKey },
|
||||
});
|
||||
logger.info('[initializeS3] S3 initialized with provided credentials.');
|
||||
} else {
|
||||
// When using IRSA, credentials are automatically provided via the IAM Role attached to the ServiceAccount.
|
||||
s3 = new S3Client(config);
|
||||
logger.info('[initializeS3] S3 initialized using default credentials (IRSA).');
|
||||
}
|
||||
|
||||
return s3;
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue