| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607 | // Sandstorm context is detected using the METEOR_SETTINGS environment variable// in the package definition.const isSandstorm = Meteor.settings && Meteor.settings.public &&  Meteor.settings.public.sandstorm;Users = Meteor.users;Users.attachSchema(new SimpleSchema({  username: {    type: String,    optional: true,    autoValue() { // eslint-disable-line consistent-return      if (this.isInsert && !this.isSet) {        const name = this.field('profile.fullname');        if (name.isSet) {          return name.value.toLowerCase().replace(/\s/g, '');        }      }    },  },  emails: {    type: [Object],    optional: true,  },  'emails.$.address': {    type: String,    regEx: SimpleSchema.RegEx.Email,  },  'emails.$.verified': {    type: Boolean,  },  createdAt: {    type: Date,    autoValue() { // eslint-disable-line consistent-return      if (this.isInsert) {        return new Date();      } else {        this.unset();      }    },  },  profile: {    type: Object,    optional: true,    autoValue() { // eslint-disable-line consistent-return      if (this.isInsert && !this.isSet) {        return {};      }    },  },  'profile.avatarUrl': {    type: String,    optional: true,  },  'profile.emailBuffer': {    type: [String],    optional: true,  },  'profile.fullname': {    type: String,    optional: true,  },  'profile.hiddenSystemMessages': {    type: Boolean,    optional: true,  },  'profile.initials': {    type: String,    optional: true,  },  'profile.invitedBoards': {    type: [String],    optional: true,  },  'profile.language': {    type: String,    optional: true,  },  'profile.notifications': {    type: [String],    optional: true,  },  'profile.showCardsCountAt': {    type: Number,    optional: true,  },  'profile.starredBoards': {    type: [String],    optional: true,  },  'profile.tags': {    type: [String],    optional: true,  },  'profile.icode': {    type: String,    optional: true,  },  services: {    type: Object,    optional: true,    blackbox: true,  },  heartbeat: {    type: Date,    optional: true,  },  isAdmin: {    type: Boolean,    optional: true,  },}));// Search a user in the complete server database by its name or username. This// is used for instance to add a new user to a board.const searchInFields = ['username', 'profile.fullname'];Users.initEasySearch(searchInFields, {  use: 'mongo-db',  returnFields: [...searchInFields, 'profile.avatarUrl'],});if (Meteor.isClient) {  Users.helpers({    isBoardMember() {      const board = Boards.findOne(Session.get('currentBoard'));      return board && board.hasMember(this._id);    },    isNotCommentOnly() {      const board = Boards.findOne(Session.get('currentBoard'));      return board && board.hasMember(this._id) && !board.hasCommentOnly(this._id);    },    isCommentOnly() {      const board = Boards.findOne(Session.get('currentBoard'));      return board && board.hasCommentOnly(this._id);    },    isBoardAdmin() {      const board = Boards.findOne(Session.get('currentBoard'));      return board && board.hasAdmin(this._id);    },  });}Users.helpers({  boards() {    return Boards.find({ userId: this._id });  },  starredBoards() {    const { starredBoards = [] } = this.profile;    return Boards.find({ archived: false, _id: { $in: starredBoards } });  },  hasStarred(boardId) {    const { starredBoards = [] } = this.profile;    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);  },  hasTag(tag) {    const { tags = [] } = this.profile;    return _.contains(tags, tag);  },  hasNotification(activityId) {    const { notifications = [] } = this.profile;    return _.contains(notifications, activityId);  },  hasHiddenSystemMessages() {    const profile = this.profile || {};    return profile.hiddenSystemMessages || false;  },  getEmailBuffer() {    const { emailBuffer = [] } = this.profile;    return emailBuffer;  },  getInitials() {    const profile = this.profile || {};    if (profile.initials)      return profile.initials;    else if (profile.fullname) {      return profile.fullname.split(/\s+/).reduce((memo, word) => {        return memo + word[0];      }, '').toUpperCase();    } else {      return this.username[0].toUpperCase();    }  },  getLimitToShowCardsCount() {    const profile = this.profile || {};    return profile.showCardsCountAt;  },  getName() {    const profile = this.profile || {};    return profile.fullname || this.username;  },  getLanguage() {    const profile = this.profile || {};    return profile.language || 'en';  },});Users.mutations({  toggleBoardStar(boardId) {    const queryKind = this.hasStarred(boardId) ? '$pull' : '$addToSet';    return {      [queryKind]: {        'profile.starredBoards': boardId,      },    };  },  addInvite(boardId) {    return {      $addToSet: {        'profile.invitedBoards': boardId,      },    };  },  removeInvite(boardId) {    return {      $pull: {        'profile.invitedBoards': boardId,      },    };  },  addTag(tag) {    return {      $addToSet: {        'profile.tags': tag,      },    };  },  removeTag(tag) {    return {      $pull: {        'profile.tags': tag,      },    };  },  toggleTag(tag) {    if (this.hasTag(tag))      this.removeTag(tag);    else      this.addTag(tag);  },  toggleSystem(value = false) {    return {      $set: {        'profile.hiddenSystemMessages': !value,      },    };  },  addNotification(activityId) {    return {      $addToSet: {        'profile.notifications': activityId,      },    };  },  removeNotification(activityId) {    return {      $pull: {        'profile.notifications': activityId,      },    };  },  addEmailBuffer(text) {    return {      $addToSet: {        'profile.emailBuffer': text,      },    };  },  clearEmailBuffer() {    return {      $set: {        'profile.emailBuffer': [],      },    };  },  setAvatarUrl(avatarUrl) {    return { $set: { 'profile.avatarUrl': avatarUrl } };  },  setShowCardsCountAt(limit) {    return { $set: { 'profile.showCardsCountAt': limit } };  },});Meteor.methods({  setUsername(username) {    check(username, String);    const nUsersWithUsername = Users.find({ username }).count();    if (nUsersWithUsername > 0) {      throw new Meteor.Error('username-already-taken');    } else {      Users.update(this.userId, { $set: { username } });    }  },  toggleSystemMessages() {    const user = Meteor.user();    user.toggleSystem(user.hasHiddenSystemMessages());  },  changeLimitToShowCardsCount(limit) {    check(limit, Number);    Meteor.user().setShowCardsCountAt(limit);  },  setEmail(email) {    check(email, String);    const existingUser = Users.findOne({ 'emails.address': email }, { fields: { _id: 1 } });    if (existingUser) {      throw new Meteor.Error('email-already-taken');    } else {      Users.update(this.userId, {        $set: {          emails: [{            address: email,            verified: false,          }],        },      });    }  },  setUsernameAndEmail(username, email) {    check(username, String);    check(email, String);    Meteor.call('setUsername', username);    Meteor.call('setEmail', email);  },});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');        if (Settings.findOne().disableRegistration) throw new Meteor.Error('error-user-notCreated');        // Set in lowercase email before creating account        const email = username.toLowerCase();        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);      try {        const params = {          user: user.username,          inviter: inviter.username,          board: board.title,          url: board.absoluteUrl(),        };        const lang = user.getLanguage();        Email.send({          to: user.emails[0].address.toLowerCase(),          from: Accounts.emailTemplates.from,          subject: TAPi18n.__('email-invite-subject', params, lang),          text: TAPi18n.__('email-invite-text', params, lang),        });      } catch (e) {        throw new Meteor.Error('email-fail', e.message);      }      return { username: user.username, email: user.emails[0].address };    },  });  Accounts.onCreateUser((options, user) => {    const userCount = Users.find().count();    if (!isSandstorm && userCount === 0) {      user.isAdmin = true;      return user;    }    const disableRegistration = Settings.findOne().disableRegistration;    if (!disableRegistration) {      return user;    }    if (!options || !options.profile) {      throw new Meteor.Error('error-invitation-code-blank', 'The invitation code is required');    }    const invitationCode = InvitationCodes.findOne({ code: options.profile.invitationcode, email: options.email, valid: true });    if (!invitationCode) {      throw new Meteor.Error('error-invitation-code-not-exist', 'The invitation code doesn\'t exist');    } else {      user.profile = { icode: options.profile.invitationcode };    }    return user;  });}if (Meteor.isServer) {  // Let mongoDB ensure username unicity  Meteor.startup(() => {    Users._collection._ensureIndex({      username: 1,    }, { unique: true });  });  // Each board document contains the de-normalized number of users that have  // starred it. If the user star or unstar a board, we need to update this  // counter.  // We need to run this code on the server only, otherwise the incrementation  // will be done twice.  Users.after.update(function (userId, user, fieldNames) {    // The `starredBoards` list is hosted on the `profile` field. If this    // field hasn't been modificated we don't need to run this hook.    if (!_.contains(fieldNames, 'profile'))      return;    // To calculate a diff of board starred ids, we get both the previous    // and the newly board ids list    function getStarredBoardsIds(doc) {      return doc.profile && doc.profile.starredBoards;    }    const oldIds = getStarredBoardsIds(this.previous);    const newIds = getStarredBoardsIds(user);    // The _.difference(a, b) method returns the values from a that are not in    // b. We use it to find deleted and newly inserted ids by using it in one    // direction and then in the other.    function incrementBoards(boardsIds, inc) {      boardsIds.forEach((boardId) => {        Boards.update(boardId, { $inc: { stars: inc } });      });    }    incrementBoards(_.difference(oldIds, newIds), -1);    incrementBoards(_.difference(newIds, oldIds), +1);  });  const fakeUserId = new Meteor.EnvironmentVariable();  const getUserId = CollectionHooks.getUserId;  CollectionHooks.getUserId = () => {    return fakeUserId.get() || getUserId();  };  if (!isSandstorm) {    Users.after.insert((userId, doc) => {      const fakeUser = {        extendAutoValueContext: {          userId: doc._id,        },      };      fakeUserId.withValue(doc._id, () => {        // Insert the Welcome Board        Boards.insert({          title: TAPi18n.__('welcome-board'),          permission: 'private',        }, fakeUser, (err, boardId) => {          ['welcome-list1', 'welcome-list2'].forEach((title) => {            Lists.insert({ title: TAPi18n.__(title), boardId }, fakeUser);          });        });      });    });  }  Users.after.insert((userId, doc) => {    //invite user to corresponding boards    const disableRegistration = Settings.findOne().disableRegistration;    if (disableRegistration) {      const invitationCode = InvitationCodes.findOne({ code: doc.profile.icode, valid: true });      if (!invitationCode) {        throw new Meteor.Error('error-invitation-code-not-exist');      } else {        invitationCode.boardsToBeInvited.forEach((boardId) => {          const board = Boards.findOne(boardId);          board.addMember(doc._id);        });        if (!doc.profile) {          doc.profile = {};        }        doc.profile.invitedBoards = invitationCode.boardsToBeInvited;        Users.update(doc._id, { $set: { profile: doc.profile } });        InvitationCodes.update(invitationCode._id, { $set: { valid: false } });      }    }  });}// USERS REST APIif (Meteor.isServer) {  JsonRoutes.add('GET', '/api/user', function(req, res, next) {    Authentication.checkLoggedIn(req.userId);    const data = Meteor.users.findOne({ _id: req.userId});    delete data.services;    JsonRoutes.sendResult(res, {      code: 200,      data,    });  });  JsonRoutes.add('GET', '/api/users', function (req, res, next) {    Authentication.checkUserId( req.userId);    JsonRoutes.sendResult(res, {      code: 200,      data: Meteor.users.find({}).map(function (doc) {        return { _id: doc._id, username: doc.username };      }),    });  });  JsonRoutes.add('GET', '/api/users/:id', function (req, res, next) {    Authentication.checkUserId( req.userId);    const id = req.params.id;    JsonRoutes.sendResult(res, {      code: 200,      data: Meteor.users.findOne({ _id: id }),    });  });  JsonRoutes.add('POST', '/api/users/', function (req, res, next) {    Authentication.checkUserId( req.userId);    const id = Accounts.createUser({      username: req.body.username,      email: req.body.email,      password: 'default',    });    JsonRoutes.sendResult(res, {      code: 200,      data: {        _id: id,      },    });  });  JsonRoutes.add('DELETE', '/api/users/:id', function (req, res, next) {    Authentication.checkUserId( req.userId);    const id = req.params.id;    Meteor.users.remove({ _id: id });    JsonRoutes.sendResult(res, {      code: 200,      data: {        _id: id,      },    });  });}
 |