src/models/users.js

/**
 * 用户model,存储了所有用户(包括管理员和普通用户)的信息。
 *
 * @module models/users
 */
const bcrypt = require('bcrypt');
const mongoose = require('mongoose');
const {addCreatedAt, addUpdatedAt, addDeleted, addFileFields} = require('./hooks');

const roleEnum = {
  SUBSCRIBER: 1 << 0,
  PUBLISHER: 1 << 1,
  TASK_ADMIN: 1 << 2,
  USER_ADMIN: 1 << 3,
  SITE_ADMIN: 1 << 4
};

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

  /**
   * `users` schema对象,包含以下字段:
   *  - `username`:字符串,必要,未删除用户应当唯一
   *  - `password`:密码,必要,可以为`null`(此时密码验证永远失败)
   *  - `email`:字符串,可选,未删除用户应当唯一
   *  - `wechatId`:字符串,可选,未删除用户应当唯一
   *  - `avatar`:字符串,可选,头像文件的位置(自动删除旧文件)
   *  - `avatarThumbnail64`:字符串,可选,64x64头像文件的位置(自动删除旧文件)
   *  - `createdAt`:创建时间,自动字段
   *  - `updatedAt`:更新时间,自动字段
   *  - `blocked`: 布尔,是否被禁
   *  - `roles`:数字,必要,权限,可通过静态成员`roleEnum`获得所有的权限,可通过位运算组合
   *    - SUBSCRIBER:可以领取活动
   *    - PUBLISHER:可以发布活动
   *    - TASK_ADMIN:可以管理活动
   *    - USER_ADMIN:可以管理用户
   *    - SITE_ADMIN:可以管理网站
   *  - `data`:用户自定义的设置
   *  - `deleted`:布尔,是否被删除,索引
   *  @class User
   */
  const userSchema = new mongoose.Schema({
    username: {type: String, required: true},
    password: {type: String, required: true},
    email: {type: String},
    wechatId: {type: String},
    nickname: {type: String},
    avatar: {type: String},
    avatarThumbnail64: {type: String},
    createdAt: {type: Date},
    updatedAt: {type: Date},
    blocked: {type: Boolean, index: true},
    roles: {type: Number, required: true, index: true},
    data: {type: mongoose.Schema.Types.Mixed, select: false},
    deleted: {type: Boolean, index: true}
  });

  userSchema.index({username: 1}, {
    unique: true,
    partialFilterExpression: {
      deleted: false
    }
  });
  userSchema.index({email: 1}, {
    unique: true,
    partialFilterExpression: {
      $and: [
        {email: {$exists: true}},
        {deleted: false}
      ]
    }
  });
  userSchema.index({wechatId: 1}, {
    unique: true,
    partialFilterExpression: {
      $and: [
        {wechatId: {$exists: true}},
        {deleted: false}
      ]
    }
  });

  /**
   * 用户的角色到编号的映射
   * @name module:models/users~User.roleEnum
   */
  userSchema.statics.roleEnum = roleEnum;

  addCreatedAt(userSchema);
  addUpdatedAt(userSchema);
  addDeleted(userSchema);
  addFileFields(userSchema, ['avatar', 'avatarThumbnail64'], config['upload-dir']);

  /**
   * 检查密码正确与否
   * @param password {string} 要检查的密码
   * @return {Promise.<boolean>} 密码是否正确
   * @function module:models/users~User#checkPassword
   */
  userSchema.methods.checkPassword = async function (password) {
    if (!this.password)
      return false;
    return bcrypt.compare(password, this.password);
  };

  /**
   * 按照请求者的权限,转换成对应的对象。
   * @param auth {object} 可选,权限信息,包含uid和role
   * 那就是权限。
   * @return {object} 对象
   * @function module:models/users~User#toPlainObject
   */
  userSchema.methods.toPlainObject = function (auth) {
    const isSelf = auth && this._id.equals(auth.uid);
    const isUserAdmin = auth && (auth.role & roleEnum.USER_ADMIN) !== 0;
    const result = {
      _id: this._id.toString(),
      username: this.username,
      createdAt: this.createdAt,
      updatedAt: this.updatedAt,
      roles: Object.keys(roleEnum).filter(role => this.roles & roleEnum[role])
    };
    if (this.avatar !== undefined && this.avatarThumbnail64 !== undefined) {
      result.avatar = '/uploads/' + this.avatar;
      result.avatarThumbnail64 = '/uploads/' + this.avatarThumbnail64;
    }
    if (this.blocked !== undefined)
      result.blocked = this.blocked;
    if (isSelf || isUserAdmin) {
      if (this.email !== undefined)
        result.email = this.email;
      if (this.wechatId !== undefined)
        result.wechatId = this.wechatId;
    }
    return result;
  };

  return db.model('users', userSchema);
};

module.exports.roleEnum = roleEnum;