UNPKG

ghost-storage-adapter-s3

Version:
151 lines (130 loc) 4.87 kB
import AWS from 'aws-sdk' import BaseStore from 'ghost-storage-base' import { join } from 'path' import { readFile } from 'fs' const readFileAsync = fp => new Promise((resolve, reject) => readFile(fp, (err, data) => err ? reject(err) : resolve(data))) const stripLeadingSlash = s => s.indexOf('/') === 0 ? s.substring(1) : s const stripEndingSlash = s => s.indexOf('/') === (s.length - 1) ? s.substring(0, s.length - 1) : s class Store extends BaseStore { constructor (config = {}) { super(config) const { accessKeyId, assetHost, bucket, pathPrefix, region, secretAccessKey, endpoint, serverSideEncryption, forcePathStyle, signatureVersion, acl } = config // Compatible with the aws-sdk's default environment variables this.accessKeyId = accessKeyId this.secretAccessKey = secretAccessKey this.region = process.env.AWS_DEFAULT_REGION || region this.bucket = process.env.GHOST_STORAGE_ADAPTER_S3_PATH_BUCKET || bucket // Optional configurations this.host = process.env.GHOST_STORAGE_ADAPTER_S3_ASSET_HOST || assetHost || `https://s3${this.region === 'us-east-1' ? '' : `-${this.region}`}.amazonaws.com/${this.bucket}` this.pathPrefix = stripLeadingSlash(process.env.GHOST_STORAGE_ADAPTER_S3_PATH_PREFIX || pathPrefix || '') this.endpoint = process.env.GHOST_STORAGE_ADAPTER_S3_ENDPOINT || endpoint || '' this.serverSideEncryption = process.env.GHOST_STORAGE_ADAPTER_S3_SSE || serverSideEncryption || '' this.s3ForcePathStyle = Boolean(process.env.GHOST_STORAGE_ADAPTER_S3_FORCE_PATH_STYLE) || Boolean(forcePathStyle) || false this.signatureVersion = process.env.GHOST_STORAGE_ADAPTER_S3_SIGNATURE_VERSION || signatureVersion || 'v4' this.acl = process.env.GHOST_STORAGE_ADAPTER_S3_ACL || acl || 'public-read' } delete (fileName, targetDir) { const directory = targetDir || this.getTargetDir(this.pathPrefix) return new Promise((resolve, reject) => { this.s3() .deleteObject({ Bucket: this.bucket, Key: stripLeadingSlash(join(directory, fileName)) }, (err) => err ? resolve(false) : resolve(true)) }) } exists (fileName, targetDir) { return new Promise((resolve, reject) => { this.s3() .getObject({ Bucket: this.bucket, Key: stripLeadingSlash(join(targetDir, fileName)) }, (err) => err ? resolve(false) : resolve(true)) }) } s3 () { const options = { bucket: this.bucket, region: this.region, signatureVersion: this.signatureVersion, s3ForcePathStyle: this.s3ForcePathStyle } // Set credentials only if provided, falls back to AWS SDK's default provider chain if (this.accessKeyId && this.secretAccessKey) { options.credentials = new AWS.Credentials(this.accessKeyId, this.secretAccessKey) } if (this.endpoint !== '') { options.endpoint = this.endpoint } return new AWS.S3(options) } save (image, targetDir) { const directory = targetDir || this.getTargetDir(this.pathPrefix) return new Promise((resolve, reject) => { Promise.all([ this.getUniqueFileName(image, directory), readFileAsync(image.path) ]).then(([ fileName, file ]) => { let config = { ACL: this.acl, Body: file, Bucket: this.bucket, CacheControl: `max-age=${30 * 24 * 60 * 60}`, ContentType: image.type, Key: stripLeadingSlash(fileName) } if (this.serverSideEncryption !== '') { config.ServerSideEncryption = this.serverSideEncryption } this.s3() .putObject(config, (err, data) => err ? reject(err) : resolve(`${this.host}/${fileName}`)) }) .catch(err => reject(err)) }) } serve () { return (req, res, next) => this.s3() .getObject({ Bucket: this.bucket, Key: stripLeadingSlash(stripEndingSlash(this.pathPrefix) + req.path) }) .on('httpHeaders', (statusCode, headers, response) => res.set(headers)) .createReadStream() .on('error', err => { res.status(404) next(err) }) .pipe(res) } read (options) { options = options || {} return new Promise((resolve, reject) => { // remove trailing slashes let path = (options.path || '').replace(/\/$|\\$/, '') // check if path is stored in s3 handled by us if (!path.startsWith(this.host)) { reject(new Error(`${path} is not stored in s3`)) } path = path.substring(this.host.length) this.s3() .getObject({ Bucket: this.bucket, Key: stripLeadingSlash(path) }, (err, data) => err ? reject(err) : resolve(data.Body)) }) } } export default Store