Skip to content

Instantly share code, notes, and snippets.

@sandeepsuvit
Last active April 27, 2024 08:22
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save sandeepsuvit/1345ab69eb819164ab497ad07c589d8d to your computer and use it in GitHub Desktop.
Save sandeepsuvit/1345ab69eb819164ab497ad07c589d8d to your computer and use it in GitHub Desktop.
nestjs file upload using multer
##
# File properties
##
UPLOAD_LOCATION=/Users/dummyusername/Documents/temp/nvaluate
# Max 5Mb allowed
MAX_FILE_SIZE=5
MAX_FILE_COUNTS=20
import { IsNotEmpty, IsBoolean, ValidateIf } from 'class-validator';
import { ApiModelProperty, ApiModelPropertyOptional } from '@nestjs/swagger';
export class AbstractAttachmentDto {
@ApiModelProperty()
@IsNotEmpty()
fileName: string;
@ApiModelProperty()
@IsNotEmpty()
originalFileName: string;
@ApiModelProperty()
@IsNotEmpty()
mediaType: string;
@ApiModelProperty()
@IsNotEmpty()
fileSize: number;
@ApiModelPropertyOptional({ default: false })
@IsBoolean()
isCommentAttachment: boolean = false;
// Validate this field if the attachment is not for a comment
@ApiModelProperty({ required: this.isCommentAttachment ? true : false })
@ValidateIf(o => o.isCommentAttachment ? false : true)
@IsNotEmpty()
parent?: string;
constructor(
fileName: string,
originalFileName: string,
mediaType: string,
fileSize: number,
isCommentAttachment: boolean,
parent?: string,
) {
this.fileName = fileName;
this.originalFileName = originalFileName;
this.mediaType = mediaType;
this.fileSize = fileSize;
this.parent = parent;
this.isCommentAttachment = isCommentAttachment;
}
}
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { IAttachment } from '../../core/interfaces/attachment.interface';
import { AbstractAttachmentDto } from './../dtos/abstract-attachment.dto';
@Injectable()
export class AbstractAttachmentService {
constructor(
@InjectModel('Attachment') private readonly attachmentModel: Model<IAttachment>,
) {}
/**
* Add attachment
*
* @param {any[]} files
* @param {string} userId
* @returns {Promise<IAttachment[]>}
* @memberof AbstractAttachmentService
*/
async addAttachments(files: any[], userId: string): Promise<IAttachment[]> {
const attachments: IAttachment[] = [];
for (const file of files) {
// Get the file properties
const { filename, originalname, mimetype, size } = file;
// Form the attachment object
let attachment = new AbstractAttachmentDto(filename, originalname, mimetype, size, true);
// Collect all attachments
attachments.push(new this.attachmentModel(attachment));
}
// Persist the data
return await this.attachmentModel.insertMany(attachments);
}
}
import { createParamDecorator } from '@nestjs/common';
// Decorator function to extract the data
export const AuthUser = createParamDecorator((data, req) => data ? req.authInfo.user[data] : req.authInfo.user);
import { Body, Controller, Post, UploadedFiles, UseGuards, UseInterceptors, Get, Query } from '@nestjs/common';
import { FilesInterceptor } from '@nestjs/platform-express/multer';
import { ApiBadRequestResponse, ApiBearerAuth, ApiConsumes, ApiCreatedResponse, ApiForbiddenResponse, ApiImplicitFile, ApiOperation, ApiUseTags, ApiOkResponse } from '@nestjs/swagger';
import { UploadTypesEnum } from '../core/enums/upload-types.enum';
import { AuthUser } from './../shared/decorators/auth-user.decorator';
import { AuthGuard } from './../shared/guards/auth.gaurd';
import { RolesGuard } from './../shared/guards/roles.guard';
import { MulterUtils } from './../shared/services/multer-utils.service';
import { AbstractAttachmentService } from './services/abstract-attachment.service';
/**
* Controller to manage all common enpoints
*
* @export
* @class CommonController
*/
@ApiUseTags('common')
@Controller('common')
export class CommonController {
constructor(
private attachmentService: AbstractAttachmentService,
) {}
/**
* Upload attachments
* Note: The controller method
*
* @param {string} authUserId
* @param {any[]} files
* @memberof CommonController
*/
@Post('/actions/addAttachments')
@ApiOperation({ title: 'Upload attachments' })
@ApiCreatedResponse({ description: 'The record has been created successfully' })
@ApiBadRequestResponse({ description: 'Bad Request' })
@ApiForbiddenResponse({ description: 'Forbidden' })
@ApiConsumes('multipart/form-data')
@ApiImplicitFile({ name: 'files', required: true, description: 'File Attachments' })
@UseInterceptors(FilesInterceptor('files', +process.env.MAX_FILE_COUNTS, MulterUtils.getConfig(UploadTypesEnum.ANY)))
@ApiBearerAuth()
@UseGuards(AuthGuard)
uploadAttachments(@AuthUser('_id') authUserId: string, @UploadedFiles() files: any[]) {
return this.attachmentService.addAttachments(files, authUserId);
}
}
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { existsSync, mkdirSync } from 'fs';
import { diskStorage } from 'multer';
import { extname } from 'path';
import { v4 as uuid } from 'uuid';
import { UploadTypesEnum } from '../../core/enums/upload-types.enum';
/**
* Multer utils
*
* @export
* @class MulterUtils
*/
@Injectable()
export class MulterUtils {
/**
* Config for allowed files
*
* @static
* @param {UploadTypeEnum} filesAllowed
* @returns
* @memberof MulterUtils
*/
static getConfig(filesAllowed: UploadTypesEnum) {
return {
// Enable file size limits
limits: {
fileSize: +process.env.MAX_FILE_SIZE * 1024 * 1024,
},
// Check the mimetypes to allow for upload
fileFilter: (req: any, file: any, cb: any) => {
if (file.mimetype.match(`/(${filesAllowed})$`)) {
// Allow storage of file
cb(null, true);
} else {
// Reject file
cb(new HttpException(`Unsupported file type ${extname(file.originalname)}`, HttpStatus.BAD_REQUEST), false);
}
},
// Storage properties
storage: diskStorage({
// Destination storage path details
destination: (req: any, file: any, cb: any) => {
const uploadPath = process.env.UPLOAD_LOCATION;
// Create folder if doesnt exist
if (!existsSync(uploadPath)) {
mkdirSync(uploadPath);
}
cb(null, uploadPath);
},
// File modification details
filename: (req: any, file: any, cb: any) => {
// Calling the callback passing the random name generated with
// the original extension name
cb(null, `${uuid()}${extname(file.originalname)}`);
},
}),
};
}
}
import { MulterModuleOptions, MulterOptionsFactory } from '@nestjs/platform-express/multer';
/**
* Multer options configuration
*
* Note: Place this file inside `src/config` folder
*
* @export
* @class CustomMulterOptions
* @implements {MulterOptionsFactory}
*/
export class CustomMulterOptions implements MulterOptionsFactory {
createMulterOptions(): MulterModuleOptions {
return {
dest: process.env.UPLOAD_LOCATION,
};
}
}
import { SetMetadata } from '@nestjs/common';
// Decorator function to extract the roles data
export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
/**
* Upload types enum
*
* @export
* @enum {number}
*/
export enum UploadTypesEnum {
ANY = 'jpg|jpeg|png|gif|pdf|docx|doc|xlsx|xls',
IMAGES = 'jpg|jpeg|png|gif',
DOCS = 'pdf|docx|doc|xlsx|xls',
}
@rostgoat
Copy link

would u please provide how you wrote import { AuthGuard } from './../shared/guards/auth.gaurd';

I want to know how you are able to execute the uploadFile in your controller after your guard?

Feel free to answer my question on SO https://stackoverflow.com/questions/66311572/nest-js-pass-data-from-guard-to-uploadfile

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