nestjs-multer-extended-aws-latest
Version:
Extended MulterModule for NestJS Latest AWS SDK
197 lines (196 loc) • 10.4 kB
JavaScript
;
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.MulterSharp = void 0;
const sharp_1 = __importDefault(require("sharp"));
const shared_utils_1 = require("@nestjs/common/utils/shared.utils");
const rxjs_1 = require("rxjs");
const operators_1 = require("rxjs/operators");
const mime_types_1 = require("mime-types");
const multer_sharp_utils_1 = require("./multer-sharp.utils");
const random_string_generator_util_1 = require("@nestjs/common/utils/random-string-generator.util");
class MulterSharp {
constructor(options) {
if (!options.s3) {
throw new Error('You have to specify s3 object.');
}
this.storageOpts = options;
this.sharpOpts = multer_sharp_utils_1.getSharpOptions(options);
if (!this.storageOpts.Bucket) {
throw new Error('You have to specify Bucket property.');
}
if (!shared_utils_1.isFunction(this.storageOpts.Key) && !shared_utils_1.isString(this.storageOpts.Key)) {
throw new TypeError(`Key must be a "string", "function" or undefined`);
}
}
_removeFile(req, file, cb) {
this.storageOpts.s3.deleteObject({ Bucket: file.Bucket, Key: file.Key }, cb);
}
_handleFile(req, file, callback) {
const { storageOpts } = this;
const { mimetype, stream } = file;
const params = {
Bucket: storageOpts.Bucket,
ACL: storageOpts.ACL,
CacheControl: storageOpts.CacheControl,
ContentType: storageOpts.ContentType,
Metadata: storageOpts.Metadata,
StorageClass: storageOpts.StorageClass,
ServerSideEncryption: storageOpts.ServerSideEncryption,
SSEKMSKeyId: storageOpts.SSEKMSKeyId,
Body: stream,
Key: storageOpts.Key,
};
if (shared_utils_1.isFunction(storageOpts.Key)) {
storageOpts.Key(req, file, (err, Key) => {
if (err) {
callback(err);
return;
}
let { originalname } = file;
if (storageOpts.randomFilename) {
originalname = `${random_string_generator_util_1.randomStringGenerator()}.${mime_types_1.extension(mimetype)}`;
}
const routeParams = Object.keys(req.params);
if (routeParams.length > 0 && storageOpts.dynamicPath) {
if (typeof storageOpts.dynamicPath === 'string') {
params.Key = routeParams.includes(storageOpts.dynamicPath)
? `${Key}/${req.params[storageOpts.dynamicPath]}/${originalname}`
: `${Key}/${storageOpts.dynamicPath}/${originalname}`;
}
else {
const paramDir = [];
storageOpts.dynamicPath.forEach((pathSegment) => {
paramDir.push(routeParams.includes(pathSegment) ? req.params[pathSegment] : pathSegment);
});
params.Key = `${Key}/${paramDir.join('/')}/${originalname}`;
}
}
else {
params.Key = storageOpts.dynamicPath
? `${Key}/${storageOpts.dynamicPath}/${originalname}`
: `${Key}/${originalname}`;
}
mimetype.includes('image')
? this.uploadImageFileToS3(params, file, callback)
: this.uploadFileToS3(params, file, callback);
});
}
}
uploadImageFileToS3(params, file, callback) {
const { storageOpts, sharpOpts } = this;
const { stream } = file;
const { ACL, ContentDisposition, ContentType: optsContentType, StorageClass, ServerSideEncryption, Metadata, } = storageOpts;
const resizeBucket = multer_sharp_utils_1.getSharpOptionProps(storageOpts);
if (Array.isArray(resizeBucket) && resizeBucket.length > 0) {
const sizes$ = rxjs_1.from(resizeBucket);
sizes$
.pipe(operators_1.map((size) => {
const resizedStream = multer_sharp_utils_1.transformImage(sharpOpts, size);
if (multer_sharp_utils_1.isOriginalSuffix(size.suffix)) {
size.Body = stream.pipe(sharp_1.default({ failOnError: false }));
}
else {
size.Body = stream.pipe(resizedStream);
}
return size;
}), operators_1.mergeMap((size) => {
const sharpStream = size.Body;
const sharpPromise = sharpStream.toBuffer({ resolveWithObject: true });
return rxjs_1.from(sharpPromise.then((result) => {
return Object.assign(Object.assign(Object.assign({}, size), result.info), { ContentType: result.info.format, currentSize: result.info.size });
}));
}), operators_1.mergeMap((size) => {
const { Body, ContentType } = size;
const newParams = Object.assign(Object.assign({}, params), { Body,
ContentType, Key: `${params.Key}-${size.suffix}` });
const upload = storageOpts.s3.upload(newParams);
const currentSize = { [size.suffix]: 0 };
upload.on('httpUploadProgress', (event) => {
if (event.total) {
currentSize[size.suffix] = event.total;
}
});
const upload$ = rxjs_1.from(upload.promise().then((result) => {
const { Body } = size, rest = __rest(size, ["Body"]);
return Object.assign(Object.assign(Object.assign({}, result), rest), { currentSize: size.currentSize || currentSize[size.suffix] });
}));
return upload$;
}), operators_1.toArray(), operators_1.first())
.subscribe((response) => {
const multipleUploadedFiles = response.reduce((acc, uploadedFile) => {
const { suffix, ContentType, currentSize } = uploadedFile, details = __rest(uploadedFile, ["suffix", "ContentType", "currentSize"]);
acc[uploadedFile.suffix] = Object.assign(Object.assign({ ACL,
ContentDisposition,
StorageClass,
ServerSideEncryption,
Metadata }, details), { size: currentSize, ContentType: optsContentType || ContentType, mimetype: mime_types_1.lookup(ContentType) || `image/${ContentType}` });
return acc;
}, {});
callback(null, JSON.parse(JSON.stringify(multipleUploadedFiles)));
}, callback);
}
else {
let currentSize = 0;
const resizedStream = multer_sharp_utils_1.transformImage(sharpOpts, sharpOpts.resize);
const newParams = Object.assign(Object.assign({}, params), { Body: stream.pipe(resizedStream) });
const meta$ = rxjs_1.from(newParams.Body.toBuffer({ resolveWithObject: true }));
meta$
.pipe(operators_1.first(), operators_1.map((metadata) => {
newParams.ContentType = storageOpts.ContentType || metadata.info.format;
return metadata;
}), operators_1.mergeMap((metadata) => {
const upload = storageOpts.s3.upload(newParams);
upload.on('httpUploadProgress', (eventProgress) => {
if (eventProgress.total) {
currentSize = eventProgress.total;
}
});
const data = upload
.promise()
.then((uploadedData) => (Object.assign(Object.assign({}, uploadedData), metadata.info)));
const upload$ = rxjs_1.from(data);
return upload$;
}))
.subscribe((response) => {
const { size, format, channels } = response, details = __rest(response, ["size", "format", "channels"]);
const data = Object.assign(Object.assign({ ACL,
ContentDisposition,
StorageClass,
ServerSideEncryption,
Metadata }, details), { size: currentSize || size, ContentType: storageOpts.ContentType || format, mimetype: mime_types_1.lookup(format) || `image/${format}` });
callback(null, JSON.parse(JSON.stringify(data)));
}, callback);
}
}
uploadFileToS3(params, file, callback) {
const { storageOpts } = this;
const { mimetype } = file;
params.ContentType = params.ContentType || mimetype;
const upload = storageOpts.s3.upload(params);
let currentSize = 0;
upload.on('httpUploadProgress', (event) => {
if (event.total) {
currentSize = event.total;
}
});
upload.promise().then((uploadedData) => {
const data = Object.assign({ size: currentSize, ACL: storageOpts.ACL, ContentType: storageOpts.ContentType || mimetype, ContentDisposition: storageOpts.ContentDisposition, StorageClass: storageOpts.StorageClass, ServerSideEncryption: storageOpts.ServerSideEncryption, Metadata: storageOpts.Metadata }, uploadedData);
callback(null, JSON.parse(JSON.stringify(data)));
}, callback);
}
}
exports.MulterSharp = MulterSharp;