From a3f1d8d944f118bc3d1bb66f82aa18725d2d113d Mon Sep 17 00:00:00 2001 From: Ritchie Martori Date: Fri, 11 Oct 2013 19:06:16 -0700 Subject: [PATCH] Refactor email model into mail connector --- docs/api.md | 53 ++++++++++++++++++ index.js | 3 +- lib/connectors/mail.js | 121 +++++++++++++++++++++++++++++++++++++++++ lib/models/email.js | 88 +----------------------------- test/email.test.js | 56 +++++++++++++++++++ test/user.test.js | 7 ++- 6 files changed, 239 insertions(+), 89 deletions(-) create mode 100644 lib/connectors/mail.js create mode 100644 test/email.test.js diff --git a/docs/api.md b/docs/api.md index f7d16ece..324693f2 100644 --- a/docs/api.md +++ b/docs/api.md @@ -1164,6 +1164,22 @@ POST /users/logout Require a user to verify their email address before being able to login. This will send an email to the user containing a link to verify their address. Once the user follows the link they will be redirected to `/` and be able to login normally. ```js +// first setup the mail datasource (see #mail-model for more info) +var mail = loopback.createDataSource({ + connector: loopback.Mail, + transports: [{ + type: 'smtp', + host: 'smtp.gmail.com', + secureConnection: true, + port: 465, + auth: { + user: 'you@gmail.com', + pass: 'your-password' + } + }] +}); + +User.email.attachTo(mail); User.requireEmailVerfication = true; User.afterRemote('create', function(ctx, user, next) { var options = { @@ -1231,6 +1247,43 @@ MySession.attachTo(loopback.memory()); Send emails from your loopback app. +```js +// extend a one-off model for sending email +var MyEmail = loopback.Email.extend('my-email'); + +// create a mail data source +var mail = loopback.createDataSource({ + connector: loopback.Mail, + transports: [{ + type: 'smtp', + host: 'smtp.gmail.com', + secureConnection: true, + port: 465, + auth: { + user: 'you@gmail.com', + pass: 'your-password' + } + }] +}); + +// attach the model +MyEmail.attachTo(mail); + +// send an email +MyEmail.send({ + to: 'foo@bar.com', + from: 'you@gmail.com', + subject: 'my subject', + text: 'my text', + html: 'my html' +}, function(err, mail) { + console.log('email sent!'); +}); +``` + +> NOTE: the mail connector uses [nodemailer](http://www.nodemailer.com/). See +> the [nodemailer docs](http://www.nodemailer.com/) for more info. + ### REST Router Expose models over rest using the `loopback.rest` router. diff --git a/index.js b/index.js index 5db6b786..737666bd 100644 --- a/index.js +++ b/index.js @@ -10,9 +10,10 @@ var loopback = module.exports = require('./lib/loopback'); loopback.Connector = require('./lib/connectors/base-connector'); loopback.Memory = require('./lib/connectors/memory'); +loopback.Mail = require('./lib/connectors/mail'); /** * Types */ -loopback.GeoPoint = require('loopback-datasource-juggler/lib/geo').GeoPoint; \ No newline at end of file +loopback.GeoPoint = require('loopback-datasource-juggler/lib/geo').GeoPoint; diff --git a/lib/connectors/mail.js b/lib/connectors/mail.js new file mode 100644 index 00000000..682f9470 --- /dev/null +++ b/lib/connectors/mail.js @@ -0,0 +1,121 @@ +/** + * Dependencies. + */ + +var mailer = require('nodemailer') + , assert = require('assert'); + +/** + * Export the MailConnector class. + */ + +module.exports = MailConnector; + +/** + * Create an instance of the connector with the given `settings`. + */ + +function MailConnector(settings) { + assert(typeof settings === 'object', 'cannot initiaze MailConnector without a settings object'); + var transports = settings.transports || []; + + transports.forEach(this.setupTransport.bind(this)); +} + +MailConnector.initialize = function(dataSource, callback) { + dataSource.connector = new MailConnector(dataSource.settings); + callback(); +} + +MailConnector.prototype.DataAccessObject = Mailer; + + +/** + * Add a transport to the available transports. See https://github.com/andris9/Nodemailer#setting-up-a-transport-method. + * + * Example: + * + * Email.setupTransport({ + * type: 'SMTP', + * host: "smtp.gmail.com", // hostname + * secureConnection: true, // use SSL + * port: 465, // port for secure SMTP + * auth: { + * user: "gmail.user@gmail.com", + * pass: "userpass" + * } + * }); + * + */ + +MailConnector.prototype.setupTransport = function(setting) { + var connector = this; + connector.transports = connector.transports || []; + connector.transportsIndex = connector.transportsIndex || {}; + var transport = mailer.createTransport(setting.type, setting); + connector.transportsIndex[setting.type] = transport; + connector.transports.push(transport); +} + +function Mailer() { + +} + +/** + * Send an email with the given `options`. + * + * Example Options: + * + * { + * from: "Fred Foo ✔ ", // sender address + * to: "bar@blurdybloop.com, baz@blurdybloop.com", // list of receivers + * subject: "Hello ✔", // Subject line + * text: "Hello world ✔", // plaintext body + * html: "Hello world ✔" // html body + * } + * + * See https://github.com/andris9/Nodemailer for other supported options. + * + * @param {Object} options + * @param {Function} callback Called after the e-mail is sent or the sending failed + */ + +Mailer.send = function (options, fn) { + var dataSource = this.dataSource; + var settings = dataSource && dataSource.settings; + var connector = dataSource.connector; + var transportsIndex = connector.transportsIndex; + var transport = transportsIndex[options.transport || 'SMTP'] || connector.transports[0]; + assert(transport, 'You must supply an Email.settings.transports array containing at least one transport'); + + if(settings && settings.debug) { + console.log('Sending Mail:'); + if(options.transport) { + console.log('\t TRANSPORT:', options.transport); + } + console.log('\t TO:', options.to); + console.log('\t FROM:', options.from); + console.log('\t SUBJECT:', options.subject); + console.log('\t TEXT:', options.text); + console.log('\t HTML:', options.html); + } + + transport.sendMail(options, fn); +} + +/** + * Send an email instance using `modelInstance.send()`. + */ + +Mailer.prototype.send = function (fn) { + this.constructor.send(this, fn); +} + +/** + * Access the node mailer object. + */ + +MailConnector.mailer = +MailConnector.prototype.mailer = +Mailer.mailer = +Mailer.prototype.mailer = mailer; diff --git a/lib/models/email.js b/lib/models/email.js index 9bb3a79c..d2c33bc2 100644 --- a/lib/models/email.js +++ b/lib/models/email.js @@ -3,9 +3,7 @@ */ var Model = require('../loopback').Model - , loopback = require('../loopback') - , mailer = require('nodemailer') - , assert = require('assert'); + , loopback = require('../loopback'); /** * Default Email properties. @@ -24,87 +22,3 @@ var properties = { */ var Email = module.exports = Model.extend('email', properties); - -/*! - * Setup the Email class after extension. - */ - -Email.setup = function (settings) { - settings = settings || this.settings; - var transports = settings.transports || []; - - transports.forEach(this.setupTransport.bind(this)); -} - -/** - * Add a transport to the available transports. See https://github.com/andris9/Nodemailer#setting-up-a-transport-method. - * - * Example: - * - * Email.setupTransport({ - * type: 'SMTP', - * host: "smtp.gmail.com", // hostname - * secureConnection: true, // use SSL - * port: 465, // port for secure SMTP - * auth: { - * user: "gmail.user@gmail.com", - * pass: "userpass" - * } - * }); - * - */ - -Email.setupTransport = function (setting) { - var Email = this; - Email.transports = Email.transports || []; - Email.transportsIndex = Email.transportsIndex || {}; - var transport = mailer.createTransport(setting.type, setting); - Email.transportsIndex[setting.type] = transport; - Email.transports.push(transport); -} - -/** - * Send an email with the given `options`. - * - * Example Options: - * - * { - * from: "Fred Foo ✔ ", // sender address - * to: "bar@blurdybloop.com, baz@blurdybloop.com", // list of receivers - * subject: "Hello ✔", // Subject line - * text: "Hello world ✔", // plaintext body - * html: "Hello world ✔" // html body - * } - * - * See https://github.com/andris9/Nodemailer for other supported options. - * - * @param {Object} options - * @param {Function} callback Called after the e-mail is sent or the sending failed - */ - -Email.send = function (options, fn) { - var transport = this.transportsIndex[options.transport || 'SMTP'] || this.transports[0]; - assert(transport, 'You must supply an Email.settings.transports array containing at least one transport'); - - transport.sendMail(options, fn); -} - -/** - * Access the node mailer object. - * - * Email.mailer - * // or - * var email = new Email({to: 'foo@bar.com', from: 'bar@bat.com'}); - * email.mailer - */ - -Email.mailer = -Email.prototype.mailer = mailer; - -/** - * Send an email instance using `Email.send()`. - */ - -Email.prototype.send = function (fn) { - this.constructor.send(this, fn); -} diff --git a/test/email.test.js b/test/email.test.js new file mode 100644 index 00000000..36727dca --- /dev/null +++ b/test/email.test.js @@ -0,0 +1,56 @@ +var loopback = require('../'); +var MailConnector = loopback.Mail; +var MyEmail = loopback.Email.extend('my-email'); +var assert = require('assert'); + +describe('Email and SMTP', function () { + var mail = loopback.createDataSource({ + connector: MailConnector, + transports: [ + {type: 'STUB'} + ] + }); + + MyEmail.attachTo(mail); + + it('should have a send method', function () { + assert(typeof MyEmail.send === 'function'); + assert(typeof MyEmail.prototype.send === 'function'); + }); + + describe('MyEmail', function () { + it('MyEmail.send(options, callback)', function (done) { + var options = { + to: 'to@to.com', + from: 'from@from.com', + subject: 'subject', + text: 'text', + html: '

