Skip to content

feat: s3 compatible storage#15

Merged
MoElkhidir merged 6 commits into
xavia-io:mainfrom
VidocqH:feat/s3-storage
Sep 27, 2025
Merged

feat: s3 compatible storage#15
MoElkhidir merged 6 commits into
xavia-io:mainfrom
VidocqH:feat/s3-storage

Conversation

@VidocqH
Copy link
Copy Markdown
Contributor

@VidocqH VidocqH commented Apr 2, 2025

Description

Support S3 compatible storage

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • Documentation update
  • Refactoring (no functional changes)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)

Testing

Modify fs mock as aws-sdk breaks it.
Related issue:
aws/aws-sdk-js-v3#3547 (comment)

Checklist

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings

@nilpntr
Copy link
Copy Markdown

nilpntr commented May 4, 2025

@VidocqH still working on this?

@VidocqH
Copy link
Copy Markdown
Contributor Author

VidocqH commented May 6, 2025

@VidocqH still working on this?

I tested with Cloudflare R2, this PR is working correctly.

@Cyberistic
Copy link
Copy Markdown

Can we get this merged?

Copy link
Copy Markdown
Collaborator

@remyoudemans remyoudemans left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have no authority over whether this PR gets merged, but left a couple comments 😄

Comment on lines +15 to +31
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;
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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;
  }

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@MoElkhidir
Copy link
Copy Markdown
Contributor

@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 :/

@remyoudemans
Copy link
Copy Markdown
Collaborator

@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 😄
I'm not likely to do more than that though, as I'm not currently invested in this. Still hoping to use Expo, but have to see if the business case goes through. It probably will, but if it doesn't I could get more involved.

@MoElkhidir
Copy link
Copy Markdown
Contributor

@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 😄 I'm not likely to do more than that though, as I'm not currently invested in this. Still hoping to use Expo, but have to see if the business case goes through. It probably will, but if it doesn't I could get more involved.

Reviewing would be more than what I can ask for currently ^^ Thank you and welcome aboard <3

@remyoudemans remyoudemans requested a review from Copilot August 4, 2025 14:53
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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,
Copy link

Copilot AI Aug 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CopySource parameter should include the bucket name. It should be ${this.bucketName}/${sourcePath} instead of just sourcePath.

Suggested change
CopySource: sourcePath,
CopySource: `${this.bucketName}/${sourcePath}`,

Copilot uses AI. Check for mistakes.
try {
const headCommand = new HeadObjectCommand({
Bucket: this.bucketName,
Key: path.split('/').shift(),
Copy link

Copilot AI Aug 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
Key: path.split('/').shift(),
Key: path,

Copilot uses AI. Check for mistakes.

async fileExists(path: string): Promise<boolean> {
try {
const files = await this.listDirectories(path);
Copy link

Copilot AI Aug 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
@remyoudemans
Copy link
Copy Markdown
Collaborator

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 😄 .
I got Copilot to review it and it left some comments.

@VidocqH would you mind taking a look through Copilot's comments and mine?
I know this PR has been a long time coming and it's a highly requested feature, so if it works well enough and Copilot's comments aren't all that relevant, let me know and I'll approve it and make a separate issue for the other method of AWS authentication I mentioned.

@nilpntr and @Cyberistic, have either of you tried this out?

@MoElkhidir
Copy link
Copy Markdown
Contributor

LGTM! Thank you all for your contribution to this!

@MoElkhidir MoElkhidir merged commit 53007f1 into xavia-io:main Sep 27, 2025
1 check passed
@Cyberistic
Copy link
Copy Markdown

@nilpntr and @Cyberistic, have either of you tried this out?

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants