diff options
-rw-r--r-- | .meteor/packages | 1 | ||||
-rw-r--r-- | .meteor/versions | 1 | ||||
-rw-r--r-- | client/components/boards/boardHeader.jade | 1 | ||||
-rw-r--r-- | client/components/boards/boardHeader.js | 6 | ||||
-rw-r--r-- | i18n/en.i18n.json | 1 | ||||
-rw-r--r-- | models/export.js | 51 |
6 files changed, 61 insertions, 0 deletions
diff --git a/.meteor/packages b/.meteor/packages index 98c06cc9..a868ec75 100644 --- a/.meteor/packages +++ b/.meteor/packages @@ -73,3 +73,4 @@ perak:markdown seriousm:emoji-continued templates:tabs verron:autosize +simple:json-routes diff --git a/.meteor/versions b/.meteor/versions index 9d7fe1b3..61df2c72 100644 --- a/.meteor/versions +++ b/.meteor/versions @@ -125,6 +125,7 @@ seriousm:emoji-continued@1.4.0 service-configuration@1.0.5 session@1.1.1 sha@1.0.4 +simple:json-routes@1.0.4 softwarerero:accounts-t9n@1.1.7 spacebars@1.0.7 spacebars-compiler@1.0.7 diff --git a/client/components/boards/boardHeader.jade b/client/components/boards/boardHeader.jade index a0160382..3e608d4a 100644 --- a/client/components/boards/boardHeader.jade +++ b/client/components/boards/boardHeader.jade @@ -56,6 +56,7 @@ template(name="boardMenuPopup") if currentUser.isBoardAdmin hr ul.pop-over-list + li: a.js-export-board(href="{{urlExport}}", download) {{_ 'export-board'}} li: a.js-archive-board {{_ 'archive-board'}} template(name="boardVisibilityList") diff --git a/client/components/boards/boardHeader.js b/client/components/boards/boardHeader.js index 3dc6d754..3503cbfb 100644 --- a/client/components/boards/boardHeader.js +++ b/client/components/boards/boardHeader.js @@ -15,6 +15,12 @@ Template.boardMenuPopup.events({ }), }); +Template.boardMenuPopup.helpers({ + urlExport() { + return Meteor.absoluteUrl(`api/b/${Session.get('currentBoard')}`); + }, +}); + Template.boardChangeTitlePopup.events({ submit(evt, tpl) { const newTitle = tpl.$('.js-board-name').val().trim(); diff --git a/i18n/en.i18n.json b/i18n/en.i18n.json index 04c0959f..238f9964 100644 --- a/i18n/en.i18n.json +++ b/i18n/en.i18n.json @@ -147,6 +147,7 @@ "error-user-doesNotExist": "This user does not exist", "error-user-notAllowSelf": "This action on self is not allowed", "error-user-notCreated": "This user is not created", + "export-board": "Export board", "filter": "Filter", "filter-cards": "Filter Cards", "filter-clear": "Clear filter", diff --git a/models/export.js b/models/export.js new file mode 100644 index 00000000..bc7cb8f9 --- /dev/null +++ b/models/export.js @@ -0,0 +1,51 @@ +/* global JsonRoutes */ +JsonRoutes.add('get', '/api/b/:id', function (req, res) { + const id = req.params.id; + const exporter = new Exporter(id); + JsonRoutes.sendResult(res, 200, exporter.build()); +}); + +class Exporter { + constructor(boardId) { + this._boardId = boardId; + } + + build() { + const byBoard = {boardId: this._boardId}; + const fields = {fields: {boardId: 0}}; + const result = Boards.findOne(this._boardId); + result.lists = Lists.find(byBoard, fields).fetch(); + result.cards = Cards.find(byBoard, fields).fetch(); + result.comments = CardComments.find(byBoard, fields).fetch(); + result.activities = Activities.find(byBoard, fields).fetch(); + + // 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;}); + 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(); + + return result; + } +} |