Skip to content

Instantly share code, notes, and snippets.

@wmakeev
Created October 22, 2023 12:21
Show Gist options
  • Save wmakeev/2d2a27900e30875f975fab945a064b31 to your computer and use it in GitHub Desktop.
Save wmakeev/2d2a27900e30875f975fab945a064b31 to your computer and use it in GitHub Desktop.
[S3BucketStorage] #s3 #bucket #upload #storage #tools
import {
S3Client,
PutObjectCommand,
GetObjectCommand,
HeadObjectCommand
} from '@aws-sdk/client-s3'
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'
import path from 'node:path'
import mime from 'mime-types'
import querystring from 'node:querystring'
import { createReadStream } from 'node:fs'
import assert from 'node:assert'
/**
* @typedef {Object} StorageConfig
* @property {string} bucket
* @property {string} endpoint
* @property {string} accessKeyId
* @property {string} secretAccessKey
* @property {string} region
*/
/**
* @typedef {Object} PutObjectParams
* @property {string} objectKey
* @property {import('@aws-sdk/client-s3').PutObjectCommandInput['Body']} body
* @property {string} [contentDisposition]
* @property {string} [mimeType]
* @property {Date} [expires]
* @property {Record<string, string>} [tags]
* @property {import('@aws-sdk/client-s3').PutObjectCommandInput['ACL']} [ACL]
* @property {import('@aws-sdk/client-s3').PutObjectCommandInput['StorageClass']} [storageClass]
*
*/
/**
* @typedef {Omit<PutObjectParams, 'body' | 'objectKey' | 'mimeType'> & { downloadFileName?: string; fileExtension?: string }} UploadOptions
*/
export class S3BucketStorage {
/**
* @param {StorageConfig} config
*/
constructor(config) {
this.config = config
const { endpoint, accessKeyId, secretAccessKey, region } = config
this.client = new S3Client({
endpoint,
forcePathStyle: true,
apiVersion: '2006-03-01',
credentials: {
accessKeyId,
secretAccessKey
},
region
})
}
/**
* @param {string} objectKey
*/
async headObject(objectKey) {
const result = await this.client.send(
new HeadObjectCommand({
Bucket: this.config.bucket,
Key: objectKey
})
)
return result
}
/**
*
* @param {PutObjectParams} params
*/
async putObject(params) {
const {
objectKey,
body,
ACL,
storageClass,
contentDisposition,
mimeType,
expires,
tags
} = params
const tagging = tags ? querystring.stringify(tags) : undefined
const result = await this.client.send(
new PutObjectCommand({
Bucket: this.config.bucket,
Key: objectKey,
Body: body,
ACL,
StorageClass: storageClass ?? 'STANDARD',
ContentDisposition: contentDisposition,
ContentType: mimeType,
Expires: expires,
Tagging: tagging
})
)
return result
}
/**
* Загрузить файл в хранилище
*
* @param {string} file Путь к файлу для загрузки
* @param {string} objectKey Ключ (путь) файла в хранилище
* @param {UploadOptions} [options] Опции
*/
async uploadFile(file, objectKey, options = {}) {
const {
downloadFileName = path.basename(file),
fileExtension,
expires,
tags,
ACL
} = options
const mimeType =
mime.lookup(fileExtension ?? downloadFileName) ||
'application/octet-stream'
const contentDisposition = downloadFileName
? `attachment; filename="${downloadFileName}"`
: undefined
const fileStream = createReadStream(file)
const result = await this.putObject({
body: fileStream,
objectKey,
ACL,
contentDisposition,
expires,
mimeType,
tags
})
return result
}
/**
* Загрузить буфер в хранилище
*
* @param {Buffer} buffer Путь к файлу для загрузки
* @param {string} objectKey Ключ (путь) файла в хранилище
* @param {UploadOptions} [options] Опции
*/
async upload(buffer, objectKey, options = {}) {
const { downloadFileName, fileExtension, expires, tags, ACL } = options
try {
const headResult = await this.headObject(objectKey)
return headResult
} catch (err) {
assert.ok(err instanceof Error)
if (err.name !== 'NotFound') {
throw err
}
}
const mimeSource = fileExtension ?? downloadFileName
const mimeType =
(mimeSource ? mime.lookup(mimeSource) : false) ||
'application/octet-stream'
const contentDisposition = downloadFileName
? `attachment; filename="${downloadFileName}"`
: undefined
const putResult = await this.putObject({
body: buffer,
objectKey,
ACL,
contentDisposition,
expires,
mimeType,
tags
})
return putResult
}
/**
* Получить публичную ссылку на скачивание файла
*
* @param {string} objectKey Ключ (путь) файла в хранилище
* @param {Object} options Опции
* @param {number} options.expiresIn The number of seconds before the presigned URL expires
*/
async getPublicDownloadUrl(objectKey, options) {
const { expiresIn } = options
const command = new GetObjectCommand({
Bucket: this.config.bucket,
Key: objectKey
})
const url = await getSignedUrl(this.client, command, { expiresIn })
return url
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment