users.js 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. Users = Meteor.users; // eslint-disable-line meteor/collections
  2. // Search a user in the complete server database by its name or username. This
  3. // is used for instance to add a new user to a board.
  4. const searchInFields = ['username', 'profile.fullname'];
  5. Users.initEasySearch(searchInFields, {
  6. use: 'mongo-db',
  7. returnFields: [...searchInFields, 'profile.avatarUrl'],
  8. });
  9. if (Meteor.isClient) {
  10. Users.helpers({
  11. isBoardMember() {
  12. const board = Boards.findOne(Session.get('currentBoard'));
  13. return board &&
  14. _.contains(_.pluck(board.members, 'userId'), this._id) &&
  15. _.where(board.members, {userId: this._id})[0].isActive;
  16. },
  17. isBoardAdmin() {
  18. const board = Boards.findOne(Session.get('currentBoard'));
  19. return board &&
  20. this.isBoardMember(board) &&
  21. _.where(board.members, {userId: this._id})[0].isAdmin;
  22. },
  23. });
  24. }
  25. Users.helpers({
  26. boards() {
  27. return Boards.find({ userId: this._id });
  28. },
  29. starredBoards() {
  30. const {starredBoards = []} = this.profile;
  31. return Boards.find({archived: false, _id: {$in: starredBoards}});
  32. },
  33. hasStarred(boardId) {
  34. const {starredBoards = []} = this.profile;
  35. return _.contains(starredBoards, boardId);
  36. },
  37. getAvatarUrl() {
  38. // Although we put the avatar picture URL in the `profile` object, we need
  39. // to support Sandstorm which put in the `picture` attribute by default.
  40. // XXX Should we move both cases to `picture`?
  41. if (this.picture) {
  42. return this.picture;
  43. } else if (this.profile && this.profile.avatarUrl) {
  44. return this.profile.avatarUrl;
  45. } else {
  46. return null;
  47. }
  48. },
  49. getInitials() {
  50. const profile = this.profile || {};
  51. if (profile.initials)
  52. return profile.initials;
  53. else if (profile.fullname) {
  54. return profile.fullname.split(/\s+/).reduce((memo = '', word) => {
  55. return memo + word[0];
  56. }).toUpperCase();
  57. } else {
  58. return this.username[0].toUpperCase();
  59. }
  60. },
  61. });
  62. Users.mutations({
  63. toggleBoardStar(boardId) {
  64. const queryKind = this.hasStarred(boardId) ? '$pull' : '$addToSet';
  65. return {
  66. [queryKind]: {
  67. 'profile.starredBoards': boardId,
  68. },
  69. };
  70. },
  71. setAvatarUrl(avatarUrl) {
  72. return { $set: { 'profile.avatarUrl': avatarUrl }};
  73. },
  74. });
  75. Meteor.methods({
  76. setUsername(username) {
  77. check(username, String);
  78. const nUsersWithUsername = Users.find({ username }).count();
  79. if (nUsersWithUsername > 0) {
  80. throw new Meteor.Error('username-already-taken');
  81. } else {
  82. Users.update(this.userId, {$set: { username }});
  83. }
  84. },
  85. });
  86. Users.before.insert((userId, doc) => {
  87. doc.profile = doc.profile || {};
  88. if (!doc.username && doc.profile.name) {
  89. doc.username = doc.profile.name.toLowerCase().replace(/\s/g, '');
  90. }
  91. });
  92. if (Meteor.isServer) {
  93. // Let mongoDB ensure username unicity
  94. Meteor.startup(() => {
  95. Users._collection._ensureIndex({
  96. username: 1,
  97. }, { unique: true });
  98. });
  99. // Each board document contains the de-normalized number of users that have
  100. // starred it. If the user star or unstar a board, we need to update this
  101. // counter.
  102. // We need to run this code on the server only, otherwise the incrementation
  103. // will be done twice.
  104. Users.after.update(function(userId, user, fieldNames) {
  105. // The `starredBoards` list is hosted on the `profile` field. If this
  106. // field hasn't been modificated we don't need to run this hook.
  107. if (!_.contains(fieldNames, 'profile'))
  108. return;
  109. // To calculate a diff of board starred ids, we get both the previous
  110. // and the newly board ids list
  111. function getStarredBoardsIds(doc) {
  112. return doc.profile && doc.profile.starredBoards;
  113. }
  114. const oldIds = getStarredBoardsIds(this.previous);
  115. const newIds = getStarredBoardsIds(user);
  116. // The _.difference(a, b) method returns the values from a that are not in
  117. // b. We use it to find deleted and newly inserted ids by using it in one
  118. // direction and then in the other.
  119. function incrementBoards(boardsIds, inc) {
  120. boardsIds.forEach((boardId) => {
  121. Boards.update(boardId, {$inc: {stars: inc}});
  122. });
  123. }
  124. incrementBoards(_.difference(oldIds, newIds), -1);
  125. incrementBoards(_.difference(newIds, oldIds), +1);
  126. });
  127. // XXX i18n
  128. Users.after.insert((userId, doc) => {
  129. const ExampleBoard = {
  130. title: 'Welcome Board',
  131. userId: doc._id,
  132. permission: 'private',
  133. };
  134. // Insert the Welcome Board
  135. Boards.insert(ExampleBoard, (err, boardId) => {
  136. ['Basics', 'Advanced'].forEach((title) => {
  137. const list = {
  138. title,
  139. boardId,
  140. userId: ExampleBoard.userId,
  141. // XXX Not certain this is a bug, but we except these fields get
  142. // inserted by the Lists.before.insert collection-hook. Since this
  143. // hook is not called in this case, we have to dublicate the logic and
  144. // set them here.
  145. archived: false,
  146. createdAt: new Date(),
  147. };
  148. Lists.insert(list);
  149. });
  150. });
  151. });
  152. }