Skip to content

Commit

Permalink
feat(multer-sharp): path parameters can be used as a dynamic key path
Browse files Browse the repository at this point in the history
  • Loading branch information
jeffminsungkim committed Jun 30, 2020
1 parent 1aaafe3 commit 98329e8
Show file tree
Hide file tree
Showing 6 changed files with 67 additions and 22 deletions.
2 changes: 1 addition & 1 deletion lib/interfaces/multer-extended-options.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export interface MultipleSizeOptions {
}

export interface MulterExtendedOptions extends Pick<MulterOptions, 'fileFilter' | 'limits'> {
dynamicPath?: string;
dynamicPath?: string | string[];
randomFilename?: boolean;
resize?: ResizeOptions;
resizeMultiple?: MultipleSizeOptions[];
Expand Down
2 changes: 1 addition & 1 deletion lib/interfaces/multer-extended-s3-options.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export interface MulterExtendedS3Options {
* @see https://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl
*/
readonly acl?: string;
/*
/**
* AWS Endpoint
*/
readonly endpoint?: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ import { S3 } from 'aws-sdk';
export interface AmazonS3UploadOptions extends Partial<S3.Types.PutObjectRequest> {
s3: S3;
Key?: any;
dynamicPath?: string;
dynamicPath?: string | string[];
randomFilename?: boolean;
}
51 changes: 32 additions & 19 deletions lib/multer-sharp/multer-sharp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,15 +77,28 @@ export class MulterSharp implements StorageEngine, S3Storage {
return;
}

let { originalname } = file;

if (storageOpts.randomFilename) {
file.originalname = `${randomStringGenerator()}.${extension(mimetype)}`;
originalname = `${randomStringGenerator()}.${extension(mimetype)}`;
}

const { originalname } = file;
const routeParams = Object.keys(req.params);

params.Key = storageOpts.dynamicPath
? `${Key}/${storageOpts.dynamicPath}/${originalname}`
: `${Key}/${originalname}`;
if (routeParams.length > 0) {
if (routeParams.length === storageOpts.dynamicPath.length) {
if (routeParams.every((param, i) => param === storageOpts.dynamicPath[i])) {
const paramDir = Object.values(req.params).join('/');
params.Key = `${Key}/${paramDir}/${originalname}`;
}
} else {
params.Key = `${Key}/${req.params[storageOpts.dynamicPath as string]}/${originalname}`;
}
} else {
params.Key = storageOpts.dynamicPath
? `${Key}/${storageOpts.dynamicPath}/${originalname}`
: `${Key}/${originalname}`;
}

mimetype.includes('image')
? this.uploadImageFileToS3(params, file, callback)
Expand Down Expand Up @@ -117,7 +130,7 @@ export class MulterSharp implements StorageEngine, S3Storage {

sizes$
.pipe(
map(size => {
map((size) => {
const resizedStream = transformImage(sharpOpts, size);

if (isOriginalSuffix(size.suffix)) {
Expand All @@ -127,12 +140,12 @@ export class MulterSharp implements StorageEngine, S3Storage {
}
return size;
}),
mergeMap(size => {
mergeMap((size) => {
const sharpStream = size.Body;
const sharpPromise = sharpStream.toBuffer({ resolveWithObject: true });

return from(
sharpPromise.then(result => {
sharpPromise.then((result) => {
return {
...size,
...result.info,
Expand All @@ -142,7 +155,7 @@ export class MulterSharp implements StorageEngine, S3Storage {
}),
);
}),
mergeMap(size => {
mergeMap((size) => {
const { Body, ContentType } = size;
const newParams = {
...params,
Expand All @@ -153,14 +166,14 @@ export class MulterSharp implements StorageEngine, S3Storage {
const upload = storageOpts.s3.upload(newParams);
const currentSize = { [size.suffix]: 0 };

upload.on('httpUploadProgress', event => {
upload.on('httpUploadProgress', (event) => {
if (event.total) {
currentSize[size.suffix] = event.total;
}
});

const upload$ = from(
upload.promise().then(result => {
upload.promise().then((result) => {
// tslint:disable-next-line
const { Body, ...rest } = size;
return {
Expand All @@ -175,7 +188,7 @@ export class MulterSharp implements StorageEngine, S3Storage {
toArray(),
first(),
)
.subscribe(response => {
.subscribe((response) => {
const multipleUploadedFiles = response.reduce((acc, uploadedFile) => {
// tslint:disable-next-line
const { suffix, ContentType, currentSize, ...details } = uploadedFile;
Expand Down Expand Up @@ -203,27 +216,27 @@ export class MulterSharp implements StorageEngine, S3Storage {
meta$
.pipe(
first(),
map(metadata => {
map((metadata) => {
newParams.ContentType = storageOpts.ContentType || metadata.info.format;
return metadata;
}),
mergeMap(metadata => {
mergeMap((metadata) => {
const upload = storageOpts.s3.upload(newParams);

upload.on('httpUploadProgress', eventProgress => {
upload.on('httpUploadProgress', (eventProgress) => {
if (eventProgress.total) {
currentSize = eventProgress.total;
}
});

const data = upload
.promise()
.then(uploadedData => ({ ...uploadedData, ...metadata.info }));
.then((uploadedData) => ({ ...uploadedData, ...metadata.info }));
const upload$ = from(data);
return upload$;
}),
)
.subscribe(response => {
.subscribe((response) => {
const { size, format, channels, ...details } = response;
const data = {
ACL,
Expand Down Expand Up @@ -254,13 +267,13 @@ export class MulterSharp implements StorageEngine, S3Storage {
const upload = storageOpts.s3.upload(params);
let currentSize = 0;

upload.on('httpUploadProgress', event => {
upload.on('httpUploadProgress', (event) => {
if (event.total) {
currentSize = event.total;
}
});

upload.promise().then(uploadedData => {
upload.promise().then((uploadedData) => {
const data = {
size: currentSize,
ACL: storageOpts.ACL,
Expand Down
20 changes: 20 additions & 0 deletions tests/src/app.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,26 @@ describe('AppModule', () => {
expect(res.body.key).toEqual(`${dynamicPath}/crying.jpg`);
});

it(`should upload an image under the first path parameter :key(abcd1234)`, async () => {
const res = await request(app.getHttpServer())
.post(`/user-profile-image-upload/use-path-param-as-a-key/abcd1234`)
.set('Content-Type', 'multipart/form-data')
.attach('file', path.resolve(__dirname, 'data/crying.jpg'));

expect(res.status).toEqual(201);
expect(res.body.key).toEqual(`user-profile-image-upload-module/abcd1234/crying.jpg`);
});

it(`should upload an image under :key(abcd1234)/:id(msk)`, async () => {
const res = await request(app.getHttpServer())
.post(`/user-profile-image-upload/use-path-param-as-a-key/abcd1234/user/msk`)
.set('Content-Type', 'multipart/form-data')
.attach('file', path.resolve(__dirname, 'data/crying.jpg'));

expect(res.status).toEqual(201);
expect(res.body.key).toEqual(`user-profile-image-upload-module/abcd1234/msk/crying.jpg`);
});

it(`should upload both an original and a thumbnail image`, async () => {
const res = await request(app.getHttpServer())
.post(`/user-profile-image-upload/create-thumbnail-with-custom-options`)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,18 @@ export class UserProfileImageUploadController {
return file;
}

@Post('use-path-param-as-a-key/:key')
@UseInterceptors(AmazonS3FileInterceptor('file', { dynamicPath: 'key' }))
async uploadImageWithPathParamKey(@UploadedFile() file: any): Promise<any> {
return file;
}

@Post('use-path-param-as-a-key/:key/user/:id')
@UseInterceptors(AmazonS3FileInterceptor('file', { dynamicPath: ['key', 'id'] }))
async uploadImageWithMultiplePathParamKeys(@UploadedFile() file: any): Promise<any> {
return file;
}

@Post('create-thumbnail-with-custom-options')
@UseInterceptors(
AmazonS3FileInterceptor('file', {
Expand Down

0 comments on commit 98329e8

Please sign in to comment.