@eleven-am/nestjs-storage
Version:
A NestJS module for uploading files to cloud storage providers
189 lines (173 loc) • 4.34 kB
text/typescript
import { BaseStorage } from './baseStorage';
import * as aws from 'aws-sdk';
import { Provider, S3Options } from '../types/options';
import { IFile } from '../types/storage';
export class S3BaseStorage extends BaseStorage {
private readonly storage: aws.S3;
private readonly bucket: string;
constructor({
provider,
options,
}: {
provider: Provider;
options: S3Options;
}) {
super(provider);
this.storage = new aws.S3({
...options,
region: options.region || 'auto',
signatureVersion: 'v4',
});
this.bucket = options.bucket;
}
async createFolder(path: string) {
await this.storage
.putObject({
Bucket: this.bucket,
Key: path,
Body: Buffer.alloc(0),
})
.promise();
return this.getFileOrFolder(path);
}
async deleteFileOrFolder(fileId: string) {
await this.storage
.deleteObject({
Bucket: this.bucket,
Key: fileId,
})
.promise();
return true;
}
async getFileOrFolder(fileId: string) {
const data = await this.storage
.getObject({
Bucket: this.bucket,
Key: fileId,
})
.promise();
return this.parseFile(fileId, data);
}
moveFileOrFolder(fileId: string, newPath: string) {
return new Promise<IFile>((resolve, reject) => {
this.storage
.copyObject({
Bucket: this.bucket,
CopySource: `${this.bucket}/${fileId}`,
Key: newPath,
})
.promise()
.then(() => {
return this.deleteFileOrFolder(fileId);
})
.then(() => {
resolve(this.getFileOrFolder(newPath));
})
.catch((error) => {
reject(error);
});
});
}
async putFile(path: string, data: Buffer) {
const params = {
Bucket: this.bucket,
Key: path,
Body: data,
};
const s3Data = await this.storage.upload(params).promise();
return this.getFileOrFolder(s3Data.Key);
}
readFile(fileId: string) {
return new Promise<NodeJS.ReadableStream>((resolve, reject) => {
this.storage
.getObject({
Bucket: this.bucket,
Key: fileId,
})
.promise()
.then((data) => {
resolve(data.Body as NodeJS.ReadableStream);
})
.catch((error) => {
reject(error);
});
});
}
readFolder(folderId: string) {
return new Promise<IFile[]>((resolve, reject) => {
this.storage
.listObjectsV2({
Bucket: this.bucket,
Prefix: folderId,
})
.promise()
.then((data) => {
const files = data.Contents?.map((file) => {
return this.parseFile(file.Key || '', file);
});
resolve(files || []);
})
.catch((error) => {
reject(error);
});
});
}
renameFileOrFolder(fileId: string, newName: string) {
return this.moveFileOrFolder(fileId, newName);
}
getSignedUrl(fileId: string, expires?: number) {
return new Promise<string>((resolve, reject) => {
this.storage.getSignedUrl(
'getObject',
{
Bucket: this.bucket,
Key: fileId,
Expires: expires || 60 * 60 * 24 * 7,
},
(err, url) => {
if (err) {
reject(err);
} else {
resolve(url);
}
},
);
});
}
readRootFolder() {
return this.readFolder('');
}
async streamFile(fileId: string, range: string) {
const file = await this.getFileOrFolder(fileId);
const { headers } = this.buildRange(range, file);
const stream = this.storage
.getObject({
Bucket: this.bucket,
Key: fileId,
Range: range,
})
.createReadStream();
return {
stream,
headers: headers,
};
}
private parseFile(
fileId: string,
data: aws.S3.Object | aws.S3.GetObjectOutput,
): IFile {
return {
name: fileId,
path: fileId,
size:
'Size' in data
? data.Size!
: 'ContentLength' in data
? data.ContentLength!
: 0,
mimeType: 'ContentType' in data ? data.ContentType! : null,
isFolder: false,
modifiedAt: data.LastModified || new Date(),
};
}
}