| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134 | 
							- /* global JsonRoutes */
 
- if (Meteor.isServer) {
 
-   // todo XXX once we have a real API in place, move that route there
 
-   // todo XXX also  share the route definition between the client and the server
 
-   // so that we could use something like
 
-   // `ApiRoutes.path('boards/export', boardId)``
 
-   // on the client instead of copy/pasting the route path manually between the
 
-   // client and the server.
 
-   /*
 
-    * This route is used to export the board FROM THE APPLICATION.
 
-    * If user is already logged-in, pass loginToken as param "authToken":
 
-    * '/api/boards/:boardId/export?authToken=:token'
 
-    *
 
-    * See https://blog.kayla.com.au/server-side-route-authentication-in-meteor/
 
-    * for detailed explanations
 
-    */
 
-   JsonRoutes.add('get', '/api/boards/:boardId/export', function (req, res) {
 
-     const boardId = req.params.boardId;
 
-     let user = null;
 
-     // todo XXX for real API, first look for token in Authentication: header
 
-     // then fallback to parameter
 
-     const loginToken = req.query.authToken;
 
-     if (loginToken) {
 
-       const hashToken = Accounts._hashLoginToken(loginToken);
 
-       user = Meteor.users.findOne({
 
-         'services.resume.loginTokens.hashedToken': hashToken,
 
-       });
 
-     }
 
-     const exporter = new Exporter(boardId);
 
-     if(exporter.canExport(user)) {
 
-       JsonRoutes.sendResult(res, { code: 200, data: exporter.build() });
 
-     } else {
 
-       // we could send an explicit error message, but on the other hand the only
 
-       // way to get there is by hacking the UI so let's keep it raw.
 
-       JsonRoutes.sendResult(res, 403);
 
-     }
 
-   });
 
- }
 
- class Exporter {
 
-   constructor(boardId) {
 
-     this._boardId = boardId;
 
-   }
 
-   build() {
 
-     const byBoard = { boardId: this._boardId };
 
-     // we do not want to retrieve boardId in related elements
 
-     const noBoardId = { fields: { boardId: 0 } };
 
-     const result = {
 
-       _format: 'wekan-board-1.0.0',
 
-     };
 
-     _.extend(result, Boards.findOne(this._boardId, { fields: { stars: 0 } }));
 
-     result.lists = Lists.find(byBoard, noBoardId).fetch();
 
-     result.cards = Cards.find(byBoard, noBoardId).fetch();
 
-     result.swimlanes = Swimlanes.find(byBoard, noBoardId).fetch();
 
-     result.comments = CardComments.find(byBoard, noBoardId).fetch();
 
-     result.activities = Activities.find(byBoard, noBoardId).fetch();
 
-     result.checklists = [];
 
-     result.cards.forEach((card) => {
 
-       result.checklists.push(...Checklists.find({ cardId: card._id }).fetch());
 
-     });
 
-     // [Old] for attachments we only export IDs and absolute url to original doc
 
-     // [New] Encode attachment to base64
 
-     const getBase64Data = function(doc, callback) {
 
-       let buffer = new Buffer(0);
 
-       // callback has the form function (err, res) {}
 
-       const readStream = doc.createReadStream();
 
-       readStream.on('data', function(chunk) {
 
-         buffer = Buffer.concat([buffer, chunk]);
 
-       });
 
-       readStream.on('error', function(err) {
 
-         callback(err, null);
 
-       });
 
-       readStream.on('end', function() {
 
-         // done
 
-         callback(null, buffer.toString('base64'));
 
-       });
 
-     };
 
-     const getBase64DataSync = Meteor.wrapAsync(getBase64Data);
 
-     result.attachments = Attachments.find(byBoard).fetch().map((attachment) => {
 
-       return {
 
-         _id: attachment._id,
 
-         cardId: attachment.cardId,
 
-         // url: FlowRouter.url(attachment.url()),
 
-         file: getBase64DataSync(attachment),
 
-         name: attachment.original.name,
 
-         type: attachment.original.type,
 
-       };
 
-     });
 
-     // we also have to export some user data - as the other elements only
 
-     // include id but we have to be careful:
 
-     // 1- only exports users that are linked somehow to that board
 
-     // 2- do not export any sensitive information
 
-     const users = {};
 
-     result.members.forEach((member) => { users[member.userId] = true; });
 
-     result.lists.forEach((list) => { users[list.userId] = true; });
 
-     result.cards.forEach((card) => {
 
-       users[card.userId] = true;
 
-       if (card.members) {
 
-         card.members.forEach((memberId) => { users[memberId] = true; });
 
-       }
 
-     });
 
-     result.comments.forEach((comment) => { users[comment.userId] = true; });
 
-     result.activities.forEach((activity) => { users[activity.userId] = true; });
 
-     result.checklists.forEach((checklist) => { users[checklist.userId] = true; });
 
-     const byUserIds = { _id: { $in: Object.getOwnPropertyNames(users) } };
 
-     // we use whitelist to be sure we do not expose inadvertently
 
-     // some secret fields that gets added to User later.
 
-     const userFields = {
 
-       fields: {
 
-         _id: 1,
 
-         username: 1,
 
-         'profile.fullname': 1,
 
-         'profile.initials': 1,
 
-         'profile.avatarUrl': 1,
 
-       },
 
-     };
 
-     result.users = Users.find(byUserIds, userFields).fetch().map((user) => {
 
-       // user avatar is stored as a relative url, we export absolute
 
-       if (user.profile.avatarUrl) {
 
-         user.profile.avatarUrl = FlowRouter.url(user.profile.avatarUrl);
 
-       }
 
-       return user;
 
-     });
 
-     return result;
 
-   }
 
-   canExport(user) {
 
-     const board = Boards.findOne(this._boardId);
 
-     return board && board.isVisibleBy(user);
 
-   }
 
- }
 
 
  |