Add endpoint for file downloads

This commit is contained in:
Jackie Luo 2016-06-29 19:13:56 -07:00
parent fd7570f6c8
commit b3b5a5106c
4 changed files with 91 additions and 4 deletions

View file

@ -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)
})
})
},
})
};

View file

@ -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
*/

View file

@ -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,
};

View file

@ -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,