From b3b5a5106c6f4f6c765d8607262051adc5850a8f Mon Sep 17 00:00:00 2001 From: Jackie Luo Date: Wed, 29 Jun 2016 19:13:56 -0700 Subject: [PATCH] Add endpoint for file downloads --- packages/nylas-api/routes/files.js | 36 +++++++++++++++++++ packages/nylas-core/imap-connection.js | 18 ++++++++++ packages/nylas-core/models/account/file.js | 32 +++++++++++++++-- .../imap/fetch-messages-in-category.js | 9 +++-- 4 files changed, 91 insertions(+), 4 deletions(-) diff --git a/packages/nylas-api/routes/files.js b/packages/nylas-api/routes/files.js index 1b6208730..b7b93e6d0 100644 --- a/packages/nylas-api/routes/files.js +++ b/packages/nylas-api/routes/files.js @@ -73,4 +73,40 @@ module.exports = (server) => { }) }, }) + + server.route({ + method: 'GET', + path: '/files/{id}/download', + config: { + description: 'Returns binary data for file with specified id.', + notes: 'Notes go here', + tags: ['files'], + validate: { + params: { + id: Joi.string(), + }, + }, + }, + handler: (request, reply) => { + request.getAccountDatabase() + .then((db) => { + const {headers: {accept}} = request + const {params: {id}} = request + const account = request.auth.credentials + + db.File.findOne({where: {id}}) + .then((file) => { + if (!file) { + return reply.notFound(`File ${id} not found`) + } + return file.fetch({account, db}) + .then((stream) => reply(stream)) + }) + .catch((error) => { + console.log('Error fetching file: ', error) + reply(error) + }) + }) + }, + }) }; diff --git a/packages/nylas-core/imap-connection.js b/packages/nylas-core/imap-connection.js index 575056b8d..4f6fcd64f 100644 --- a/packages/nylas-core/imap-connection.js +++ b/packages/nylas-core/imap-connection.js @@ -73,6 +73,24 @@ class IMAPBox { }) } + fetchStream({messageId, options}) { + if (!messageId) { + throw new Error("IMAPConnection.fetchStream requires a message identifier.") + } + if (!options) { + throw new Error("IMAPConnection.fetchStream requires an options object.") + } + return new Promise((resolve, reject) => { + const f = this._imap.fetch(messageId, options); + f.on('message', (imapMessage) => { + imapMessage.on('body', (stream) => { + resolve(stream) + }) + }) + f.once('error', (error) => reject) + }) + } + /** * @return {Promise} that resolves to requested message */ diff --git a/packages/nylas-core/models/account/file.js b/packages/nylas-core/models/account/file.js index 8655189df..ca9dcb2c8 100644 --- a/packages/nylas-core/models/account/file.js +++ b/packages/nylas-core/models/account/file.js @@ -1,9 +1,12 @@ +const IMAPConnection = require('../../imap-connection') +const NylasError = require('../../nylas-error') + module.exports = (sequelize, Sequelize) => { const File = sequelize.define('file', { accountId: { type: Sequelize.STRING, allowNull: false }, version: Sequelize.INTEGER, filename: Sequelize.STRING, - contentId: Sequelize.STRING, + partId: Sequelize.STRING, contentType: Sequelize.STRING, size: Sequelize.INTEGER, }, { @@ -13,6 +16,31 @@ module.exports = (sequelize, Sequelize) => { }, }, instanceMethods: { + fetch: function fetch({account, db}) { + const settings = Object.assign({}, account.connectionSettings, account.decryptedCredentials()) + return Promise.props({ + message: this.getMessage(), + connection: IMAPConnection.connect(db, settings), + }) + .then(({message, connection}) => { + return message.getCategory() + .then((category) => connection.openBox(category.name)) + .then((imapBox) => imapBox.fetchStream({ + messageId: message.categoryUID, + options: { + bodies: [this.partId], + struct: true, + } + })) + .then((stream) => { + if (stream) { + return Promise.resolve(stream) + } + return Promise.reject(new NylasError(`Unable to fetch binary data for File ${this.id}`)) + }) + .finally(() => connection.end()) + }) + }, toJSON: function toJSON() { return { id: this.id, @@ -20,7 +48,7 @@ module.exports = (sequelize, Sequelize) => { account_id: this.accountId, message_id: this.messageId, filename: this.filename, - content_id: this.contentId, + part_id: this.partId, content_type: this.contentType, size: this.size, }; diff --git a/packages/nylas-sync/imap/fetch-messages-in-category.js b/packages/nylas-sync/imap/fetch-messages-in-category.js index 745dd5b10..57737e171 100644 --- a/packages/nylas-sync/imap/fetch-messages-in-category.js +++ b/packages/nylas-sync/imap/fetch-messages-in-category.js @@ -181,14 +181,19 @@ class FetchMessagesInCategory { for (const part of struct) { if (part.constructor === Array) { this._createFilesFromStruct({message, struct: part}) - } else if (part.disposition) { + } else if (part.type !== 'text' && part.disposition) { let filename = null if (part.disposition.params) { filename = part.disposition.params.filename } + // Only exposes partId for inline attachments + let partId = null + if (part.disposition.type === 'inline') { + partId = part.partID + } File.create({ filename: filename, - contentId: part.partID, + partId: partId, contentType: `${part.type}/${part.subtype}`, accountId: this._db.accountId, size: part.size,