/**
* @module flitter-upload/store/AWSS3Store
*/
const Store = require('./Store')
const S3 = require('aws-sdk/clients/s3')
const fs = require('fs').promises
const uuid = require('uuid/v4')
const tempfile = require('tempfile')
/**
* Amazon S3-backed file store provider.
* @extends module:flitter-upload/store/Store~Store
*/
class AWSS3Store extends Store {
/**
* The S3 client.
*/
#s3
/**
* Defines the services required by this store.
* @returns {Array<string>}
*/
static get services() {
return [...super.services, 'utility', 'output', 'models', 'configs']
}
/**
* Initializes the store.
* @returns {Promise<void>}
*/
async init() {
this.#s3 = new S3({
region: this.config.region,
...(this.config.aws.endpoint ? { endpoint: this.config.aws.endpoint } : {}),
})
}
/**
* Permanently store a temporarily uploaded file in this store.
* @param {object} params
* @param {string} params.temp_path - absolute path to the temporarily uploaded file
* @param {string} params.original_name - the original upload name of the file
* @param {string} params.mime_type - the MIME type of the file.
* @returns {Promise<module:flitter-upload/model/File~File>}
*/
async store({ temp_path, original_name, mime_type, tag = '' }) {
const File = this.models.get('upload::File')
const upload_name = uuid()
const upload_key = this.upload_key(upload_name, tag)
await this.upload_file_as_key(temp_path, upload_key)
const f = new File({
original_name,
upload_name,
mime_type,
store: this.config.name,
store_id: upload_name,
tag,
})
await f.save()
return f
}
/**
* Send the specified file as the data for the response.
* Sets the appropriate Content-Type and Content-Disposition headers.
* @param {module:flitter-upload/model/File~File} file - the file to send
* @param {express/response} response - the response
* @returns {Promise<void>}
*/
async send_file(file, response) {
const file_path = tempfile()
await this.download_file(file, file_path)
response.setHeader('Content-Type', file.mime_type)
response.setHeader('Content-Disposition', `inline; filename="${file.original_name}";`)
response.sendFile(file_path)
}
/**
* Download the file locally to the given destination.
* @param {module:flitter-upload/model/File~File} file
* @param destination
* @return {Promise<void>}
*/
async download_file(file, destination) {
const params = {
Bucket: this.config.bucket,
Key: this.upload_key(file.store_id, file.tag)
}
const data = await new Promise((res, rej) => {
this.#s3.getObject(params, async (err, data) => {
if ( err ) return rej(err)
else return res(await data)
})
})
await fs.writeFile(destination, data.Body)
}
/**
* Create a Readable stream for this file.
* @param {module:flitter-upload/model/File~File} file
* @return {ReadStream}
*/
read_stream(file) {
const params = {
Bucket: this.config.bucket,
Key: this.upload_key(file.store_id, file.tag)
}
return this.#s3.getObject(params).createReadStream()
}
/**
* Upload the file path to the S3 key.
* @param {string} file_path - the local file path
* @param {string} key_path - the S3 key
* @return {Promise<unknown>}
*/
async upload_file_as_key(file_path, key_path) {
const params = {
Bucket: this.config.bucket,
Key: key_path,
Body: require('fs').createReadStream(file_path)
}
return new Promise((res, rej) => {
this.#s3.upload(params, (err, data) => {
if ( err ) {
return rej(err)
} else {
return res(data)
}
})
})
}
/**
* Given an upload UUID and some tag, returns the full S3 upload key of the file.
* @param {string} upload_uuid
* @param {string} [tag]
* @return {string}
*/
upload_key(upload_uuid, tag = undefined) {
let upload_path = upload_uuid
if ( tag ) {
upload_path = `${tag}/${upload_path}`
}
if ( this.config.prefix ) {
upload_path = `${this.config.prefix}/${upload_path}`
}
return upload_path
}
}
module.exports = exports = AWSS3Store