text: module.exports = function(crowi) {
    var debug = require('debug')('growi:models:attachment')
      , mongoose = require('mongoose')
      , ObjectId = mongoose.Schema.Types.ObjectId
      , fileUploader = require('../util/fileUploader')(crowi)
      , attachmentSchema
    ;
  
    function generateFileHash(fileName) {
      var hasher = require('crypto').createHash('md5');
      hasher.update(fileName);
  
      return hasher.digest('hex');
    }
  
    attachmentSchema = new mongoose.Schema({
      page: { type: ObjectId, ref: 'Page', index: true },
      creator: { type: ObjectId, ref: 'User', index: true  },
      filePath: { type: String, required: true },
      fileName: { type: String, required: true },
      originalName: { type: String },
      fileFormat: { type: String, required: true },
      fileSize: { type: Number, default: 0 },
      createdAt: { type: Date, default: Date.now }
    }, {
      toJSON: {
        virtuals: true
      },
    });
  
    attachmentSchema.virtual('fileUrl').get(function() {
      // NOTE: use original generated Url directly (not proxy) -- 2017.05.08 Yuki Takei
      // reason:
      //   1. this is buggy (doesn't work on Win)
      //   2. ensure backward compatibility of data
  
      // return `/files/${this._id}`;
      return fileUploader.generateUrl(this.filePath);
    });
  
    attachmentSchema.statics.findById = function(id) {
      var Attachment = this;
  
      return new Promise(function(resolve, reject) {
        Attachment.findOne({_id: id}, function(err, data) {
          if (err) {
            return reject(err);
          }
  
          if (data === null) {
            return reject(new Error('Attachment not found'));
          }
          return resolve(data);
        });
  
      });
    };
  
    attachmentSchema.statics.getListByPageId = function(id) {
      var self = this;
  
      return new Promise(function(resolve, reject) {
  
        self
          .find({page: id})
          .sort({'updatedAt': 1})
          .populate('creator')
          .exec(function(err, data) {
            if (err) {
              return reject(err);
            }
  
            if (data.length < 1) {
              return resolve([]);
            }
  
            debug(data);
            return resolve(data);
          });
      });
    };
  
    attachmentSchema.statics.create = function(pageId, creator, filePath, originalName, fileName, fileFormat, fileSize) {
      var Attachment = this;
  
      return new Promise(function(resolve, reject) {
        var newAttachment = new Attachment();
  
        newAttachment.page = pageId;
        newAttachment.creator = creator._id;
        newAttachment.filePath = filePath;
        newAttachment.originalName = originalName;
        newAttachment.fileName = fileName;
        newAttachment.fileFormat = fileFormat;
        newAttachment.fileSize = fileSize;
        newAttachment.createdAt = Date.now();
  
        newAttachment.save(function(err, data) {
          if (err) {
            debug('Error on saving attachment.', err);
            return reject(err);
          }
          debug('Attachment saved.', data);
          return resolve(data);
        });
      });
    };
  
    attachmentSchema.statics.guessExtByFileType = function(fileType) {
      let ext = '';
      const isImage = fileType.match(/^image\/(.+)/i);
  
      if (isImage) {
        ext = isImage[1].toLowerCase();
      }
  
      return ext;
    };
  
    attachmentSchema.statics.createAttachmentFilePath = function(pageId, fileName, fileType) {
      const Attachment = this;
      let ext = '';
      const fnExt = fileName.match(/(.*)(?:\.([^.]+$))/);
  
      if (fnExt) {
        ext = '.' + fnExt[2];
      }
      else {
        ext = Attachment.guessExtByFileType(fileType);
        if (ext !== '') {
          ext = '.' + ext;
        }
      }
  
      return 'attachment/' + pageId + '/' + generateFileHash(fileName) + ext;
    };
  
    attachmentSchema.statics.removeAttachmentsByPageId = function(pageId) {
      var Attachment = this;
  
      return new Promise((resolve, reject) => {
        Attachment.getListByPageId(pageId)
        .then((attachments) => {
          for (let attachment of attachments) {
            Attachment.removeAttachment(attachment).then((res) => {
              // do nothing
            }).catch((err) => {
              debug('Attachment remove error', err);
            });
          }
  
          resolve(attachments);
        }).catch((err) => {
          reject(err);
        });
      });
  
    };
  
    attachmentSchema.statics.findDeliveryFile = function(attachment, forceUpdate) {
      // TODO force update
      // var forceUpdate = forceUpdate || false;
  
      return fileUploader.findDeliveryFile(attachment._id, attachment.filePath);
    };
  
    attachmentSchema.statics.removeAttachment = function(attachment) {
      const Attachment = this;
      const filePath = attachment.filePath;
  
      return new Promise((resolve, reject) => {
        Attachment.remove({_id: attachment._id}, (err, data) => {
          if (err) {
            return reject(err);
          }
  
          fileUploader.deleteFile(attachment._id, filePath)
          .then(data => {
            resolve(data); // this may null
          }).catch(err => {
            reject(err);
          });
        });
      });
    };
  
    return mongoose.model('Attachment', attachmentSchema);
  };
  