import fs from "fs" ; import { Readable } from "stream" ; import S3 from "aws-sdk/clients/s3" ; import { AbstractAdapter } from "./AbstractAdapter" ; import { ConfigAmazonS3 , AdapterConfig , StorageType } from "./types" ; import { parseUrl } from "./util" ; export class AdapterAmazonS3 extends AbstractAdapter { protected type = StorageType . S3 ; private storage : S3 ; private bucketNames : string [ ] = [ ] ; constructor ( config ) { super ( ) ; const cfg = this . parseConfig ( config as ConfigAmazonS3 ) ; this . config = { ... cfg } ; if ( cfg . slug ) { this . slug = cfg . slug ; delete cfg . slug ; } if ( cfg . bucketName ) { this . bucketName = this . generateSlug ( cfg . bucketName , this . slug ) ; delete cfg . bucketName ; } delete cfg . type ; this . storage = new S3 ( cfg ) ; } async init ( ) { if ( this . bucketName ) { await this . createBucket ( this . bucketName ) ; this . bucketNames . push ( this . bucketName ) ; } this . initialized = true ; return Promise . resolve ( true ) ; } private parseConfig ( config ) { let cfg ; if ( typeof config === "string" ) { const { type , part1 : accessKeyId , part2 : secretAccessKey , part3 : region , bucketName , queryString , } = parseUrl ( config ) ; cfg = { type , accessKeyId , secretAccessKey , region , bucketName , ... queryString , } ; } else { cfg = { ... config } ; } if ( ! cfg . accessKeyId || ! cfg . secretAccessKey ) { throw new Error ( "You must specify a value for both 'applicationKeyId' and  'applicationKey' for storage type 's3'" ) ; } return cfg ; } async getFileAsReadable ( fileName , options = { start : 0 } ) { const params = { Bucket : this . bucketName , Key : fileName , Range : `bytes=${ options . start }-${ options . end }` , } ; await this . storage . headObject ( params ) . promise ( ) ; return this . storage . getObject ( params ) . createReadStream ( ) ; } async removeFile ( fileName ) { const params = { Bucket : this . bucketName , Key : fileName , } ; await this . storage . deleteObject ( params ) . promise ( ) ; return "file removed" ; } async createBucket ( name ) { const msg = this . validateName ( name ) ; if ( msg !== null ) { return Promise . reject ( msg ) ; } const n = this . generateSlug ( name ) ; if ( this . bucketNames . findIndex ( b => b === n ) !== - 1 ) { return ; } try { await this . storage . headBucket ( { Bucket : n } ) . promise ( ) ; this . bucketNames . push ( n ) ; } catch ( e ) { if ( e . code === "Forbidden" ) { const msg = [ "The requested bucket name is not available." , "The bucket namespace is shared by all users of the system." , "Please select a different name and try again." , ] ; return Promise . reject ( msg . join ( " " ) ) ; } const data = await this . storage . createBucket ( { Bucket : n } ) . promise ( ) ; this . bucketNames . push ( n ) ; } } async selectBucket ( name ) { if ( name === null ) { this . bucketName = "" ; return "bucket deselected" ; } await this . createBucket ( name ) ; this . bucketName = name ; return "bucket selected" ; } async clearBucket ( name ? ) { let n = name || this . bucketName ; n = this . generateSlug ( n ) ; if ( ! n ) { throw new Error ( "no bucket selected" ) ; } const params1 = { Bucket : n , MaxKeys : 1000 , } ; const { Contents : content } = await this . storage . listObjects ( params1 ) . promise ( ) ; if ( content . length === 0 ) { return ; } const params2 = { Bucket : n , Delete : { Objects : content . map ( value => ( { Key : value . Key } ) ) , Quiet : false , } , } ; await this . storage . deleteObjects ( params2 ) . promise ( ) ; return "bucket cleared" ; } async deleteBucket ( name ? ) { let n = name || this . bucketName ; n = this . generateSlug ( n ) ; if ( n === "" ) { throw new Error ( "no bucket selected" ) ; } try { await this . clearBucket ( name ) ; const result = await this . storage . deleteBucket ( { Bucket : n , } ) . promise ( ) ; if ( n === this . bucketName ) { this . bucketName = "" ; } this . bucketNames = this . bucketNames . filter ( b => b !== n ) ; return "bucket deleted" ; } catch ( e ) { if ( e . code === "NoSuchBucket" ) { throw new Error ( "bucket not found" ) ; } throw e ; } } async listBuckets ( ) { const data = await this . storage . listBuckets ( ) . promise ( ) ; this . bucketNames = data . Buckets . map ( d => d . Name ) ; return this . bucketNames ; } protected async store ( buffer , targetPath ) ; protected async store ( stream , targetPath ) ; protected async store ( origPath , targetPath ) ; protected async store ( arg , targetPath ) { if ( ! this . bucketName ) { throw new Error ( "no bucket selected" ) ; } await this . createBucket ( this . bucketName ) ; let readable ; if ( typeof arg === "string" ) { readable = fs . createReadStream ( arg ) ; } else if ( arg instanceof Buffer ) { readable = new Readable ( ) ; readable . _read = ( ) : void => { } ; readable . push ( arg ) ; readable . push ( null ) ; } else if ( arg instanceof Readable ) { readable = arg ; } const params = { Bucket : this . bucketName , Key : targetPath , Body : readable , } ; await this . storage . upload ( params ) . promise ( ) ; } async listFiles ( maxFiles = 1000 ) { if ( ! this . bucketName ) { throw new Error ( "no bucket selected" ) ; } const params = { Bucket : this . bucketName , MaxKeys : maxFiles , } ; const { Contents : content } = await this . storage . listObjects ( params ) . promise ( ) ; return content . map ( o => [ o . Key , o . Size ] ) as [ string , number ] [ ] ; } async sizeOf ( name ) { if ( ! this . bucketName ) { throw new Error ( "no bucket selected" ) ; } const params = { Bucket : this . bucketName , Key : name , } ; return await this . storage . headObject ( params ) . promise ( ) . then ( res => res . ContentLength ) ; } async fileExists ( name ) { if ( ! this . bucketName ) { throw new Error ( "no bucket selected" ) ; } const params = { Bucket : this . bucketName , Key : name , } ; return await this . storage . headObject ( params ) . promise ( ) . then ( ( ) => true ) . catch ( ( ) => false ) ; } }