diff options
author | floatinghotpot <rjfun.mobile@gmail.com> | 2015-12-07 11:15:57 +0800 |
---|---|---|
committer | floatinghotpot <rjfun.mobile@gmail.com> | 2015-12-07 11:15:57 +0800 |
commit | 011f53ad0828c0979d15e232abf501180c741288 (patch) | |
tree | 41330fe4e47c443dd9fefd0493f30a186e4c4999 /models | |
parent | d4c5310d65cbdfbd002288d33eba429ace33bc3c (diff) | |
download | wekan-011f53ad0828c0979d15e232abf501180c741288.tar.gz wekan-011f53ad0828c0979d15e232abf501180c741288.tar.bz2 wekan-011f53ad0828c0979d15e232abf501180c741288.zip |
add: invite user via email, invited user can accept or decline, allow member to quit
Diffstat (limited to 'models')
-rw-r--r-- | models/boards.js | 86 | ||||
-rw-r--r-- | models/users.js | 105 |
2 files changed, 172 insertions, 19 deletions
diff --git a/models/boards.js b/models/boards.js index 98d6ec77..c10e51a3 100644 --- a/models/boards.js +++ b/models/boards.js @@ -80,8 +80,7 @@ Boards.helpers({ }, lists() { - return Lists.find({ boardId: this._id, archived: false }, - { sort: { sort: 1 }}); + return Lists.find({ boardId: this._id, archived: false }, { sort: { sort: 1 }}); }, activities() { @@ -92,6 +91,14 @@ Boards.helpers({ return _.where(this.members, {isActive: true}); }, + activeAdmins() { + return _.where(this.members, {isActive: true, isAdmin: true}); + }, + + memberUsers() { + return Users.find({ _id: {$in: _.pluck(this.members, 'userId')} }); + }, + getLabel(name, color) { return _.findWhere(this.labels, { name, color }); }, @@ -172,20 +179,30 @@ Boards.mutations({ addMember(memberId) { const memberIndex = this.memberIndex(memberId); if (memberIndex === -1) { - return { - $push: { - members: { - userId: memberId, - isAdmin: false, - isActive: true, + const xIndex = this.memberIndex('x'); + if (xIndex === -1) { + return { + $push: { + members: { + userId: memberId, + isAdmin: false, + isActive: true, + }, }, - }, - }; + }; + } else { + return { + $set: { + [`members.${xIndex}.userId`]: memberId, + [`members.${xIndex}.isActive`]: true, + [`members.${xIndex}.isAdmin`]: false, + }, + }; + } } else { return { $set: { [`members.${memberIndex}.isActive`]: true, - [`members.${memberIndex}.isAdmin`]: false, }, }; } @@ -194,16 +211,34 @@ Boards.mutations({ removeMember(memberId) { const memberIndex = this.memberIndex(memberId); - return { - $set: { - [`members.${memberIndex}.isActive`]: false, - }, - }; + // we do not allow the only one admin to be removed + const allowRemove = (!this.members[memberIndex].isAdmin) || (this.activeAdmins().length > 1); + + if (allowRemove) { + return { + $set: { + [`members.${memberIndex}.userId`]: 'x', + [`members.${memberIndex}.isActive`]: false, + [`members.${memberIndex}.isAdmin`]: false, + }, + }; + } else { + return { + $set: { + [`members.${memberIndex}.isActive`]: true, + }, + }; + } }, setMemberPermission(memberId, isAdmin) { const memberIndex = this.memberIndex(memberId); + // do not allow change permission of self + if (memberId === Meteor.userId()) { + isAdmin = this.members[memberIndex].isAdmin; + } + return { $set: { [`members.${memberIndex}.isAdmin`]: isAdmin, @@ -240,9 +275,7 @@ if (Meteor.isServer) { return false; // If there is more than one admin, it's ok to remove anyone - const nbAdmins = _.filter(doc.members, (member) => { - return member.isAdmin; - }).length; + const nbAdmins = _.where(doc.members, {isActive: true, isAdmin: true}).length; if (nbAdmins > 1) return false; @@ -256,6 +289,21 @@ if (Meteor.isServer) { }, fetch: ['members'], }); + + Meteor.methods({ + quitBoard(boardId) { + check(boardId, String); + const board = Boards.findOne(boardId); + if (board) { + const userId = Meteor.userId(); + const index = board.memberIndex(userId); + if (index>=0) { + board.removeMember(userId); + return true; + } else throw new Meteor.Error('error-board-notAMember'); + } else throw new Meteor.Error('error-board-doesNotExist'); + }, + }); } Boards.before.insert((userId, doc) => { diff --git a/models/users.js b/models/users.js index 49c30127..2c9ae380 100644 --- a/models/users.js +++ b/models/users.js @@ -41,6 +41,16 @@ Users.helpers({ return _.contains(starredBoards, boardId); }, + invitedBoards() { + const {invitedBoards = []} = this.profile; + return Boards.find({archived: false, _id: {$in: invitedBoards}}); + }, + + isInvitedTo(boardId) { + const {invitedBoards = []} = this.profile; + return _.contains(invitedBoards, boardId); + }, + getAvatarUrl() { // Although we put the avatar picture URL in the `profile` object, we need // to support Sandstorm which put in the `picture` attribute by default. @@ -90,6 +100,22 @@ Users.mutations({ }; }, + addInvite(boardId) { + return { + $addToSet: { + 'profile.invitedBoards': boardId, + }, + }; + }, + + removeInvite(boardId) { + return { + $pull: { + 'profile.invitedBoards': boardId, + }, + }; + }, + setAvatarUrl(avatarUrl) { return { $set: { 'profile.avatarUrl': avatarUrl }}; }, @@ -107,6 +133,85 @@ Meteor.methods({ }, }); +if (Meteor.isServer) { + Meteor.methods({ + // we accept userId, username, email + inviteUserToBoard(username, boardId) { + check(username, String); + check(boardId, String); + + const inviter = Meteor.user(); + const board = Boards.findOne(boardId); + const allowInvite = inviter && + board && + board.members && + _.contains(_.pluck(board.members, 'userId'), inviter._id) && + _.where(board.members, {userId: inviter._id})[0].isActive && + _.where(board.members, {userId: inviter._id})[0].isAdmin; + if (!allowInvite) throw new Meteor.Error('error-board-notAMember'); + + this.unblock(); + + const posAt = username.indexOf('@'); + let user = null; + if (posAt>=0) { + user = Users.findOne({emails: {$elemMatch: {address: username}}}); + } else { + user = Users.findOne(username) || Users.findOne({ username }); + } + if (user) { + if (user._id === inviter._id) throw new Meteor.Error('error-user-notAllowSelf'); + } else { + if (posAt <= 0) throw new Meteor.Error('error-user-doesNotExist'); + + const email = username; + username = email.substring(0, posAt); + const newUserId = Accounts.createUser({ username, email }); + if (!newUserId) throw new Meteor.Error('error-user-notCreated'); + // assume new user speak same language with inviter + if (inviter.profile && inviter.profile.language) { + Users.update(newUserId, { + $set: { + 'profile.language': inviter.profile.language, + }, + }); + } + Accounts.sendEnrollmentEmail(newUserId); + user = Users.findOne(newUserId); + } + + board.addMember(user._id); + user.addInvite(boardId); + + if (!process.env.MAIL_URL || (!Email)) return { username: user.username }; + + try { + let rootUrl = Meteor.absoluteUrl.defaultOptions.rootUrl || ''; + if (!rootUrl.endsWith('/')) rootUrl = `${rootUrl}/`; + const boardUrl = `${rootUrl}b/${board._id}/${board.slug}`; + + const vars = { + user: user.username, + inviter: inviter.username, + board: board.title, + url: boardUrl, + }; + const lang = user.getLanguage(); + Email.send({ + to: user.emails[0].address, + from: Accounts.emailTemplates.from, + subject: TAPi18n.__('email-invite-subject', vars, lang), + text: TAPi18n.__('email-invite-text', vars, lang), + }); + } catch (e) { + throw new Meteor.Error('email-fail', e.message); + } + + return { username: user.username, email: user.emails[0].address }; + }, + }); +} + Users.before.insert((userId, doc) => { doc.profile = doc.profile || {}; |