export.js 2.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  1. /* global JsonRoutes */
  2. if(Meteor.isServer) {
  3. JsonRoutes.add('get', '/api/b/:boardId/:userId/:loginToken', function (req, res) {
  4. const { userId, loginToken, boardId } = req.params;
  5. const hashToken = Accounts._hashLoginToken(loginToken);
  6. const user = Meteor.users.findOne({
  7. _id: userId,
  8. 'services.resume.loginTokens.hashedToken': hashToken,
  9. });
  10. const exporter = new Exporter(boardId);
  11. if(user && exporter.canExport(user)) {
  12. JsonRoutes.sendResult(res, 200, exporter.build());
  13. } else {
  14. // we could send an explicit error message, but on the other
  15. // hand the only way to get there is by hacking the UI so...
  16. JsonRoutes.sendResult(res, 403);
  17. }
  18. });
  19. }
  20. Meteor.methods({
  21. exportBoard(boardId) {
  22. check(boardId, String);
  23. const exporter = new Exporter(boardId);
  24. if(exporter.canExport(Meteor.user())) {
  25. return exporter.build();
  26. } else {
  27. throw new Meteor.Error('error-board-notAMember');
  28. }
  29. },
  30. });
  31. class Exporter {
  32. constructor(boardId) {
  33. this._boardId = boardId;
  34. }
  35. build() {
  36. const byBoard = {boardId: this._boardId};
  37. // we do not want to retrieve boardId in related elements
  38. const noBoardId = {fields: {boardId: 0}};
  39. const result = Boards.findOne(this._boardId, {fields: {stars: 0}});
  40. result.lists = Lists.find(byBoard, noBoardId).fetch();
  41. result.cards = Cards.find(byBoard, noBoardId).fetch();
  42. result.comments = CardComments.find(byBoard, noBoardId).fetch();
  43. result.activities = Activities.find(byBoard, noBoardId).fetch();
  44. // we also have to export some user data - as the other elements only include id
  45. // but we have to be careful:
  46. // 1- only exports users that are linked somehow to that board
  47. // 2- do not export any sensitive information
  48. const users = {};
  49. result.members.forEach((member) => {users[member.userId] = true;});
  50. result.lists.forEach((list) => {users[list.userId] = true;});
  51. result.cards.forEach((card) => {
  52. users[card.userId] = true;
  53. if (card.members) {
  54. card.members.forEach((memberId) => {users[memberId] = true;});
  55. }
  56. });
  57. result.comments.forEach((comment) => {users[comment.userId] = true;});
  58. result.activities.forEach((activity) => {users[activity.userId] = true;});
  59. const byUserIds = {_id: {$in: Object.getOwnPropertyNames(users)}};
  60. // we use whitelist to be sure we do not expose inadvertently
  61. // some secret fields that gets added to User later.
  62. const userFields = {fields: {
  63. _id: 1,
  64. username: 1,
  65. 'profile.fullname': 1,
  66. 'profile.initials': 1,
  67. 'profile.avatarUrl': 1,
  68. }};
  69. result.users = Users.find(byUserIds, userFields).fetch();
  70. return result;
  71. }
  72. canExport(user) {
  73. const board = Boards.findOne(this._boardId);
  74. return board && board.isVisibleBy(user);
  75. }
  76. }