blob: ecfdc0be8954b963f09172108b0cd5a3c29f2c5e [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
const _ = require('lodash');
// Fire me up!
module.exports = {
implements: 'services/users',
inject: ['errors', 'settings', 'mongo', 'services/spaces', 'services/mails', 'services/activities', 'services/utils', 'agents-handler']
};
/**
* @param mongo
* @param errors
* @param settings
* @param {SpacesService} spacesService
* @param {MailsService} mailsService
* @param {ActivitiesService} activitiesService
* @param {UtilsService} utilsService
* @param {AgentsHandler} agentHnd
* @returns {UsersService}
*/
module.exports.factory = (errors, settings, mongo, spacesService, mailsService, activitiesService, utilsService, agentHnd) => {
class UsersService {
/**
* Save profile information.
*
* @param {String} host - The host.
* @param {Object} user - The user.
* @param {Object} createdByAdmin - Whether user created by admin.
* @returns {Promise.<mongo.ObjectId>} that resolves account id of merge operation.
*/
static create(host, user, createdByAdmin) {
return mongo.Account.count().exec()
.then((cnt) => {
user.admin = cnt === 0;
user.registered = new Date();
user.token = utilsService.randomString(settings.tokenLength);
user.resetPasswordToken = utilsService.randomString(settings.tokenLength);
user.activated = false;
if (settings.server.disableSignup && !user.admin && !createdByAdmin)
throw new errors.ServerErrorException('Sign-up is not allowed. Ask your Web Console administrator to create account for you.');
return new mongo.Account(user);
})
.then((created) => {
return new Promise((resolve, reject) => {
mongo.Account.register(created, user.password, (err, registered) => {
if (err)
reject(err);
if (!registered)
reject(new errors.ServerErrorException('Failed to register user.'));
resolve(registered);
});
});
})
.then((registered) => {
return mongo.Space.create({name: 'Personal space', owner: registered._id})
.then(() => registered);
})
.then((registered) => {
if (settings.activation.enabled) {
registered.activationToken = utilsService.randomString(settings.tokenLength);
registered.activationSentAt = new Date();
if (!createdByAdmin) {
return registered.save()
.then(() => {
mailsService.emailUserActivation(host, registered);
throw new errors.MissingConfirmRegistrationException(registered.email);
});
}
}
mailsService.emailUserSignUp(host, registered, createdByAdmin);
return registered;
});
}
/**
* Save user.
*
* @param userId User ID.
* @param {Object} changed Changed user.
* @returns {Promise.<mongo.ObjectId>} that resolves account id of merge operation.
*/
static save(userId, changed) {
delete changed.admin;
delete changed.activated;
delete changed.activationSentAt;
delete changed.activationToken;
return mongo.Account.findById(userId).exec()
.then((user) => {
if (!changed.password)
return Promise.resolve(user);
return new Promise((resolve, reject) => {
user.setPassword(changed.password, (err, _user) => {
if (err)
return reject(err);
delete changed.password;
resolve(_user);
});
});
})
.then((user) => {
if (!changed.email || user.email === changed.email)
return Promise.resolve(user);
return new Promise((resolve, reject) => {
mongo.Account.findOne({email: changed.email}, (err, _user) => {
// TODO send error to admin
if (err)
reject(new Error('Failed to check email!'));
if (_user && _user._id !== user._id)
reject(new Error('User with this email already registered!'));
resolve(user);
});
});
})
.then((user) => {
if (changed.token && user.token !== changed.token)
agentHnd.onTokenReset(user);
_.extend(user, changed);
return user.save();
});
}
/**
* Get list of user accounts and summary information.
*
* @returns {mongo.Account[]} - returns all accounts with counters object
*/
static list(params) {
return Promise.all([
Promise.all([
mongo.Account.aggregate([
{$lookup: {from: 'spaces', localField: '_id', foreignField: 'owner', as: 'spaces'}},
{$project: {
_id: 1,
firstName: 1,
lastName: 1,
admin: 1,
email: 1,
company: 1,
country: 1,
lastLogin: 1,
lastActivity: 1,
activated: 1,
spaces: {
$filter: {
input: '$spaces',
as: 'space',
cond: {$eq: ['$$space.demo', false]}
}
}
}},
{ $sort: {firstName: 1, lastName: 1}}
]).exec(),
mongo.Cluster.aggregate([{$group: {_id: '$space', count: { $sum: 1 }}}]).exec(),
mongo.Cache.aggregate([{$group: {_id: '$space', count: { $sum: 1 }}}]).exec(),
mongo.DomainModel.aggregate([{$group: {_id: '$space', count: { $sum: 1 }}}]).exec(),
mongo.Igfs.aggregate([{$group: {_id: '$space', count: { $sum: 1 }}}]).exec()
]).then(([users, clusters, caches, models, igfs]) => {
const clustersMap = _.mapValues(_.keyBy(clusters, '_id'), 'count');
const cachesMap = _.mapValues(_.keyBy(caches, '_id'), 'count');
const modelsMap = _.mapValues(_.keyBy(models, '_id'), 'count');
const igfsMap = _.mapValues(_.keyBy(igfs, '_id'), 'count');
_.forEach(users, (user) => {
const counters = user.counters = {};
counters.clusters = _.sumBy(user.spaces, ({_id}) => clustersMap[_id]) || 0;
counters.caches = _.sumBy(user.spaces, ({_id}) => cachesMap[_id]) || 0;
counters.models = _.sumBy(user.spaces, ({_id}) => modelsMap[_id]) || 0;
counters.igfs = _.sumBy(user.spaces, ({_id}) => igfsMap[_id]) || 0;
delete user.spaces;
});
return users;
}),
activitiesService.total(params),
activitiesService.detail(params)
])
.then(([users, activitiesTotal, activitiesDetail]) => {
_.forEach(users, (user) => {
user.activitiesTotal = activitiesTotal[user._id];
user.activitiesDetail = activitiesDetail[user._id];
});
return users;
});
}
/**
* Remove account.
*
* @param {String} host.
* @param {mongo.ObjectId|String} userId - The account id for remove.
* @returns {Promise.<{rowsAffected}>} - The number of affected rows.
*/
static remove(host, userId) {
return mongo.Account.findByIdAndRemove(userId).exec()
.then((user) => {
return spacesService.spaceIds(userId)
.then((spaceIds) => Promise.all([
mongo.Cluster.remove({space: {$in: spaceIds}}).exec(),
mongo.Cache.remove({space: {$in: spaceIds}}).exec(),
mongo.DomainModel.remove({space: {$in: spaceIds}}).exec(),
mongo.Igfs.remove({space: {$in: spaceIds}}).exec(),
mongo.Notebook.remove({space: {$in: spaceIds}}).exec(),
mongo.Space.remove({owner: userId}).exec()
]))
.catch((err) => console.error(`Failed to cleanup spaces [user=${user.username}, err=${err}`))
.then(() => user);
})
.then((user) => mailsService.emailUserDeletion(host, user));
}
/**
* Get account information.
*/
static get(user, viewedUser) {
if (_.isNil(user))
return Promise.reject(new errors.AuthFailedException('The user profile service failed the sign in. User profile cannot be loaded.'));
const becomeUsed = viewedUser && user.admin;
if (becomeUsed)
user = _.extend({}, viewedUser, {becomeUsed: true, becameToken: user.token});
else
user = user.toJSON();
return mongo.Space.findOne({owner: user._id, demo: true}).exec()
.then((demoSpace) => {
if (user && demoSpace)
user.demoCreated = true;
return user;
});
}
}
return UsersService;
};