Skip to content

Instantly share code, notes, and snippets.

@maticzav
Created July 25, 2023 06:49
Show Gist options
  • Save maticzav/411a3296f7b611db2b6efd6e4815fed4 to your computer and use it in GitHub Desktop.
Save maticzav/411a3296f7b611db2b6efd6e4815fed4 to your computer and use it in GitHub Desktop.
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