diff --git a/packages/nylas-api/routes/contacts.js b/packages/nylas-api/routes/contacts.js new file mode 100644 index 000000000..778f348cf --- /dev/null +++ b/packages/nylas-api/routes/contacts.js @@ -0,0 +1,83 @@ +const Joi = require('joi'); +const Serialization = require('../serialization'); + +module.exports = (server) => { + server.route({ + method: 'GET', + path: '/contacts', + config: { + description: 'Returns an array of contacts', + notes: 'Notes go here', + tags: ['contacts'], + validate: { + query: { + name: Joi.string(), + email: Joi.string().email(), + limit: Joi.number().integer().min(1).max(2000).default(100), + offset: Joi.number().integer().min(0).default(0), + }, + }, + response: { + schema: Joi.array().items( + Serialization.jsonSchema('Contact') + ), + }, + }, + handler: (request, reply) => { + request.getAccountDatabase().then((db) => { + const {Contact} = db; + const query = request.query; + const where = {}; + + if (query.name) { + where.name = {like: query.name}; + } + if (query.email) { + where.email = query.email; + } + + Contact.findAll({ + where: where, + limit: request.query.limit, + offset: request.query.offset, + }).then((contacts) => { + reply(Serialization.jsonStringify(contacts)) + }) + }) + }, + }) + + server.route({ + method: 'GET', + path: '/contacts/{id}', + config: { + description: 'Returns a contact with specified id.', + notes: 'Notes go here', + tags: ['contacts'], + validate: { + params: { + id: Joi.string(), + }, + }, + response: { + schema: Serialization.jsonSchema('Contact'), + }, + }, + handler: (request, reply) => { + request.getAccountDatabase().then(({Contact}) => { + const {params: {id}} = request + + Contact.findOne({where: {id}}).then((contact) => { + if (!contact) { + return reply.notFound(`Contact ${id} not found`) + } + return reply(Serialization.jsonStringify(contact)) + }) + .catch((error) => { + console.log('Error fetching contacts: ', error) + reply(error) + }) + }) + }, + }) +} diff --git a/packages/nylas-api/serialization.js b/packages/nylas-api/serialization.js index 3b0fa5f3b..f01d9d757 100644 --- a/packages/nylas-api/serialization.js +++ b/packages/nylas-api/serialization.js @@ -6,7 +6,8 @@ function replacer(key, value) { } function jsonSchema(modelName) { - const models = ['Message', 'Thread', 'File', 'Error', 'SyncbackRequest', 'Account'] + const models = ['Message', 'Thread', 'File', 'Error', 'SyncbackRequest', 'Account', 'Contact'] + if (models.includes(modelName)) { return Joi.object(); } diff --git a/packages/nylas-core/models/account/contact.js b/packages/nylas-core/models/account/contact.js new file mode 100644 index 000000000..243787a64 --- /dev/null +++ b/packages/nylas-core/models/account/contact.js @@ -0,0 +1,22 @@ +module.exports = (sequelize, Sequelize) => { + const Contact = sequelize.define('contact', { + accountId: { type: Sequelize.STRING, allowNull: false }, + version: Sequelize.INTEGER, + name: Sequelize.STRING, + email: Sequelize.STRING, + }, { + instanceMethods: { + toJSON: function toJSON() { + return { + id: this.id, + account_id: this.accountId, + object: 'contact', + email: this.email, + name: this.name, + } + }, + }, + }) + + return Contact; +} diff --git a/packages/nylas-message-processor/processors/contact.js b/packages/nylas-message-processor/processors/contact.js new file mode 100644 index 000000000..11d1423e1 --- /dev/null +++ b/packages/nylas-message-processor/processors/contact.js @@ -0,0 +1,53 @@ + +class ContactProcessor { + + verified(contact) { + // some suggestions: http://stackoverflow.com/questions/6317714/apache-camel-mail-to-identify-auto-generated-messages + const regex = new RegExp(/^(noreply|no-reply|donotreply|mailer|support|webmaster|news(letter)?@)/ig) + + if (regex.test(contact.email) || contact.email.length > 60) { + console.log('Email address doesn\'t seem to be areal person') + return false + } + return true + } + + emptyContact(Contact, options = {}, accountId) { + options.accountId = accountId + return Contact.create(options) + } + + findOrCreateByContactId(Contact, contact, accountId) { + return Contact.find({where: {email: contact.email}}) + .then((contactDb) => { + return contactDb || this.emptyContact(Contact, contact, accountId) + }) + } + + + processMessage({db, message}) { + const {Contact} = db; + + let allContacts = [] + const fields = ['to', 'from', 'bcc', 'cc'] + fields.forEach((field) => { + allContacts = allContacts.concat(message[field]) + }) + const filtered = allContacts.filter(this.verified) + const contactPromises = filtered.map((contact) => { + return this.findOrCreateByContactId(Contact, contact, message.accountId) + }) + + return Promise.all(contactPromises) + .then(() => { + return message + }) + } +} + +const processor = new ContactProcessor() + +module.exports = { + order: 3, + processMessage: processor.processMessage.bind(processor), +}