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

async upload (req, res, user) {
  let uploader = this.createSingleUploader();
  await PromiseHelper.promise(uploader.bind(this, req, res));
  this.populateFileStats(req.file, user);
  this.set('file', this.getFileStats());
  return this.save();
}

createSingleUploader () {
  return multer({
    'storage': this.createUploaderStorage()
  }).single('file');
}

createUploaderStorage () {
  return multer.diskStorage({
    'destination': this.generateStoreDir.bind(this),
    'filename': this.generateFilename.bind(this)
  });
}

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

module/admin/model/File.js

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

generateFilename (req, file, callback) {
  callback(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, req, user) {
  this.setAttrs({
    'userId': user.getId(),
    'originalName': file.originalname,
    'filename': file.filename,
    'mime': file.mimetype,
    'extension': path.extname(file.originalname).substring(1).toLowerCase(),
    'size': file.size,
    'ip': 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

async afterDelete () {
  await super.afterRemove();
  fs.unlinkSync(this.getPath());
}