Home Intro Source Mebo GitHub

src/Inputs/FilePath.js

const fs = require('fs');
const path = require('path');
const util = require('util');
const debug = require('debug')('Mebo');
const ValidationFail = require('../MeboErrors/ValidationFail');
const Input = require('../Input');
const BaseText = require('./BaseText');

// promisifying
const stat = util.promisify(fs.stat);


/**
* File Path input.
*
* ```javascript
* const input = Input.create('myInput: filePath');
* input.setValue('/tmp/foo.txt');
* ```
*
* <h2>Property Summary</h2>
*
* Property Name | Description | Defined&nbsp;by Default | Default Value
* --- | --- | :---: | :---:
* restrictWebAccess | boolean telling if the input should have restrict access \
* when handling requests. When enabled it only lets the input to be set by a file upload, \
* making sure that the input cannot be set otherwise (like through a string) |  | 
* maxFileSize | maximum file size in bytes |  | 
* exists | checks if the file path exists |  | 
* allowedExtensions | specific list of extensions allowed by the input \
* (this check is case insensitive), example: ['jpg', 'png'] |  | 
*
* All properties including the inherited ones can be listed via
* {@link registeredPropertyNames}
*/
class FilePath extends BaseText{

  /**
  * Returns either the extension of the file path or an empty string in case the
  * file path does not have an extension
  *
  * ```javascript
  * let myInput = Input.createInput('myInput: filePath');
  * myInput.setValue('/tmp/file.jpg');
  * console.log(myInput.extension()); // jpg
  * ```
  *
  * @param {null|number} [at] - index used when input has been created as a vector that
  * tells which value should be used
  * @return {string}
  */
  extension(at=null){

    let result = '';

    if (!this.isEmpty()){
      const value = this.valueAt(at);
      const ext = path.extname(value);

      if (ext.length > 1){
        result = ext.slice(1);
      }
    }

    return result;
  }

  /**
  * Returns the basename of the file path
  *
  * ```javascript
  * const myInput = Input.createInput('myInput: filePath');
  * myInput.setValue('/tmp/file.jpg');
  * console.log(myInput.basename()); // 'file.jpg'
  * ```
  *
  * @param {null|number} [at] - index used when input has been created as a vector that
  * tells which value should be used
  * @return {string}
  */
  basename(at=null){
    return path.basename(this.valueAt(at));
  }

  /**
  * Returns the dirname of the file path
  *
  * ```javascript
  * let myInput = Input.createInput('myInput: filePath');
  * myInput.setValue('/tmp/file.jpg');
  * console.log(myInput.dirname()); // tmp
  * ```
  *
  * @param {null|number} [at] - index used when input has been created as a vector that
  * tells which value should be used
  * @return {string}
  */
  dirname(at=null){
    return path.dirname(this.valueAt(at));
  }

  /**
  * Returns the file stats
  *
  * @param {null|number} [at] - index used when input has been created as a vector that
  * tells which value should be used
  * @return {Promise<Object>}
  */
  async stat(at=null){

    // returning from cache
    if (this._isCached('stats', at)){
      return this._getFromCache('stats', at);
    }

    // otherwise processing stats
    const stats = await stat(this.valueAt(at));
    this._setToCache('stats', stats, at);
    return stats;
  }

  /**
   * Implements input's validations
   *
   * @param {null|number} at - index used when input has been created as a vector that
   * tells which value should be used
   * @return {Promise<*>} value held by the input based on the current context (at)
   * @protected
   */
  async _validation(at){
    const value = await super._validation(at);

    // only specific extensions
    if (this.property('allowedExtensions') && !this.property('allowedExtensions').map((x) => x.toLowerCase()).includes(this.extension(at).toLowerCase())){
      throw new ValidationFail(
        util.format("Extension '%s' is not supported! (supported extensions: %s)", this.extension(at), this.property('allowedExtensions')),
        '05139388-f4ec-4496-be20-f794eb14d1ff',
      );
    }

    // file exists & max size
    if (this.property('exists') || this.property('maxFileSize')){

      let stats = null;
      let err = null;
      try{
        stats = await this.stat(at);
      }
      catch(errr){
        err = errr;
        debug(err);
      }

      if (this.property('exists') && err && err.code === 'ENOENT'){
        err = new ValidationFail(
          'File does not exist',
          'dedf89bc-c57a-4ce7-ab84-f84f49144230',
        );
      }
      else if (stats !== null && this.property('maxFileSize') && stats.size > this.property('maxFileSize')){
        err = new ValidationFail(
          util.format('File size (%.1f mb) exceeds the limit allowed (%.1f mb)', stats.size/1024/1024, this.property('maxFileSize')/1024/1024),
          '99c3aeff-241b-4120-a708-d2e1ca1a1dce',
        );
      }

      if (err){
        throw (err);
      }
    }

    return value;
  }
}

// registering the input
Input.register(FilePath);

// registering properties
Input.registerProperty(FilePath, 'maxFileSize');
Input.registerProperty(FilePath, 'exists');
Input.registerProperty(FilePath, 'allowedExtensions');

module.exports = FilePath;