Строим блог на Areto фреймворк

Модель загружаемого файла

Модель File обеспечивает загрузку файлов на сервер. Она реализована для поддержки асинхронной загрузки с форм. Загруженные файлы не используются напрямую, а лишь служат источником данных в других моделях.

В поведение timestamp отключите атрибут updatedAttr. Потому что однажды загруженный файл не может быть отредактирован, а только либо удален, либо использован для пересохранения в какой-либо модели.

Константа STORE_DIR формирует абсолютный путь до директории размещения файлов.

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');

Метод findExpired ищет все файлы старше указанного периода.

module/admin/model/File.js

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

Название модели формируется из исходного имени файла и имени хранения.

module/admin/model/File.js

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

Метод isImage определяет является ли файл изображением.

module/admin/model/File.js

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

Метод getPath возвращает полный путь до сохраненного файла.

module/admin/model/File.js

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

Метод upload сохраняет файл на сервер. Для это используется npm-модуль multer.

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)
  });
}

Методы generateStoreDir и generateFilename создают директорию и генерируют имя, под которым файл будет сохранен на сервере.

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));
}

Метод populateFileStats извлекает значения для атрибутов модели из параметров файла и окружения.

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
  });
}

Метод getFileStats возвращает структуру необходимую для валидации загруженного файла.

module/admin/model/File.js

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

Обработчик afterDelete вызывается после удаления модели и удаляет файл из файловой системы.

module/admin/model/File.js

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