html

' + }; + + MyEmail.send(options, function(err, mail) { + assert(mail.message); + assert(mail.envelope); + assert(mail.messageId); + done(err); + }); + }); + + it('myEmail.send(callback)', function (done) { + var message = new MyEmail({ + to: 'to@to.com', + from: 'from@from.com', + subject: 'subject', + text: 'text', + html: '

html

' + }); + + message.send(function (err, mail) { + assert(mail.message); + assert(mail.envelope); + assert(mail.messageId); + done(err); + }); + }); + }); +}); diff --git a/test/user.test.js b/test/user.test.js index 51857050..e664c73a 100644 --- a/test/user.test.js +++ b/test/user.test.js @@ -1,6 +1,7 @@ var User = loopback.User.extend('user'); var Session = loopback.Session; var passport = require('passport'); +var MailConnector = require('../lib/connectors/mail'); var userMemory = loopback.createDataSource({ connector: loopback.Memory @@ -9,9 +10,13 @@ var userMemory = loopback.createDataSource({ describe('User', function(){ + var mailDataSource = loopback.createDataSource({ + connector: MailConnector, + transports: [{type: 'STUB'}] + }); User.attachTo(userMemory); User.session.attachTo(userMemory); - User.email.setup({transports: [{type: 'STUB'}]}); + User.email.attachTo(mailDataSource); // allow many User.afterRemote's to be called User.setMaxListeners(0);