From 5fa76f6aebb9c2f10b68d27fcfebee5b4f05a8ff Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Mon, 19 Sep 2016 21:32:58 -0500 Subject: [PATCH] feat(api): Add route to export pms as HTML fixes #7939 closes #7999 --- .../dataexport/GET-export_inbox.html.test.js | 57 +++++++++++++++++++ website/common/locales/en/groups.json | 5 ++ .../controllers/top-level/dataexport.js | 56 ++++++++++++++++++ website/views/options/social/index.jade | 5 ++ 4 files changed, 123 insertions(+) create mode 100644 test/api/v3/integration/dataexport/GET-export_inbox.html.test.js diff --git a/test/api/v3/integration/dataexport/GET-export_inbox.html.test.js b/test/api/v3/integration/dataexport/GET-export_inbox.html.test.js new file mode 100644 index 0000000000..aade366802 --- /dev/null +++ b/test/api/v3/integration/dataexport/GET-export_inbox.html.test.js @@ -0,0 +1,57 @@ +import { + generateUser, +} from '../../../../helpers/api-v3-integration.helper'; + +describe('GET /export/inbox.html', () => { + let user; + + before(async () => { + let otherUser = await generateUser({ + 'profile.name': 'Other User', + }); + user = await generateUser({ + 'profile.name': 'Main User', + }); + + await otherUser.post('/members/send-private-message', { + toUserId: user.id, + message: ':smile: hi', + }); + await user.post('/members/send-private-message', { + toUserId: otherUser.id, + message: '# Hello!', + }); + await otherUser.post('/members/send-private-message', { + toUserId: user.id, + message: '* list 1\n* list 2\n * list 3 \n * [list with a link](http://example.com)', + }); + + await user.sync(); + }); + + it('returns an html page', async () => { + let res = await user.get('/export/inbox.html'); + + expect(res.substring(0, 100).indexOf('')).to.equal(0); + }); + + it('renders the markdown messages as html', async () => { + let res = await user.get('/export/inbox.html'); + + expect(res).to.include('img class="habitica-emoji"'); + expect(res).to.include('

Hello!

'); + expect(res).to.include('
  • list 1
  • '); + }); + + it('sorts messages from newest to oldest', async () => { + let res = await user.get('/export/inbox.html'); + + let emojiPosition = res.indexOf('img class="habitica-emoji"'); + let headingPosition = res.indexOf('

    Hello!

    '); + let listPosition = res.indexOf('
  • list 1
  • '); + + expect(emojiPosition).to.be.greaterThan(headingPosition); + expect(headingPosition).to.be.greaterThan(listPosition); + expect(listPosition).to.be.greaterThan(-1); // make sure it exists at all + }); +}); diff --git a/website/common/locales/en/groups.json b/website/common/locales/en/groups.json index 12600710fb..c925ab569c 100644 --- a/website/common/locales/en/groups.json +++ b/website/common/locales/en/groups.json @@ -200,5 +200,10 @@ "onlyGroupTasksCanBeAssigned": "Only group tasks can be assigned", "newChatMessagePlainNotification": "New message in <%= groupName %> by <%= authorName %>. Click here to open the chat page!", "newChatMessageTitle": "New message in <%= groupName %>", + "exportInbox": "Export Messages", + "exportInboxPopoverTitle": "Export your messages as HTML", + "exportInboxPopoverBody": "HTML allows easy reading of messages in a browser. For a machine-readable format, use Data > Export Data", + "to": "To:", + "from": "From:", "desktopNotificationsText": "We need your permission to enable desktop notifications for new messages in party chat! Follow your browser's instructions to turn them on.

    You'll receive these notifications only while you have Habitica open. If you decide you don't like them, they can be disabled in your browser's settings.

    This box will close automatically when a decision is made." } diff --git a/website/server/controllers/top-level/dataexport.js b/website/server/controllers/top-level/dataexport.js index c042198a11..52cf945133 100644 --- a/website/server/controllers/top-level/dataexport.js +++ b/website/server/controllers/top-level/dataexport.js @@ -13,6 +13,7 @@ import nconf from 'nconf'; import got from 'got'; import Bluebird from 'bluebird'; import locals from '../../middlewares/locals'; +import md from 'habitica-markdown'; import { S3, } from '../../libs/aws'; @@ -243,4 +244,59 @@ api.exportUserAvatarPng = { }, }; +/** + * @api {get} /export/inbox.html Export user private messages as HTML document + * @apiName ExportUserPrivateMessages + * @apiGroup DataExport + * @apiDescription NOTE: Part of the private API that may change at any time. + * + * @apiSuccess {String} An html page + */ +api.exportUserPrivateMessages = { + method: 'GET', + url: '/export/inbox.html', + middlewares: [authWithSession], + async handler (req, res) { + let user = res.locals.user; + + const timezoneOffset = user.preferences.timezoneOffset; + const dateFormat = user.preferences.dateFormat.toUpperCase(); + const TO = res.t('to'); + const FROM = res.t('from'); + + let inbox = Object.keys(user.inbox.messages).map(key => user.inbox.messages[key]); + + inbox = _.sortBy(inbox, function sortBy (num) { + return num.sort * -1; + }); + + let messages = ''; + + inbox.forEach((message, index) => { + let recipientLabel = message.sent ? TO : FROM; + let messageUser = message.user; + let timestamp = moment.utc(message.timestamp).zone(timezoneOffset).format(`${dateFormat} HH:mm:ss`); + let text = md.render(message.text); + index = `(${index + 1}/${inbox.length})`; + messages += ` +

    + ${recipientLabel} ${messageUser} ${timestamp} + ${index} +
    + ${text} +

    +
    `; + }); + + messages += ''; + + res.set({ + 'Content-Type': 'text/html', + 'Content-disposition': 'attachment; filename=inbox.html', + }); + + res.status(200).send(messages); + }, +}; + module.exports = api; diff --git a/website/views/options/social/index.jade b/website/views/options/social/index.jade index 754d3d08f6..a98187adaf 100644 --- a/website/views/options/social/index.jade +++ b/website/views/options/social/index.jade @@ -17,6 +17,11 @@ script(type='text/ng-template', id='partials/options.social.inbox.html') .form-inline a.btn.btn-xs.btn-danger(ng-click='deleteAllMessages()')=env.t('clearAll') |       + a.btn.btn-xs.btn-default(href='/export/inbox.html', popover=env.t('exportInboxPopoverBody'), + popover-title=env.t('exportInboxPopoverTitle'), + popover-trigger='mouseenter', popover-placement='right', + popover-append-to-body='true')=env.t('exportInbox') + |       .checkbox label input(type='checkbox', ng-model='user.inbox.optOut', ng-change='set({"inbox.optOut": user.inbox.optOut?true: false})')