Mailspring/packages/isomorphic-core/models/account/message.js

120 lines
4.1 KiB
JavaScript

const crypto = require('crypto');
const PromiseUtils = require('../../promise-utils')
const IMAPConnection = require('../../imap-connection')
const {JSONType, JSONARRAYType} = require('../../database-types');
module.exports = (sequelize, Sequelize) => {
return sequelize.define('message', {
accountId: { type: Sequelize.STRING, allowNull: false },
version: Sequelize.INTEGER,
headerMessageId: Sequelize.STRING,
body: Sequelize.TEXT('long'),
headers: JSONType('headers'),
subject: Sequelize.STRING(500),
snippet: Sequelize.STRING(255),
hash: Sequelize.STRING(65),
date: Sequelize.DATE,
unread: Sequelize.BOOLEAN,
starred: Sequelize.BOOLEAN,
processed: Sequelize.INTEGER,
to: JSONARRAYType('to'),
from: JSONARRAYType('from'),
cc: JSONARRAYType('cc'),
bcc: JSONARRAYType('bcc'),
replyTo: JSONARRAYType('replyTo'),
folderImapUID: { type: Sequelize.STRING, allowNull: true},
folderImapXGMLabels: { type: Sequelize.TEXT, allowNull: true},
}, {
indexes: [
{
unique: true,
fields: ['hash'],
},
],
classMethods: {
associate: ({Message, Folder, Label, File, Thread, MessageLabel}) => {
Message.belongsTo(Thread)
Message.belongsTo(Folder)
Message.belongsToMany(Label, {through: MessageLabel})
Message.hasMany(File)
},
hashForHeaders: (headers) => {
return crypto.createHash('sha256').update(headers, 'utf8').digest('hex');
},
},
instanceMethods: {
setLabelsFromXGM(xGmLabels, {Label, preloadedLabels} = {}) {
if (!xGmLabels) {
return Promise.resolve();
}
const labelNames = xGmLabels.filter(l => l[0] !== '\\')
const labelRoles = xGmLabels.filter(l => l[0] === '\\').map(l => l.substr(1).toLowerCase())
let getLabels = null;
if (preloadedLabels) {
getLabels = Promise.resolve(preloadedLabels.filter(l => labelNames.includes(l.name) || labelRoles.includes(l.role)));
} else {
getLabels = Label.findAll({
where: sequelize.or({name: labelNames}, {role: labelRoles}),
})
}
this.folderImapXGMLabels = JSON.stringify(xGmLabels);
return getLabels.then((labels) =>
this.save().then(() => this.setLabels(labels))
)
},
fetchRaw: function fetchRaw({account, db, logger}) {
const settings = Object.assign({}, account.connectionSettings, account.decryptedCredentials())
return PromiseUtils.props({
folder: this.getFolder(),
connection: IMAPConnection.connect({db, settings, logger}),
})
.then(({folder, connection}) => {
return connection.openBox(folder.name)
.then((imapBox) => imapBox.fetchMessage(this.folderImapUID))
.then((message) => {
if (message) {
return Promise.resolve(`${message.headers}${message.parts.TEXT}`)
}
return Promise.reject(new Error(`Unable to fetch raw message for Message ${this.id}`))
})
.finally(() => connection.end())
})
},
toJSON: function toJSON() {
if (this.folder_id && !this.folder) {
throw new Error("Message.toJSON called on a message where folder were not eagerly loaded.")
}
// When we request messages as a sub-object of a thread, we only
// request the `id` field from the database. We still toJSON the
// Message though and need to protect `this.date` from null
// errors.
return {
id: `${this.id}`,
account_id: this.accountId,
object: 'message',
body: this.body,
subject: this.subject,
snippet: this.snippet,
to: this.to,
from: this.from,
cc: this.cc,
bcc: this.bcc,
reply_to: this.replyTo,
date: this.date ? this.date.getTime() / 1000.0 : null,
unread: this.unread,
starred: this.starred,
folder: this.folder,
labels: this.labels,
thread_id: this.threadId,
};
},
},
});
};