mirror of
https://github.com/wassname/talk.git
synced 2026-07-02 00:47:06 +08:00
Added documentation to the user model + abstracted it
This commit is contained in:
+118
-34
@@ -2,32 +2,72 @@ const mongoose = require('../mongoose');
|
||||
const uuid = require('uuid');
|
||||
const bcrypt = require('bcrypt');
|
||||
|
||||
// SALT_ROUNDS is the number of rounds that the bcrypt algorithm will run
|
||||
// through during the salting process.
|
||||
const SALT_ROUNDS = 10;
|
||||
|
||||
// USER_ROLES is the array of roles that is permissible as a user role.
|
||||
const USER_ROLES = [
|
||||
'admin',
|
||||
'moderator'
|
||||
];
|
||||
|
||||
// UserSchema is the mongoose schema defined as the representation of a User in
|
||||
// MongoDB.
|
||||
const UserSchema = new mongoose.Schema({
|
||||
|
||||
// This ID represents the most unique identifier for a user, it is generated
|
||||
// when the user is created as a random uuid.
|
||||
id: {
|
||||
type: String,
|
||||
default: uuid.v4,
|
||||
unique: true,
|
||||
required: true
|
||||
},
|
||||
|
||||
// This is sourced from the social provider or set manually during user setup
|
||||
// and simply provides a name to display for the given user.
|
||||
displayName: String,
|
||||
|
||||
// This is true when the user account is disabled, no action should be
|
||||
// acknowledged when they are disabled. Logins are also prevented.
|
||||
disabled: Boolean,
|
||||
|
||||
// This provides a source of identity proof for users who login using the
|
||||
// local provider. A local provider will be assumed for users who do not
|
||||
// have any social profiles.
|
||||
password: String,
|
||||
profiles: [{
|
||||
|
||||
// Profiles describes the array of identities for a given user. Any one user
|
||||
// can have multiple profiles associated with them, including multiple email
|
||||
// addresses.
|
||||
profiles: [new mongoose.Schema({
|
||||
|
||||
// ID provides the identifier for the user profile, in the case of a local
|
||||
// provider, the id would be an email, in the case of a social provider,
|
||||
// the id would be the foreign providers identifier.
|
||||
id: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
|
||||
// Provider is simply the name attached to the authentication mode. In the
|
||||
// case of a locally provided profile, this will simply be `local`, or a
|
||||
// social provider which for Facebook would just be `facebook`.
|
||||
provider: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
}],
|
||||
roles: {
|
||||
type: [{type: String, enum: ['admin', 'moderator']}]
|
||||
}
|
||||
}, {
|
||||
_id: false
|
||||
})],
|
||||
|
||||
// Roles provides an array of roles (as strings) that is associated with a
|
||||
// user.
|
||||
roles: [String]
|
||||
}, {
|
||||
|
||||
// This will ensure that we have proper timestamps available on this model.
|
||||
timestamps: {
|
||||
createdAt: 'created_at',
|
||||
updatedAt: 'updated_at'
|
||||
@@ -75,6 +115,13 @@ UserSchema.options.toObject.transform = (doc, ret, options) => {
|
||||
return ret;
|
||||
};
|
||||
|
||||
// Create the User model.
|
||||
const UserModel = mongoose.model('User', UserSchema);
|
||||
|
||||
// UserService is the interface for the application to interact with the
|
||||
// UserModel through.
|
||||
const UserService = module.exports = {};
|
||||
|
||||
/**
|
||||
* Finds a user given their email address that we have for them in the system
|
||||
* and ensures that the retuned user matches the password passed in as well.
|
||||
@@ -82,8 +129,8 @@ UserSchema.options.toObject.transform = (doc, ret, options) => {
|
||||
* @param {string} password - password to match against the found user
|
||||
* @param {Function} done [description]
|
||||
*/
|
||||
UserSchema.statics.findLocalUser = function(email, password) {
|
||||
return User.findOne({
|
||||
UserService.findLocalUser = (email, password) => {
|
||||
return UserModel.findOne({
|
||||
profiles: {
|
||||
$elemMatch: {
|
||||
id: email,
|
||||
@@ -120,13 +167,13 @@ UserSchema.statics.findLocalUser = function(email, password) {
|
||||
* @param {String} srcUserID id of the user to which is the source of the merge
|
||||
* @return {Promise} resolves when the users are merged
|
||||
*/
|
||||
UserSchema.statics.mergeUsers = function(dstUserID, srcUserID) {
|
||||
UserService.mergeUsers = (dstUserID, srcUserID) => {
|
||||
let srcUser, dstUser;
|
||||
|
||||
return Promise
|
||||
.all([
|
||||
User.findOne({id: dstUserID}).exec(),
|
||||
User.findOne({id: srcUserID}).exec()
|
||||
UserModel.findOne({id: dstUserID}).exec(),
|
||||
UserModel.findOne({id: srcUserID}).exec()
|
||||
])
|
||||
.then((users) => {
|
||||
dstUser = users[0];
|
||||
@@ -147,8 +194,8 @@ UserSchema.statics.mergeUsers = function(dstUserID, srcUserID) {
|
||||
* @param {Object} profile - User social/external profile
|
||||
* @param {Function} done [description]
|
||||
*/
|
||||
UserSchema.statics.findOrCreateExternalUser = function(profile) {
|
||||
return User
|
||||
UserService.findOrCreateExternalUser = (profile) => {
|
||||
return UserModel
|
||||
.findOne({
|
||||
profiles: {
|
||||
$elemMatch: {
|
||||
@@ -163,7 +210,7 @@ UserSchema.statics.findOrCreateExternalUser = function(profile) {
|
||||
}
|
||||
|
||||
// The user was not found, lets create them!
|
||||
user = new User({
|
||||
user = new UserModel({
|
||||
displayName: profile.displayName,
|
||||
roles: [],
|
||||
profiles: [
|
||||
@@ -178,7 +225,7 @@ UserSchema.statics.findOrCreateExternalUser = function(profile) {
|
||||
});
|
||||
};
|
||||
|
||||
UserSchema.statics.changePassword = function(id, password) {
|
||||
UserService.changePassword = (id, password) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
bcrypt.hash(password, SALT_ROUNDS, (err, hashedPassword) => {
|
||||
if (err) {
|
||||
@@ -189,7 +236,7 @@ UserSchema.statics.changePassword = function(id, password) {
|
||||
});
|
||||
})
|
||||
.then((hashedPassword) => {
|
||||
return User.update({id}, {
|
||||
return UserModel.update({id}, {
|
||||
$set: {
|
||||
password: hashedPassword
|
||||
}
|
||||
@@ -202,9 +249,9 @@ UserSchema.statics.changePassword = function(id, password) {
|
||||
* @param {Array} users Users to create
|
||||
* @return {Promise} Resolves with the users that were created
|
||||
*/
|
||||
UserSchema.statics.createLocalUsers = function(users) {
|
||||
UserService.createLocalUsers = (users) => {
|
||||
return Promise.all(users.map((user) => {
|
||||
return User
|
||||
return UserService
|
||||
.createLocalUser(user.email, user.password, user.displayName);
|
||||
}));
|
||||
};
|
||||
@@ -216,7 +263,7 @@ UserSchema.statics.createLocalUsers = function(users) {
|
||||
* @param {String} displayName name of the display user
|
||||
* @param {Function} done callback
|
||||
*/
|
||||
UserSchema.statics.createLocalUser = function(email, password, displayName) {
|
||||
UserService.createLocalUser = (email, password, displayName) => {
|
||||
if (!email) {
|
||||
return Promise.reject('email is required');
|
||||
}
|
||||
@@ -235,7 +282,7 @@ UserSchema.statics.createLocalUser = function(email, password, displayName) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
let user = new User({
|
||||
let user = new UserModel({
|
||||
displayName: displayName,
|
||||
password: hashedPassword,
|
||||
roles: [],
|
||||
@@ -263,8 +310,8 @@ UserSchema.statics.createLocalUser = function(email, password, displayName) {
|
||||
* @param {String} id id of a user
|
||||
* @param {Function} done callback after the operation is complete
|
||||
*/
|
||||
UserSchema.statics.disableUser = function(id) {
|
||||
return User.update({
|
||||
UserService.disableUser = (id) => {
|
||||
return UserModel.update({
|
||||
id: id
|
||||
}, {
|
||||
$set: {
|
||||
@@ -278,8 +325,8 @@ UserSchema.statics.disableUser = function(id) {
|
||||
* @param {String} id id of a user
|
||||
* @param {Function} done callback after the operation is complete
|
||||
*/
|
||||
UserSchema.statics.enableUser = function(id) {
|
||||
return User.update({
|
||||
UserService.enableUser = (id) => {
|
||||
return UserModel.update({
|
||||
id: id
|
||||
}, {
|
||||
$set: {
|
||||
@@ -294,8 +341,16 @@ UserSchema.statics.enableUser = function(id) {
|
||||
* @param {String} role role to add
|
||||
* @param {Function} done callback after the operation is complete
|
||||
*/
|
||||
UserSchema.statics.addRoleToUser = function(id, role) {
|
||||
return User.update({
|
||||
UserService.addRoleToUser = (id, role) => {
|
||||
|
||||
// Check to see if the user role is in the allowable set of roles.
|
||||
if (USER_ROLES.indexOf(role) === -1) {
|
||||
|
||||
// User role is not supported! Error out here.
|
||||
return Promise.reject(new Error(`role ${role} is not supported`));
|
||||
}
|
||||
|
||||
return UserModel.update({
|
||||
id: id
|
||||
}, {
|
||||
$addToSet: {
|
||||
@@ -310,8 +365,8 @@ UserSchema.statics.addRoleToUser = function(id, role) {
|
||||
* @param {String} role role to remove
|
||||
* @param {Function} done callback after the operation is complete
|
||||
*/
|
||||
UserSchema.statics.removeRoleFromUser = function(id, role) {
|
||||
return User.update({
|
||||
UserService.removeRoleFromUser = (id, role) => {
|
||||
return UserModel.update({
|
||||
id: id
|
||||
}, {
|
||||
$pull: {
|
||||
@@ -324,21 +379,50 @@ UserSchema.statics.removeRoleFromUser = function(id, role) {
|
||||
* Finds a user with the id.
|
||||
* @param {String} id user id (uuid)
|
||||
*/
|
||||
UserSchema.statics.findById = function(id) {
|
||||
return User.findOne({id});
|
||||
UserService.findById = (id) => {
|
||||
return UserModel.findOne({id});
|
||||
};
|
||||
|
||||
/**
|
||||
* Finds users in an array of idd.
|
||||
* @param {Array} ids array of user identifiers (uuid)
|
||||
*/
|
||||
UserSchema.statics.findByIdArray = function(ids) {
|
||||
return User.find({
|
||||
UserService.findByIdArray = (ids) => {
|
||||
return UserModel.find({
|
||||
'id': {$in: ids}
|
||||
});
|
||||
};
|
||||
|
||||
const User = mongoose.model('User', UserSchema);
|
||||
/**
|
||||
* Finds a user using a value which gets compared using a prefix match against
|
||||
* the user's email address and/or their display name.
|
||||
* @param {String} value value to search by
|
||||
* @return {Promise}
|
||||
*/
|
||||
UserService.search = (value) => {
|
||||
return UserModel.find({
|
||||
$or: [
|
||||
|
||||
module.exports = User;
|
||||
module.exports.Schema = UserSchema;
|
||||
// Search by a prefix match on the displayName.
|
||||
{
|
||||
'displayName': {
|
||||
$regex: new RegExp(`^${value}`),
|
||||
$options: 'i'
|
||||
}
|
||||
},
|
||||
|
||||
// Search by a prefix match on the email address.
|
||||
{
|
||||
'profiles': {
|
||||
$elemMatch: {
|
||||
id: {
|
||||
$regex: new RegExp(`^${value}`),
|
||||
$options: 'i'
|
||||
},
|
||||
provider: 'local'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
};
|
||||
|
||||
@@ -11,28 +11,9 @@ router.get('/', (req, res, next) => {
|
||||
limit = 50 // Total Per Page
|
||||
} = req.query;
|
||||
|
||||
let q = {
|
||||
$or: [
|
||||
{
|
||||
'displayName': {
|
||||
$regex: new RegExp(`^${value}`),
|
||||
$options: 'i'
|
||||
},
|
||||
'profiles': {
|
||||
$elemMatch: {
|
||||
id: {
|
||||
$regex: new RegExp(`^${value}`),
|
||||
$options: 'i'
|
||||
},
|
||||
provider: 'local'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
Promise.all([
|
||||
User.find(q)
|
||||
User
|
||||
.search(value)
|
||||
.sort({[field]: (asc === 'true') ? 1 : -1})
|
||||
.skip((page - 1) * limit)
|
||||
.limit(limit),
|
||||
@@ -63,11 +44,12 @@ router.get('/', (req, res, next) => {
|
||||
});
|
||||
|
||||
router.post('/:user_id/role', (req, res, next) => {
|
||||
User.addRoleToUser(req.params.user_id, req.body.role)
|
||||
.then(role => {
|
||||
res.send(role);
|
||||
})
|
||||
.catch(next);
|
||||
User
|
||||
.addRoleToUser(req.params.user_id, req.body.role)
|
||||
.then(role => {
|
||||
res.send(role);
|
||||
})
|
||||
.catch(next);
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
Reference in New Issue
Block a user