Skip to content

Instantly share code, notes, and snippets.

@TimVosch
Last active February 22, 2024 05:00
Show Gist options
  • Save TimVosch/e2d8504c43b60d9b23cb0796b4127772 to your computer and use it in GitHub Desktop.
Save TimVosch/e2d8504c43b60d9b23cb0796b4127772 to your computer and use it in GitHub Desktop.
Get request parameters in FileInterceptor
import { Controller, UseInterceptors, Post } from '@nestjs/common';
import { AppService } from './app.service';
import { diskStorage } from 'multer';
import { Request } from 'express';
import { MyNewFileInterceptor } from './file.interceptor';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Post('/profile/:id/picture')
@UseInterceptors(
/**
* The `options` is a now function executed on intercept.
* This function receives the context parameter allowing you
* to use get the request and subsequentially the parameters
*/
MyNewFileInterceptor('picture', ctx => {
// Get request from Context
const req = ctx.switchToHttp().getRequest() as Request;
// Return the options
return {
storage: diskStorage({
destination: `.upload/profile/${req.params.id}/picture`,
// tslint:disable-next-line: variable-name
filename: (_req, file, cb) => {
const randomName = Array(32)
.fill(null)
.map(() => Math.round(Math.random() * 16).toString(16))
.join('');
return cb(null, `${randomName}-${file.originalname}`);
},
}),
};
}),
)
uploadProfilePicture(): string {
return this.appService.getHello();
}
}
import { FileInterceptor, MulterModuleOptions } from '@nestjs/platform-express';
import { MulterOptions } from '@nestjs/platform-express/multer/interfaces/multer-options.interface';
import { MULTER_MODULE_OPTIONS } from '@nestjs/platform-express/multer/files.constants';
import * as multer from 'multer';
import {
ExecutionContext,
Optional,
Inject,
CallHandler,
mixin,
Type,
NestInterceptor,
} from '@nestjs/common';
export const MyNewFileInterceptor = (
fieldName: string,
localOptions?: (context: ExecutionContext) => MulterOptions,
) => {
const FileInterceptorInstance = FileInterceptor(fieldName);
class MixinInterceptor extends FileInterceptorInstance {
protected multer: any;
protected moduleOptions: {};
constructor(
@Optional()
@Inject(MULTER_MODULE_OPTIONS)
options: MulterModuleOptions = {},
) {
super();
this.moduleOptions = options;
}
intercept(context: ExecutionContext, next: CallHandler<any>): any {
this.multer = (multer as any)({
...this.moduleOptions,
...localOptions(context),
});
return super.intercept(context, next);
}
}
const Interceptor = mixin(MixinInterceptor);
return Interceptor as Type<NestInterceptor>;
};
@UseInterceptors(
FileInterceptor('picture', {
storage: diskStorage({
destination: function(req, file, cb) {
cb(null, `.upload/profile/${req.params.id}/picture`);
},
// tslint:disable-next-line: variable-name
filename: (_req, file, cb) => {
const randomName = Array(32)
.fill(null)
.map(() => Math.round(Math.random() * 16).toString(16))
.join('');
return cb(null, `${randomName}-${file.originalname}`);
},
}),
}),
)
@DanielSeldura
Copy link

This worked like a charm and was exactly the help I needed! Thank you! I have a question though, how would I go about writing an implementation that gets multiple files?

@TimVosch
Copy link
Author

The gist I provided is unnecessary since the DiskStorage class already implements similar functionality which should work better.

I have added a new file to the gist:https://gist.github.com/TimVosch/e2d8504c43b60d9b23cb0796b4127772#file-pure-file-intercepting-ts
Besides the FileInterceptor there should also  be a File s interceptor (notice plural). That might help you.

@DanielSeldura
Copy link

DanielSeldura commented May 15, 2020

The snippet was exactly what I needed! Thank you!

This was my longer implementation prior to receiving the response
upload.controller.ts

...
@Post('images/:userUid/:itemUid')
  @UseInterceptors(FilesInterceptor('image', 9))
  async uploadFiles(
    @UploadedFiles() files: PropertyDecorator,
    @Param('userUid') userUid: string,
    @Param('itemUid') itemUid: string,
  ) {
    const response = await this._uploadService.write(files, [userUid, itemUid]);
    if (typeof response != 'string') return response;
    else
      throw new HttpException(
        'Only image files are allowed: [' + response + ']',
        HttpStatus.INTERNAL_SERVER_ERROR,
      );
  }

  @Post('image/:userUid/:itemUid')
  @UseInterceptors(FileInterceptor('image'))
  async uploadFile(
    @UploadedFiles() file: PropertyDecorator,
    @Param('userUid') userUid: string,
    @Param('itemUid') itemUid: string,
  ) {
    const response = await this._uploadService.write(file, [userUid, itemUid]);
    if (typeof response != 'string') return response;
    else
      throw new HttpException(
        'Only image files are allowed: [' + response + ']',
        HttpStatus.INTERNAL_SERVER_ERROR,
      );
  }

upload.service.ts

export class UploadService {
  returnVal: any;
  async write(files: PropertyDecorator, itemDetails: Array<string>) {
    try {
      this.returnVal = {};
      const fileIndex = Object.keys(files);
      //file format checks
      fileIndex.forEach((key) => {
          if (!files[key].originalname.match(/\.(jpg|jpeg|png|JPG|JPEG|PNG)$/)) {
              this.returnVal = files[key].originalname;
              throw new HttpException('Only image files are allowed! File:' + files[key].originalname, HttpStatus.INTERNAL_SERVER_ERROR);
          }
      });
      //Directory checks
      if (!fs.existsSync(UPLOAD_DIRECTORY + itemDetails[0])) {
        fs.mkdirSync(UPLOAD_DIRECTORY + itemDetails[0]);
      }
      if (!fs.existsSync(UPLOAD_DIRECTORY + itemDetails[0] + '/' + itemDetails[1])) {
        fs.mkdirSync(UPLOAD_DIRECTORY + itemDetails[0] + '/' + itemDetails[1]);
      }
      //
      Object.keys(files).forEach((key) => {
        const filename = this.randomizeFileName(files[key].originalname);
        const path = UPLOAD_DIRECTORY + itemDetails[0] + '/' + itemDetails[1] + '/' + filename;
        const writeStream = fs.createWriteStream(path);
        writeStream.write(files[key].buffer);
        writeStream.end();
        this.returnVal[key] = filename;
      });
      return this.returnVal;
    } catch (e) {
        console.log(e.message);
        return this.returnVal;
    }
  }

  randomizeFileName(value: string): string {
    return uuidv4() + extname(value).toLowerCase();
  }
}

The new one is a lot shorter haha

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment