src/models/tasks.js

/**
 * 任务model,存储了任务的信息。
 *
 * @module models/tasks
 */
const mongoose = require('mongoose');
const {addCreatedAt, addUpdatedAt, addDeleted, addFileFields} = require('./hooks');
const {roleEnum} = require('./users');

/**
 * 创建`tasks` model。
 *、
 * @param global {object} 全局对象,主要使用了以下字段:
 *   - config:读取上传目录
 *   - db:Mongoose链接
 * @return {mongoose.model} `tasks` model
 */
module.exports = function (global) {
  const {db, config} = global;

  const statusEnum = {
    EDITING: 0,
    SUBMITTED: 1,
    ADMITTED: 2,
    PUBLISHED: 3
  };

  /**
   * `tasks` schema对象,包含以下字段:
   *  - `name`:字符串,必要
   *  - `publisher`:ObjectId,必要,为某user的`_id`
   *  - `description`:字符串,必要,任务介绍,Markdown
   *  - `excerption`:字符串,必要,任务摘要,短文本,无Markdown
   *  - `picture`:图片
   *  - `pictureThumbnail`:图片缩略图
   *  - `type`:类型,必要
   *  - `valid`:是否可以发布,必要
   *  - `tags`:字符串数组
   *  - `deadline`:截止时间,可选
   *  - `status`:数字,必要,状态,可通过静态成员`statusEnum`获得所有的状态
   *    - EDITING:待提交
   *    - SUBMITTED:待审核
   *    - ADMITTED:待发布
   *    - PUBLISHED:已发布
   *  - `remain`:数字,进度
   *  - `total`:数字,进度总数,如果为-1表示无穷
   *  - `data` 额外数据
   *  - `createdAt`:创建时间,自动字段
   *  - `updatedAt`:更新时间,自动字段
   *  - `deleted` 是否被删除
   *
   *  注意,其中`valid`,`remain`、`total`和`data`都是交给特殊逻辑处理的,其余都是通用逻辑处理。
   *  @class Task
   */
  const taskSchema = new mongoose.Schema({
    name: {type: String, required: true},
    publisher: {type: mongoose.Schema.Types.ObjectId, required: true},
    description: {type: String, required: true},
    excerption: {type: String, required: true},
    picture: {type: String},
    pictureThumbnail: {type: String},
    type: {type: String},
    valid: {type: Boolean, required: true},
    tags: {type: [String]},
    deadline: {type: Date},
    status: {type: Number, required: true, index: true},
    remain: {type: Number},
    total: {type: Number},
    data: {type: mongoose.Schema.Types.Mixed, select: false},
    createdAt: {type: Date},
    updatedAt: {type: Date},
    deleted: {type: Boolean, index: true}
  });

  /**
   * 任务的状态到编号的映射
   * @name module:models/tasks~Task.statusEnum
   */
  taskSchema.statics.statusEnum = statusEnum;

  addCreatedAt(taskSchema);
  addUpdatedAt(taskSchema);
  addDeleted(taskSchema);
  addFileFields(taskSchema, ['picture', 'pictureThumbnail'], config['upload-dir']);

  /**
   * 按照请求者的权限,转换成对应的对象。
   * @param auth {object} 可选,权限信息,包含uid和role
   * 那就是权限。
   * @return {object} 对象
   * @function module:models/tasks~Task#toPlainObject
   */
  taskSchema.methods.toPlainObject = function (auth) {
    const isPublisher = auth && this.publisher.equals(auth.uid);
    const isTaskAdmin = auth && (auth.role & roleEnum.TASK_ADMIN) !== 0;
    const result = {
      _id: this._id.toString(),
      name: this.name,
      publisher: this.publisher,
      description: this.description,
      excerption: this.excerption,
      createdAt: this.createdAt,
      updatedAt: this.updatedAt,
      tags: this.tags,
      status: this.status
    };
    if (this.type !== undefined)
      result.type = this.type;
    if (this.picture !== undefined && this.pictureThumbnail !== undefined) {
      result.picture = '/uploads/' + this.picture;
      result.pictureThumbnail = '/uploads/' + this.pictureThumbnail;
    }
    if (this.remain !== undefined && this.total !== undefined) {
      result.remain = this.remain;
      result.total = this.total;
    }
    if (this.deadline !== undefined)
      result.deadline = this.deadline;
    if (isPublisher || isTaskAdmin)
      result.valid = !!this.valid;
    return result;
  };

  return db.model('tasks', taskSchema);
};