feat: s3 compatible storage#15
Conversation
|
@VidocqH still working on this? |
I tested with Cloudflare R2, this PR is working correctly. |
|
Can we get this merged? |
remyoudemans
left a comment
There was a problem hiding this comment.
I have no authority over whether this PR gets merged, but left a couple comments 😄
| constructor() { | ||
| if (!process.env.S3_ACCESS_KEY_ID || !process.env.S3_SECRET_ACCESS_KEY) { | ||
| throw new Error('S3 credentials not configured'); | ||
| } | ||
| if (!process.env.S3_BUCKET_NAME) { | ||
| throw new Error('S3 bucket name not configured'); | ||
| } | ||
| this.client = new S3Client({ | ||
| region: process.env.S3_REGION ?? 'auto', | ||
| endpoint: process.env.S3_ENDPOINT, | ||
| credentials: { | ||
| accessKeyId: process.env.S3_ACCESS_KEY_ID, | ||
| secretAccessKey: process.env.S3_SECRET_ACCESS_KEY, | ||
| }, | ||
| }); | ||
| this.bucketName = process.env.S3_BUCKET_NAME; | ||
| } |
There was a problem hiding this comment.
Ideally this would also work with IAM roles where the AWS SDK automatically handles fetching credentials (this is how I would need to use it).
constructor() {
if (!process.env.S3_ACCESS_KEY_ID || !process.env.S3_SECRET_ACCESS_KEY) {
console.warn('S3 credentials not configured. AWS will fetch temporary credentials based on the IAM role.');
}
if (!process.env.S3_BUCKET_NAME) {
throw new Error('S3 bucket name not configured');
}
this.client = new S3Client({
region: process.env.S3_REGION ?? 'auto',
endpoint: process.env.S3_ENDPOINT,
credentials: process.env.S3_ACCESS_KEY_ID && process.env.S3_SECRET_ACCESS_KEY
? {
accessKeyId: process.env.S3_ACCESS_KEY_ID,
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
}
: undefined,
});
this.bucketName = process.env.S3_BUCKET_NAME;
}
There was a problem hiding this comment.
Could be handy to have some logging in here. There's quite a lot of logging in the GCS equivalent.
That GCS one just uses console log, but there is also a logger in this repo, as used here
|
@remyoudemans would you like to become a contributor? I have been so busy recently with the started I couldn't manage to review the contributions on a frequent basis unfortunately :/ |
I can review some PRs for sure. I'm pretty fastidious 😄 |
Reviewing would be more than what I can ask for currently ^^ Thank you and welcome aboard <3 |
There was a problem hiding this comment.
Pull Request Overview
This PR adds support for AWS S3 compatible storage as a new storage backend option. It allows the application to work with any S3-compatible storage service including AWS S3, Digital Ocean Spaces, and Cloudflare R2.
- Implements S3Storage class with full StorageInterface compliance
- Adds S3 configuration documentation and environment variables
- Modifies test mocking to be compatible with aws-sdk
Reviewed Changes
Copilot reviewed 6 out of 7 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| package.json | Adds @aws-sdk/client-s3 dependency |
| docs/supportedStorageAlternatives.md | Documents S3 storage configuration and environment variables |
| apiUtils/storage/StorageFactory.ts | Adds S3 storage option to factory pattern |
| apiUtils/storage/S3Storage.ts | Implements complete S3Storage class with all required methods |
| tests/upload.test.ts | Updates fs mocking to be compatible with aws-sdk |
| README.md | Updates supported storage providers list |
Comments suppressed due to low confidence (1)
package.json:16
- The version 3.779.0 of @aws-sdk/client-s3 appears to be very high. The latest stable version as of my knowledge cutoff was around 3.400.x. Please verify this version exists.
"@aws-sdk/client-s3": "^3.779.0",
| async copyFile(sourcePath: string, destinationPath: string): Promise<void> { | ||
| const copyCommand = new CopyObjectCommand({ | ||
| Bucket: this.bucketName, | ||
| CopySource: sourcePath, |
There was a problem hiding this comment.
The CopySource parameter should include the bucket name. It should be ${this.bucketName}/${sourcePath} instead of just sourcePath.
| CopySource: sourcePath, | |
| CopySource: `${this.bucketName}/${sourcePath}`, |
| try { | ||
| const headCommand = new HeadObjectCommand({ | ||
| Bucket: this.bucketName, | ||
| Key: path.split('/').shift(), |
There was a problem hiding this comment.
Using shift() returns the first part of the path, but for file existence check you should use the full path. This should be Key: path instead.
| Key: path.split('/').shift(), | |
| Key: path, |
|
|
||
| async fileExists(path: string): Promise<boolean> { | ||
| try { | ||
| const files = await this.listDirectories(path); |
There was a problem hiding this comment.
The fileExists method is checking for directories first, but this is inefficient and incorrect logic. A file path should not be checked as a directory. This will cause incorrect behavior when checking for actual files.
|
Sorry it has taken me so long to come back to this. The PR looks good, but to be honest I haven't worked with S3 so I can't tell if there might be a lot of edge cases or issues. All I can really confirm is that the code doesn't look malicious, so that's a start 😄 . @VidocqH would you mind taking a look through Copilot's comments and mine? @nilpntr and @Cyberistic, have either of you tried this out? |
|
LGTM! Thank you all for your contribution to this! |
Sorry for not answering earlier, I wasn’t pinged and randomly stumbled into the same issue while browsing latest solutions. Seems like it’s working. |
Description
Support S3 compatible storage
Type of change
Testing
Modify
fsmock asaws-sdkbreaks it.Related issue:
aws/aws-sdk-js-v3#3547 (comment)
Checklist