Created
July 25, 2023 06:49
-
-
Save maticzav/411a3296f7b611db2b6efd6e4815fed4 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { PutObjectCommand, S3Client, GetObjectCommand } from '@aws-sdk/client-s3' | |
import * as S3 from '@aws-sdk/s3-request-presigner' | |
import { v4 as uuid } from 'uuid' | |
import { RandomUtils } from '../lib/utils/random' | |
export interface IAWSSource { | |
/** | |
* Gets upload parameters for a file. | |
*/ | |
getFileUploadParameters(params: { extension?: string | null; contentType: string; folder: string }): Promise<{ | |
file_url: string | |
file_key: string | |
upload_url: string | |
}> | |
/** | |
* Uploads a file to the S3 bucket. | |
*/ | |
uploadFile(params: { extension?: string | null; contentType: string; folder: string; file: Uint8Array }): Promise<{ | |
file_url: string | |
file_key: string | |
}> | |
/** | |
* Returns a key of the file from a given URL. | |
*/ | |
getFileKey(params: { fileURL: string }): string | |
/** | |
* Pulls a file from the S3 bucket if it exists. | |
*/ | |
getFile(params: { key: string }): Promise<{ file: Uint8Array; contentType: string } | null> | |
} | |
type AWSConfig = { | |
accessKeyId: string | |
secretAccessKey: string | |
/** | |
* Region identifier for the given bucket. | |
*/ | |
region: string | |
/** | |
* Base access URL of the AWS bucket (e.g. https://s3.eu-central-1.amazonaws.com/). | |
*/ | |
baseURL: string | |
s3BucketName: string | |
} | |
/** | |
* A utility class that helps with S3 communication. | |
*/ | |
export class AWSSource implements IAWSSource { | |
private readonly _client: S3Client | |
private readonly _config: AWSConfig | |
constructor(config: AWSConfig) { | |
this._config = config | |
this._client = new S3Client({ | |
credentials: { | |
accessKeyId: config.accessKeyId, | |
secretAccessKey: config.secretAccessKey, | |
}, | |
region: config.region, | |
}) | |
} | |
/** | |
* Get file and content type from AWS S3 bucket. | |
*/ | |
public async getFile({ key }: { key: string }): Promise<{ | |
file: Uint8Array | |
contentType: string | |
} | null> { | |
const command = new GetObjectCommand({ | |
Bucket: this._config.s3BucketName, | |
Key: key, | |
}) | |
const response = await this._client.send(command) | |
if (!response.Body) { | |
return null | |
} | |
const file = await response.Body.transformToByteArray() | |
return { | |
file: file, | |
contentType: response.ContentType as string, | |
} | |
} | |
/** | |
* Uploads a file to the AWS S3 bucket. | |
*/ | |
public async uploadFile({ | |
extension, | |
contentType, | |
folder, | |
file, | |
}: { | |
extension?: string | null | |
contentType: string | |
folder: string | |
file: Uint8Array | |
}): Promise<{ | |
file_url: string | |
file_key: string | |
}> { | |
const Key = this.generateS3Key({ folder, extension }) | |
const command = new PutObjectCommand({ | |
Bucket: this._config.s3BucketName, | |
Key, | |
ContentType: contentType, | |
Body: file, | |
ACL: 'public-read', | |
}) | |
await this._client.send(command) | |
const file_url = this.getFileURL({ fileKey: Key }) | |
return { | |
file_url, | |
file_key: Key, | |
} | |
} | |
/** | |
* Returns information that you need to upload a file to the AWS S3 bucket. | |
*/ | |
public async getFileUploadParameters({ | |
extension, | |
contentType, | |
folder, | |
}: { | |
extension?: string | null | |
contentType: string | |
folder: string | |
}): Promise<{ | |
file_url: string | |
file_key: string | |
upload_url: string | |
}> { | |
const Key = this.generateS3Key({ folder, extension }) | |
const command = new PutObjectCommand({ | |
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion | |
Bucket: process.env.AWS_S3_BUCKET!, | |
Key, | |
ContentType: contentType, | |
ACL: 'public-read', | |
}) | |
const upload_url = await S3.getSignedUrl(this._client, command, { expiresIn: 3600 }) | |
const file_url = this.getFileURL({ fileKey: Key }) | |
return { | |
upload_url, | |
file_url, | |
file_key: Key, | |
} | |
} | |
/** | |
* Returns a file key from a file url. | |
*/ | |
public getFileKey({ fileURL }: { fileURL: string }): string { | |
return fileURL.replace(this._config.baseURL, '').replace(`${this._config.s3BucketName}/`, '') | |
} | |
/** | |
* Converts file key to a public URL. | |
*/ | |
private getFileURL({ fileKey }: { fileKey: string }): string { | |
return this._config.baseURL + this._config.s3BucketName + `/` + fileKey | |
} | |
private generateS3Key({ folder, extension }: { folder: string; extension?: string | null }): string { | |
// S3 performance is tied to the file prefix. By creating more prefixes, | |
// we improve S3 performance (https://docs.aws.amazon.com/AmazonS3/latest/dev/optimizing-performance.html). | |
const subfolder = RandomUtils.generateRandomAlphaNumericString(2) | |
const id = uuid() | |
let key = `${folder}/${subfolder}/${id}` | |
if (extension) { | |
key += '.' + extension | |
} | |
return key | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment