Skip to content

Commit 4d0b59c

Browse files
committed
feat(storage): add CloudFront CDN file strategy
- Add `FileSources.cloudfront` enum and `cloudfrontConfigSchema` to data-provider - Implement `packages/api/src/cdn/cloudfront.ts` for initialization and config access - Implement `packages/api/src/storage/cloudfront/crud.ts` for URL generation, signed URLs, S3-delegated uploads/deletes, and optional cache invalidation - Refactor `S3ImageService` into a unified `ImageService` class with injected `saveBuffer` fn, eliminating many lines of per-strategy duplication - Refactor `initializeFileStorage` to deduplicate and handle `fileStrategies` config - Propagate `cloudfront` config through `AppConfig` and `AppService` - Add CloudFront strategy to `strategies.js` and `librechat.example.yaml` - Added unit tests across cdn/cloudfront, storage/cloudfront/crud, and ImageService
1 parent ca6ce8f commit 4d0b59c

24 files changed

Lines changed: 2292 additions & 1060 deletions

File tree

api/server/services/Files/strategies.js

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,20 @@ const { FileSources } = require('librechat-data-provider');
22
const {
33
getS3URL,
44
saveURLToS3,
5+
ImageService,
56
parseDocument,
67
uploadFileToS3,
7-
S3ImageService,
88
saveBufferToS3,
99
getS3FileStream,
1010
deleteFileFromS3,
11+
getCloudFrontURL,
1112
uploadMistralOCR,
13+
saveURLToCloudFront,
1214
uploadAzureMistralOCR,
15+
uploadFileToCloudFront,
16+
saveBufferToCloudFront,
17+
getCloudFrontFileStream,
18+
deleteFileFromCloudFront,
1319
uploadGoogleVertexMistralOCR,
1420
} = require('@librechat/api');
1521
const {
@@ -37,15 +43,21 @@ const {
3743
const { resizeImageBuffer } = require('./images/resize');
3844
const { updateUser, updateFile } = require('~/models');
3945

40-
const s3ImageService = new S3ImageService({
46+
const imageServiceDeps = {
4147
resizeImageBuffer,
4248
updateUser,
4349
updateFile,
44-
});
50+
};
4551

46-
const uploadImageToS3 = (params) => s3ImageService.uploadImageToS3(params);
52+
const s3ImageService = new ImageService(saveBufferToS3, imageServiceDeps);
53+
const uploadImageToS3 = (params) => s3ImageService.uploadImage(params);
4754
const prepareImageURLS3 = (_req, file) => s3ImageService.prepareImageURL(file);
4855
const processS3Avatar = (params) => s3ImageService.processAvatar(params);
56+
57+
const cloudFrontImageService = new ImageService(saveBufferToCloudFront, imageServiceDeps);
58+
const uploadImageToCloudFront = (params) => cloudFrontImageService.uploadImage(params);
59+
const prepareCloudFrontImageURL = (_req, file) => cloudFrontImageService.prepareImageURL(file);
60+
const processCloudFrontAvatar = (params) => cloudFrontImageService.processAvatar(params);
4961
const {
5062
saveBufferToAzure,
5163
saveURLToAzure,
@@ -109,6 +121,22 @@ const s3Strategy = () => ({
109121
getDownloadStream: getS3FileStream,
110122
});
111123

124+
/**
125+
* CloudFront CDN Strategy Functions
126+
* Uses S3 for storage, CloudFront for URL delivery
127+
*/
128+
const cloudfrontStrategy = () => ({
129+
handleFileUpload: uploadFileToCloudFront,
130+
saveURL: saveURLToCloudFront,
131+
getFileURL: getCloudFrontURL,
132+
deleteFile: deleteFileFromCloudFront,
133+
saveBuffer: saveBufferToCloudFront,
134+
prepareImagePayload: prepareCloudFrontImageURL,
135+
processAvatar: processCloudFrontAvatar,
136+
handleImageUpload: uploadImageToCloudFront,
137+
getDownloadStream: getCloudFrontFileStream,
138+
});
139+
112140
/**
113141
* Azure Blob Storage Strategy Functions
114142
*
@@ -291,6 +319,8 @@ const getStrategyFunctions = (fileSource) => {
291319
return vectorStrategy();
292320
} else if (fileSource === FileSources.s3) {
293321
return s3Strategy();
322+
} else if (fileSource === FileSources.cloudfront) {
323+
return cloudfrontStrategy();
294324
} else if (fileSource === FileSources.execute_code) {
295325
return codeOutputStrategy();
296326
} else if (fileSource === FileSources.mistral_ocr) {

librechat.example.yaml

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,26 @@ cache: true
1818
# image: "firebase" # Storage for uploaded images in chats
1919
# document: "local" # Storage for document uploads (PDFs, text files, etc.)
2020

21-
# Available strategies: "local", "s3", "firebase"
21+
# Available strategies: "local", "s3", "firebase", "cloudfront"
2222
# If not specified, defaults to "local" for all file types
2323
# You can mix and match strategies based on your needs:
2424
# - Use S3 for avatars for fast global access
2525
# - Use Firebase for images with automatic optimization
2626
# - Use local storage for documents for privacy/compliance
27+
# - Use CloudFront for CDN-accelerated delivery (requires S3 + cloudfront config below)
28+
29+
# CloudFront CDN Configuration (optional)
30+
# Use when fileStrategy: "cloudfront" or fileStrategies includes cloudfront
31+
# Requires: AWS_REGION, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_BUCKET_NAME
32+
# For signed URLs: CLOUDFRONT_KEY_PAIR_ID, CLOUDFRONT_PRIVATE_KEY
33+
# cloudfront:
34+
# domain: "https://d1234abcd.cloudfront.net"
35+
# distributionId: "E1234ABCD" # Required if invalidateOnDelete is true
36+
# invalidateOnDelete: false # Create cache invalidation on file delete
37+
# # NOTE: imageSigning is not yet implemented. All URLs are unsigned regardless of value.
38+
# # Do NOT restrict your CloudFront distribution to signed requests.
39+
# imageSigning: "none" # "none" | "cookies" | "url"
40+
# urlExpiry: 3600 # Signed URL expiry in seconds
2741

2842
# Custom interface configuration
2943
interface:

0 commit comments

Comments
 (0)