Home Reference Source

src/database/models/user.model.js

import mongoose from 'mongoose';
import bcrypt from 'bcryptjs';
import {
  pick, set, get,
  isEmpty, isEqual
} from 'lodash';

/**
 * The user schema consists of :
 * - An email, unique, for the user
 * - A username
 * - A role
 * - A password, that is null and only used for pre
 *   save hook
 * - A hashed password
 */
const userSchema = new mongoose.Schema({
  email: {
    type: String,
    required: true
  },
  username: {
    type: String,
    required: true
  },
  role: {
    type: String,
    default: 'user',
    enum: ['user', 'admin']
  },
  password: {
    type: String
  },
  hash: {
    type: String
  }
});

userSchema.virtual('profile').get(function getProfile() {
  return pick(this, ['_id', 'email', 'role']);
});

userSchema.methods = {
  /**
   * This function is used to compare the user password with
   * a given password
   * @param { String } password The password to check
   * @return { Promise } A promise that resolves a boolean
   * indicating if passwords match
   */
  comparePassword: function comparePassword(password) {
    const self = this;
    return bcrypt.compare(password, get(self, 'hash', ''));
  }
};

userSchema.pre('save', function preSave(next) {
  const self = this;
  return self.constructor
  .findOne({ email: self.email })
  .exec()
  .then((user) => {
    if (user &&
      (isEqual(get(self, 'isNew', false), true) ||
        !isEqual(get(user, '_id', ''), get(self, '_id', '')))) {
      return next(new Error('Email already registered'));
    }
    if (!isEmpty(get(self, 'password', ''))) {
      return bcrypt.hash(self.password, 10, (err, hash) => {
        if (err) {
          return next(err);
        }
        set(self, 'hash', hash);
        set(self, 'password', null);
        return next();
      });
    }
    return next();
  })
  .catch(err => next(err));
});

export default mongoose.model('User', userSchema);