Build a blog with Areto Node.js framework

File model

The File model provides a file upload. It supports asynchronous uploading from web forms. Downloaded files are not used directly, but only as a source of data in other models.

Disable the updatedAttr attribute of the timestamp behavior. Downloaded files can not be edited, but only either deleted or used for re-saving in some models.

The STORE_DIR constant creates an absolute path to the directory of the file allocation.

module/admin/model/File.js

const Base = require('areto/db/ActiveRecord');
const path = require('path');

module.exports = class File extends Base {
  static getConstants () {
    return {
      TABLE: 'file',
      ATTRS: [
        'userId',
        'originalName',
        'filename',
        'mime',
        'extension',
        'size',
        'ip',
        'createdAt'
      ],
      RULES: [
        ['file', 'required'],
        ['file', 'file']
      ],
      BEHAVIORS: {
        timestamp: {
          Class: require('areto/behaviors/Timestamp'),
          updatedAttr: false
        }
      },
      STORE_DIR: path.join(__dirname, '../uploads/temp')
    };
  }
};
module.exports.init(module);

const fs = require('fs');
const multer = require('multer');
const mkdirp = require('mkdirp');
const CommonHelper = require('areto/helper/CommonHelper');

The findExpired method finds all files older than the specified period.

module/admin/model/File.js

static findExpired (elapsedSeconds = 3600) {
  let expired = new Date(Date.now() - parseInt(timeout) * 1000);
  return this.find(['<', 'updatedAt', expired]);
}

The model title is formed from the original file name and stored name.

module/admin/model/File.js

getTitle () {
  return `${this.get('originalName')} (${this.get('filename')})`;
}

The isImage method recognizes a image file.

module/admin/model/File.js

isImage () {
  return this.get('mime').indexOf('image') === 0;
}

The getPath method returns absolute path of a saved file.

module/admin/model/File.js

getPath () {
  return path.join(this.STORE_DIR, this.get('filename'));
}

The upload method saves a file to the server, using the multer npm-module.

module/admin/model/File.js

upload (controller, cb) {
  let req = controller.req;
  async.series([
    cb => multer({
      'storage': multer.diskStorage({
        'destination': this.generateStoreDir.bind(this),
        'filename': this.generateFilename.bind(this)
      })
    }).single('file')(req, controller.res, cb),
    cb => {
      this.populateFileStats(req.file, controller);
      this.set('file', this.getFileStats());
      this.save(cb);
    }
  ], cb);
}

The generateStoreDir, generateFilename methods create a directory and generate a file name to save.

module/admin/model/File.js

generateStoreDir (req, file, cb) {
  mkdirp(this.STORE_DIR, err => cb(err, this.STORE_DIR));
}

generateFilename (req, file, cb) {
  cb(null, Date.now().toString() + CommonHelper.getRandom(11, 99));
}

The populateFileStats method extracts values of a file and request.

module/admin/model/File.js

populateFileStats (file, controller) {
  this.set('userId', controller.user.getId());
  this.set('originalName', file.originalname);
  this.set('filename', file.filename);
  this.set('mime', file.mimetype);
  this.set('extension', path.extname(file.originalname).substring(1).toLowerCase());
  this.set('size', file.size);
  this.set('ip', controller.req.ip);
}

The getFileStats method returns the data needed to validate a uploaded file.

module/admin/model/File.js

getFileStats () {
  return {
    model: this,
    path: this.getPath(),
    size: this.get('size'),
    extension: this.get('extension'),
    mime: this.get('mime')
  };
}

The afterDelete handler deletes a file from the file system. It is called after the removal of the model.

module/admin/model/File.js

afterDelete (cb) {
  async.series([
    cb => super.afterRemove(cb),
    cb => fs.unlink(this.getPath(), cb)
  ], cb);
}