Search code examples
node.jstypescriptnestjsmulter

Is there a way to count the number of pages of a pdf file to be uploaded using multer?


I'm working on a feature to define a limit size of a pdf file to be uploaded. That part is ok, but I also need to define a limit for how many pages a pdf file has, and I couldn't find any answer to that. I am using NestJS, and my code block looks like this so far:

  @UseInterceptors(
FilesInterceptor('files', 10, {
  limits: { fileSize: 10242880 },
  fileFilter: (req, file, callback) => {
    if (!file.originalname.match(/\.(jpg|jpeg|png|pdf)$/)) {
      req.fileValidationError =
        'Invalid file type provided. Valid types: [jpg, jpeg, png, pdf]';
      //TODO: Add validation to check if the file is pdf, and if so, define a limit of pages for the file. (max * pages)
      callback(null, false);
    }

    const fileSize = parseInt(req.headers['content-length']);
    if (fileSize > 10242880) {
      req.fileValidationError = 'File size exceeds the maximum limit [10mb]';
      callback(null, false);
    }
    callback(null, true);
    if (!file.originalname.match(/\.(pdf)$/)) {
      //what to do here??
      
    }
  },
})

The multer options available in the interface, are the following. Maybe any of them can provide me with such an info, but I would really appreciate a hand with this. Thanks

export interface MulterOptions {
dest?: string;
/** The storage engine to use for uploaded files. */
storage?: any;
/**
 * An object specifying the size limits of the following optional properties. This object is passed to busboy
 * directly, and the details of properties can be found on https://github.com/mscdex/busboy#busboy-methods
 */
limits?: {
    /** Max field name size (Default: 100 bytes) */
    fieldNameSize?: number;
    /** Max field value size (Default: 1MB) */
    fieldSize?: number;
    /** Max number of non- file fields (Default: Infinity) */
    fields?: number;
    /** For multipart forms, the max file size (in bytes)(Default: Infinity) */
    fileSize?: number;
    /** For multipart forms, the max number of file fields (Default: Infinity) */
    files?: number;
    /** For multipart forms, the max number of parts (fields + files)(Default: Infinity) */
    parts?: number;
    /** For multipart forms, the max number of header key=> value pairs to parse Default: 2000(same as node's http). */
    headerPairs?: number;
};
/** Keep the full path of files instead of just the base name (Default: false) */
preservePath?: boolean;
fileFilter?(req: any, file: {
    /** Field name specified in the form */
    fieldname: string;
    /** Name of the file on the user's computer */
    originalname: string;
    /** Encoding type of the file */
    encoding: string;
    /** Mime type of the file */
    mimetype: string;
    /** Size of the file in bytes */
    size: number;
    /** The folder to which the file has been saved (DiskStorage) */
    destination: string;
    /** The name of the file within the destination (DiskStorage) */
    filename: string;
    /** Location of the uploaded file (DiskStorage) */
    path: string;
    /** A Buffer of the entire file (MemoryStorage) */
    buffer: Buffer;
}, callback: (error: Error | null, acceptFile: boolean) => void): void;

}


Solution

  • The best solution I was able to provide was using pdf-lib. I moved the page limit check, outside the FIleFilter from multer, because there is no buffer from a file at that point. The solution was used inside a different function, as suggested by @bogdanoff

    try {
      for (const file of files) {
        const isPdf = file.mimetype === 'application/pdf';
        if (isPdf) {
          const loadPdf = PDFDocument.load(file.buffer); 
          //the PDFDocument above, is the pdf-lib imported
          const pdf = await loadPdf;
          const pagesCount = pdf.getPageCount();
          if (pagesCount > 5) {
            throw new BadRequestException({
              statusCode: 400,
              message: 'PDF file must have a maximum of 5 pages.',
            });
          }
        }
      }
    } catch (error) {
      this.commonLogger.error({
        error,
        serviceName: this.controllerName,
        method: 'createCredential',
        params: { vendorId, userId, credentialDto },
      });
      throw new BadRequestException({ statusCode: 400, message: error.message });
    }
    

    }