From 90e450c11a6cfa49b01f1e3c39190e5fdd2fc029 Mon Sep 17 00:00:00 2001 From: user Date: Mon, 20 Jan 2014 22:56:57 +0200 Subject: [PATCH 01/24] feature(module) added clever-email module --- modules/clever-email/config/default.json | 31 ++ .../controllers/EmailAlertController.js | 87 +++ .../controllers/EmailController.js | 96 ++++ .../controllers/EmailTemplateController.js | 154 ++++++ modules/clever-email/lib/sendgrid.js | 27 + .../mail-template/accountActivation.ejs | 15 + .../mail-template/passwordRecovery.ejs | 17 + .../models/orm/EmailAttachmentModel.js | 29 + modules/clever-email/models/orm/EmailModel.js | 30 ++ .../models/orm/EmailReplyModel.js | 29 + .../models/orm/EmailTemplateModel.js | 82 +++ .../clever-email/models/orm/EmailUserModel.js | 22 + modules/clever-email/module.js | 5 + modules/clever-email/package.json | 25 + modules/clever-email/routes.js | 27 + modules/clever-email/schema/seedData.json | 156 ++++++ modules/clever-email/services/EmailService.js | 503 ++++++++++++++++++ .../services/EmailTemplateService.js | 391 ++++++++++++++ .../test.controller.EmailAlertController.js | 461 ++++++++++++++++ .../test.service.EmailTemplateService.js | 211 ++++++++ package.json | 3 +- 21 files changed, 2400 insertions(+), 1 deletion(-) create mode 100644 modules/clever-email/config/default.json create mode 100644 modules/clever-email/controllers/EmailAlertController.js create mode 100644 modules/clever-email/controllers/EmailController.js create mode 100644 modules/clever-email/controllers/EmailTemplateController.js create mode 100644 modules/clever-email/lib/sendgrid.js create mode 100644 modules/clever-email/mail-template/accountActivation.ejs create mode 100644 modules/clever-email/mail-template/passwordRecovery.ejs create mode 100644 modules/clever-email/models/orm/EmailAttachmentModel.js create mode 100644 modules/clever-email/models/orm/EmailModel.js create mode 100644 modules/clever-email/models/orm/EmailReplyModel.js create mode 100644 modules/clever-email/models/orm/EmailTemplateModel.js create mode 100644 modules/clever-email/models/orm/EmailUserModel.js create mode 100644 modules/clever-email/module.js create mode 100644 modules/clever-email/package.json create mode 100644 modules/clever-email/routes.js create mode 100644 modules/clever-email/schema/seedData.json create mode 100644 modules/clever-email/services/EmailService.js create mode 100644 modules/clever-email/services/EmailTemplateService.js create mode 100644 modules/clever-email/tests/integration/test.controller.EmailAlertController.js create mode 100644 modules/clever-email/tests/integration/test.service.EmailTemplateService.js diff --git a/modules/clever-email/config/default.json b/modules/clever-email/config/default.json new file mode 100644 index 0000000..9b2192e --- /dev/null +++ b/modules/clever-email/config/default.json @@ -0,0 +1,31 @@ +{ + "clever-email": { + "emailsToCC": true, + "emailsToBCC": true, + "messageTXT": true, + "priority": true, + + "systems": { + "Mandrill": { + "isActive": true, + "apiKey" : "j8Gzc7vRpJ36FabmOsbk6g" + }, + "SendGrid": { + "isActive": false, + "apiUser" : "", + "apiKey" : "" + }, + "MailGun": { + "isActive": false, + "apiUser" : "", + "apiKey" : "key-97zzcleh7dgz0kp4jfy1qp0fn38lml73" + } + }, + + "default": { + "noReply": "true", + "from": "", + "subject": "test email" + } + } +} \ No newline at end of file diff --git a/modules/clever-email/controllers/EmailAlertController.js b/modules/clever-email/controllers/EmailAlertController.js new file mode 100644 index 0000000..0baa4cd --- /dev/null +++ b/modules/clever-email/controllers/EmailAlertController.js @@ -0,0 +1,87 @@ +var Q = require( 'q' ); + +module.exports = function ( RecentActivityService, AttributeKeyService, AttributeEmailAlertService, PermissionService ) { + return (require( 'classes' ).Controller).extend( { + service: AttributeEmailAlertService + }, { + listAction: function () { + var self = this; + + AttributeEmailAlertService.list( { + AccountId: self.req.user.AccountId + } ) + .then( self.proxy( 'handleServiceMessage' ) ) + .fail( self.proxy( 'handleException' ) ); + }, + + getAction: function () { + if ( !this.req.params.id ) { + return this.send( 404, 'Not found' ); + } + + AttributeEmailAlertService.get( { + AccountId: this.req.user.AccountId, + id: this.req.params.id + } ) + .then( this.proxy( 'send' ) ) + .fail( this.proxy( 'handleException' ) ); + }, + + postAction: function () { + var self = this + , data = self.req.body + , alerts = []; + + data.alert = Array.isArray( data.alert ) ? data.alert : [data.alert]; + + if ( !data.alert || !data.alert[0].alertName ) { + return self.send( { message: 'More information is required before creating this e-mail alert.', statuscode: 400 } ); + } + + if ( !!data.alert[0]._id ) { + return this.putAction(); + } + + AttributeEmailAlertService.saveOrUpdate( self.req.user, data.submitted, data.alert ) + .then( self.proxy( 'send' ) ) + .fail( self.proxy( 'handleException' ) ); + }, + + putAction: function () { + var self = this + , data = self.req.body + , alerts = []; + + data.alert = Array.isArray( data.alert ) ? data.alert : [data.alert]; + + if ( !data.alert || !data.alert[0].alertName ) { + return self.send( { message: 'More information is required before creating this e-mail alert.', statuscode: 400 } ); + } + + AttributeEmailAlertService.saveOrUpdate( self.req.user, data.submitted, data.alert ) + .then( self.proxy( 'send' ) ) + .fail( self.proxy( 'handleException' ) ); + }, + + deleteAction: function () { + var data = this.req.params; + + if ( !data.id ) { + return this.res.send( 404, 'Not found' ); + } + + AttributeEmailAlertService.remove( this.req.user.AccountId, data.id ) + .then( this.proxy( 'handleServiceMessage' ) ) + .fail( this.proxy( 'handleException' ) ); + }, + + handleServiceMessage: function ( obj ) { + if ( obj.statuscode ) { + this.send( obj.message, obj.statuscode ); + return; + } + + this.send( obj, 200 ); + } + } ); +} diff --git a/modules/clever-email/controllers/EmailController.js b/modules/clever-email/controllers/EmailController.js new file mode 100644 index 0000000..82d5843 --- /dev/null +++ b/modules/clever-email/controllers/EmailController.js @@ -0,0 +1,96 @@ +module.exports = function ( EmailService ) { + + return (require( 'classes' ).Controller).extend( { + service: null, checkEventMailData: function ( req, res, next ) { + var data = req.body + , fltData = []; + //console.log("\n *** SendGrid Event Data *** \n",data); + + var item; + while ( item = data.pop() ) { + if ( ( item.event == 'open' ) && item.email_id ) { + fltData.push( item ); + } + } + console.log( "\n\nReceived Event Notification POST from Sendgrid: " ); + req.body = fltData; + next(); + } + }, + /* @Prototype */ + { + + listAction: function () { + var userId = this.req.user.id; + + EmailService + .listEmails( userId ) + .then( this.proxy( 'handleServiceMessage' ) ) + .fail( this.proxy( 'handleException' ) ); + }, + + getAction: function () { + var userId = this.req.user.id + , emailId = this.req.params.id; + + EmailService + .getEmailById( userId, emailId ) + .then( this.proxy( 'handleServiceMessage' ) ) + .fail( this.proxy( 'handleException' ) ); + }, + + postAction: function () { + var userId = this.req.user.id + , accId = this.req.user.account.id + , accLogo = this.req.user.account.logo + , accName = this.req.user.account.name + , userFirstName = this.req.user.firstname + , userLastName = this.req.user.lastname + , data = this.req.body; + + data = data.map( function ( x ) { + x.userId = userId; + x.accId = accId; + x.userFirstName = userFirstName; + x.userLastName = userLastName; + x['accLogo'] = accLogo; + x['accName'] = accName; + return x; + } ); + + EmailService + .handleEmailCreation( data ) + .then( this.proxy( 'handleServiceMessage' ) ) + .fail( this.proxy( 'handleException' ) ); + }, + + putAction: function () { + this.send( 403, 'invalid' ); + }, + + deleteAction: function () { + this.send( 403, 'invalid' ); + }, + + eventsMailAction: function () { + var data = this.req.body; + + EmailService + .processMailEvents( data ) + .then( this.proxy( 'handleServiceMessage' ) ) + .fail( this.proxy( 'handleException' ) ); + + }, + + handleServiceMessage: function ( obj ) { + + if ( obj.statuscode ) { + this.send( obj.message, obj.statuscode ); + return; + } + + this.send( obj, 200 ); + } + + } ); +}; \ No newline at end of file diff --git a/modules/clever-email/controllers/EmailTemplateController.js b/modules/clever-email/controllers/EmailTemplateController.js new file mode 100644 index 0000000..2993346 --- /dev/null +++ b/modules/clever-email/controllers/EmailTemplateController.js @@ -0,0 +1,154 @@ +module.exports = function ( EmailTemplateService ) { + + return (require( 'classes' ).Controller).extend( + { + service: EmailTemplateService, + checkRequiredFields: function ( req, res, next ) { + var data = req.body; + + if ( !data || !data.title || !data.subject || !data.body ) { + res.json( 400, 'Please fill required fields' ); + return; + } + + next(); + } + }, + /* @Prototype */ + { + + listAction: function () { + var accId = this.req.user.account.id + , userId = this.req.user.id + , teamId = this.req.user.TeamId + , roleName = this.req.user.role.name; + + EmailTemplateService + .listTemplates( accId, userId, teamId, roleName ) + .then( this.proxy( 'handleServiceMessage' ) ) + .fail( this.proxy( 'handleException' ) ); + }, + + getAction: function () { + var userId = this.req.user.id + , accId = this.req.user.account.id + , teamId = this.req.user.TeamId + , tplId = this.req.params.id + , roleName = this.req.user.role.name; + + EmailTemplateService + .getTemplateById( accId, userId, teamId, tplId, roleName ) + .then( this.proxy( 'handleServiceMessage' ) ) + .fail( this.proxy( 'handleException' ) ); + }, + + postAction: function () { + var accId = this.req.user.account.id + , userId = this.req.user.id + , data = this.req.body; + + if ( data.id ) { + this.putAction(); + return; + } + + data['accId'] = accId; + data['userId'] = userId; + + EmailTemplateService + .createEmailTemplate( data ) + .then( this.proxy( 'handleEmailTemplateAssoc', data ) ) + .fail( this.proxy( 'handleException' ) ); + }, + + handleEmailTemplateAssoc: function ( dataWithAssocs, savedEmailTpl ) { + + if ( savedEmailTpl['statuscode'] != undefined ) { + this.handleServiceMessage( savedEmailTpl ); + return; + } + + EmailTemplateService + .processEmailTemplateAssoc( dataWithAssocs, savedEmailTpl ) + .then( this.proxy( 'handleServiceMessage' ) ) + .fail( this.proxy( 'handleException' ) ); + }, + + putAction: function () { + var accId = this.req.user.account.id + , userId = this.req.user.id + , data = this.req.body; + + if ( !data.id ) { + this.send( 'invalid id', 401 ); + return; + } + + data['accId'] = accId; + data['userId'] = userId; + + EmailTemplateService + .handleEmailTemplateUpdate( data ) + .then( this.proxy( 'handleEmailTemplateAssoc', data ) ) + .fail( this.proxy( 'handleException' ) ); + }, + + previewAction: function () { + var accId = this.req.user.account.id + , tplId = this.req.params.id + , data = {}; + + if ( !tplId ) { + this.send( 403, 'Invalid Template ID' ); + return; + } + + data.accId = accId; + data.template_id = tplId; + data.prospect_id = this.req.query.prospect_id || null; + data.job_id = this.req.query.job_id || null; + + EmailTemplateService + .getPlaceholderData( data ) + .then( this.proxy( 'handleTemplateInterpolation' ) ) + .fail( this.proxy( 'handleException' ) ); + + }, + + handleTemplateInterpolation: function ( data ) { + var user = this.req.user; + + if ( !data ) { + this.send( 403, 'Invalid Template ID' ); + return; + } + + EmailTemplateService + .processTemplateIntrpolation( data, user ) + .then( function ( html ) { + this.render( 'preview', { strHTML: html, tplTitle: 'Email Template Preview' } ); + }.bind( this ) ) + .fail( this.proxy( 'handleException' ) ); + }, + + deleteAction: function () { + var userId = this.req.user.id + , tplId = this.req.params.id; + + EmailTemplateService + .removeEmailTemplate( userId, tplId ) + .then( this.proxy( 'handleServiceMessage' ) ) + .fail( this.proxy( 'handleException' ) ); + }, + + handleServiceMessage: function ( obj ) { + + if ( obj.statuscode ) { + this.send( obj.message, obj.statuscode ); + return; + } + + this.send( obj, 200 ); + } + } ); +}; \ No newline at end of file diff --git a/modules/clever-email/lib/sendgrid.js b/modules/clever-email/lib/sendgrid.js new file mode 100644 index 0000000..e3abe93 --- /dev/null +++ b/modules/clever-email/lib/sendgrid.js @@ -0,0 +1,27 @@ +module.exports = function ( config ) { + var Q = require( 'q' ) + , sendgrid = require( 'sendgrid' )( config.apiUser, config.apiKey ) + , Email = sendgrid.Email; + + return function ( payload ) { + var deferred = Q.defer() + , email = new Email( payload ); + + if ( payload.emailId ) { + email.setUniqueArgs( { email_id: payload.emailId } ); + } + + sendgrid.send( email, function ( err, response ) { + + if ( err ) { + console.log( "SendGrid Error: \n", err.toString() ); + deferred.reject( err ); + return; + } + + deferred.resolve( response ); + } ); + + return deferred.promise; + }; +}; \ No newline at end of file diff --git a/modules/clever-email/mail-template/accountActivation.ejs b/modules/clever-email/mail-template/accountActivation.ejs new file mode 100644 index 0000000..4d14b5d --- /dev/null +++ b/modules/clever-email/mail-template/accountActivation.ejs @@ -0,0 +1,15 @@ + + + + Account Activation URL + + +
+
+

Account Activation

+ +

Please click here to activate your account.

+
+
+ + diff --git a/modules/clever-email/mail-template/passwordRecovery.ejs b/modules/clever-email/mail-template/passwordRecovery.ejs new file mode 100644 index 0000000..0bb0123 --- /dev/null +++ b/modules/clever-email/mail-template/passwordRecovery.ejs @@ -0,0 +1,17 @@ + + + + + Password Recovery + + + +
+
+

Password Recovery

+ +

Please click here to reset your password.

+
+
+ + diff --git a/modules/clever-email/models/orm/EmailAttachmentModel.js b/modules/clever-email/models/orm/EmailAttachmentModel.js new file mode 100644 index 0000000..d9e297a --- /dev/null +++ b/modules/clever-email/models/orm/EmailAttachmentModel.js @@ -0,0 +1,29 @@ +module.exports = function ( sequelize, DataTypes ) { + return sequelize.define( "EmailAttachment", + { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true + }, + fileName: { + type: DataTypes.STRING, + allowNull: true + }, + filePath: { + type: DataTypes.STRING, + allowNull: false + }, + fileType: { + type: DataTypes.STRING, + allowNull: true + } + }, + { + paranoid: true, instanceMethods: { + toJSON: function () { + return this.values; + } + } + } ); +}; diff --git a/modules/clever-email/models/orm/EmailModel.js b/modules/clever-email/models/orm/EmailModel.js new file mode 100644 index 0000000..4c7eb90 --- /dev/null +++ b/modules/clever-email/models/orm/EmailModel.js @@ -0,0 +1,30 @@ +module.exports = function ( sequelize, DataTypes ) { + return sequelize.define( "Email", + { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true + }, + subject: { + type: DataTypes.STRING, + allowNull: false + }, + body: { + type: DataTypes.TEXT, + allowNull: true + }, + isDelivered: { + type: DataTypes.BOOLEAN, + allowNull: false, + default: true + } + }, + { + paranoid: true, instanceMethods: { + toJSON: function () { + return this.values; + } + } + } ); +}; diff --git a/modules/clever-email/models/orm/EmailReplyModel.js b/modules/clever-email/models/orm/EmailReplyModel.js new file mode 100644 index 0000000..afdfd7f --- /dev/null +++ b/modules/clever-email/models/orm/EmailReplyModel.js @@ -0,0 +1,29 @@ +module.exports = function ( sequelize, DataTypes ) { + return sequelize.define( "EmailReply", + { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true + }, + reply: { + type: DataTypes.TEXT, + allowNull: true + }, + from: { + type: DataTypes.TEXT, + allowNull: true + }, + to: { + type: DataTypes.TEXT, + allowNull: true + } + }, + { + paranoid: true, instanceMethods: { + toJSON: function () { + return this.values; + } + } + } ); +}; diff --git a/modules/clever-email/models/orm/EmailTemplateModel.js b/modules/clever-email/models/orm/EmailTemplateModel.js new file mode 100644 index 0000000..9472af2 --- /dev/null +++ b/modules/clever-email/models/orm/EmailTemplateModel.js @@ -0,0 +1,82 @@ +module.exports = function ( sequelize, DataTypes ) { + return sequelize.define( "EmailTemplate", + { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true + }, + title: { + type: DataTypes.STRING, + allowNull: false, + validate: { + len: [ 2, 256 ] + } + }, + subject: { + type: DataTypes.STRING, + allowNull: false, + validate: { + len: [ 2, 256 ] + } + }, + body: { + type: DataTypes.TEXT, + allowNull: false + }, + isActive: { + type: DataTypes.BOOLEAN, + allowNull: false, + default: false + }, + isDefault: { + type: DataTypes.BOOLEAN, + allowNull: false, + default: false + }, + useDefault: { + type: DataTypes.BOOLEAN, + allowNull: false, + default: false + }, + hasPermission: { + type: DataTypes.BOOLEAN, + allowNull: false, + default: true + } + }, + { + instanceMethods: { + toJSON: function () { + var values = this.values; + values['permittedToUsers'] = []; + values['permittedToTeams'] = []; + + delete hasPermission; + + function uniqueValues( arr, attr ) { + var o = {}, i, l = arr.length, r = []; + for ( i = 0; i < l; i += 1 ) o[arr[i][attr]] = arr[i]; + for ( i in o ) r.push( o[i] ); + return r; + } + + if ( values['users'] && values['users'].length ) { + values['permittedToUsers'] = uniqueValues( values['users'], 'id' ).map( function ( user ) { + return user.id; + } ); + delete values['users']; + } + + if ( values['teams'] && values['teams'].length ) { + values['permittedToTeams'] = uniqueValues( values['teams'], 'id' ).map( function ( team ) { + return team.id; + } ); + delete values['teams']; + } + + return values; + } + } + } ); +}; diff --git a/modules/clever-email/models/orm/EmailUserModel.js b/modules/clever-email/models/orm/EmailUserModel.js new file mode 100644 index 0000000..86f21b8 --- /dev/null +++ b/modules/clever-email/models/orm/EmailUserModel.js @@ -0,0 +1,22 @@ +module.exports = function ( sequelize, DataTypes ) { + return sequelize.define( "EmailUser", + { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true + }, + status: { + type: DataTypes.ENUM, + values: ['cc', 'bcc'], + allowNull: false + } + }, + { + instanceMethods: { + toJSON: function () { + return this.values; + } + } + } ); +}; diff --git a/modules/clever-email/module.js b/modules/clever-email/module.js new file mode 100644 index 0000000..a5f6986 --- /dev/null +++ b/modules/clever-email/module.js @@ -0,0 +1,5 @@ +var Module = require( 'classes' ).ModuleClass.extend( { + +} ); + +module.exports = new Module( 'clever-email', injector ); \ No newline at end of file diff --git a/modules/clever-email/package.json b/modules/clever-email/package.json new file mode 100644 index 0000000..0f27176 --- /dev/null +++ b/modules/clever-email/package.json @@ -0,0 +1,25 @@ +{ + "name": "clever-email", + "version": "0.0.1", + "private": true, + "dependencies": { + "q": "", + "lodash": "2.4.1", + "mandrill-api": "*", + "sendgrid": "~0.4.2", + "mailgun": "*", + "ejs": "*", + "shortid": "~2.0.0", + "cheerio": "~0.12.4" + }, + "author": { + "name": "Clevertech", + "email": "info@clevertech.biz", + "web": "http://www.clevertech.biz" + }, + "main": "module.js", + "description": "", + "devDependencies": { + + } +} \ No newline at end of file diff --git a/modules/clever-email/routes.js b/modules/clever-email/routes.js new file mode 100644 index 0000000..7f318fd --- /dev/null +++ b/modules/clever-email/routes.js @@ -0,0 +1,27 @@ +module.exports = function ( + app, + EmailController, + EmailAlertController, + EmailTemplateController ) { + + app.get( '/email_templates/:id/preview', UserController.requiresLogin, EmailTemplateController.attach() ); + app.get( '/email_templates', UserController.requiresLogin, EmailTemplateController.attach() ); + app.get( '/email_templates/:id', UserController.requiresLogin, EmailTemplateController.attach() ); + app.post( '/email_templates', UserController.requiresLogin, EmailTemplateController.checkRequiredFields, EmailTemplateController.attach() ); + app.post( '/email_templates/:id', UserController.requiresLogin, EmailTemplateController.checkRequiredFields, EmailTemplateController.attach() ); + app['delete']( '/email_templates/:id', UserController.requiresLogin, EmailTemplateController.attach() ); + //app.post('/emails/:id/send' , UserController.requiresLogin, EmailTemplateController.attach()); + //app.get('/emails/:id/permission' , UserController.requiresLogin, EmailTemplateController.attach()); + + app.get( '/emails', UserController.requiresLogin, EmailController.attach() ); + app.get( '/emails/:id', UserController.requiresLogin, EmailController.attach() ); + app.post( '/emails', UserController.requiresLogin, EmailController.attach() ); + + app.post( '/emails/:pubkey/eventsMail', EmailController.checkEventMailData, EmailController.attach() ); + + app.all('/alerts/?', UserController.requiresLogin, EmailAlertController.attach()); + app.put('/alerts/?', UserController.requiresLogin, EmailAlertController.attach()); + app.get('/alerts/:id/?', UserController.requiresLogin, EmailAlertController.attach()); + app['delete']('/alerts/:id/?', UserController.requiresLogin, EmailAlertController.attach()); + +}; \ No newline at end of file diff --git a/modules/clever-email/schema/seedData.json b/modules/clever-email/schema/seedData.json new file mode 100644 index 0000000..93a0bc7 --- /dev/null +++ b/modules/clever-email/schema/seedData.json @@ -0,0 +1,156 @@ +{ + "DefaultEmailTemplate": [ + { + "title": "application submitted successfully", + "subject": "<%=company_name%> :Application Submitted Successfully", + "body": "Dear <%=firstName%> <%=lastName%>,

Thank you for your interest in <%=company_name%>. We received your application for the following position(s) submitted on <%=submitted_date%>:
<%=job_title%>
Our team is reviewing your qualifications and will contact you if there is a match with any of our open positions. Please note that new jobs are posted to the site often. Please check back regularly for future openings that may be of interest to you.
We appreciate your interest <%=company_name%> and wish you the best of luck in your job search
Sincerely
Company Human Resources Team
<%=career_link%>
Note: This message was automatically generated. Please do not respond to this email.

" + }, + { + "title": "application status update", + "subject": "<%=company_name%> :Application Status Update", + "body": "Dear <%=firstName%> <%=lastName%>,

Thank you for applying for the position <%=job_title%> with <%=company_name%>. We appreciate your interest in our organization.We received a significant number of applications for the position, and the hiring process has been a very competitive one. After a review of your application, we have decided not to move your application forward. However, we greatly appreciate your interest in working with us and wish you the best of luck with your job search. We welcome you to visit our careers page regularly for future openings that may be of interest to you
Sincerely
Company Human Resources Team
<%=career_link%>

" + }, + { + "title": "new career opportunity", + "subject": "<%=company_name%> :New career opportunity", + "body": "

<%=company_name%> has just posted the following new jobs to our careers page on <%=published_date%>
<%=job_title_link%>
If you or anybody you know may be a good fit for this position, please encourage them to apply immediately. If passing along to others, please be sure to remind them to mention how they heard about the opportunity.
Thanks,
Company Human Resources Team
<%=career_link%>

" + } + ], + "EmailTemplate": [ + { + "title": "Application submitted successfully", + "subject": "{{Company_Name}} :Application Submitted Successfully", + "body": "Dear Applicant First Name  Applicant Last Name ,

Thank you for your interest in Company Name . We received your application for the following position(s) submitted on {{submitted_date}}:
{{job_title}}
Our team is reviewing your qualifications and will contact you if there is a match with any of our open positions. Please note that new jobs are posted to the site often. Please check back regularly for future openings that may be of interest to you.
We appreciate your interest Company Name  and wish you the best of luck in your job search
Sincerely
Company Human Resources Team
{{career_link}}
Note: This message was automatically generated. Please do not respond to this email.

", + "hasPermission": true, + "isActive": true, + "useDefault": false, + "isDefault": true, + "UserId": 1, + "associations": { + "Account": { + "id": 1 + } + } + }, + { + "title": "Application status update", + "subject": "{{Company_Name}} :Application Status Update", + "body": "Dear Applicant First Name  Applicant Last Name ,

Thank you for applying for the position {{job_title}} with Company Name . We appreciate your interest in our organization.We received a significant number of applications for the position, and the hiring process has been a very competitive one. After a review of your application, we have decided not to move your application forward. However, we greatly appreciate your interest in working with us and wish you the best of luck with your job search. We welcome you to visit our careers page regularly for future openings that may be of interest to you
Sincerely
Company Human Resources Team
{{career_link}}

", + "hasPermission": true, + "isActive": true, + "useDefault": false, + "isDefault": true, + "UserId": 1, + "associations": { + "Account": { + "id": 1 + } + } + }, + { + "title": "new career opportunity", + "subject": "{{Company_Name}} :New career opportunity", + "body": "

Company Name  has just posted the following new jobs to our careers page on {{published_date}}
{{job_title_link}}
If you or anybody you know may be a good fit for this position, please encourage them to apply immediately. If passing along to others, please be sure to remind them to mention how they heard about the opportunity.
Thanks,
Company Human Resources Team
{{career_link}}

", + "hasPermission": true, + "isActive": true, + "useDefault": false, + "isDefault": true, + "UserId": 1, + "associations": { + "Account": { + "id": 1 + } + } + }, + { + "title": "Application submitted successfully", + "subject": "{{Company_Name}} :Application Submitted Successfully", + "body": "Dear Applicant First Name  Applicant Last Name ,

Thank you for your interest in Company Name . We received your application for the following position(s) submitted on {{submitted_date}}:
{{job_title}}
Our team is reviewing your qualifications and will contact you if there is a match with any of our open positions. Please note that new jobs are posted to the site often. Please check back regularly for future openings that may be of interest to you.
We appreciate your interest Company Name  and wish you the best of luck in your job search
Sincerely
Company Human Resources Team
{{career_link}}
Note: This message was automatically generated. Please do not respond to this email.

", + "hasPermission": true, + "isActive": true, + "useDefault": false, + "isDefault": true, + "UserId": 2, + "associations": { + "Account": { + "id": 1 + } + } + }, + { + "title": "Application status update", + "subject": "{{Company_Name}} :Application Status Update", + "body": "Dear Applicant First Name  Applicant Last Name ,

Thank you for applying for the position {{job_title}} with Company Name . We appreciate your interest in our organization.We received a significant number of applications for the position, and the hiring process has been a very competitive one. After a review of your application, we have decided not to move your application forward. However, we greatly appreciate your interest in working with us and wish you the best of luck with your job search. We welcome you to visit our careers page regularly for future openings that may be of interest to you
Sincerely
Company Human Resources Team
{{career_link}}

", + "hasPermission": true, + "isActive": true, + "useDefault": false, + "isDefault": true, + "UserId": 2, + "associations": { + "Account": { + "id": 1 + } + } + }, + { + "title": "new career opportunity", + "subject": "{{Company_Name}} :New career opportunity", + "body": "

Company Name  has just posted the following new jobs to our careers page on {{published_date}}
{{job_title_link}}
If you or anybody you know may be a good fit for this position, please encourage them to apply immediately. If passing along to others, please be sure to remind them to mention how they heard about the opportunity.
Thanks,
Company Human Resources Team
{{career_link}}

", + "hasPermission": true, + "isActive": true, + "useDefault": false, + "isDefault": true, + "UserId": 2, + "associations": { + "Account": { + "id": 1 + } + } + }, + { + "title": "Application submitted successfully", + "subject": "{{Company_Name}} :Application Submitted Successfully", + "body": "Dear Applicant First Name  Applicant Last Name ,

Thank you for your interest in Company Name . We received your application for the following position(s) submitted on {{submitted_date}}:
{{job_title}}
Our team is reviewing your qualifications and will contact you if there is a match with any of our open positions. Please note that new jobs are posted to the site often. Please check back regularly for future openings that may be of interest to you.
We appreciate your interest Company Name  and wish you the best of luck in your job search
Sincerely
Company Human Resources Team
{{career_link}}
Note: This message was automatically generated. Please do not respond to this email.

", + "hasPermission": true, + "isActive": true, + "useDefault": false, + "isDefault": true, + "UserId": 3, + "associations": { + "Account": { + "id": 1 + } + } + }, + { + "title": "Application status update", + "subject": "{{Company_Name}} :Application Status Update", + "body": "Dear Applicant First Name  Applicant Last Name ,

Thank you for applying for the position {{job_title}} with Company Name . We appreciate your interest in our organization.We received a significant number of applications for the position, and the hiring process has been a very competitive one. After a review of your application, we have decided not to move your application forward. However, we greatly appreciate your interest in working with us and wish you the best of luck with your job search. We welcome you to visit our careers page regularly for future openings that may be of interest to you
Sincerely
Company Human Resources Team
{{career_link}}

", + "hasPermission": true, + "isActive": true, + "useDefault": false, + "isDefault": true, + "UserId": 3, + "associations": { + "Account": { + "id": 1 + } + } + }, + { + "title": "new career opportunity", + "subject": "{{Company_Name}} :New career opportunity", + "body": "

Company Name  has just posted the following new jobs to our careers page on {{published_date}}
{{job_title_link}}
If you or anybody you know may be a good fit for this position, please encourage them to apply immediately. If passing along to others, please be sure to remind them to mention how they heard about the opportunity.
Thanks,
Company Human Resources Team
{{career_link}}

", + "hasPermission": true, + "isActive": true, + "useDefault": false, + "isDefault": true, + "UserId": 3, + "associations": { + "Account": { + "id": 1 + } + } + } + ] +} \ No newline at end of file diff --git a/modules/clever-email/services/EmailService.js b/modules/clever-email/services/EmailService.js new file mode 100644 index 0000000..97a4841 --- /dev/null +++ b/modules/clever-email/services/EmailService.js @@ -0,0 +1,503 @@ +var BaseService = require( './BaseService' ) + , Q = require( 'q' ) + , Sequelize = require( 'sequelize' ) + , sendgrid = require( 'utils' ).sendgrid + , ejsFileRender = require( 'utils' ).ejsfilerender + , shortid = require( 'shortid' ) + , EmailService = null; + +module.exports = function ( db, + EmailModel, + EmailAttachmentModel, + ProspectModel, + EmailReplyModel, + UserModel, + JobModel, + EmailUserModel, + EmailTemplateService, + ProspectSurveyService, + config ) { + + var mailer = sendgrid( config.sendgrid ) + , bakeTemplate = ejsFileRender(); + + if ( EmailService && EmailService.instance ) { + return EmailService.instance; + } + + EmailService = BaseService.extend( { + + formatReplyAddress: function ( emailToken ) { + var addr = '' + , envName = ''; + + envName = ( config.environmentName == 'DEV' ) + ? 'dev' + : ( config.environmentName == 'PROD' ) + ? 'prod' + : 'stage'; + + addr = ( envName != 'prod' ) + ? 'reply_' + emailToken + '@' + envName + '.bolthr.clevertech.biz' + : 'reply_' + emailToken + '@app-mail.bolthr.com'; + + return addr; + }, + + formatData: function ( data, operation ) { + var o = { email: {}, prospect: {}, usersCC: [], usersBCC: [], attachments: [], sender: {} }; + var emailURL = ''; + + // o['account'] = {}; + // o['account']['logo'] = data.accLogo; + // o['account']['name'] = data.accName; + + //Email Object + o.email.subject = data.subject || null; + o.email.body = data.body || null; + o.email.token = data.userId + data.to.id + shortid.seed( 10000 ).generate(); + o.email.UserId = data.userId; + o.email.AccountId = data.accId; + o.email.ProspectId = data.to.id; + o.email.JobId = data.JobId || null; + + o.hasTemplate = (/false|true/.test( data.hasTemplate )) ? data.hasTemplate : true; + + emailURL = this.formatReplyAddress( o.email.token ); + + // o.sender.fullName = data.userFirstName + ' ' + data.userLastName; + // o.sender.email = emailURL; + + //EmailProspect object + // o.prospect = data.to; + + //EmailUsers Object + o.usersCC = ( data.cc && data.cc.length ) ? data.cc : []; + o.usersBCC = ( data.bcc && data.bcc.length) ? data.bcc : []; + + var usersCC = ( data.cc && data.cc.length ) ? data.cc.map( function ( x ) { return x.email } ) : []; + var usersBCC = ( data.bcc && data.bcc.length) ? data.bcc.map( function ( x ) { return x.email } ) : []; + + //Dump email dependency data + var dataDump = { + companyLogo: data.accLogo, + companyName: data.accName, + fromName: data.userFirstName + ' ' + data.userLastName, + fromMail: emailURL, + toMail: data.to.email, + usersCC: usersCC, + usersBCC: usersBCC, + tplName: 'default', + tplTitle: data.subject || 'BoltHR: Autogenerated Notification', + hasTemplate: (/false|true/.test( data.hasTemplate )) ? data.hasTemplate : true + }; + + o['email']['dump'] = JSON.stringify( dataDump ); + + //EmailAttachements Object + o.attachments = ( data.attachments && data.attachments.length ) ? data.attachments : []; + + o.survey = ( data.survey ) ? data.survey : null; + + if ( operation === 'create' ) { + o.email.UserId = data.userId; + o.email.EmailTemplateId = data.EmailTemplateId || null + o.email.sentAttemps = 0; + o.email.isDelivered = false; + o.email.isOpened = false; + o.email.id = null; + } + + return o; + }, + //TODO: change sentAttempts and isDelivered values when we move notification mail into background tasks + formatRepliedData: function ( data ) { + var replyAddr = this.formatReplyAddress( data['emailToken'] ); + + var o = { + id: null, + reply: data.reply, + EmailId: data.emailId, + token: data.emailId + '_' + shortid.seed( 10000 ).generate(), + sentAttemps: 1, + isDelivered: true, + isOpened: false + }; + + var t = { + subject: data.subject, + html: data.replyHTML, + replyAddress: replyAddr + }; + + if ( data.from.indexOf( data[ 'userEmail' ] ) != -1 ) { + + o['from'] = data.userEmail; + t['fromname'] = null; + + o['to'] = data.prospectEmail; + t['toname'] = data.prospectName; + + } else if ( data.from.indexOf( data[ 'prospectEmail' ] ) != -1 ) { + + o['from'] = data.prospectEmail; + t['fromname'] = data.prospectName; + + o['to'] = data.userEmail; + t['toname'] = null; + + } else { + + o['from'] = t['fromname'] = o['to'] = t['toname'] = null; + + } + + o['dump'] = JSON.stringify( t ); + + return o; + }, + + listEmails: function ( userId, emailId ) { + var deferred = Q.defer(); + + this + .find( { where: { UserId: userId }, include: [ EmailAttachmentModel, ProspectModel, EmailReplyModel ] } ) + .then( deferred.resolve ) + .fail( deferred.reject ); + + return deferred.promise; + }, + + getEmailById: function ( userId, emailId ) { + var deferred = Q.defer() + , service = this + , chainer = new Sequelize.Utils.QueryChainer(); + + chainer.add( + EmailModel.find( { + where: { id: emailId, UserId: userId, 'deletedAt': null }, include: [ EmailAttachmentModel, ProspectModel, EmailReplyModel ] + } ) + ); + + chainer.add( + EmailUserModel.find( { + where: { EmailId: emailId, 'deletedAt': null }, include: [ UserModel ] + } ) + ); + + chainer + .run() + .success( function ( results ) { + + if ( !results[0] ) { + deferred.resolve( {statuscode: '403', message: 'invalid'} ); + return; + } + + var emailJson = JSON.parse( JSON.stringify( results[ 0 ] ) ); + var emailUsers = results[ 1 ]; + + emailJson['users'] = emailUsers; + deferred.resolve( emailJson ); + + } ) + .error( deferred.reject ); + + return deferred.promise; + }, + + handleEmailCreation: function ( data ) { + var promises = [] + , service = this; + + data.forEach( function ( item ) { + promises.push( service.processEmailCreation( item ) ); + } ); + + return Q.all( promises ); + }, + + processEmailCreation: function ( emailItem ) { + var deferred = Q.defer() + , service = this + , fData = this.formatData( emailItem, 'create' ); + + service + .create( fData.email ) + .then( function ( savedEmail ) { + console.log( 'Email saved!' ); + + service + .saveEmailAssociation( savedEmail, fData ) + .then( function () { + console.log( 'Email assoc saved!' ); + + if ( !fData.survey ) { + deferred.resolve( {statucode: 200, message: "email has been sent"} ); + return; + } + + service + .handleProspectSurveyCreation( fData.email, fData.survey ) + .then( deferred.resolve ) + .fail( deferred.reject ); + + } ) + .then( deferred.resolve ) + .fail( deferred.reject ); + } ) + .fail( deferred.reject ); + + return deferred.promise; + }, + + handleProspectSurveyCreation: function ( email, survey ) { + var deferred = Q.defer(); + + ProspectSurveyService + .createProspectSurvey( { + pointsAwarded: survey.pointsPossible, + SurveyId: survey.id, + accId: email.AccountId, + ProspectId: email.ProspectId, + token: survey.token + } ) + .then( deferred.resolve ) + .fail( deferred.reject ); + + return deferred.promise; + }, + + saveEmailAssociation: function ( savedEmail, fData ) { + var deferred = Q.defer() + , chainer = new Sequelize.Utils.QueryChainer(); + + //USERS: CC + if ( fData.usersCC.length ) { + var l = fData.usersCC.length, item, cc = []; + while ( l-- ) { + item = fData.usersCC[l]; + cc.push( { + EmailId: savedEmail.id, UserId: item.id, status: 'cc' + } ); + } + + chainer.add( EmailUserModel.bulkCreate( cc ) ); + } + + //USERS: BCC + if ( fData.usersBCC.length ) { + var l = fData.usersBCC.length, itm, bcc = []; + + while ( l-- ) { + itm = fData.usersBCC[l]; + bcc.push( { + EmailId: savedEmail.id, UserId: itm.id, status: 'bcc' + } ); + } + + chainer.add( EmailUserModel.bulkCreate( bcc ) ); + } + + //Attachments + if ( fData.attachments.length ) { + var l = fData.attachments.length, attch, emailDocs = []; + + while ( l-- ) { + attch = fData.attachments[l]; + emailDocs.push( { + id: null, filePath: attch.filePath, fileName: attch.fileName, mimeType: attch.mimeType, EmailId: savedEmail.id + } ); + } + + chainer.add( EmailAttachmentModel.bulkCreate( emailDocs ) ); + } + + chainer + .run() + .success( function ( result ) { + //console.log("RESULT:", JSON.parse(JSON.stringify(result)) ); + deferred.resolve(); + } ) + .error( function ( err ) { + console.log( err ); + deferred.reject( err ); + } ); + + return deferred.promise; + }, + + renderTemplate: function ( data ) { + var deferred = Q.defer() + , email = data['email'] + , user = data['user'] || null + , tplName = data['tplName'] || null + , tpl = {}; + + tpl['tplName'] = tplName || 'default'; + tpl['tplTitle'] = email.subject || 'BoltHR: Autogenerated Notification'; + tpl['companyName'] = ( email.dump.fromCompanyName ) ? email.dump.fromCompanyName : 'BoltHR'; + tpl['companyLogo'] = ( email.dump.fromCompanyLogo ) ? email.dump.fromCompanyLogo : 'http://app.bolthr.com/images/logo.png'; + + //Text has already being parsed from frontend + if ( !email.EmailTemplateId ) { + tpl['strHTML'] = email.body; + + bakeTemplate( tpl ) + .then( deferred.resolve ) + .fail( deferred.reject ); + + } else { + + EmailTemplateService + .getPlaceholderData( { + accId: email.AccountId, ProspectId: email.ProspectId, EmailTemplateId: email.EmailTemplateId, JobId: null + } ) + .then( function ( emailTemplate ) { + return EmailTemplateService.processTemplateIntrpolation( user, emailTemplate ); + } ) + .then( function ( html ) { + tpl['strHTML'] = html; + + return bakeTemplate( tpl ); + } ) + .then( deferred.resolve ) + .fail( deferred.reject ); + } + + return deferred.promise; + }, + + sendEmail: function ( email, html ) { + var deferred = Q.defer() + , fromMail = 'no-reply@app.bolthr.com' + , fromName = 'BoltHR' + , subject = email.subject || 'BoltHR: Notification' + , emailId = email.id + , payload = {}; + + if ( email.dump.fromCompanyName ) { + fromName = email.dump.fromName; + fromMail = email.dump.fromMail; + } + + payload = { + to: [ email.dump.toMail ], + bcc: email.dump.usersBCC, + subject: subject, + html: html, + from: fromMail, + fromname: fromName, + emailId: emailId + }; + + if ( email.dump.usersCC && email.dump.usersCC.length ) { + + email.dump.usersCC.forEach( function ( userEmail ) { + payload.to.push( userEmail ); + } ); + + } + + mailer( payload ) + .then( deferred.resolve ) + .fail( deferred.reject ); + + return deferred.promise; + }, + + processMailReply: function ( data ) { + var deferred = Q.defer() + , service = this; + + this + .findOne( { where: { token: data.replyMailHash }, include: [ UserModel, ProspectModel ] } ) + .then( function ( email ) { + + if ( !email || !email.id ) { + console.log( "\n\n----- EMAIL REPLY TOKEN DOES NOT EXISTS ------\n" ); + deferred.resolve(); + return; + } + + console.log( "\n\n----- EMAIL REPLY TOKEN EXISTS ------\n" ); + + data['emailId'] = email.id; + data['emailToken'] = email.token; + data['userEmail'] = email.user.email; + data['userName'] = email.user.firstname + ' ' + email.user.lastname; + data['prospectEmail'] = email.prospect.email; + data['prospectName'] = email.prospect.firstName + ' ' + email.prospect.lastName; + + service + .saveMailReply( data ) + .then( deferred.resolve ) + .fail( deferred.reject ); + + } ) + .fail( deferred.reject ); + + return deferred.promise; + }, + + saveMailReply: function ( data ) { + var deferred = Q.defer() + , replyData = this.formatRepliedData( data ); + + EmailReplyModel + .create( replyData ) + .success( deferred.resolve ) + .error( deferred.reject ); + + return deferred.promise; + }, + + //TODO: Move this function into Background Tasks + processMailReplyNotification: function ( savedReply ) { + var deferred = Q.defer() + , mail = JSON.parse( JSON.stringify( savedReply ) ) + , payload = { }; + + payload[ 'from' ] = mail.dump.replyAddress; + payload[ 'fromname' ] = mail.dump.fromname; + payload[ 'to' ] = [ mail.to ]; + payload[ 'toname' ] = mail.dump.toname; + + payload[ 'subject' ] = mail.dump.subject; + payload[ 'text' ] = mail.reply; + payload[ 'html' ] = mail.dump.html; + + mailer( payload ) + .then( deferred.resolve ) + .fail( deferred.resolve ); + + return deferred.promise; + }, + + processMailEvents: function ( evns ) { + var deferred = Q.defer() + , item = null + , chainer = new Sequelize.Utils.QueryChainer(); + + while ( item = evns.pop() ) { + console.log( "\nUPDATING: ", item.email_id ); + chainer.add( EmailModel.update( { isOpened: true }, { id: item.email_id, 'deletedAt': null} ) ); + } + + chainer + .run() + .success( function () { + deferred.resolve( {statuscode: 200, message: 'ok'} ); + } ) + .error( deferred.reject ); + + return deferred.promise; + } + + } ); + + EmailService.instance = new EmailService( db ); + EmailService.Model = EmailModel; +console.log('-----------------------') + return EmailService.instance; +}; \ No newline at end of file diff --git a/modules/clever-email/services/EmailTemplateService.js b/modules/clever-email/services/EmailTemplateService.js new file mode 100644 index 0000000..0ad27a1 --- /dev/null +++ b/modules/clever-email/services/EmailTemplateService.js @@ -0,0 +1,391 @@ +var BaseService = require( './BaseService' ) + , Q = require( 'q' ) + , moment = require( 'moment' ) + , Sequelize = require( 'sequelize' ) + , cheerio = require( 'cheerio' ) + , sendgrid = require( 'utils' ).sendgrid + , EmailTemplateService = null; + + +module.exports = function ( db, + EmailTemplateModel, + UserModel, TeamModel, + ProspectModel, + JobModel, + config ) { + + var mailer = sendgrid( config.sendgrid ); + + if ( EmailTemplateService && EmailTemplateService.instance ) { + return EmailTemplateService.instance; + } + + EmailTemplateService = BaseService.extend( { + + formatData: function ( data ) { + var o = { + id: data.id || null, + title: data.title, + subject: data.subject, + body: data.body, + AccountId: data.accId, + UserId: data.userId || null, + isActive: (/false|true/.test( data.isActive )) ? data.isActive : false, + isDefault: (/false|true/.test( data.isDefault )) ? data.isDefault : false, + useDefault: (/false|true/.test( data.useDefault )) ? data.useDefault : false, + hasPermission: (/false|true/.test( data.hasPermission )) ? data.hasPermission : true + }; + + return o; + }, + + formatEmailTemplateUserForSave: function ( permittedToUsers ) { + var tu = [] + , arr = permittedToUsers + , item; + + if ( arr && arr.length ) { + while ( item = arr.pop() ) { + var team = UserModel.build( { id: item } ); + tu.push( team ); + } + } + + return tu; + }, + + formatEmailTemplateTeamForSave: function ( permittedToTeams ) { + var tm = [] + , arr = permittedToTeams + , item; + + if ( arr && arr.length ) { + while ( item = arr.pop() ) { + var team = TeamModel.build( { id: item } ); + tm.push( team ); + } + } + + return tm; + }, + + listTemplates: function ( accId, userId, teamId, role ) { + var deferred = Q.defer() + , chainer = new Sequelize.Utils.QueryChainer() + , query = 'EmailTemplates.AccountId=' + accId; + + if ( !role || (role != 'Owner') ) { + query += ' AND ( '; + query += 'EmailTemplates.UserId = ' + userId + ' OR ' + + 'EmailTemplatesUsers.UserId =' + userId + ' OR ' + + 'EmailTemplates.hasPermission = false'; + + if ( teamId ) { + query += ' OR EmailTemplatesTeams.TeamId = ' + teamId; + } + + query += ' )'; + } + + this + .find( { where: [ query ], include: [ UserModel, TeamModel ] } ) + .then( function ( result ) { + if ( !result.length ) { + deferred.resolve( [] ); + return; + } + deferred.resolve( result ); + } ) + .fail( deferred.reject ); + + return deferred.promise; + }, + + getTemplateById: function ( accId, userId, teamId, tplId, role ) { + var deferred = Q.defer() + , query = '( EmailTemplates.id = ' + tplId + ' AND EmailTemplates.AccountId= ' + accId + ' )'; + + if ( !role || (role != 'Owner') ) { + + query += ' AND ( '; + query += '( EmailTemplates.UserId = ' + userId + ' AND EmailTemplates.AccountId= ' + accId + ' ) OR ' + + '( EmailTemplatesUsers.UserId = ' + userId + ' AND EmailTemplates.AccountId= ' + accId + ' ) OR ' + + '( EmailTemplates.hasPermission = false AND EmailTemplates.AccountId= ' + accId + ' )'; + + if ( teamId ) { + query += ' OR ( EmailTemplatesTeams.TeamId = ' + teamId + ' AND EmailTemplates.AccountId= ' + accId + ' )'; + } + + query += ' )'; + } + + this + .findOne( { where: [ query ], include: [ UserModel, TeamModel ] } ) + .then( function ( result ) { + + if ( !result ) { + deferred.resolve( {statuscode: 403, message: 'invalid'} ); + return; + } + deferred.resolve( result ); + } ) + .fail( deferred.reject ); + + return deferred.promise; + }, + + createEmailTemplate: function ( data ) { + var deferred = Q.defer() + , emailData = this.formatData( data ); + + this + .create( emailData ) + .then( deferred.resolve ) + .fail( deferred.reject ); + + return deferred.promise; + }, + + getPlaceholderData: function ( data ) { + var deferred = Q.defer() + , plData = { template: null, prospect: null, job: null } + , chainer = new Sequelize.Utils.QueryChainer(); + + var tplId = data.template_id || data.EmailTemplateId + , accId = data.accId || data.AccountId + , prospId = data.prospect_id || data.ProspectId || null + , jobId = data.job_id || data.JobId || null; + + chainer.add( + EmailTemplateModel.find( { + where: { id: tplId, AccountId: accId } + } ) + ); + + chainer.add( + ProspectModel.find( { + where: { id: prospId, AccountId: accId, "deletedAt": null } + } ) + ); + + chainer.add( + JobModel.find( { + where: { id: jobId, AccountId: accId, "deletedAt": null } + } ) + ); + + chainer + .run() + .success( function ( result ) { + + if ( !result[0] ) { // If template does not exist + deferred.resolve( null ); + return; + } + + plData['template'] = JSON.parse( JSON.stringify( result[0] ) ); + plData['prospect'] = JSON.parse( JSON.stringify( result[1] ) ); + plData['job'] = JSON.parse( JSON.stringify( result[2] ) ); + + deferred.resolve( plData ); + } ) + .error( deferred.reject ); + + return deferred.promise; + }, + processTemplateIntrpolation: function ( data, user ) { + var deferred = Q.defer() + , service = this + , tpl = data.template.body; + + var $ = cheerio.load( tpl ); + + $( 'span[rel="placeholder"]' ).each( function ( i, elem ) { + text = service.getPlaceholderText( $( elem ), data.prospect, data.job, user ); + $( elem ).replaceWith( text ); + } ); + + deferred.resolve( $.root().html() ); + + return deferred.promise; + }, + + getPlaceholderText: function ( placeholderNode, prospect, job, user ) { + var id = placeholderNode.attr( 'id' ); + var parts = id.split( '-' ); + var domain = parts[0]; + var prop = parts[1]; + var propValue; + + //console.log("\n\nPLACEHOLDER: ",domain, prop); + + if ( domain === 'prospect' ) { + + if ( prop == 'createdAt' ) { + var d = moment( prospect[prop] ).format( 'YY:MM:DD' ); + propValue = d + ' EST'; + } else { + propValue = prospect && prospect[prop]; + } + + } + if ( domain === 'job' ) { + + if ( prop == 'url' ) { // Make it compatible with backend property + prop = 'jobmail'; + } + + if ( prop == 'publishDate' ) { + var d = moment( job[prop] ).format( 'YY:MM:DD' ); + propValue = d + ' EST'; + } else { + propValue = job && job[prop]; + } + } + + if ( domain === 'user' ) { + propValue = user && user[prop]; + } + + if ( domain === 'account' ) { + + if ( prop == 'url' ) { + var url = config.hosturl.replace( '://', '://' + user['account']['subdomain'] + '.' ); + url += '/careers'; + propValue = url; + } else { + propValue = user['account'] && user['account'][prop]; + } + } + + return propValue; + }, + + processEmailTemplateAssoc: function ( data, tpl ) { + var deferred = Q.defer() + , emailTplUsers = null + , emailTplTeams = null + , chainer = new Sequelize.Utils.QueryChainer(); + + chainer.add( this.query( 'delete from EmailTemplatesUsers where EmailTemplateId = ' + tpl.id ) ); + chainer.add( this.query( 'delete from EmailTemplatesTeams where EmailTemplateId = ' + tpl.id ) ); + + if ( (emailTplUsers = this.formatEmailTemplateUserForSave( data.permittedToUsers )).length ) { + chainer.add( tpl.setUsers( emailTplUsers ) ); + } + + if ( (emailTplTeams = this.formatEmailTemplateTeamForSave( data.permittedToTeams )).length ) { + chainer.add( tpl.setTeams( emailTplTeams ) ); + } + + chainer + .runSerially() + .success( function ( results ) { + + var tplJson = JSON.parse( JSON.stringify( tpl ) ); + tplJson['permittedToUsers'] = []; + tplJson['permittedToTeams'] = []; + + if ( ( emailTplUsers.length ) && ( emailTplTeams.length ) ) { + tplJson['permittedToUsers'] = results[ 2 ]; + tplJson['permittedToTeams'] = results[ 3 ]; + + } else if ( emailTplUsers.length ) { + + tplJson['permittedToUsers'] = results[ 2 ]; + } else { + + tplJson['permittedToTeams'] = results[ 2 ]; + } + + deferred.resolve( tplJson ); + } ) + .error( deferred.reject ); + + return deferred.promise; + }, + + handleEmailTemplateUpdate: function ( data ) { + var deferred = Q.defer() + , emailData = this.formatData( data ); + + this + .findOne( { where: { id: emailData.id, AccountId: data.accId } } ) + .then( function ( emailTpl ) { + + // If template is not Default and the User is not the creator send invalid + if ( !emailTpl || (!emailTpl.isDefault && ( emailTpl.UserId != data.userId )) ) { + deferred.resolve( { statuscode: 401, message: 'invalid' } ); + return; + } + + return this.updateEmailTemplate( emailTpl, emailData ); + + }.bind( this ) ) + .then( deferred.resolve ) + .fail( deferred.reject ); + + return deferred.promise; + }, + + updateEmailTemplate: function ( emailTemplate, data ) { + var deferred = Q.defer(); + + //If the EmailTemplate is default do not consider UserId + if ( emailTemplate.isDefault ) { + data['UserId'] = null; + } + + emailTemplate.updateAttributes( data ) + .success( function ( data ) { + deferred.resolve( data ); + } ) + .error( deferred.reject ); + + return deferred.promise; + }, + removeEmailTemplate: function ( userId, tplId ) { + var deferred = Q.defer() + , chainer = new Sequelize.Utils.QueryChainer(); + + + this + .find( { where: { id: tplId, "UserId": userId } } ) + .then( function ( result ) { + + if ( !result.length ) { + deferred.resolve( { statuscode: 400, message: 'email tempplate does not exist'} ); + return; + } + + var tpl = result[0]; + + chainer.add( tpl.setTeams( [] ) ); + chainer.add( tpl.setUsers( [] ) ); + chainer.add( tpl.destroy() ); + chainer.run() + .success( function () { + deferred.resolve( { statuscode: 200, message: 'operation was successfull' } ); + } ) + .error( deferred.promise ); + } ); + + return deferred.promise; + }, + + sendEmail: function ( payload ) { + var deferred = Q.defer(); + + mailer( payload ) + .then( deferred.resolve ) + .fail( deferred.resolve ) + + return deferred.promise; + } + } ); + + EmailTemplateService.instance = new EmailTemplateService( db ); + EmailTemplateService.Model = EmailTemplateModel; + + return EmailTemplateService.instance; +}; \ No newline at end of file diff --git a/modules/clever-email/tests/integration/test.controller.EmailAlertController.js b/modules/clever-email/tests/integration/test.controller.EmailAlertController.js new file mode 100644 index 0000000..5e6e141 --- /dev/null +++ b/modules/clever-email/tests/integration/test.controller.EmailAlertController.js @@ -0,0 +1,461 @@ +var should = require( 'should' ) + , sinon = require( 'sinon' ) + , testEnv = require( './utils' ).testEnv + , request = require( 'supertest' ) + , async = require( 'async' ) + , app = require( './../../../index' ); + +describe('controllers.EmailAlertController', function () { + this.timeout( 10000 ); + var env, EmailAlertController, ODMAttributeValueModel, ctrl, HRManagerSession, HRManager, EmployeeSession, Employee; + + before(function (done) { + var self = this; + + testEnv(function ( _ODMAttributeValueModel_ ) { + Model = _ODMAttributeValueModel_; + + async.parallel( [ + function loginAsHRManager ( next ) { + request( app ) + .post( '/user/login' ) + .set( 'Accept', 'application/json' ) + .send( { username: 'bolt-hris@clevertech.biz', password: 'password' }) + .expect( 'Content-Type' , /json/ ) + .expect( 200 ) + .end( function ( err, res ) { + HRManagerSession = res.headers[ 'set-cookie' ].pop().split( ';' )[ 0 ]; + HRManager = res.body; + next ( err ); + }); + }, + function loginAsEmployee ( next ) { + request( app ) + .post( '/user/login' ) + .set( 'Accept', 'application/json' ) + .send( { username: 'bolt-employee@clevertech.biz', password: 'password' } ) + .expect( 'Content-Type' , /json/ ) + .expect( 200 ) + .end( function ( err, res ) { + EmployeeSession = res.headers[ 'set-cookie' ].pop().split( ';' )[ 0 ]; + Employee = res.body; + next( err ); + }); + } + ], + done ); + }); + }); + + describe('.postAction()', function() { + it('should allow us to make a new e-mail alert with the correct permissions', function ( done ) { + var req = request( app ).post( '/alerts' ); + var date = ((new Date()).getTime() * 1000); + + req.cookies = HRManagerSession; + req.set( 'Accept','application/json' ) + .send ( { + alert: { + alertEmployee: true, + alertName: "New Benefit PTO", + category: "Benefit Eligibility", + customMessage: "My Message", + customSubject: "My Subject", + list: [], + otherFields: { + education: true + }, + reminders: [ + { + duration: "", + reminderDuration: "1", + reminderFrequency: "days", + reminderOffset: "before" + } + ], + tables: "Customized Education Table" + }, + submitted: date + } ) + .expect( 'Content-Type', /json/ ) + .expect( 200 ) + .end( function (err, res) { + res.body.should.be.instanceof( Object ); + res.body.should.have.properties( '_id', 'AttributeDocumentId', 'value' ); + res.body.value.should.be.instanceof( Object ); + res.body.value.should.have.properties( 'lastModifiedOn', 'lastModifiedBy', 'accountId', 'tables', 'reminders', 'otherFields', 'list', 'customSubject', 'customMessage', 'category', 'alertName', 'alertEmployee' ); + res.body.value.lastModifiedOn.value.should.equal( date ); + res.body.value.lastModifiedBy.value.should.equal( 9 ); + res.body.value.accountId.value.should.equal( 1 ); + res.body.value.tables.value.should.equal( 'Customized Education Table' ) + res.body.value.reminders.value.should.be.instanceOf( Array ); + res.body.value.reminders.value[0].duration.should.equal( '' ); + res.body.value.reminders.value[0].reminderDuration.should.equal( '1' ); + res.body.value.reminders.value[0].reminderFrequency.should.equal( 'days' ); + res.body.value.reminders.value[0].reminderOffset.should.equal( 'before' ); + res.body.value.otherFields.value.should.be.instanceof( Object ); + res.body.value.otherFields.value.should.have.properties( 'education' ); + res.body.value.otherFields.value.education.should.equal( true ); + res.body.value.list.value.should.be.instanceof( Array ); + res.body.value.list.value.length.should.equal( 0 ); + res.body.value.customSubject.value.should.equal( 'My Subject' ); + res.body.value.customMessage.value.should.equal( 'My Message' ); + res.body.value.category.value.should.equal( 'Benefit Eligibility' ); + res.body.value.alertName.value.should.equal( 'New Benefit PTO' ); + res.body.value.alertEmployee.value.should.equal( true ); + done(); + } ); + }); + + it.skip('shouldn\'t allow us to make a new e-mail alert if we don\'t have the correct permissions', function ( done ) { + var req = request( app ).post('/alerts'); + req.cookies = EmployeeSession; + req.set( 'Accept','application/json' ) + .send ( { + }) + .expect( 'Content-Type', /json/ ) + .expect( 403 ) + .end(function (err, res) { + done(); + }); + }); + + it.skip('shouldn\'t allow us to update an e-mail alert if we don\'t have the correct data with the correct permissions', function ( done ) { + var req = request( app ).post( '/alerts' ); + req.cookies = HRManagerSession; + req.set( 'Accept','application/json' ) + .send ( { + }) + .expect( 'Content-Type', /json/ ) + .expect( 400 ) + .end(function (err, res) { + done(); + }); + }); + + it.skip('shouldn\'t allow us to save/update am e-mail alert if we don\'t have any data with the correct permissions', function ( done ) { + var req = request( app ).post( '/alerts' ); + req.cookies = HRManagerSession; + req.set( 'Accept','application/json' ) + .send ( ) + .expect( 'Content-Type', /json/ ) + .expect( 400 ) + .end(function (err, res) { + done(); + }); + }); + }); + + describe('.putAction()', function() { + before( function ( done ) { + var self = this + , req = request( app ).post( '/alerts' ); + + var date = ((new Date()).getTime() * 1000); + + req.cookies = HRManagerSession; + req.set( 'Accept','application/json' ) + .send ( { + alert: { + alertEmployee: true, + alertName: "New Benefit PTO", + category: "Benefit Eligibility", + customMessage: "My Message", + customSubject: "My Subject", + list: [], + otherFields: { + education: true + }, + reminders: [ + { + duration: "", + reminderDuration: "1", + reminderFrequency: "days", + reminderOffset: "before" + } + ], + tables: "Customized Education Table" + }, + submitted: date + } ) + .expect( 'Content-Type', /json/ ) + .expect( 200 ) + .end(function (err, res) { + self.alert = res.body; + done(); + }); + }); + + it('should allow us to modify an existing alert', function ( done ) { + var self = this + , req = request( app ).put('/alerts'); + + var date = (new Date()).getTime() * 1000; + + req.cookies = HRManagerSession; + req.set( 'Accept','application/json' ) + .send({ + alert: { + _id: self.alert._id, + alertName: '123' + }, + submitted: date + }) + .expect( 'Content-Type', /json/ ) + .expect( 200 ) + .end(function (err, res) { + res.body.should.be.instanceof( Object ); + res.body.should.have.properties( '_id', 'AttributeDocumentId', 'value' ); + res.body.value.should.be.instanceof( Object ); + res.body.value.lastModifiedOn.value.should.equal( date ); + res.body.value.lastModifiedBy.value.should.equal( 9 ); + res.body.value.accountId.value.should.equal( 1 ); + res.body.value.alertName.value.should.equal( '123' ); + done(); + }); + }); + + it('should give us an error when we try to edit a non-existing alert', function ( done ) { + var self = this + , req = request( app ).put('/alerts'); + + req.cookies = HRManagerSession; + req.set( 'Accept','application/json' ) + .send( { + alert: { + _id: '1212121212121212', + alertName: 'Hello' + } + } ) + .expect( 'Content-Type', /json/ ) + .expect( 404 ) + .end(function (err, res) { + done(); + }); + }); + + it.skip('should give us an error when we don\'t have the correct permissions', function ( done ) { + var self = this + , req = request( app ).put('/alert'); + + req.cookies = EmployeeSession; + req.set( 'Accept','application/json' ) + .send({ + }) + .expect( 'Content-Type', /json/ ) + .expect( 403 ) + .end(function (err, res) { + done(); + }); + }); + }); + + describe('.deleteAction()', function() { + before(function ( done ) { + var self = this; + + async.parallel( [ + function ( next ) { + var date = (new Date()).getTime() * 1000; + var req = request( app ).post( '/alerts' ); + req.cookies = HRManagerSession; + req.set( 'Accept','application/json' ) + .send ( { + alert: { + alertEmployee: true, + alertName: "New Benefit PTO", + category: "Benefit Eligibility", + customMessage: "My Message", + customSubject: "My Subject", + list: [], + otherFields: { + education: true + }, + reminders: [ + { + duration: "", + reminderDuration: "1", + reminderFrequency: "days", + reminderOffset: "before" + } + ], + tables: "Customized Education Table" + }, + submitted: date + } ) + .expect( 'Content-Type', /json/ ) + .expect( 200 ) + .end(function (err, res) { + self.alert = res.body; + next(); + }); + } + ], done ); + }); + + it.skip('shouldn\'t be able to delete an alert if we don\'t have the permissions', function ( done ) { + var self = this + , req = request( app ).del('/alerts'); + + req.cookies = EmployeeSession; + req.set( 'Accept','application/json' ) + .send ( { + }) + .expect( 'Content-Type', /json/ ) + .expect( 403 ) + .end(function ( err, res ) { + done(); + }); + }); + + it('should be able to delete an alert', function ( done ) { + var self = this + , req = request( app ).del( '/alerts/' + self.alert._id ); + + req.cookies = HRManagerSession; + req.set( 'Accept','application/json' ) + .expect( 'Content-Type', /json/ ) + .expect( 200 ) + .end(function ( err, res ) { + done( err ); + }); + }); + + it('should give us a 404 if we try to delete a non-existant alert', function ( done ) { + var self = this + , req = request( app ).del('/alerts/' + self.alert._id ); + + req.cookies = HRManagerSession; + req.set( 'Accept','application/json' ) + .expect( 'Content-Type', /json/ ) + .expect( 404 ) + .end(function (err, res) { + done(); + }); + }); + }); + + describe('.listAction()', function() { + before(function ( done ) { + var self = this; + + async.parallel( [ + function ( next ) { + var date = (new Date()).getTime() * 1000; + var req = request( app ).post('/alerts'); + req.cookies = HRManagerSession; + req.set( 'Accept','application/json' ) + .send ( { + alert: { + alertEmployee: true, + alertName: "New Benefit PTO", + category: "Benefit Eligibility", + customMessage: "My Message", + customSubject: "My Subject", + list: [], + otherFields: { + education: true + }, + reminders: [ + { + duration: "", + reminderDuration: "1", + reminderFrequency: "days", + reminderOffset: "before" + } + ], + tables: "Customized Education Table" + }, + submitted: date + } ) + .expect( 'Content-Type', /json/ ) + .expect( 200 ) + .end(function (err, res) { + self.alert = res.body; + next(); + }); + } + ], done ); + }); + + it('should be able to get alerts with permissions', function ( done ) { + var self = this + , req = request( app ).get( '/alerts' ); + + req.cookies = HRManagerSession; + req.set( 'Accept','application/json' ) + .expect( 'Content-Type', /json/ ) + .expect( 200 ) + .end(function (err, res) { + res.body.should.be.instanceof( Array ); + res.body.length.should.be.above( 0 ); + res.body[0].should.have.properties( '_id', 'AttributeDocumentId', 'value' ); + res.body[0].value.should.be.instanceof( Object ); + res.body[0].value.should.have.properties( 'lastModifiedOn', 'lastModifiedBy', 'accountId', 'tables', 'reminders', 'otherFields', 'list', 'customSubject', 'customMessage', 'category', 'alertName', 'alertEmployee' ); + res.body[0].value.lastModifiedBy.value.should.equal( 9 ); + res.body[0].value.accountId.value.should.equal( 1 ); + res.body[0].value.tables.value.should.equal( 'Customized Education Table' ) + res.body[0].value.reminders.value.should.be.instanceOf( Array ); + res.body[0].value.reminders.value[0].duration.should.equal( '' ); + res.body[0].value.reminders.value[0].reminderDuration.should.equal( '1' ); + res.body[0].value.reminders.value[0].reminderFrequency.should.equal( 'days' ); + res.body[0].value.reminders.value[0].reminderOffset.should.equal( 'before' ); + res.body[0].value.otherFields.value.should.be.instanceof( Object ); + res.body[0].value.otherFields.value.should.have.properties( 'education' ); + res.body[0].value.otherFields.value.education.should.equal( true ); + res.body[0].value.list.value.should.be.instanceof( Array ); + res.body[0].value.list.value.length.should.equal( 0 ); + res.body[0].value.customSubject.value.should.equal( 'My Subject' ); + res.body[0].value.customMessage.value.should.equal( 'My Message' ); + res.body[0].value.category.value.should.equal( 'Benefit Eligibility' ); + res.body[0].value.alertName.value.should.equal( 'New Benefit PTO' ); + res.body[0].value.alertEmployee.value.should.equal( true ); + done(); + }); + }); + + it.skip('shouldn\'t be able to get a list of alerts without permissions', function ( done ) { + done(); + }); + + it('should be able to get a specific alert', function ( done ) { + var self = this + , req = request( app ).get( '/alerts/' + self.alert._id ); + + req.cookies = HRManagerSession; + req.set( 'Accept','application/json' ) + .expect( 'Content-Type', /json/ ) + .expect( 200 ) + .end(function (err, res) { + res.body.should.be.instanceof( Array ); + res.body.length.should.be.above( 0 ); + res.body[0].should.have.properties( '_id', 'AttributeDocumentId', 'value' ); + res.body[0].value.should.be.instanceof( Object ); + res.body[0].value.should.have.properties( 'lastModifiedOn', 'lastModifiedBy', 'accountId', 'tables', 'reminders', 'otherFields', 'list', 'customSubject', 'customMessage', 'category', 'alertName', 'alertEmployee' ); + res.body[0].value.lastModifiedBy.value.should.equal( 9 ); + res.body[0].value.accountId.value.should.equal( 1 ); + res.body[0].value.tables.value.should.equal( 'Customized Education Table' ) + res.body[0].value.reminders.value.should.be.instanceOf( Array ); + res.body[0].value.reminders.value[0].duration.should.equal( '' ); + res.body[0].value.reminders.value[0].reminderDuration.should.equal( '1' ); + res.body[0].value.reminders.value[0].reminderFrequency.should.equal( 'days' ); + res.body[0].value.reminders.value[0].reminderOffset.should.equal( 'before' ); + res.body[0].value.otherFields.value.should.be.instanceof( Object ); + res.body[0].value.otherFields.value.should.have.properties( 'education' ); + res.body[0].value.otherFields.value.education.should.equal( true ); + res.body[0].value.list.value.should.be.instanceof( Array ); + res.body[0].value.list.value.length.should.equal( 0 ); + res.body[0].value.customSubject.value.should.equal( 'My Subject' ); + res.body[0].value.customMessage.value.should.equal( 'My Message' ); + res.body[0].value.category.value.should.equal( 'Benefit Eligibility' ); + res.body[0].value.alertName.value.should.equal( 'New Benefit PTO' ); + res.body[0].value.alertEmployee.value.should.equal( true ); + done(); + }); + }); + + it('shouldn\'t be able to get a specific alert if we don\'t have the correct permissions', function ( done ) { + done(); + }); + }); +}); diff --git a/modules/clever-email/tests/integration/test.service.EmailTemplateService.js b/modules/clever-email/tests/integration/test.service.EmailTemplateService.js new file mode 100644 index 0000000..baedd9a --- /dev/null +++ b/modules/clever-email/tests/integration/test.service.EmailTemplateService.js @@ -0,0 +1,211 @@ +var should = require( 'should' ) + , sinon = require( 'sinon' ) + , Q = require( 'q' ) + , testEnv = require( './utils' ).testEnv + , EmailTemplateClass = require( 'services' ).EmailTemplateService; + +describe( 'service.EmailTemplateService', function () { + var EmailTemplateService, EmailTempalteModel, UserModel, user; + + before( function ( done ) { + this.timeout( 15000 ); + testEnv( function ( db, models ) { + + EmailTemplateService = new EmailTemplateClass( db, models.ORM.EmailTemplate ); + EmailTempalteModel = models.ORM.EmailTemplate; + UserModel = models.ORM.User; + + UserModel + .create( { + username: 'joe@example.com', email: 'joe@example.com', password: '1234', AccountId: 1, RoleId: 1, TeamId: 1 + } ) + .success( function ( _user_ ) { + _user_.should.be.a( 'object' ); + _user_.should.have.property( 'id' ); + user = _user_; + + done(); + } ) + .error( done ); + + }, done ); + } ); + + describe( '.formatData', function () { + it( 'should return an object with filtered data', function ( done ) { + var data = { + title: 'some title', subject: 'some subject', body: 'some body', useDefault: false, permission: 'none', someattr: 'this attribute is not part of the model' + }; + + var emailData = EmailTemplateService.formatData( data ) + + emailData.should.have.property( 'id', null ); + emailData.should.have.property( 'isActive', false ); + emailData.should.have.property( 'isDefault', false ); + + emailData.should.not.have.property( 'someattr' ); + done(); + } ); + + } ); + + describe( '.handleEmailTemplateCreation', function () { + + it( 'should save a new email template with given options', function ( done ) { + var emailTemplateData = { + title: 'schedule interview', subject: 'congradulations! we would love to meet you', body: 'Dear John Appleseed,
we are interest by your skill set your qualifications for the job', useDefault: 'false', permission: 'none', isActive: true, useDefault: false, isDefault: false + }; + + EmailTemplateService + .handleEmailTemplateCreation( user.AccountId, user.id, emailTemplateData ) + .then( function ( emailTemplate ) { + + emailTemplate.should.have.property( 'id' ); + emailTemplate.should.have.property( 'title', 'schedule interview' ); + + emailTemplate.should.have.property( 'AccountId', user.AccountId ); + emailTemplate.should.have.property( 'UserId', user.id ); + + done(); + + } ) + .fail( done ); + } ); + } ); + + describe( '.handleEmailTemplateUpdate', function () { + + it( 'should return code 401 and a message if email template does not exist ', function ( done ) { + var data = { id: 1233444444444444444444 }; + + EmailTemplateService + .handleEmailTemplateUpdate( user.AccountId, user.id, data ) + .then( function ( msg ) { + + msg.should.be.a( 'object' ); + msg.should.have.property( 'statuscode', 401 ); + msg.should.have.property( 'message' ).and.not.be.empty; + + done(); + + } ) + .fail( done ); + + } ); + + it( 'should call .updateEmailTemplate ', function ( done ) { + + + var emailTemplateData = { + title: 'schedule interview', subject: 'congradulations! we would love to meet you', body: 'Dear John Appleseed,
we are interest by your skill set your qualifications for the job', useDefault: 'false', permission: 'none', isActive: true, useDefault: false, isDefault: false, AccountId: user.AccountId, UserId: user.id + }; + + var spyUpdateEmailTemplate = sinon.spy( EmailTemplateService, 'updateEmailTemplate' ); + + EmailTempalteModel + .create( emailTemplateData ) + .success( function ( emailTemplate ) { + + emailTemplate.should.have.property( 'id' ); + emailTemplate.should.have.property( 'title', 'schedule interview' ); + + EmailTemplateService + .handleEmailTemplateUpdate( user.AccountId, user.id, emailTemplate ) + .then( function () { + + spyUpdateEmailTemplate.called.should.be.true; + done(); + + } ) + .fail( done ); + } ) + .error( done ); + } ); + } ); + + describe( '.updateEmailTemplate', function () { + + it( 'should update an email Template record', function ( done ) { + + var emailTemplateData = { + title: 'schedule interview', subject: 'congradulations! we would love to meet you', body: 'Dear John Appleseed,
we are interest by your skill set your qualifications for the job', useDefault: 'false', permission: 'none', isActive: true, useDefault: false, isDefault: false, AccountId: user.AccountId, UserId: user.id + }; + + var updatedData = { + id: null, title: 'This is an updated title', subject: 'congradulations! we would love to meet you', body: 'Dear John Appleseed,
we are interest by your skill set your qualifications for the job', useDefault: 'false', permission: 'none', isActive: true, useDefault: false, isDefault: false + }; + + + EmailTempalteModel + .create( emailTemplateData ) + .success( function ( emailTemplate ) { + + emailTemplate.should.have.property( 'title', 'schedule interview' ); + updatedData['id'] = emailTemplate.id; + + EmailTemplateService + .updateEmailTemplate( emailTemplate, updatedData ) + .then( function ( updatedEmailTpl ) { + + updatedEmailTpl.id.should.equal( emailTemplate.id ); + updatedEmailTpl.title.should.not.equal( emailTemplateData.title ); + updatedEmailTpl.title.should.equal( updatedData.title ); + + done(); + } ) + .fail( done ); + } ) + .error( done ); + } ); + } ); + + describe( '.removeEmailTemplate', function () { + + it( 'should remove an email template record ', function ( done ) { + + var emailTemplateData = { + title: 'schedule interview', subject: 'congradulations! we would love to meet you', body: 'Dear John Appleseed,
we are interest by your skill set your qualifications for the job', useDefault: 'false', permission: 'none', isActive: true, useDefault: false, isDefault: false, AccountId: user.AccountId, UserId: user.id + }; + + + EmailTempalteModel + .create( emailTemplateData ) + .success( function ( emailTemplate ) { + + emailTemplate.should.have.property( 'id' ); + emailTemplate.should.have.property( 'title', 'schedule interview' ); + + EmailTemplateService + .removeEmailTemplate( user.id, emailTemplate.id ) + .then( function ( msg ) { + + msg.should.be.a( 'object' ); + msg.should.have.property( 'statuscode', 200 ); + msg.should.have.property( 'message' ).and.not.be.empty; + + done(); + + } ) + .fail( done ); + } ) + .error( done ); + } ); + + it( 'should return code 400 and a message if email template does not exist ', function ( done ) { + var tplId = '1233333333333333333333'; + + EmailTemplateService + .removeEmailTemplate( user.id, tplId ) + .then( function ( msg ) { + + msg.should.be.a( 'object' ); + msg.should.have.property( 'statuscode', 400 ); + msg.should.have.property( 'message' ).and.not.be.empty; + + done(); + + } ) + .fail( done ); + } ); + } ); +} ); \ No newline at end of file diff --git a/package.json b/package.json index c31a10a..9dcc66f 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,7 @@ "auth", "orm", "odm", - "example-module" + "example-module", + "clever-email" ] } From 269098b8d183d71a9e8b1d13f1f6ef1ef220df9e Mon Sep 17 00:00:00 2001 From: Volodymyr Denshchykov Date: Tue, 21 Jan 2014 17:28:17 +0200 Subject: [PATCH 02/24] added ability to send mail in different systems --- lib/config/index.js | 2 +- modules/auth/models/orm/UserModel.js | 80 ++++++ modules/clever-email/config/default.json | 22 +- .../controllers/EmailAlertController.js | 87 ------- .../controllers/EmailController.js | 4 +- modules/clever-email/lib/ejsfilerender.js | 25 ++ modules/clever-email/lib/mailer.js | 17 ++ modules/clever-email/lib/mailgun.js | 71 ++++++ modules/clever-email/lib/mandrill.js | 75 ++++++ modules/clever-email/lib/sendgrid.js | 74 ++++-- modules/clever-email/package.json | 9 +- modules/clever-email/routes.js | 41 ++-- modules/clever-email/schema/seedData.json | 58 +---- modules/clever-email/services/EmailService.js | 159 ++++-------- .../services/EmailTemplateService.js | 91 ++----- .../test.service.EmailTemplateService.js | 211 ---------------- .../test.controller.EmailAlertController.js | 0 .../tests/unit/test.service.EmailService.js | 227 ++++++++++++++++++ .../unit/test.service.EmailTemplateService.js | 192 +++++++++++++++ .../tests/integration/test.api.example.js | 4 +- modules/orm/config/default.json | 6 +- 21 files changed, 870 insertions(+), 585 deletions(-) create mode 100644 modules/auth/models/orm/UserModel.js delete mode 100644 modules/clever-email/controllers/EmailAlertController.js create mode 100644 modules/clever-email/lib/ejsfilerender.js create mode 100644 modules/clever-email/lib/mailer.js create mode 100644 modules/clever-email/lib/mailgun.js create mode 100644 modules/clever-email/lib/mandrill.js delete mode 100644 modules/clever-email/tests/integration/test.service.EmailTemplateService.js rename modules/clever-email/tests/{integration => temp}/test.controller.EmailAlertController.js (100%) create mode 100644 modules/clever-email/tests/unit/test.service.EmailService.js create mode 100644 modules/clever-email/tests/unit/test.service.EmailTemplateService.js diff --git a/lib/config/index.js b/lib/config/index.js index 8da4b2d..6e73c16 100644 --- a/lib/config/index.js +++ b/lib/config/index.js @@ -1,3 +1,3 @@ var path = require( 'path' ); -module.exports = require( path.resolve( [ __dirname, '..', 'config' ].join( path.sep ) ) + path.sep ); \ No newline at end of file +module.exports = require( path.resolve( [ __dirname, '..', '..', 'config' ].join( path.sep ) ) + path.sep ); \ No newline at end of file diff --git a/modules/auth/models/orm/UserModel.js b/modules/auth/models/orm/UserModel.js new file mode 100644 index 0000000..d3bfc61 --- /dev/null +++ b/modules/auth/models/orm/UserModel.js @@ -0,0 +1,80 @@ +module.exports = function(sequelize, DataTypes) { + return sequelize.define("User", { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true + }, + username: { + type: DataTypes.STRING, + allowNull: false, + unique: true + }, + email: { + type: DataTypes.STRING, + allowNull: false, + unique: true, + validate: { + isEmail: true + } + }, + password: { + type: DataTypes.STRING, + allowNull: false + }, + firstname: { + type: DataTypes.STRING, + allowNull: true + // validate: { + // isAlphanumeric: true + // } + }, + lastname: { + type: DataTypes.STRING, + allowNull: true + // validate: { + // isAlphanumeric: true + // } + }, + phone : { + type:DataTypes.STRING, + allowNull: true + }, + confirmed : { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false + }, + active : { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: true + }, + hasAdminRight : { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false + }, + accessedAt : { + type: DataTypes.DATE, + allowNull: true + } + + }, + { + paranoid: true, + getterMethods: { + fullName: function(){ + return [ this.getDataValue('firstname'), this.getDataValue('lastname')].join(' '); + } + }, + instanceMethods: { + toJSON: function () { + var values = this.values; + values.fullName = this.fullName; + delete values.password; + return values; + } + } + }); +}; diff --git a/modules/clever-email/config/default.json b/modules/clever-email/config/default.json index 9b2192e..5cd1aea 100644 --- a/modules/clever-email/config/default.json +++ b/modules/clever-email/config/default.json @@ -1,31 +1,33 @@ { "clever-email": { - "emailsToCC": true, - "emailsToBCC": true, - "messageTXT": true, - "priority": true, + "cc": true, + "bcc": true, + "text": true, "systems": { "Mandrill": { "isActive": true, - "apiKey" : "j8Gzc7vRpJ36FabmOsbk6g" + "apiKey": "j8Gzc7vRpJ36FabmOsbk6g", + "ipPool": "Example Pool", + "async": false }, "SendGrid": { "isActive": false, - "apiUser" : "", + "apiUser" : "", "apiKey" : "" }, "MailGun": { "isActive": false, - "apiUser" : "", + "domain" : "mytestdomain.com", "apiKey" : "key-97zzcleh7dgz0kp4jfy1qp0fn38lml73" } }, "default": { - "noReply": "true", - "from": "", - "subject": "test email" + "noReply": "true", + "from": "no-reply@app.bolthr.com", + "fromName": "BoltHR", + "subject": "BoltHR: Notification" } } } \ No newline at end of file diff --git a/modules/clever-email/controllers/EmailAlertController.js b/modules/clever-email/controllers/EmailAlertController.js deleted file mode 100644 index 0baa4cd..0000000 --- a/modules/clever-email/controllers/EmailAlertController.js +++ /dev/null @@ -1,87 +0,0 @@ -var Q = require( 'q' ); - -module.exports = function ( RecentActivityService, AttributeKeyService, AttributeEmailAlertService, PermissionService ) { - return (require( 'classes' ).Controller).extend( { - service: AttributeEmailAlertService - }, { - listAction: function () { - var self = this; - - AttributeEmailAlertService.list( { - AccountId: self.req.user.AccountId - } ) - .then( self.proxy( 'handleServiceMessage' ) ) - .fail( self.proxy( 'handleException' ) ); - }, - - getAction: function () { - if ( !this.req.params.id ) { - return this.send( 404, 'Not found' ); - } - - AttributeEmailAlertService.get( { - AccountId: this.req.user.AccountId, - id: this.req.params.id - } ) - .then( this.proxy( 'send' ) ) - .fail( this.proxy( 'handleException' ) ); - }, - - postAction: function () { - var self = this - , data = self.req.body - , alerts = []; - - data.alert = Array.isArray( data.alert ) ? data.alert : [data.alert]; - - if ( !data.alert || !data.alert[0].alertName ) { - return self.send( { message: 'More information is required before creating this e-mail alert.', statuscode: 400 } ); - } - - if ( !!data.alert[0]._id ) { - return this.putAction(); - } - - AttributeEmailAlertService.saveOrUpdate( self.req.user, data.submitted, data.alert ) - .then( self.proxy( 'send' ) ) - .fail( self.proxy( 'handleException' ) ); - }, - - putAction: function () { - var self = this - , data = self.req.body - , alerts = []; - - data.alert = Array.isArray( data.alert ) ? data.alert : [data.alert]; - - if ( !data.alert || !data.alert[0].alertName ) { - return self.send( { message: 'More information is required before creating this e-mail alert.', statuscode: 400 } ); - } - - AttributeEmailAlertService.saveOrUpdate( self.req.user, data.submitted, data.alert ) - .then( self.proxy( 'send' ) ) - .fail( self.proxy( 'handleException' ) ); - }, - - deleteAction: function () { - var data = this.req.params; - - if ( !data.id ) { - return this.res.send( 404, 'Not found' ); - } - - AttributeEmailAlertService.remove( this.req.user.AccountId, data.id ) - .then( this.proxy( 'handleServiceMessage' ) ) - .fail( this.proxy( 'handleException' ) ); - }, - - handleServiceMessage: function ( obj ) { - if ( obj.statuscode ) { - this.send( obj.message, obj.statuscode ); - return; - } - - this.send( obj, 200 ); - } - } ); -} diff --git a/modules/clever-email/controllers/EmailController.js b/modules/clever-email/controllers/EmailController.js index 82d5843..6c606b5 100644 --- a/modules/clever-email/controllers/EmailController.js +++ b/modules/clever-email/controllers/EmailController.js @@ -1,7 +1,9 @@ module.exports = function ( EmailService ) { return (require( 'classes' ).Controller).extend( { - service: null, checkEventMailData: function ( req, res, next ) { + service: null, + + checkEventMailData: function ( req, res, next ) { var data = req.body , fltData = []; //console.log("\n *** SendGrid Event Data *** \n",data); diff --git a/modules/clever-email/lib/ejsfilerender.js b/modules/clever-email/lib/ejsfilerender.js new file mode 100644 index 0000000..bb3bcfa --- /dev/null +++ b/modules/clever-email/lib/ejsfilerender.js @@ -0,0 +1,25 @@ +var path = require( 'path' ) + , Q = require( 'q' ) + , ejs = require( 'ejs' ) + , approot = path.resolve( __dirname + '../../../' ); + +module.exports = function ( tpls ) { + + return function ( prop, data ) { + var deferred = Q.defer(); + + ejs.renderFile( approot + '/' + tpls[ prop ], data, function ( err, html ) { + + if ( err ) { + deferred.reject( err ); + return; + } + + deferred.resolve( html ); + + } ); + + return deferred.promise; + } + +}; \ No newline at end of file diff --git a/modules/clever-email/lib/mailer.js b/modules/clever-email/lib/mailer.js new file mode 100644 index 0000000..dc50866 --- /dev/null +++ b/modules/clever-email/lib/mailer.js @@ -0,0 +1,17 @@ +module.exports = function ( config ) { + + var mandrill = require( './mandrill' ) + , sendgrid = require( './sendgrid' ) + , mailgun = require( './mailgun' ) + , confs = config.systems; + + var mailer = confs.Mandrill.isActive + ? mandrill + : confs.SendGrid.isActive + ? sendgrid + : confs.MailGun.isActive + ? mailgun + : sendgrid; + + return mailer( config ); +}; \ No newline at end of file diff --git a/modules/clever-email/lib/mailgun.js b/modules/clever-email/lib/mailgun.js new file mode 100644 index 0000000..36f22c5 --- /dev/null +++ b/modules/clever-email/lib/mailgun.js @@ -0,0 +1,71 @@ +module.exports = function ( config ) { + + var Q = require( 'q' ) + , defConf = config.default + , confMailgun = config.systems.MailGun + , mailgun = require( 'mailgun-js' )( confMailgun.apiKey, confMailgun.domain ); + + return { + send: function ( email, html, text ) { + var deferred = Q.defer(); + + var message = this.createMessage( email, html, text ); + + mailgun + .messages + .send( message, function ( err, response, body ) { + + if ( err ) { + console.log( "MailGun Error: ", err.toString() ); + deferred.reject( err ); + return; + } + + deferred.resolve( response ); + } ); + + return deferred.promise; + }, + + createMessage: function( email, html, text ){ + var fromMail = defConf.from + , fromName = defConf.fromName; + + if ( email.dump.fromCompanyName ) { + fromName = email.dump.fromName; + fromMail = email.dump.fromMail; + } + + var message = { + from: [ fromName, ' <', fromMail, '>' ].join( '' ), + to: [ '<', email.dump.toMail, '>' ].join( '' ), + subject: email.subject || defConf.subject, + emailId: email.id + }; + + if ( config.text && !!text ){ + message.text = text; + } else { + message.html = html; + } + + if ( config.cc && email.dump.usersCC && email.dump.usersCC.length ) { + var cc = []; + email.dump.usersCC.forEach( function ( userEmail ) { + cc.push( [ '<', userEmail, '>' ].join( '' ) ); + } ); + message.cc = cc.join( ', ' ) + } + + if ( config.bcc && email.dump.usersBCC && email.dump.usersBCC.length ) { + var bcc = []; + email.dump.usersBCC.forEach( function ( userEmail ) { + bcc.push( [ '<', userEmail, '>' ].join( '' ) ); + } ); + message.bcc = bcc.join( ', ' ) + } + + return message; + } + }; +}; \ No newline at end of file diff --git a/modules/clever-email/lib/mandrill.js b/modules/clever-email/lib/mandrill.js new file mode 100644 index 0000000..a7bb4a2 --- /dev/null +++ b/modules/clever-email/lib/mandrill.js @@ -0,0 +1,75 @@ +module.exports = function ( config ) { + + var Q = require( 'q' ) + , mandrill = require( 'mandrill-api/mandrill' ) + , defConf = config.default + , confMandrill = config.systems.Mandrill + , mandrill_client = new mandrill.Mandrill( confMandrill.apiKey ); + + return { + send: function ( email, html, text ) { + var deferred = Q.defer(); + + var message = this.createMessage( email, html, text ) + , async = confMandrill.async + , ip_pool = confMandrill.ipPool; + + mandrill_client + .messages + .send( { + "message": message, + "async": async, + "ip_pool": ip_pool }, function ( result ) { + + deferred.resolve( result ); + + }, function ( err ) { + + console.log( 'A mandrill error occurred: ' + err.name + ' - ' + err.message ); + + deferred.reject( err ); + + } ); + + return deferred.promise; + }, + + createMessage: function( email, html, text ){ + var fromMail = defConf.from + , fromName = defConf.fromName; + + if ( email.dump.fromCompanyName ) { + fromName = email.dump.fromName; + fromMail = email.dump.fromMail; + } + + var message = { + to: [ { email: email.dump.toMail, type: 'to' } ], + subject: email.subject || defConf.subject, + from_email: fromMail, + from_name: fromName, + emailId: email.id + }; + + if ( config.text && !!text ){ + message.text = text; + } else { + message.html = html; + } + + if ( config.cc && email.dump.usersCC && email.dump.usersCC.length ) { + email.dump.usersCC.forEach( function ( userEmail ) { + message.to.push( { email: userEmail, type: 'cc' } ); + } ); + } + + if ( config.bcc && email.dump.usersBCC && email.dump.usersBCC.length ) { + email.dump.usersBCC.forEach( function ( userEmail ) { + message.to.push( { email: userEmail, type: 'bcc' } ); + } ); + } + + return message; + } + }; +}; \ No newline at end of file diff --git a/modules/clever-email/lib/sendgrid.js b/modules/clever-email/lib/sendgrid.js index e3abe93..63f50d6 100644 --- a/modules/clever-email/lib/sendgrid.js +++ b/modules/clever-email/lib/sendgrid.js @@ -1,27 +1,69 @@ module.exports = function ( config ) { + var Q = require( 'q' ) - , sendgrid = require( 'sendgrid' )( config.apiUser, config.apiKey ) + , defConf = config.default + , confSendGrid = config.systems.SendGrid + , sendgrid = require( 'sendgrid' )( confSendGrid.apiUser, confSendGrid.apiKey ) , Email = sendgrid.Email; - return function ( payload ) { - var deferred = Q.defer() - , email = new Email( payload ); + return { + send: function ( email, html, text ) { + var deferred = Q.defer() + , message = this.createMessage( email, html, text ) + , email = new Email( message ); - if ( payload.emailId ) { - email.setUniqueArgs( { email_id: payload.emailId } ); - } + if ( message.emailId ) { + email.setUniqueArgs( { email_id: message.emailId } ); + } + + sendgrid.send( email, function ( err, response ) { + + if ( err ) { + console.log( "SendGrid Error: ", err.toString() ); + deferred.reject( err ); + return; + } + + deferred.resolve( response ); + } ); + + return deferred.promise; + }, - sendgrid.send( email, function ( err, response ) { + createMessage: function( email, html, text ){ + var fromMail = defConf.from + , fromName = defConf.fromName; - if ( err ) { - console.log( "SendGrid Error: \n", err.toString() ); - deferred.reject( err ); - return; + if ( email.dump.fromCompanyName ) { + fromName = email.dump.fromName; + fromMail = email.dump.fromMail; } - deferred.resolve( response ); - } ); + var message = { + to: [ email.dump.toMail ], + subject: email.subject || defConf.subject, + from: fromMail, + fromname: fromName, + emailId: emailId + }; - return deferred.promise; - }; + if ( config.text && !!text ){ + message.text = text; + } else { + message.html = html; + } + + if ( config.cc && email.dump.usersCC && email.dump.usersCC.length ) { + email.dump.usersCC.forEach( function ( userEmail ) { + message.to.push( userEmail ); + } ); + } + + if ( config.bcc && email.dump.usersBCC && email.dump.usersBCC.length ) { + message.bcc = email.dump.usersBCC; + } + + return message; + } + } }; \ No newline at end of file diff --git a/modules/clever-email/package.json b/modules/clever-email/package.json index 0f27176..6caa0ba 100644 --- a/modules/clever-email/package.json +++ b/modules/clever-email/package.json @@ -7,10 +7,12 @@ "lodash": "2.4.1", "mandrill-api": "*", "sendgrid": "~0.4.2", - "mailgun": "*", + "mailgun-js": "*", "ejs": "*", "shortid": "~2.0.0", - "cheerio": "~0.12.4" + "cheerio": "~0.12.4", + "sequelize": "", + "moment": "" }, "author": { "name": "Clevertech", @@ -20,6 +22,7 @@ "main": "module.js", "description": "", "devDependencies": { - + "chai": "*", + "mocha": "*" } } \ No newline at end of file diff --git a/modules/clever-email/routes.js b/modules/clever-email/routes.js index 7f318fd..b158444 100644 --- a/modules/clever-email/routes.js +++ b/modules/clever-email/routes.js @@ -1,27 +1,34 @@ module.exports = function ( app, EmailController, - EmailAlertController, EmailTemplateController ) { - app.get( '/email_templates/:id/preview', UserController.requiresLogin, EmailTemplateController.attach() ); - app.get( '/email_templates', UserController.requiresLogin, EmailTemplateController.attach() ); - app.get( '/email_templates/:id', UserController.requiresLogin, EmailTemplateController.attach() ); - app.post( '/email_templates', UserController.requiresLogin, EmailTemplateController.checkRequiredFields, EmailTemplateController.attach() ); - app.post( '/email_templates/:id', UserController.requiresLogin, EmailTemplateController.checkRequiredFields, EmailTemplateController.attach() ); - app['delete']( '/email_templates/:id', UserController.requiresLogin, EmailTemplateController.attach() ); - //app.post('/emails/:id/send' , UserController.requiresLogin, EmailTemplateController.attach()); - //app.get('/emails/:id/permission' , UserController.requiresLogin, EmailTemplateController.attach()); +// app.get( '/email_templates/:id/preview', UserController.requiresLogin, EmailTemplateController.attach() ); +// app.get( '/email_templates', UserController.requiresLogin, EmailTemplateController.attach() ); +// app.get( '/email_templates/:id', UserController.requiresLogin, EmailTemplateController.attach() ); +// app.post( '/email_templates', UserController.requiresLogin, EmailTemplateController.checkRequiredFields, EmailTemplateController.attach() ); +// app.post( '/email_templates/:id', UserController.requiresLogin, EmailTemplateController.checkRequiredFields, EmailTemplateController.attach() ); +// app['delete']( '/email_templates/:id', UserController.requiresLogin, EmailTemplateController.attach() ); +// //app.post('/emails/:id/send' , UserController.requiresLogin, EmailTemplateController.attach()); +// //app.get('/emails/:id/permission' , UserController.requiresLogin, EmailTemplateController.attach()); +// +// app.get( '/emails', UserController.requiresLogin, EmailController.attach() ); +// app.get( '/emails/:id', UserController.requiresLogin, EmailController.attach() ); +// app.post( '/emails', UserController.requiresLogin, EmailController.attach() ); +// +// app.post( '/emails/:pubkey/eventsMail', EmailController.checkEventMailData, EmailController.attach() ); - app.get( '/emails', UserController.requiresLogin, EmailController.attach() ); - app.get( '/emails/:id', UserController.requiresLogin, EmailController.attach() ); - app.post( '/emails', UserController.requiresLogin, EmailController.attach() ); + app.get( '/email_templates/:id/preview', EmailTemplateController.attach() ); + app.get( '/email_templates', EmailTemplateController.attach() ); + app.get( '/email_templates/:id', EmailTemplateController.attach() ); + app.post( '/email_templates', EmailTemplateController.checkRequiredFields, EmailTemplateController.attach() ); + app.post( '/email_templates/:id', EmailTemplateController.checkRequiredFields, EmailTemplateController.attach() ); + app['delete']( '/email_templates/:id', EmailTemplateController.attach() ); - app.post( '/emails/:pubkey/eventsMail', EmailController.checkEventMailData, EmailController.attach() ); + app.get( '/emails', EmailController.attach() ); + app.get( '/emails/:id', EmailController.attach() ); + app.post( '/emails', EmailController.attach() ); - app.all('/alerts/?', UserController.requiresLogin, EmailAlertController.attach()); - app.put('/alerts/?', UserController.requiresLogin, EmailAlertController.attach()); - app.get('/alerts/:id/?', UserController.requiresLogin, EmailAlertController.attach()); - app['delete']('/alerts/:id/?', UserController.requiresLogin, EmailAlertController.attach()); + app.post( '/emails/:pubkey/eventsMail', EmailController.checkEventMailData, EmailController.attach() ); }; \ No newline at end of file diff --git a/modules/clever-email/schema/seedData.json b/modules/clever-email/schema/seedData.json index 93a0bc7..e6bf30b 100644 --- a/modules/clever-email/schema/seedData.json +++ b/modules/clever-email/schema/seedData.json @@ -1,5 +1,5 @@ { - "DefaultEmailTemplate": [ + "DefaultEmailTemplateModel": [ { "title": "application submitted successfully", "subject": "<%=company_name%> :Application Submitted Successfully", @@ -16,7 +16,7 @@ "body": "

<%=company_name%> has just posted the following new jobs to our careers page on <%=published_date%>
<%=job_title_link%>
If you or anybody you know may be a good fit for this position, please encourage them to apply immediately. If passing along to others, please be sure to remind them to mention how they heard about the opportunity.
Thanks,
Company Human Resources Team
<%=career_link%>

" } ], - "EmailTemplate": [ + "EmailTemplateModel": [ { "title": "Application submitted successfully", "subject": "{{Company_Name}} :Application Submitted Successfully", @@ -26,11 +26,7 @@ "useDefault": false, "isDefault": true, "UserId": 1, - "associations": { - "Account": { - "id": 1 - } - } + "AccountId": 1 }, { "title": "Application status update", @@ -41,11 +37,7 @@ "useDefault": false, "isDefault": true, "UserId": 1, - "associations": { - "Account": { - "id": 1 - } - } + "AccountId": 1 }, { "title": "new career opportunity", @@ -56,11 +48,7 @@ "useDefault": false, "isDefault": true, "UserId": 1, - "associations": { - "Account": { - "id": 1 - } - } + "AccountId": 1 }, { "title": "Application submitted successfully", @@ -71,11 +59,7 @@ "useDefault": false, "isDefault": true, "UserId": 2, - "associations": { - "Account": { - "id": 1 - } - } + "AccountId": 1 }, { "title": "Application status update", @@ -86,11 +70,7 @@ "useDefault": false, "isDefault": true, "UserId": 2, - "associations": { - "Account": { - "id": 1 - } - } + "AccountId": 1 }, { "title": "new career opportunity", @@ -101,11 +81,7 @@ "useDefault": false, "isDefault": true, "UserId": 2, - "associations": { - "Account": { - "id": 1 - } - } + "AccountId": 1 }, { "title": "Application submitted successfully", @@ -116,11 +92,7 @@ "useDefault": false, "isDefault": true, "UserId": 3, - "associations": { - "Account": { - "id": 1 - } - } + "AccountId": 1 }, { "title": "Application status update", @@ -131,11 +103,7 @@ "useDefault": false, "isDefault": true, "UserId": 3, - "associations": { - "Account": { - "id": 1 - } - } + "AccountId": 1 }, { "title": "new career opportunity", @@ -146,11 +114,7 @@ "useDefault": false, "isDefault": true, "UserId": 3, - "associations": { - "Account": { - "id": 1 - } - } + "AccountId": 1 } ] } \ No newline at end of file diff --git a/modules/clever-email/services/EmailService.js b/modules/clever-email/services/EmailService.js index 97a4841..e9b8ccb 100644 --- a/modules/clever-email/services/EmailService.js +++ b/modules/clever-email/services/EmailService.js @@ -1,31 +1,27 @@ -var BaseService = require( './BaseService' ) - , Q = require( 'q' ) +var Q = require( 'q' ) , Sequelize = require( 'sequelize' ) - , sendgrid = require( 'utils' ).sendgrid - , ejsFileRender = require( 'utils' ).ejsfilerender + , ejsFileRender = require( '../lib/ejsfilerender' ) , shortid = require( 'shortid' ) + , config = require( 'config' )['clever-email'] + , sendgrid = require( '../lib/sendgrid' ) , EmailService = null; -module.exports = function ( db, - EmailModel, - EmailAttachmentModel, - ProspectModel, - EmailReplyModel, - UserModel, - JobModel, - EmailUserModel, - EmailTemplateService, - ProspectSurveyService, - config ) { - - var mailer = sendgrid( config.sendgrid ) +module.exports = function ( sequelize, + ORMEmailModel, + ORMEmailAttachmentModel, + ORMEmailReplyModel, + ORMUserModel, + ORMEmailUserModel, + EmailTemplateService ) { + + var mailer = sendgrid( config ) , bakeTemplate = ejsFileRender(); if ( EmailService && EmailService.instance ) { return EmailService.instance; } - EmailService = BaseService.extend( { + EmailService = require( 'services' ).BaseService.extend( { formatReplyAddress: function ( emailToken ) { var addr = '' @@ -45,7 +41,7 @@ module.exports = function ( db, }, formatData: function ( data, operation ) { - var o = { email: {}, prospect: {}, usersCC: [], usersBCC: [], attachments: [], sender: {} }; + var o = { email: {}, usersCC: [], usersBCC: [], attachments: [], sender: {} }; var emailURL = ''; // o['account'] = {}; @@ -58,8 +54,6 @@ module.exports = function ( db, o.email.token = data.userId + data.to.id + shortid.seed( 10000 ).generate(); o.email.UserId = data.userId; o.email.AccountId = data.accId; - o.email.ProspectId = data.to.id; - o.email.JobId = data.JobId || null; o.hasTemplate = (/false|true/.test( data.hasTemplate )) ? data.hasTemplate : true; @@ -68,9 +62,6 @@ module.exports = function ( db, // o.sender.fullName = data.userFirstName + ' ' + data.userLastName; // o.sender.email = emailURL; - //EmailProspect object - // o.prospect = data.to; - //EmailUsers Object o.usersCC = ( data.cc && data.cc.length ) ? data.cc : []; o.usersBCC = ( data.bcc && data.bcc.length) ? data.bcc : []; @@ -110,7 +101,7 @@ module.exports = function ( db, return o; }, - //TODO: change sentAttempts and isDelivered values when we move notification mail into background tasks + formatRepliedData: function ( data ) { var replyAddr = this.formatReplyAddress( data['emailToken'] ); @@ -135,17 +126,6 @@ module.exports = function ( db, o['from'] = data.userEmail; t['fromname'] = null; - o['to'] = data.prospectEmail; - t['toname'] = data.prospectName; - - } else if ( data.from.indexOf( data[ 'prospectEmail' ] ) != -1 ) { - - o['from'] = data.prospectEmail; - t['fromname'] = data.prospectName; - - o['to'] = data.userEmail; - t['toname'] = null; - } else { o['from'] = t['fromname'] = o['to'] = t['toname'] = null; @@ -161,7 +141,7 @@ module.exports = function ( db, var deferred = Q.defer(); this - .find( { where: { UserId: userId }, include: [ EmailAttachmentModel, ProspectModel, EmailReplyModel ] } ) + .find( { where: { UserId: userId }, include: [ ORMEmailAttachmentModel, ORMEmailReplyModel ] } ) .then( deferred.resolve ) .fail( deferred.reject ); @@ -170,18 +150,18 @@ module.exports = function ( db, getEmailById: function ( userId, emailId ) { var deferred = Q.defer() - , service = this - , chainer = new Sequelize.Utils.QueryChainer(); + , service = this + , chainer = new Sequelize.Utils.QueryChainer(); chainer.add( - EmailModel.find( { - where: { id: emailId, UserId: userId, 'deletedAt': null }, include: [ EmailAttachmentModel, ProspectModel, EmailReplyModel ] + ORMEmailModel.find( { + where: { id: emailId, UserId: userId, 'deletedAt': null }, include: [ ORMEmailAttachmentModel, ORMEmailReplyModel ] } ) ); chainer.add( - EmailUserModel.find( { - where: { EmailId: emailId, 'deletedAt': null }, include: [ UserModel ] + ORMEmailUserModel.find( { + where: { EmailId: emailId, 'deletedAt': null }, include: [ ORMUserModel ] } ) ); @@ -237,11 +217,7 @@ module.exports = function ( db, return; } - service - .handleProspectSurveyCreation( fData.email, fData.survey ) - .then( deferred.resolve ) - .fail( deferred.reject ); - + deferred.resolve(); } ) .then( deferred.resolve ) .fail( deferred.reject ); @@ -251,30 +227,15 @@ module.exports = function ( db, return deferred.promise; }, - handleProspectSurveyCreation: function ( email, survey ) { - var deferred = Q.defer(); - - ProspectSurveyService - .createProspectSurvey( { - pointsAwarded: survey.pointsPossible, - SurveyId: survey.id, - accId: email.AccountId, - ProspectId: email.ProspectId, - token: survey.token - } ) - .then( deferred.resolve ) - .fail( deferred.reject ); - - return deferred.promise; - }, - saveEmailAssociation: function ( savedEmail, fData ) { var deferred = Q.defer() - , chainer = new Sequelize.Utils.QueryChainer(); + , chainer = new Sequelize.Utils.QueryChainer(); //USERS: CC if ( fData.usersCC.length ) { - var l = fData.usersCC.length, item, cc = []; + var l = fData.usersCC.length + , item + , cc = []; while ( l-- ) { item = fData.usersCC[l]; cc.push( { @@ -282,12 +243,14 @@ module.exports = function ( db, } ); } - chainer.add( EmailUserModel.bulkCreate( cc ) ); + chainer.add( ORMEmailUserModel.bulkCreate( cc ) ); } //USERS: BCC if ( fData.usersBCC.length ) { - var l = fData.usersBCC.length, itm, bcc = []; + var l = fData.usersBCC.length + , itm + , bcc = []; while ( l-- ) { itm = fData.usersBCC[l]; @@ -296,12 +259,14 @@ module.exports = function ( db, } ); } - chainer.add( EmailUserModel.bulkCreate( bcc ) ); + chainer.add( ORMEmailUserModel.bulkCreate( bcc ) ); } //Attachments if ( fData.attachments.length ) { - var l = fData.attachments.length, attch, emailDocs = []; + var l = fData.attachments.length + , attch + , emailDocs = []; while ( l-- ) { attch = fData.attachments[l]; @@ -310,7 +275,7 @@ module.exports = function ( db, } ); } - chainer.add( EmailAttachmentModel.bulkCreate( emailDocs ) ); + chainer.add( ORMEmailAttachmentModel.bulkCreate( emailDocs ) ); } chainer @@ -351,7 +316,8 @@ module.exports = function ( db, EmailTemplateService .getPlaceholderData( { - accId: email.AccountId, ProspectId: email.ProspectId, EmailTemplateId: email.EmailTemplateId, JobId: null + accId: email.AccountId, + EmailTemplateId: email.EmailTemplateId } ) .then( function ( emailTemplate ) { return EmailTemplateService.processTemplateIntrpolation( user, emailTemplate ); @@ -369,37 +335,9 @@ module.exports = function ( db, }, sendEmail: function ( email, html ) { - var deferred = Q.defer() - , fromMail = 'no-reply@app.bolthr.com' - , fromName = 'BoltHR' - , subject = email.subject || 'BoltHR: Notification' - , emailId = email.id - , payload = {}; - - if ( email.dump.fromCompanyName ) { - fromName = email.dump.fromName; - fromMail = email.dump.fromMail; - } - - payload = { - to: [ email.dump.toMail ], - bcc: email.dump.usersBCC, - subject: subject, - html: html, - from: fromMail, - fromname: fromName, - emailId: emailId - }; - - if ( email.dump.usersCC && email.dump.usersCC.length ) { - - email.dump.usersCC.forEach( function ( userEmail ) { - payload.to.push( userEmail ); - } ); - - } + var deferred = Q.defer(); - mailer( payload ) + mailer.send( email, html ) .then( deferred.resolve ) .fail( deferred.reject ); @@ -411,7 +349,7 @@ module.exports = function ( db, , service = this; this - .findOne( { where: { token: data.replyMailHash }, include: [ UserModel, ProspectModel ] } ) + .findOne( { where: { token: data.replyMailHash }, include: [ ORMUserModel ] } ) .then( function ( email ) { if ( !email || !email.id ) { @@ -426,8 +364,6 @@ module.exports = function ( db, data['emailToken'] = email.token; data['userEmail'] = email.user.email; data['userName'] = email.user.firstname + ' ' + email.user.lastname; - data['prospectEmail'] = email.prospect.email; - data['prospectName'] = email.prospect.firstName + ' ' + email.prospect.lastName; service .saveMailReply( data ) @@ -444,7 +380,7 @@ module.exports = function ( db, var deferred = Q.defer() , replyData = this.formatRepliedData( data ); - EmailReplyModel + ORMEmailReplyModel .create( replyData ) .success( deferred.resolve ) .error( deferred.reject ); @@ -452,7 +388,6 @@ module.exports = function ( db, return deferred.promise; }, - //TODO: Move this function into Background Tasks processMailReplyNotification: function ( savedReply ) { var deferred = Q.defer() , mail = JSON.parse( JSON.stringify( savedReply ) ) @@ -481,7 +416,7 @@ module.exports = function ( db, while ( item = evns.pop() ) { console.log( "\nUPDATING: ", item.email_id ); - chainer.add( EmailModel.update( { isOpened: true }, { id: item.email_id, 'deletedAt': null} ) ); + chainer.add( ORMEmailModel.update( { isOpened: true }, { id: item.email_id, 'deletedAt': null} ) ); } chainer @@ -496,8 +431,8 @@ module.exports = function ( db, } ); - EmailService.instance = new EmailService( db ); - EmailService.Model = EmailModel; -console.log('-----------------------') + EmailService.instance = new EmailService( sequelize ); + EmailService.Model = ORMEmailModel; + return EmailService.instance; }; \ No newline at end of file diff --git a/modules/clever-email/services/EmailTemplateService.js b/modules/clever-email/services/EmailTemplateService.js index 0ad27a1..be320e6 100644 --- a/modules/clever-email/services/EmailTemplateService.js +++ b/modules/clever-email/services/EmailTemplateService.js @@ -1,26 +1,20 @@ -var BaseService = require( './BaseService' ) - , Q = require( 'q' ) +var Q = require( 'q' ) , moment = require( 'moment' ) , Sequelize = require( 'sequelize' ) , cheerio = require( 'cheerio' ) - , sendgrid = require( 'utils' ).sendgrid + , config = require( 'config' )['clever-email'] + , mailer = require( '../lib/mailer' )( config ) , EmailTemplateService = null; - -module.exports = function ( db, +module.exports = function ( sequelize, EmailTemplateModel, - UserModel, TeamModel, - ProspectModel, - JobModel, - config ) { - - var mailer = sendgrid( config.sendgrid ); + UserModel ) { if ( EmailTemplateService && EmailTemplateService.instance ) { return EmailTemplateService.instance; } - EmailTemplateService = BaseService.extend( { + EmailTemplateService = require( 'services' ).BaseService.extend( { formatData: function ( data ) { var o = { @@ -54,21 +48,6 @@ module.exports = function ( db, return tu; }, - formatEmailTemplateTeamForSave: function ( permittedToTeams ) { - var tm = [] - , arr = permittedToTeams - , item; - - if ( arr && arr.length ) { - while ( item = arr.pop() ) { - var team = TeamModel.build( { id: item } ); - tm.push( team ); - } - } - - return tm; - }, - listTemplates: function ( accId, userId, teamId, role ) { var deferred = Q.defer() , chainer = new Sequelize.Utils.QueryChainer() @@ -88,7 +67,7 @@ module.exports = function ( db, } this - .find( { where: [ query ], include: [ UserModel, TeamModel ] } ) + .find( { where: [ query ], include: [ UserModel ] } ) .then( function ( result ) { if ( !result.length ) { deferred.resolve( [] ); @@ -120,7 +99,7 @@ module.exports = function ( db, } this - .findOne( { where: [ query ], include: [ UserModel, TeamModel ] } ) + .findOne( { where: [ query ], include: [ UserModel ] } ) .then( function ( result ) { if ( !result ) { @@ -148,13 +127,11 @@ module.exports = function ( db, getPlaceholderData: function ( data ) { var deferred = Q.defer() - , plData = { template: null, prospect: null, job: null } + , plData = { template: null } , chainer = new Sequelize.Utils.QueryChainer(); var tplId = data.template_id || data.EmailTemplateId - , accId = data.accId || data.AccountId - , prospId = data.prospect_id || data.ProspectId || null - , jobId = data.job_id || data.JobId || null; + , accId = data.accId || data.AccountId; chainer.add( EmailTemplateModel.find( { @@ -162,18 +139,6 @@ module.exports = function ( db, } ) ); - chainer.add( - ProspectModel.find( { - where: { id: prospId, AccountId: accId, "deletedAt": null } - } ) - ); - - chainer.add( - JobModel.find( { - where: { id: jobId, AccountId: accId, "deletedAt": null } - } ) - ); - chainer .run() .success( function ( result ) { @@ -184,8 +149,6 @@ module.exports = function ( db, } plData['template'] = JSON.parse( JSON.stringify( result[0] ) ); - plData['prospect'] = JSON.parse( JSON.stringify( result[1] ) ); - plData['job'] = JSON.parse( JSON.stringify( result[2] ) ); deferred.resolve( plData ); } ) @@ -193,6 +156,7 @@ module.exports = function ( db, return deferred.promise; }, + processTemplateIntrpolation: function ( data, user ) { var deferred = Q.defer() , service = this @@ -201,7 +165,7 @@ module.exports = function ( db, var $ = cheerio.load( tpl ); $( 'span[rel="placeholder"]' ).each( function ( i, elem ) { - text = service.getPlaceholderText( $( elem ), data.prospect, data.job, user ); + text = service.getPlaceholderText( $( elem ), user ); $( elem ).replaceWith( text ); } ); @@ -210,7 +174,7 @@ module.exports = function ( db, return deferred.promise; }, - getPlaceholderText: function ( placeholderNode, prospect, job, user ) { + getPlaceholderText: function ( placeholderNode, user ) { var id = placeholderNode.attr( 'id' ); var parts = id.split( '-' ); var domain = parts[0]; @@ -219,30 +183,6 @@ module.exports = function ( db, //console.log("\n\nPLACEHOLDER: ",domain, prop); - if ( domain === 'prospect' ) { - - if ( prop == 'createdAt' ) { - var d = moment( prospect[prop] ).format( 'YY:MM:DD' ); - propValue = d + ' EST'; - } else { - propValue = prospect && prospect[prop]; - } - - } - if ( domain === 'job' ) { - - if ( prop == 'url' ) { // Make it compatible with backend property - prop = 'jobmail'; - } - - if ( prop == 'publishDate' ) { - var d = moment( job[prop] ).format( 'YY:MM:DD' ); - propValue = d + ' EST'; - } else { - propValue = job && job[prop]; - } - } - if ( domain === 'user' ) { propValue = user && user[prop]; } @@ -344,6 +284,7 @@ module.exports = function ( db, return deferred.promise; }, + removeEmailTemplate: function ( userId, tplId ) { var deferred = Q.defer() , chainer = new Sequelize.Utils.QueryChainer(); @@ -378,13 +319,13 @@ module.exports = function ( db, mailer( payload ) .then( deferred.resolve ) - .fail( deferred.resolve ) + .fail( deferred.resolve ); return deferred.promise; } } ); - EmailTemplateService.instance = new EmailTemplateService( db ); + EmailTemplateService.instance = new EmailTemplateService( sequelize ); EmailTemplateService.Model = EmailTemplateModel; return EmailTemplateService.instance; diff --git a/modules/clever-email/tests/integration/test.service.EmailTemplateService.js b/modules/clever-email/tests/integration/test.service.EmailTemplateService.js deleted file mode 100644 index baedd9a..0000000 --- a/modules/clever-email/tests/integration/test.service.EmailTemplateService.js +++ /dev/null @@ -1,211 +0,0 @@ -var should = require( 'should' ) - , sinon = require( 'sinon' ) - , Q = require( 'q' ) - , testEnv = require( './utils' ).testEnv - , EmailTemplateClass = require( 'services' ).EmailTemplateService; - -describe( 'service.EmailTemplateService', function () { - var EmailTemplateService, EmailTempalteModel, UserModel, user; - - before( function ( done ) { - this.timeout( 15000 ); - testEnv( function ( db, models ) { - - EmailTemplateService = new EmailTemplateClass( db, models.ORM.EmailTemplate ); - EmailTempalteModel = models.ORM.EmailTemplate; - UserModel = models.ORM.User; - - UserModel - .create( { - username: 'joe@example.com', email: 'joe@example.com', password: '1234', AccountId: 1, RoleId: 1, TeamId: 1 - } ) - .success( function ( _user_ ) { - _user_.should.be.a( 'object' ); - _user_.should.have.property( 'id' ); - user = _user_; - - done(); - } ) - .error( done ); - - }, done ); - } ); - - describe( '.formatData', function () { - it( 'should return an object with filtered data', function ( done ) { - var data = { - title: 'some title', subject: 'some subject', body: 'some body', useDefault: false, permission: 'none', someattr: 'this attribute is not part of the model' - }; - - var emailData = EmailTemplateService.formatData( data ) - - emailData.should.have.property( 'id', null ); - emailData.should.have.property( 'isActive', false ); - emailData.should.have.property( 'isDefault', false ); - - emailData.should.not.have.property( 'someattr' ); - done(); - } ); - - } ); - - describe( '.handleEmailTemplateCreation', function () { - - it( 'should save a new email template with given options', function ( done ) { - var emailTemplateData = { - title: 'schedule interview', subject: 'congradulations! we would love to meet you', body: 'Dear John Appleseed,
we are interest by your skill set your qualifications for the job', useDefault: 'false', permission: 'none', isActive: true, useDefault: false, isDefault: false - }; - - EmailTemplateService - .handleEmailTemplateCreation( user.AccountId, user.id, emailTemplateData ) - .then( function ( emailTemplate ) { - - emailTemplate.should.have.property( 'id' ); - emailTemplate.should.have.property( 'title', 'schedule interview' ); - - emailTemplate.should.have.property( 'AccountId', user.AccountId ); - emailTemplate.should.have.property( 'UserId', user.id ); - - done(); - - } ) - .fail( done ); - } ); - } ); - - describe( '.handleEmailTemplateUpdate', function () { - - it( 'should return code 401 and a message if email template does not exist ', function ( done ) { - var data = { id: 1233444444444444444444 }; - - EmailTemplateService - .handleEmailTemplateUpdate( user.AccountId, user.id, data ) - .then( function ( msg ) { - - msg.should.be.a( 'object' ); - msg.should.have.property( 'statuscode', 401 ); - msg.should.have.property( 'message' ).and.not.be.empty; - - done(); - - } ) - .fail( done ); - - } ); - - it( 'should call .updateEmailTemplate ', function ( done ) { - - - var emailTemplateData = { - title: 'schedule interview', subject: 'congradulations! we would love to meet you', body: 'Dear John Appleseed,
we are interest by your skill set your qualifications for the job', useDefault: 'false', permission: 'none', isActive: true, useDefault: false, isDefault: false, AccountId: user.AccountId, UserId: user.id - }; - - var spyUpdateEmailTemplate = sinon.spy( EmailTemplateService, 'updateEmailTemplate' ); - - EmailTempalteModel - .create( emailTemplateData ) - .success( function ( emailTemplate ) { - - emailTemplate.should.have.property( 'id' ); - emailTemplate.should.have.property( 'title', 'schedule interview' ); - - EmailTemplateService - .handleEmailTemplateUpdate( user.AccountId, user.id, emailTemplate ) - .then( function () { - - spyUpdateEmailTemplate.called.should.be.true; - done(); - - } ) - .fail( done ); - } ) - .error( done ); - } ); - } ); - - describe( '.updateEmailTemplate', function () { - - it( 'should update an email Template record', function ( done ) { - - var emailTemplateData = { - title: 'schedule interview', subject: 'congradulations! we would love to meet you', body: 'Dear John Appleseed,
we are interest by your skill set your qualifications for the job', useDefault: 'false', permission: 'none', isActive: true, useDefault: false, isDefault: false, AccountId: user.AccountId, UserId: user.id - }; - - var updatedData = { - id: null, title: 'This is an updated title', subject: 'congradulations! we would love to meet you', body: 'Dear John Appleseed,
we are interest by your skill set your qualifications for the job', useDefault: 'false', permission: 'none', isActive: true, useDefault: false, isDefault: false - }; - - - EmailTempalteModel - .create( emailTemplateData ) - .success( function ( emailTemplate ) { - - emailTemplate.should.have.property( 'title', 'schedule interview' ); - updatedData['id'] = emailTemplate.id; - - EmailTemplateService - .updateEmailTemplate( emailTemplate, updatedData ) - .then( function ( updatedEmailTpl ) { - - updatedEmailTpl.id.should.equal( emailTemplate.id ); - updatedEmailTpl.title.should.not.equal( emailTemplateData.title ); - updatedEmailTpl.title.should.equal( updatedData.title ); - - done(); - } ) - .fail( done ); - } ) - .error( done ); - } ); - } ); - - describe( '.removeEmailTemplate', function () { - - it( 'should remove an email template record ', function ( done ) { - - var emailTemplateData = { - title: 'schedule interview', subject: 'congradulations! we would love to meet you', body: 'Dear John Appleseed,
we are interest by your skill set your qualifications for the job', useDefault: 'false', permission: 'none', isActive: true, useDefault: false, isDefault: false, AccountId: user.AccountId, UserId: user.id - }; - - - EmailTempalteModel - .create( emailTemplateData ) - .success( function ( emailTemplate ) { - - emailTemplate.should.have.property( 'id' ); - emailTemplate.should.have.property( 'title', 'schedule interview' ); - - EmailTemplateService - .removeEmailTemplate( user.id, emailTemplate.id ) - .then( function ( msg ) { - - msg.should.be.a( 'object' ); - msg.should.have.property( 'statuscode', 200 ); - msg.should.have.property( 'message' ).and.not.be.empty; - - done(); - - } ) - .fail( done ); - } ) - .error( done ); - } ); - - it( 'should return code 400 and a message if email template does not exist ', function ( done ) { - var tplId = '1233333333333333333333'; - - EmailTemplateService - .removeEmailTemplate( user.id, tplId ) - .then( function ( msg ) { - - msg.should.be.a( 'object' ); - msg.should.have.property( 'statuscode', 400 ); - msg.should.have.property( 'message' ).and.not.be.empty; - - done(); - - } ) - .fail( done ); - } ); - } ); -} ); \ No newline at end of file diff --git a/modules/clever-email/tests/integration/test.controller.EmailAlertController.js b/modules/clever-email/tests/temp/test.controller.EmailAlertController.js similarity index 100% rename from modules/clever-email/tests/integration/test.controller.EmailAlertController.js rename to modules/clever-email/tests/temp/test.controller.EmailAlertController.js diff --git a/modules/clever-email/tests/unit/test.service.EmailService.js b/modules/clever-email/tests/unit/test.service.EmailService.js new file mode 100644 index 0000000..a17fdff --- /dev/null +++ b/modules/clever-email/tests/unit/test.service.EmailService.js @@ -0,0 +1,227 @@ +var expect = require ( 'chai' ).expect + , request = require ( 'supertest' ) + , path = require( 'path' ) + , app = require ( path.resolve( __dirname + '/../../../../' ) + '/index.js' ) + , testEnv = require ( 'utils' ).testEnv(); + +describe( 'service.EmailService', function () { + var Service, Model; + + before( function ( done ) { + testEnv( function ( EmailService, EmailModel ) { + + Service = EmailService; + Model = EmailModel; + + done(); + + }, done ); + } ); + + describe( '.formatReplyAddress', function () { + it( 'should return an address according to environmentName', function ( done ) { + + var data = { + title: 'some title', + subject: 'some subject', + body: 'some body', + accId: 15, + userId: 5, + useDefault: true, + permission: 'none', + someattr: 'this attribute is not part of the model' + }; + + var emailData = Service.formatData( data ); + + expect( emailData ).to.have.property( 'id' ).and.equal( null ); + expect( emailData ).to.have.property( 'title' ).and.equal( data.title ); + expect( emailData ).to.have.property( 'subject' ).and.equal( data.subject ); + expect( emailData ).to.have.property( 'body' ).and.equal( data.body ); + expect( emailData ).to.have.property( 'AccountId' ).and.equal( data.accId ); + expect( emailData ).to.have.property( 'UserId' ).and.equal( data.userId ); + expect( emailData ).to.have.property( 'isActive' ).and.equal( false ); + expect( emailData ).to.have.property( 'isDefault' ).and.equal( false ); + expect( emailData ).to.have.property( 'useDefault' ).and.equal( true ); + expect( emailData ).to.have.property( 'hasPermission' ).and.equal( true ); + + expect( emailData ).to.not.have.property( 'permission' ); + expect( emailData ).to.not.have.property( 'someattr' ); + + done(); + } ); + + } ); + + + describe( '.formatData', function () { + it( 'should return an object with filtered data', function ( done ) { + + var data = { + title: 'some title', + subject: 'some subject', + body: 'some body', + accId: 15, + userId: 5, + useDefault: true, + permission: 'none', + someattr: 'this attribute is not part of the model' + }; + + var emailData = Service.formatData( data ); + + expect( emailData ).to.have.property( 'id' ).and.equal( null ); + expect( emailData ).to.have.property( 'title' ).and.equal( data.title ); + expect( emailData ).to.have.property( 'subject' ).and.equal( data.subject ); + expect( emailData ).to.have.property( 'body' ).and.equal( data.body ); + expect( emailData ).to.have.property( 'AccountId' ).and.equal( data.accId ); + expect( emailData ).to.have.property( 'UserId' ).and.equal( data.userId ); + expect( emailData ).to.have.property( 'isActive' ).and.equal( false ); + expect( emailData ).to.have.property( 'isDefault' ).and.equal( false ); + expect( emailData ).to.have.property( 'useDefault' ).and.equal( true ); + expect( emailData ).to.have.property( 'hasPermission' ).and.equal( true ); + + expect( emailData ).to.not.have.property( 'permission' ); + expect( emailData ).to.not.have.property( 'someattr' ); + + done(); + } ); + + } ); + +// describe( '.handleEmailTemplateUpdate', function () { +// +// it( 'should return code 401 and a message if email template does not exist ', function ( done ) { +// var data = { id: 1233444444444444444444 }; +// +// EmailTemplateService +// .handleEmailTemplateUpdate( user.AccountId, user.id, data ) +// .then( function ( msg ) { +// +// msg.should.be.a( 'object' ); +// msg.should.have.property( 'statuscode', 401 ); +// msg.should.have.property( 'message' ).and.not.be.empty; +// +// done(); +// +// } ) +// .fail( done ); +// +// } ); +// +// it( 'should call .updateEmailTemplate ', function ( done ) { +// +// +// var emailTemplateData = { +// title: 'schedule interview', subject: 'congradulations! we would love to meet you', body: 'Dear John Appleseed,
we are interest by your skill set your qualifications for the job', useDefault: 'false', permission: 'none', isActive: true, useDefault: false, isDefault: false, AccountId: user.AccountId, UserId: user.id +// }; +// +// var spyUpdateEmailTemplate = sinon.spy( EmailTemplateService, 'updateEmailTemplate' ); +// +// EmailTempalteModel +// .create( emailTemplateData ) +// .success( function ( emailTemplate ) { +// +// emailTemplate.should.have.property( 'id' ); +// emailTemplate.should.have.property( 'title', 'schedule interview' ); +// +// EmailTemplateService +// .handleEmailTemplateUpdate( user.AccountId, user.id, emailTemplate ) +// .then( function () { +// +// spyUpdateEmailTemplate.called.should.be.true; +// done(); +// +// } ) +// .fail( done ); +// } ) +// .error( done ); +// } ); +// } ); +// +// describe( '.updateEmailTemplate', function () { +// +// it( 'should update an email Template record', function ( done ) { +// +// var emailTemplateData = { +// title: 'schedule interview', subject: 'congradulations! we would love to meet you', body: 'Dear John Appleseed,
we are interest by your skill set your qualifications for the job', useDefault: 'false', permission: 'none', isActive: true, useDefault: false, isDefault: false, AccountId: user.AccountId, UserId: user.id +// }; +// +// var updatedData = { +// id: null, title: 'This is an updated title', subject: 'congradulations! we would love to meet you', body: 'Dear John Appleseed,
we are interest by your skill set your qualifications for the job', useDefault: 'false', permission: 'none', isActive: true, useDefault: false, isDefault: false +// }; +// +// +// EmailTempalteModel +// .create( emailTemplateData ) +// .success( function ( emailTemplate ) { +// +// emailTemplate.should.have.property( 'title', 'schedule interview' ); +// updatedData['id'] = emailTemplate.id; +// +// EmailTemplateService +// .updateEmailTemplate( emailTemplate, updatedData ) +// .then( function ( updatedEmailTpl ) { +// +// updatedEmailTpl.id.should.equal( emailTemplate.id ); +// updatedEmailTpl.title.should.not.equal( emailTemplateData.title ); +// updatedEmailTpl.title.should.equal( updatedData.title ); +// +// done(); +// } ) +// .fail( done ); +// } ) +// .error( done ); +// } ); +// } ); +// +// describe( '.removeEmailTemplate', function () { +// +// it( 'should remove an email template record ', function ( done ) { +// +// var emailTemplateData = { +// title: 'schedule interview', subject: 'congradulations! we would love to meet you', body: 'Dear John Appleseed,
we are interest by your skill set your qualifications for the job', useDefault: 'false', permission: 'none', isActive: true, useDefault: false, isDefault: false, AccountId: user.AccountId, UserId: user.id +// }; +// +// +// EmailTempalteModel +// .create( emailTemplateData ) +// .success( function ( emailTemplate ) { +// +// emailTemplate.should.have.property( 'id' ); +// emailTemplate.should.have.property( 'title', 'schedule interview' ); +// +// EmailTemplateService +// .removeEmailTemplate( user.id, emailTemplate.id ) +// .then( function ( msg ) { +// +// msg.should.be.a( 'object' ); +// msg.should.have.property( 'statuscode', 200 ); +// msg.should.have.property( 'message' ).and.not.be.empty; +// +// done(); +// +// } ) +// .fail( done ); +// } ) +// .error( done ); +// } ); +// +// it( 'should return code 400 and a message if email template does not exist ', function ( done ) { +// var tplId = '1233333333333333333333'; +// +// EmailTemplateService +// .removeEmailTemplate( user.id, tplId ) +// .then( function ( msg ) { +// +// msg.should.be.a( 'object' ); +// msg.should.have.property( 'statuscode', 400 ); +// msg.should.have.property( 'message' ).and.not.be.empty; +// +// done(); +// +// } ) +// .fail( done ); +// } ); +// } ); +} ); \ No newline at end of file diff --git a/modules/clever-email/tests/unit/test.service.EmailTemplateService.js b/modules/clever-email/tests/unit/test.service.EmailTemplateService.js new file mode 100644 index 0000000..2285dd2 --- /dev/null +++ b/modules/clever-email/tests/unit/test.service.EmailTemplateService.js @@ -0,0 +1,192 @@ +var expect = require ( 'chai' ).expect + , request = require ( 'supertest' ) + , path = require( 'path' ) + , app = require ( path.resolve( __dirname + '/../../../../' ) + '/index.js' ) + , testEnv = require ( 'utils' ).testEnv(); + +describe( 'service.EmailTemplateService', function () { + var Service, Model, UserModel, user; + + before( function ( done ) { + testEnv( function ( EmailTemplateService, EmailTemplateModel, UserModel ) { + + Service = EmailTemplateService; + Model = EmailTemplateModel; + UserModel = UserModel; + + done(); + + }, done ); + } ); + + describe( '.formatData', function () { + it( 'should return an object with filtered data', function ( done ) { + + var data = { + title: 'some title', + subject: 'some subject', + body: 'some body', + accId: 15, + userId: 5, + useDefault: true, + permission: 'none', + someattr: 'this attribute is not part of the model' + }; + + var emailData = Service.formatData( data ); + + expect( emailData ).to.have.property( 'id' ).and.equal( null ); + expect( emailData ).to.have.property( 'title' ).and.equal( data.title ); + expect( emailData ).to.have.property( 'subject' ).and.equal( data.subject ); + expect( emailData ).to.have.property( 'body' ).and.equal( data.body ); + expect( emailData ).to.have.property( 'AccountId' ).and.equal( data.accId ); + expect( emailData ).to.have.property( 'UserId' ).and.equal( data.userId ); + expect( emailData ).to.have.property( 'isActive' ).and.equal( false ); + expect( emailData ).to.have.property( 'isDefault' ).and.equal( false ); + expect( emailData ).to.have.property( 'useDefault' ).and.equal( true ); + expect( emailData ).to.have.property( 'hasPermission' ).and.equal( true ); + + expect( emailData ).to.not.have.property( 'permission' ); + expect( emailData ).to.not.have.property( 'someattr' ); + + done(); + } ); + + } ); + +// describe( '.handleEmailTemplateUpdate', function () { +// +// it( 'should return code 401 and a message if email template does not exist ', function ( done ) { +// var data = { id: 1233444444444444444444 }; +// +// EmailTemplateService +// .handleEmailTemplateUpdate( user.AccountId, user.id, data ) +// .then( function ( msg ) { +// +// msg.should.be.a( 'object' ); +// msg.should.have.property( 'statuscode', 401 ); +// msg.should.have.property( 'message' ).and.not.be.empty; +// +// done(); +// +// } ) +// .fail( done ); +// +// } ); +// +// it( 'should call .updateEmailTemplate ', function ( done ) { +// +// +// var emailTemplateData = { +// title: 'schedule interview', subject: 'congradulations! we would love to meet you', body: 'Dear John Appleseed,
we are interest by your skill set your qualifications for the job', useDefault: 'false', permission: 'none', isActive: true, useDefault: false, isDefault: false, AccountId: user.AccountId, UserId: user.id +// }; +// +// var spyUpdateEmailTemplate = sinon.spy( EmailTemplateService, 'updateEmailTemplate' ); +// +// EmailTempalteModel +// .create( emailTemplateData ) +// .success( function ( emailTemplate ) { +// +// emailTemplate.should.have.property( 'id' ); +// emailTemplate.should.have.property( 'title', 'schedule interview' ); +// +// EmailTemplateService +// .handleEmailTemplateUpdate( user.AccountId, user.id, emailTemplate ) +// .then( function () { +// +// spyUpdateEmailTemplate.called.should.be.true; +// done(); +// +// } ) +// .fail( done ); +// } ) +// .error( done ); +// } ); +// } ); +// +// describe( '.updateEmailTemplate', function () { +// +// it( 'should update an email Template record', function ( done ) { +// +// var emailTemplateData = { +// title: 'schedule interview', subject: 'congradulations! we would love to meet you', body: 'Dear John Appleseed,
we are interest by your skill set your qualifications for the job', useDefault: 'false', permission: 'none', isActive: true, useDefault: false, isDefault: false, AccountId: user.AccountId, UserId: user.id +// }; +// +// var updatedData = { +// id: null, title: 'This is an updated title', subject: 'congradulations! we would love to meet you', body: 'Dear John Appleseed,
we are interest by your skill set your qualifications for the job', useDefault: 'false', permission: 'none', isActive: true, useDefault: false, isDefault: false +// }; +// +// +// EmailTempalteModel +// .create( emailTemplateData ) +// .success( function ( emailTemplate ) { +// +// emailTemplate.should.have.property( 'title', 'schedule interview' ); +// updatedData['id'] = emailTemplate.id; +// +// EmailTemplateService +// .updateEmailTemplate( emailTemplate, updatedData ) +// .then( function ( updatedEmailTpl ) { +// +// updatedEmailTpl.id.should.equal( emailTemplate.id ); +// updatedEmailTpl.title.should.not.equal( emailTemplateData.title ); +// updatedEmailTpl.title.should.equal( updatedData.title ); +// +// done(); +// } ) +// .fail( done ); +// } ) +// .error( done ); +// } ); +// } ); +// +// describe( '.removeEmailTemplate', function () { +// +// it( 'should remove an email template record ', function ( done ) { +// +// var emailTemplateData = { +// title: 'schedule interview', subject: 'congradulations! we would love to meet you', body: 'Dear John Appleseed,
we are interest by your skill set your qualifications for the job', useDefault: 'false', permission: 'none', isActive: true, useDefault: false, isDefault: false, AccountId: user.AccountId, UserId: user.id +// }; +// +// +// EmailTempalteModel +// .create( emailTemplateData ) +// .success( function ( emailTemplate ) { +// +// emailTemplate.should.have.property( 'id' ); +// emailTemplate.should.have.property( 'title', 'schedule interview' ); +// +// EmailTemplateService +// .removeEmailTemplate( user.id, emailTemplate.id ) +// .then( function ( msg ) { +// +// msg.should.be.a( 'object' ); +// msg.should.have.property( 'statuscode', 200 ); +// msg.should.have.property( 'message' ).and.not.be.empty; +// +// done(); +// +// } ) +// .fail( done ); +// } ) +// .error( done ); +// } ); +// +// it( 'should return code 400 and a message if email template does not exist ', function ( done ) { +// var tplId = '1233333333333333333333'; +// +// EmailTemplateService +// .removeEmailTemplate( user.id, tplId ) +// .then( function ( msg ) { +// +// msg.should.be.a( 'object' ); +// msg.should.have.property( 'statuscode', 400 ); +// msg.should.have.property( 'message' ).and.not.be.empty; +// +// done(); +// +// } ) +// .fail( done ); +// } ); +// } ); +} ); \ No newline at end of file diff --git a/modules/example-module/tests/integration/test.api.example.js b/modules/example-module/tests/integration/test.api.example.js index 968280a..3fc9704 100644 --- a/modules/example-module/tests/integration/test.api.example.js +++ b/modules/example-module/tests/integration/test.api.example.js @@ -2,7 +2,7 @@ var expect = require ( 'chai' ).expect , request = require ( 'supertest' ) , path = require( 'path' ) , app = require ( path.resolve( __dirname + '/../../../../' ) + '/index.js' ); -console.dir(app) +//console.dir(app) describe ( '/example', function () { describe ( 'POST /example', function () { it ( 'should return valid status', function ( done ) { @@ -11,7 +11,7 @@ describe ( '/example', function () { .expect ( 'Content-Type', /json/ ) .expect ( 200 ) .end ( function ( err, res ) { - console.dir( arguments) +// console.dir( arguments) if ( err ) return done ( err ); expect ( res.body ).to.eql ( { status: 'Created record!' diff --git a/modules/orm/config/default.json b/modules/orm/config/default.json index 20ea3de..8354c09 100644 --- a/modules/orm/config/default.json +++ b/modules/orm/config/default.json @@ -1,9 +1,9 @@ { "orm": { "db": { - "username": "john", - "password": "secret", - "database": "nodeseed", + "username": "root", + "password": "qqq", + "database": "clevertech", "options": { "host": "localhost", "dialect": "mysql", From 21f8ba8e8417f090f8638400dfc8144526ee1359 Mon Sep 17 00:00:00 2001 From: Volodymyr Denshchykov Date: Tue, 21 Jan 2014 22:29:10 +0200 Subject: [PATCH 03/24] befor testing WIP --- Gruntfile.js | 3 +- modules/auth/models/orm/AccountModel.js | 118 +++++++++++ modules/auth/models/orm/UserModel.js | 3 + .../models/orm/EmailTemplateModel.js | 3 + modules/clever-email/schema/seedData.json | 196 +++++++++++++++--- modules/clever-email/services/EmailService.js | 13 +- .../tests/unit/test.service.EmailService.js | 85 ++++---- modules/orm/bin/seedModels.js | 2 +- modules/orm/config/default.json | 27 ++- 9 files changed, 373 insertions(+), 77 deletions(-) create mode 100644 modules/auth/models/orm/AccountModel.js diff --git a/Gruntfile.js b/Gruntfile.js index 8953639..725f82f 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -37,7 +37,8 @@ module.exports = function( grunt ) { unit: { options: { require: [ 'chai' ], - reporter: 'spec' + reporter: 'spec', + timeout: 5000 }, src: [ 'tests/unit/*.js' ].concat( getModulePaths( 'tests', 'unit', '*.js' ) ) }, diff --git a/modules/auth/models/orm/AccountModel.js b/modules/auth/models/orm/AccountModel.js new file mode 100644 index 0000000..c3bc71d --- /dev/null +++ b/modules/auth/models/orm/AccountModel.js @@ -0,0 +1,118 @@ +module.exports = function(sequelize, DataTypes) { + return sequelize.define("Account", { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true + }, + name: { + type: DataTypes.STRING, + validate: { + len: [ 2, 50 ] + } + }, + subdomain: { + type: DataTypes.STRING, + allowNull: false, + unique: true, + validate: { + isAlphanumeric: true, + len: [ 3, 16 ] + } + }, + active: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false + }, + logo : { + type: DataTypes.STRING, + allowNull: true + }, + themeColor : { + type: DataTypes.STRING, + allowNull: true + }, + email : { + type: DataTypes.STRING, + allowNull: true, + unique: true, + validate: { + isEmail: true + } + }, + emailFwd : { + type: DataTypes.STRING, + allowNull: true + }, + info: { + type: DataTypes.TEXT, + allowNull: true + }, + benefits: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false + }, + dependents: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false + }, + documents: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false + }, + emergency: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false + }, + boarding: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false + }, + timeoff: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false + }, + training: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false + }, + date_format: { + type: DataTypes.STRING, + allowNull: false, + defaultValue: "yyyy-mm-dd" + }, + number_format: { + type: DataTypes.STRING, + allowNull: false, + defaultValue: "123'456.78" + }, + currency: { + type: DataTypes.STRING, + allowNull: false, + defaultValue: "USD" + }, + quickstart: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false + } + }, + { + instanceMethods: { + toJSON: function () { + var values = this.values; + delete values.createdAt; + delete values.updatedAt; + return values; + } + } + }); +}; diff --git a/modules/auth/models/orm/UserModel.js b/modules/auth/models/orm/UserModel.js index d3bfc61..39161fb 100644 --- a/modules/auth/models/orm/UserModel.js +++ b/modules/auth/models/orm/UserModel.js @@ -58,6 +58,9 @@ module.exports = function(sequelize, DataTypes) { accessedAt : { type: DataTypes.DATE, allowNull: true + }, + AccountId : { + type: DataTypes.INTEGER } }, diff --git a/modules/clever-email/models/orm/EmailTemplateModel.js b/modules/clever-email/models/orm/EmailTemplateModel.js index 9472af2..94df239 100644 --- a/modules/clever-email/models/orm/EmailTemplateModel.js +++ b/modules/clever-email/models/orm/EmailTemplateModel.js @@ -43,6 +43,9 @@ module.exports = function ( sequelize, DataTypes ) { type: DataTypes.BOOLEAN, allowNull: false, default: true + }, + UserId: { + type: DataTypes.INTEGER } }, { diff --git a/modules/clever-email/schema/seedData.json b/modules/clever-email/schema/seedData.json index e6bf30b..e84916b 100644 --- a/modules/clever-email/schema/seedData.json +++ b/modules/clever-email/schema/seedData.json @@ -1,21 +1,134 @@ { - "DefaultEmailTemplateModel": [ + "AccountModel": [ { - "title": "application submitted successfully", - "subject": "<%=company_name%> :Application Submitted Successfully", - "body": "Dear <%=firstName%> <%=lastName%>,

Thank you for your interest in <%=company_name%>. We received your application for the following position(s) submitted on <%=submitted_date%>:
<%=job_title%>
Our team is reviewing your qualifications and will contact you if there is a match with any of our open positions. Please note that new jobs are posted to the site often. Please check back regularly for future openings that may be of interest to you.
We appreciate your interest <%=company_name%> and wish you the best of luck in your job search
Sincerely
Company Human Resources Team
<%=career_link%>
Note: This message was automatically generated. Please do not respond to this email.

" + "name": "Default Account", + "subdomain": "dev", + "emailFwd": "dev23io@stage.bolthr.clevertech.biz", + "active": true }, { - "title": "application status update", - "subject": "<%=company_name%> :Application Status Update", - "body": "Dear <%=firstName%> <%=lastName%>,

Thank you for applying for the position <%=job_title%> with <%=company_name%>. We appreciate your interest in our organization.We received a significant number of applications for the position, and the hiring process has been a very competitive one. After a review of your application, we have decided not to move your application forward. However, we greatly appreciate your interest in working with us and wish you the best of luck with your job search. We welcome you to visit our careers page regularly for future openings that may be of interest to you
Sincerely
Company Human Resources Team
<%=career_link%>

" + "name": "Testing Account", + "subdomain": "testing", + "emailFwd": "dfg344dc@stage.bolthr.clevertech.biz", + "active": true + } + ], + + "UserModel": [ + { + "username": "dimitrios@clevertech.biz", + "email": "dimitrios@clevertech.biz", + "password": "9ac20922b054316be23842a5bca7d69f29f69d77", + "firstname": "dimitris", + "lastname": "ioakimidis", + "phone": "8900000", + "active": true, + "confirmed": true, + "hasAdminRight": true, + "associations": { + "AccountModel": { + "name": "Default Account" + } + } }, { - "title": "new career opportunity", - "subject": "<%=company_name%> :New career opportunity", - "body": "

<%=company_name%> has just posted the following new jobs to our careers page on <%=published_date%>
<%=job_title_link%>
If you or anybody you know may be a good fit for this position, please encourage them to apply immediately. If passing along to others, please be sure to remind them to mention how they heard about the opportunity.
Thanks,
Company Human Resources Team
<%=career_link%>

" + "username": "adnan@clevertech.biz", + "email": "adnan@clevertech.biz", + "password": "f10e2821bbbea527ea02200352313bc059445190", + "firstname": "Adnan", + "lastname": "Ibrisimbegovic", + "phone": "8900000", + "active": true, + "confirmed": true, + "hasAdminRight": true, + "associations": { + "AccountModel": { + "name": "Default Account" + } + } + }, + { + "username": "richard", + "email": "richard@clevertech.biz", + "password": "9ac20922b054316be23842a5bca7d69f29f69d77", + "firstname": "Richard", + "lastname": "Gustin", + "phone": "+61448048484", + "active": true, + "confirmed": true, + "hasAdminRight": true, + "associations": { + "AccountModel": { + "name": "Default Account" + } + } + }, + { + "username": "joeJunior", + "email": "joeJunior@clevertech2.biz", + "password": "9ac20922b054316be23842a5bca7d69f29f69d77", + "firstname": "joeJunior", + "lastname": "joeJunior", + "phone": "+3044444", + "active": true, + "confirmed": false, + "hasAdminRight": true, + "associations": { + "AccountModel": { + "name": "Default Account" + } + } + }, + { + "username": "joeJunior3", + "email": "joeJunior3@clevertech2.biz", + "password": "9ac20922b054316be23842a5bca7d69f29f69d77", + "firstname": "joeJunior3", + "lastname": "joeJunior3", + "phone": "+3044444", + "active": false, + "confirmed": false, + "hasAdminRight": true, + "associations": { + "AccountModel": { + "name": "Default Account" + } + } + }, + { + "username": "test", + "email": "bolt-hris@clevertech.biz", + "password": "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8", + "firstname": "Test", + "lastname": "Account", + "phone": "+61448048484", + "active": true, + "confirmed": true, + "hasAdminRight": true, + "associations": { + "AccountModel": { + "name": "Default Account" + } + } + }, + { + "username": "test_employee", + "email": "bolt-employee@clevertech.biz", + "password": "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8", + "firstname": "Test", + "lastname": "Employee", + "phone": "+61448048484", + "active": true, + "confirmed": true, + "hasAdminRight": false, + "associations": { + "AccountModel": { + "name": "Default Account" + } + } } ], + "EmailTemplateModel": [ { "title": "Application submitted successfully", @@ -25,8 +138,11 @@ "isActive": true, "useDefault": false, "isDefault": true, - "UserId": 1, - "AccountId": 1 + "associations": { + "UserModel": { + "username": "test_employee" + } + } }, { "title": "Application status update", @@ -36,8 +152,11 @@ "isActive": true, "useDefault": false, "isDefault": true, - "UserId": 1, - "AccountId": 1 + "associations": { + "UserModel": { + "username": "test_employee" + } + } }, { "title": "new career opportunity", @@ -47,8 +166,11 @@ "isActive": true, "useDefault": false, "isDefault": true, - "UserId": 1, - "AccountId": 1 + "associations": { + "UserModel": { + "username": "test_employee" + } + } }, { "title": "Application submitted successfully", @@ -58,8 +180,11 @@ "isActive": true, "useDefault": false, "isDefault": true, - "UserId": 2, - "AccountId": 1 + "associations": { + "UserModel": { + "id": 1 + } + } }, { "title": "Application status update", @@ -69,8 +194,11 @@ "isActive": true, "useDefault": false, "isDefault": true, - "UserId": 2, - "AccountId": 1 + "associations": { + "UserModel": { + "id": 1 + } + } }, { "title": "new career opportunity", @@ -80,8 +208,11 @@ "isActive": true, "useDefault": false, "isDefault": true, - "UserId": 2, - "AccountId": 1 + "associations": { + "UserModel": { + "id": 1 + } + } }, { "title": "Application submitted successfully", @@ -91,8 +222,11 @@ "isActive": true, "useDefault": false, "isDefault": true, - "UserId": 3, - "AccountId": 1 + "associations": { + "UserModel": { + "id": 1 + } + } }, { "title": "Application status update", @@ -102,8 +236,11 @@ "isActive": true, "useDefault": false, "isDefault": true, - "UserId": 3, - "AccountId": 1 + "associations": { + "UserModel": { + "id": 1 + } + } }, { "title": "new career opportunity", @@ -113,8 +250,11 @@ "isActive": true, "useDefault": false, "isDefault": true, - "UserId": 3, - "AccountId": 1 + "associations": { + "UserModel": { + "id": 1 + } + } } ] } \ No newline at end of file diff --git a/modules/clever-email/services/EmailService.js b/modules/clever-email/services/EmailService.js index e9b8ccb..b30cf16 100644 --- a/modules/clever-email/services/EmailService.js +++ b/modules/clever-email/services/EmailService.js @@ -2,8 +2,8 @@ var Q = require( 'q' ) , Sequelize = require( 'sequelize' ) , ejsFileRender = require( '../lib/ejsfilerender' ) , shortid = require( 'shortid' ) - , config = require( 'config' )['clever-email'] - , sendgrid = require( '../lib/sendgrid' ) + , config = require( 'config' ) + , mailer = require( '../lib/mailer' )( config['clever-email'] ) , EmailService = null; module.exports = function ( sequelize, @@ -14,8 +14,7 @@ module.exports = function ( sequelize, ORMEmailUserModel, EmailTemplateService ) { - var mailer = sendgrid( config ) - , bakeTemplate = ejsFileRender(); + var bakeTemplate = ejsFileRender(); if ( EmailService && EmailService.instance ) { return EmailService.instance; @@ -31,7 +30,9 @@ module.exports = function ( sequelize, ? 'dev' : ( config.environmentName == 'PROD' ) ? 'prod' - : 'stage'; + : ( config.environmentName == 'STAGE' ) + ? 'stage' + : 'local'; addr = ( envName != 'prod' ) ? 'reply_' + emailToken + '@' + envName + '.bolthr.clevertech.biz' @@ -137,7 +138,7 @@ module.exports = function ( sequelize, return o; }, - listEmails: function ( userId, emailId ) { + listEmails: function ( userId ) { var deferred = Q.defer(); this diff --git a/modules/clever-email/tests/unit/test.service.EmailService.js b/modules/clever-email/tests/unit/test.service.EmailService.js index a17fdff..544a3be 100644 --- a/modules/clever-email/tests/unit/test.service.EmailService.js +++ b/modules/clever-email/tests/unit/test.service.EmailService.js @@ -2,6 +2,7 @@ var expect = require ( 'chai' ).expect , request = require ( 'supertest' ) , path = require( 'path' ) , app = require ( path.resolve( __dirname + '/../../../../' ) + '/index.js' ) + , config = require( 'config' ) , testEnv = require ( 'utils' ).testEnv(); describe( 'service.EmailService', function () { @@ -21,32 +22,19 @@ describe( 'service.EmailService', function () { describe( '.formatReplyAddress', function () { it( 'should return an address according to environmentName', function ( done ) { - var data = { - title: 'some title', - subject: 'some subject', - body: 'some body', - accId: 15, - userId: 5, - useDefault: true, - permission: 'none', - someattr: 'this attribute is not part of the model' - }; - - var emailData = Service.formatData( data ); + var emailToken = '15da5AS15A1s'; - expect( emailData ).to.have.property( 'id' ).and.equal( null ); - expect( emailData ).to.have.property( 'title' ).and.equal( data.title ); - expect( emailData ).to.have.property( 'subject' ).and.equal( data.subject ); - expect( emailData ).to.have.property( 'body' ).and.equal( data.body ); - expect( emailData ).to.have.property( 'AccountId' ).and.equal( data.accId ); - expect( emailData ).to.have.property( 'UserId' ).and.equal( data.userId ); - expect( emailData ).to.have.property( 'isActive' ).and.equal( false ); - expect( emailData ).to.have.property( 'isDefault' ).and.equal( false ); - expect( emailData ).to.have.property( 'useDefault' ).and.equal( true ); - expect( emailData ).to.have.property( 'hasPermission' ).and.equal( true ); + var addr = Service.formatReplyAddress( emailToken ); - expect( emailData ).to.not.have.property( 'permission' ); - expect( emailData ).to.not.have.property( 'someattr' ); + if ( config.environmentName == 'DEV' ){ + expect( addr ).to.equal( 'reply_15da5AS15A1s@dev.bolthr.clevertech.biz' ); + } else if ( config.environmentName == 'PROD' ) { + expect( addr ).to.equal( 'reply_15da5AS15A1s@app-mail.bolthr.com' ); + } else if ( config.environmentName == 'STAGE' ) { + expect( addr ).to.equal( 'reply_15da5AS15A1s@stage.bolthr.clevertech.biz' ); + } else { + expect( addr ).to.equal( 'reply_15da5AS15A1s@local.bolthr.clevertech.biz' ); + } done(); } ); @@ -61,28 +49,45 @@ describe( 'service.EmailService', function () { title: 'some title', subject: 'some subject', body: 'some body', - accId: 15, userId: 5, - useDefault: true, + accId: 1, + to: { + id: 15, + email: 'to1@email.za' + }, + hasTemplate: false, + cc: [ 'cc1@mail.ru', 'cc2@mail.ru' ], + bcc: [ 'bcc1@mail.com', 'bcc2@mail.com' ], + accLogo: 'LoGo', + accName: 'Default Name', + userFirstName: 'Dmitrij', + userLastName: 'Safronov', + permission: 'none', someattr: 'this attribute is not part of the model' }; var emailData = Service.formatData( data ); - - expect( emailData ).to.have.property( 'id' ).and.equal( null ); - expect( emailData ).to.have.property( 'title' ).and.equal( data.title ); - expect( emailData ).to.have.property( 'subject' ).and.equal( data.subject ); - expect( emailData ).to.have.property( 'body' ).and.equal( data.body ); - expect( emailData ).to.have.property( 'AccountId' ).and.equal( data.accId ); - expect( emailData ).to.have.property( 'UserId' ).and.equal( data.userId ); - expect( emailData ).to.have.property( 'isActive' ).and.equal( false ); - expect( emailData ).to.have.property( 'isDefault' ).and.equal( false ); - expect( emailData ).to.have.property( 'useDefault' ).and.equal( true ); - expect( emailData ).to.have.property( 'hasPermission' ).and.equal( true ); - - expect( emailData ).to.not.have.property( 'permission' ); - expect( emailData ).to.not.have.property( 'someattr' ); +console.log(emailData.email) +console.log(emailData.usersCC) + expect( emailData ).to.be.an('object'); + expect( emailData ).to.contain.keys('email', 'usersCC', 'usersBCC', 'attachments', 'sender', 'hasTemplate'); + + expect( emailData.email ).to.be.an('object'); + expect( emailData.email ).to.have.property; + +// expect( emailData ).to.have.property( 'title' ).and.equal( data.title ); +// expect( emailData ).to.have.property( 'subject' ).and.equal( data.subject ); +// expect( emailData ).to.have.property( 'body' ).and.equal( data.body ); +// expect( emailData ).to.have.property( 'AccountId' ).and.equal( data.accId ); +// expect( emailData ).to.have.property( 'UserId' ).and.equal( data.userId ); +// expect( emailData ).to.have.property( 'isActive' ).and.equal( false ); +// expect( emailData ).to.have.property( 'isDefault' ).and.equal( false ); +// expect( emailData ).to.have.property( 'useDefault' ).and.equal( true ); +// expect( emailData ).to.have.property( 'hasPermission' ).and.equal( true ); +// +// expect( emailData ).to.not.have.property( 'permission' ); +// expect( emailData ).to.not.have.property( 'someattr' ); done(); } ); diff --git a/modules/orm/bin/seedModels.js b/modules/orm/bin/seedModels.js index c1ca5ac..a32aadc 100644 --- a/modules/orm/bin/seedModels.js +++ b/modules/orm/bin/seedModels.js @@ -73,7 +73,7 @@ async.forEachSeries( // Handle hasOne if ( typeof model[funcName] !== 'function' ) { - funcName = 'set' + assocModelName; + funcName = 'set' + assocModelName.replace( "Model", "" ); associations = associations[0]; } diff --git a/modules/orm/config/default.json b/modules/orm/config/default.json index 8354c09..64dedb0 100644 --- a/modules/orm/config/default.json +++ b/modules/orm/config/default.json @@ -11,7 +11,32 @@ } }, "modelAssociations": { - "Example": {} + "AccountModel": { + "hasMany": [ "UserModel", "EmailModel", "EmailTemplateModel" ] + }, + "UserModel": { + "belongsTo": [ "AccountModel" ] + }, + "ExampleModel": {}, + "EmailTemplateModel": { + "hasMany": [ "UserModel" ], + "belongsTo": [ "AccountModel", "UserModel" ] + }, + "EmailModel": { + "hasMany": [ "EmailAttachmentModel", "EmailReplyModel" ], + "belongsTo": [ "UserModel", "AccountModel" ] + }, + "EmailUserModel": { + "belongsTo": [ "EmailModel", "UserModel" ] + }, + "EmailAttachmentModel": { + "belongsTo": [ "EmailModel" ] + }, + "EmailReplyModel": { + "belongsTo": [ "EmailModel" ] + } + + } } } \ No newline at end of file From 1b5e927be45733e618c14569984cb008f5997807 Mon Sep 17 00:00:00 2001 From: Volodymyr Denshchykov Date: Wed, 22 Jan 2014 16:20:36 +0200 Subject: [PATCH 04/24] testing email servece WIP --- modules/auth/models/orm/UserModel.js | 3 + modules/clever-email/config/default.json | 4 +- .../controllers/EmailController.js | 2 +- .../models/orm/EmailAttachmentModel.js | 18 +- modules/clever-email/models/orm/EmailModel.js | 59 +- .../models/orm/EmailReplyModel.js | 45 +- .../models/orm/EmailTemplateModel.js | 11 +- .../clever-email/models/orm/EmailUserModel.js | 9 +- modules/clever-email/services/EmailService.js | 173 +++-- .../tests/unit/test.service.EmailService.js | 637 +++++++++++++----- modules/orm/config/default.json | 22 +- 11 files changed, 693 insertions(+), 290 deletions(-) diff --git a/modules/auth/models/orm/UserModel.js b/modules/auth/models/orm/UserModel.js index 39161fb..6c20417 100644 --- a/modules/auth/models/orm/UserModel.js +++ b/modules/auth/models/orm/UserModel.js @@ -61,6 +61,9 @@ module.exports = function(sequelize, DataTypes) { }, AccountId : { type: DataTypes.INTEGER + }, + EmailTemplateId : { + type: DataTypes.INTEGER } }, diff --git a/modules/clever-email/config/default.json b/modules/clever-email/config/default.json index 5cd1aea..436a679 100644 --- a/modules/clever-email/config/default.json +++ b/modules/clever-email/config/default.json @@ -27,7 +27,9 @@ "noReply": "true", "from": "no-reply@app.bolthr.com", "fromName": "BoltHR", - "subject": "BoltHR: Notification" + "subject": "BoltHR: Autogenerated Notification", + "tplName": "default", + "logo": "http://app.bolthr.com/images/logo.png" } } } \ No newline at end of file diff --git a/modules/clever-email/controllers/EmailController.js b/modules/clever-email/controllers/EmailController.js index 6c606b5..cf7210f 100644 --- a/modules/clever-email/controllers/EmailController.js +++ b/modules/clever-email/controllers/EmailController.js @@ -36,7 +36,7 @@ module.exports = function ( EmailService ) { , emailId = this.req.params.id; EmailService - .getEmailById( userId, emailId ) + .getEmailByIds( userId, emailId ) .then( this.proxy( 'handleServiceMessage' ) ) .fail( this.proxy( 'handleException' ) ); }, diff --git a/modules/clever-email/models/orm/EmailAttachmentModel.js b/modules/clever-email/models/orm/EmailAttachmentModel.js index d9e297a..f4526d3 100644 --- a/modules/clever-email/models/orm/EmailAttachmentModel.js +++ b/modules/clever-email/models/orm/EmailAttachmentModel.js @@ -14,16 +14,20 @@ module.exports = function ( sequelize, DataTypes ) { type: DataTypes.STRING, allowNull: false }, - fileType: { + mimeType: { type: DataTypes.STRING, allowNull: true + }, + EmailId: { + type: DataTypes.INTEGER } }, { - paranoid: true, instanceMethods: { - toJSON: function () { - return this.values; - } + paranoid: true, + instanceMethods: { + toJSON: function () { + return this.values; + } } - } ); -}; + } ); +}; \ No newline at end of file diff --git a/modules/clever-email/models/orm/EmailModel.js b/modules/clever-email/models/orm/EmailModel.js index 4c7eb90..3391123 100644 --- a/modules/clever-email/models/orm/EmailModel.js +++ b/modules/clever-email/models/orm/EmailModel.js @@ -18,13 +18,62 @@ module.exports = function ( sequelize, DataTypes ) { type: DataTypes.BOOLEAN, allowNull: false, default: true + }, + sentAttemps: { + type: DataTypes.INTEGER, + allowNull: false, + default: 0 + }, + isOpened: { + type: DataTypes.BOOLEAN, + allowNull: false, + default: false + }, + token: { + type: DataTypes.STRING, + allowNull: true + }, + dump: { + type: DataTypes.TEXT, + allowNull: true + }, + EmailTemplateId : { + type: DataTypes.INTEGER + }, + UserId : { + type: DataTypes.INTEGER + }, + AccountId : { + type: DataTypes.INTEGER } + }, { - paranoid: true, instanceMethods: { - toJSON: function () { - return this.values; - } + paranoid: true, + instanceMethods: { + toJSON: function () { + var values = this.values + , userData = {}; + + if ( values.user && values.user.id ) { + userData = { + id: values.user.id, + firstName: values.user.firstname, + lastName: values.user.lastname, + fullName: values.user.fullName, + email: values.user.email, + source: values.user.source + }; + + values.user = userData; + } + + if ( values.dump ) { + values.dump = JSON.parse( JSON.stringify( values.dump ) ); + } + + return values; + } } } ); -}; +}; \ No newline at end of file diff --git a/modules/clever-email/models/orm/EmailReplyModel.js b/modules/clever-email/models/orm/EmailReplyModel.js index afdfd7f..595815c 100644 --- a/modules/clever-email/models/orm/EmailReplyModel.js +++ b/modules/clever-email/models/orm/EmailReplyModel.js @@ -17,13 +17,46 @@ module.exports = function ( sequelize, DataTypes ) { to: { type: DataTypes.TEXT, allowNull: true + }, + token: { + type: DataTypes.STRING, + allowNull: true + }, + isDelivered: { + type: DataTypes.BOOLEAN, + allowNull: false, + default: true + }, + sentAttemps: { + type: DataTypes.INTEGER, + allowNull: false, + default: 0 + }, + isOpened: { + type: DataTypes.BOOLEAN, + allowNull: false, + default: false + }, + dump: { + type: DataTypes.TEXT, + allowNull: true + }, + EmailId: { + type: DataTypes.INTEGER } }, { - paranoid: true, instanceMethods: { - toJSON: function () { - return this.values; - } + paranoid: true, + instanceMethods: { + toJSON: function () { + var values = this.values; + + if ( values.dump ) { + values.dump = JSON.parse( values.dump ); + } + + return value + } } - } ); -}; + } ); +}; \ No newline at end of file diff --git a/modules/clever-email/models/orm/EmailTemplateModel.js b/modules/clever-email/models/orm/EmailTemplateModel.js index 94df239..bd4ef04 100644 --- a/modules/clever-email/models/orm/EmailTemplateModel.js +++ b/modules/clever-email/models/orm/EmailTemplateModel.js @@ -44,8 +44,14 @@ module.exports = function ( sequelize, DataTypes ) { allowNull: false, default: true }, + AccountId: { + type: DataTypes.INTEGER + }, UserId: { type: DataTypes.INTEGER + }, + EmailId: { + type: DataTypes.INTEGER } }, { @@ -62,7 +68,7 @@ module.exports = function ( sequelize, DataTypes ) { for ( i = 0; i < l; i += 1 ) o[arr[i][attr]] = arr[i]; for ( i in o ) r.push( o[i] ); return r; - } + }; if ( values['users'] && values['users'].length ) { values['permittedToUsers'] = uniqueValues( values['users'], 'id' ).map( function ( user ) { @@ -70,6 +76,7 @@ module.exports = function ( sequelize, DataTypes ) { } ); delete values['users']; } + ; if ( values['teams'] && values['teams'].length ) { values['permittedToTeams'] = uniqueValues( values['teams'], 'id' ).map( function ( team ) { @@ -82,4 +89,4 @@ module.exports = function ( sequelize, DataTypes ) { } } } ); -}; +}; \ No newline at end of file diff --git a/modules/clever-email/models/orm/EmailUserModel.js b/modules/clever-email/models/orm/EmailUserModel.js index 86f21b8..1df4ec1 100644 --- a/modules/clever-email/models/orm/EmailUserModel.js +++ b/modules/clever-email/models/orm/EmailUserModel.js @@ -10,13 +10,20 @@ module.exports = function ( sequelize, DataTypes ) { type: DataTypes.ENUM, values: ['cc', 'bcc'], allowNull: false + }, + EmailId: { + type: DataTypes.INTEGER + }, + UserId: { + type: DataTypes.INTEGER } }, { + paranoid: true, instanceMethods: { toJSON: function () { return this.values; } } } ); -}; +}; \ No newline at end of file diff --git a/modules/clever-email/services/EmailService.js b/modules/clever-email/services/EmailService.js index b30cf16..729d7e6 100644 --- a/modules/clever-email/services/EmailService.js +++ b/modules/clever-email/services/EmailService.js @@ -39,15 +39,21 @@ module.exports = function ( sequelize, : 'reply_' + emailToken + '@app-mail.bolthr.com'; return addr; - }, + }, /* tested */ - formatData: function ( data, operation ) { - var o = { email: {}, usersCC: [], usersBCC: [], attachments: [], sender: {} }; - var emailURL = ''; + formatData: function ( data ) { + var o = { + email: {}, + usersCC: [], + usersBCC: [], + attachments: [], + sender: {} + }; - // o['account'] = {}; - // o['account']['logo'] = data.accLogo; - // o['account']['name'] = data.accName; + var hasTemplate = (/false|true/.test( data.hasTemplate )) ? data.hasTemplate : true + , fullName = data.userFirstName || data.userLastName + ? [ data.userFirstName, data.userLastName ].join( ' ' ) + : config['clever-email'].default.fromName; //Email Object o.email.subject = data.subject || null; @@ -55,56 +61,52 @@ module.exports = function ( sequelize, o.email.token = data.userId + data.to.id + shortid.seed( 10000 ).generate(); o.email.UserId = data.userId; o.email.AccountId = data.accId; + o.email.EmailTemplateId = data.EmailTemplateId || null; + o.email.sentAttemps = 0; + o.email.isDelivered = false; + o.email.isOpened = false; + o.email.id = null; - o.hasTemplate = (/false|true/.test( data.hasTemplate )) ? data.hasTemplate : true; - - emailURL = this.formatReplyAddress( o.email.token ); - - // o.sender.fullName = data.userFirstName + ' ' + data.userLastName; - // o.sender.email = emailURL; - - //EmailUsers Object - o.usersCC = ( data.cc && data.cc.length ) ? data.cc : []; - o.usersBCC = ( data.bcc && data.bcc.length) ? data.bcc : []; - - var usersCC = ( data.cc && data.cc.length ) ? data.cc.map( function ( x ) { return x.email } ) : []; - var usersBCC = ( data.bcc && data.bcc.length) ? data.bcc.map( function ( x ) { return x.email } ) : []; + var emailURL = this.formatReplyAddress( o.email.token ); //Dump email dependency data var dataDump = { companyLogo: data.accLogo, companyName: data.accName, - fromName: data.userFirstName + ' ' + data.userLastName, + fromName: fullName, fromMail: emailURL, toMail: data.to.email, - usersCC: usersCC, - usersBCC: usersBCC, - tplName: 'default', - tplTitle: data.subject || 'BoltHR: Autogenerated Notification', - hasTemplate: (/false|true/.test( data.hasTemplate )) ? data.hasTemplate : true + usersCC: ( data.cc && data.cc.length ) ? data.cc.map( function ( x ) { return x.email } ) : [], + usersBCC: ( data.bcc && data.bcc.length) ? data.bcc.map( function ( x ) { return x.email } ) : [], + tplName: config['clever-email'].default.tplName, + tplTitle: data.subject || config['clever-email'].default.subject, + hasTemplate: hasTemplate }; - o['email']['dump'] = JSON.stringify( dataDump ); + o.email.dump = JSON.stringify( dataDump ); + + //EmailUsers Object + o.usersCC = ( data.cc && data.cc.length ) ? data.cc : []; + o.usersBCC = ( data.bcc && data.bcc.length) ? data.bcc : []; //EmailAttachements Object o.attachments = ( data.attachments && data.attachments.length ) ? data.attachments : []; + //EmailSender Object + o.sender.fullName = fullName; + o.sender.email = emailURL; + + //EmailSurvey Object o.survey = ( data.survey ) ? data.survey : null; - if ( operation === 'create' ) { - o.email.UserId = data.userId; - o.email.EmailTemplateId = data.EmailTemplateId || null - o.email.sentAttemps = 0; - o.email.isDelivered = false; - o.email.isOpened = false; - o.email.id = null; - } + o.hasTemplate = hasTemplate; return o; - }, + }, /* tested */ formatRepliedData: function ( data ) { - var replyAddr = this.formatReplyAddress( data['emailToken'] ); + + var replyAddr = this.formatReplyAddress( data.emailToken ); var o = { id: null, @@ -113,30 +115,23 @@ module.exports = function ( sequelize, token: data.emailId + '_' + shortid.seed( 10000 ).generate(), sentAttemps: 1, isDelivered: true, - isOpened: false + isOpened: false, + from: data.from.indexOf( data.userEmail ) != -1 ? data.userEmail : null, + to: data.to }; var t = { subject: data.subject, html: data.replyHTML, - replyAddress: replyAddr + replyAddress: replyAddr, + from: data.from, + to: data.to }; - if ( data.from.indexOf( data[ 'userEmail' ] ) != -1 ) { - - o['from'] = data.userEmail; - t['fromname'] = null; - - } else { - - o['from'] = t['fromname'] = o['to'] = t['toname'] = null; - - } - - o['dump'] = JSON.stringify( t ); + o.dump = JSON.stringify( t ); return o; - }, + }, /* tested */ listEmails: function ( userId ) { var deferred = Q.defer(); @@ -147,9 +142,9 @@ module.exports = function ( sequelize, .fail( deferred.reject ); return deferred.promise; - }, + }, /* tested */ - getEmailById: function ( userId, emailId ) { + getEmailByIds: function ( userId, emailId ) { var deferred = Q.defer() , service = this , chainer = new Sequelize.Utils.QueryChainer(); @@ -161,7 +156,7 @@ module.exports = function ( sequelize, ); chainer.add( - ORMEmailUserModel.find( { + ORMEmailUserModel.findAll( { where: { EmailId: emailId, 'deletedAt': null }, include: [ ORMUserModel ] } ) ); @@ -171,53 +166,51 @@ module.exports = function ( sequelize, .success( function ( results ) { if ( !results[0] ) { - deferred.resolve( {statuscode: '403', message: 'invalid'} ); + deferred.resolve( {statuscode: 403, message: 'invalid'} ); return; } var emailJson = JSON.parse( JSON.stringify( results[ 0 ] ) ); var emailUsers = results[ 1 ]; - emailJson['users'] = emailUsers; + emailJson.users = emailUsers; deferred.resolve( emailJson ); } ) .error( deferred.reject ); return deferred.promise; - }, + }, /* tested */ handleEmailCreation: function ( data ) { - var promises = [] + var deferred = Q.defer() + , promises = [] , service = this; data.forEach( function ( item ) { promises.push( service.processEmailCreation( item ) ); } ); - return Q.all( promises ); - }, + Q.all( promises ) + .then( function() { + deferred.resolve(); + }) + .fail( deferred.reject ); + + return deferred.promise; + }, /* tested */ processEmailCreation: function ( emailItem ) { var deferred = Q.defer() , service = this - , fData = this.formatData( emailItem, 'create' ); + , fData = this.formatData( emailItem ); service .create( fData.email ) .then( function ( savedEmail ) { - console.log( 'Email saved!' ); - service .saveEmailAssociation( savedEmail, fData ) .then( function () { - console.log( 'Email assoc saved!' ); - - if ( !fData.survey ) { - deferred.resolve( {statucode: 200, message: "email has been sent"} ); - return; - } - deferred.resolve(); } ) .then( deferred.resolve ) @@ -226,13 +219,13 @@ module.exports = function ( sequelize, .fail( deferred.reject ); return deferred.promise; - }, + }, /* tested */ saveEmailAssociation: function ( savedEmail, fData ) { var deferred = Q.defer() , chainer = new Sequelize.Utils.QueryChainer(); - //USERS: CC + //Users: CC if ( fData.usersCC.length ) { var l = fData.usersCC.length , item @@ -247,7 +240,7 @@ module.exports = function ( sequelize, chainer.add( ORMEmailUserModel.bulkCreate( cc ) ); } - //USERS: BCC + //Users: BCC if ( fData.usersBCC.length ) { var l = fData.usersBCC.length , itm @@ -272,7 +265,11 @@ module.exports = function ( sequelize, while ( l-- ) { attch = fData.attachments[l]; emailDocs.push( { - id: null, filePath: attch.filePath, fileName: attch.fileName, mimeType: attch.mimeType, EmailId: savedEmail.id + id: null, + filePath: attch.filePath, + fileName: attch.fileName, + mimeType: attch.mimeType, + EmailId: savedEmail.id } ); } @@ -282,32 +279,30 @@ module.exports = function ( sequelize, chainer .run() .success( function ( result ) { - //console.log("RESULT:", JSON.parse(JSON.stringify(result)) ); - deferred.resolve(); + deferred.resolve(result); } ) .error( function ( err ) { - console.log( err ); deferred.reject( err ); } ); return deferred.promise; - }, + }, /* tested */ renderTemplate: function ( data ) { var deferred = Q.defer() - , email = data['email'] - , user = data['user'] || null - , tplName = data['tplName'] || null - , tpl = {}; + , email = data.email + , user = data.user || null + , tplName = data.tplName || null + , tpl = {}; - tpl['tplName'] = tplName || 'default'; - tpl['tplTitle'] = email.subject || 'BoltHR: Autogenerated Notification'; - tpl['companyName'] = ( email.dump.fromCompanyName ) ? email.dump.fromCompanyName : 'BoltHR'; - tpl['companyLogo'] = ( email.dump.fromCompanyLogo ) ? email.dump.fromCompanyLogo : 'http://app.bolthr.com/images/logo.png'; + tpl.tplName = tplName || config['clever-email'].default.tplName; + tpl.tplTitle = email.subject || config['clever-email'].default.subject; + tpl.companyName = ( email.dump.companyName ) ? email.dump.companyName : config['clever-email'].default.fromName; + tpl.companyLogo = ( email.dump.companyLogo ) ? email.dump.companyLogo : config['clever-email'].default.logo; //Text has already being parsed from frontend if ( !email.EmailTemplateId ) { - tpl['strHTML'] = email.body; + tpl.strHTML = email.body; bakeTemplate( tpl ) .then( deferred.resolve ) @@ -379,7 +374,7 @@ module.exports = function ( sequelize, saveMailReply: function ( data ) { var deferred = Q.defer() - , replyData = this.formatRepliedData( data ); + , replyData = this.formatRepliedData( data ); ORMEmailReplyModel .create( replyData ) diff --git a/modules/clever-email/tests/unit/test.service.EmailService.js b/modules/clever-email/tests/unit/test.service.EmailService.js index 544a3be..d119ac7 100644 --- a/modules/clever-email/tests/unit/test.service.EmailService.js +++ b/modules/clever-email/tests/unit/test.service.EmailService.js @@ -3,23 +3,44 @@ var expect = require ( 'chai' ).expect , path = require( 'path' ) , app = require ( path.resolve( __dirname + '/../../../../' ) + '/index.js' ) , config = require( 'config' ) - , testEnv = require ( 'utils' ).testEnv(); + , testEnv = require ( 'utils' ).testEnv() + , Q = require ( 'q' ); describe( 'service.EmailService', function () { - var Service, Model; + var Service, Model, UsrModel, EmailUsrModel; + + var userId, userId_1, userId_2, userId_3, userId_4 + , emailId_1; before( function ( done ) { - testEnv( function ( EmailService, EmailModel ) { + testEnv( function ( EmailService, EmailModel, UserModel, EmailUserModel ) { Service = EmailService; Model = EmailModel; + UsrModel = UserModel; + EmailUsrModel = EmailUserModel; + + var user = { username: 'sender', email: 'sender@mail.ru', password: '1234' }; + + UsrModel + .create( user ) + .success( function( sender ) { + + expect( sender ).to.be.an( 'object' ); + expect( sender ).to.have.property( 'id' ).and.be.ok; + expect( sender ).to.have.property( 'username' ).and.equal( user.username ); + + userId = sender.id; + + done(); + }) + .error( done ); - done(); }, done ); } ); - describe( '.formatReplyAddress', function () { + describe( '.formatReplyAddress( emailToken )', function () { it( 'should return an address according to environmentName', function ( done ) { var emailToken = '15da5AS15A1s'; @@ -41,8 +62,7 @@ describe( 'service.EmailService', function () { } ); - - describe( '.formatData', function () { + describe( '.formatData( data )', function () { it( 'should return an object with filtered data', function ( done ) { var data = { @@ -56,177 +76,460 @@ describe( 'service.EmailService', function () { email: 'to1@email.za' }, hasTemplate: false, - cc: [ 'cc1@mail.ru', 'cc2@mail.ru' ], - bcc: [ 'bcc1@mail.com', 'bcc2@mail.com' ], + cc: [ { email:'cc1@mail.ru' }, { email: 'cc2@mail.ru' } ], + bcc: [ { email: 'bcc1@mail.com' }, { email: 'bcc2@mail.com' } ], accLogo: 'LoGo', accName: 'Default Name', userFirstName: 'Dmitrij', userLastName: 'Safronov', - - permission: 'none', - someattr: 'this attribute is not part of the model' + EmailTemplateId: 45 }; var emailData = Service.formatData( data ); -console.log(emailData.email) -console.log(emailData.usersCC) - expect( emailData ).to.be.an('object'); - expect( emailData ).to.contain.keys('email', 'usersCC', 'usersBCC', 'attachments', 'sender', 'hasTemplate'); - - expect( emailData.email ).to.be.an('object'); - expect( emailData.email ).to.have.property; - -// expect( emailData ).to.have.property( 'title' ).and.equal( data.title ); -// expect( emailData ).to.have.property( 'subject' ).and.equal( data.subject ); -// expect( emailData ).to.have.property( 'body' ).and.equal( data.body ); -// expect( emailData ).to.have.property( 'AccountId' ).and.equal( data.accId ); -// expect( emailData ).to.have.property( 'UserId' ).and.equal( data.userId ); -// expect( emailData ).to.have.property( 'isActive' ).and.equal( false ); -// expect( emailData ).to.have.property( 'isDefault' ).and.equal( false ); -// expect( emailData ).to.have.property( 'useDefault' ).and.equal( true ); -// expect( emailData ).to.have.property( 'hasPermission' ).and.equal( true ); -// -// expect( emailData ).to.not.have.property( 'permission' ); -// expect( emailData ).to.not.have.property( 'someattr' ); + + expect( emailData ).to.be.an( 'object' ); + expect( emailData ).to.contain.keys( 'email', 'usersCC', 'usersBCC', 'attachments', 'survey', 'sender', 'hasTemplate' ); + + expect( emailData.email ).to.be.an( 'object' ); + expect( emailData.email ).to.have.property( 'subject' ).and.equal( data.subject ); + expect( emailData.email ).to.have.property( 'body' ).and.equal( data.body ); + expect( emailData.email ).to.have.property( 'token' ); + expect( emailData.email ).to.have.property( 'UserId' ).and.equal( data.userId ); + expect( emailData.email ).to.have.property( 'AccountId' ).and.equal( data.accId ); + expect( emailData.email ).to.have.property( 'EmailTemplateId' ).and.equal( data.EmailTemplateId ); + expect( emailData.email ).to.have.property( 'sentAttemps' ).and.equal( 0 ); + expect( emailData.email ).to.have.property( 'isDelivered' ).and.equal( false ); + expect( emailData.email ).to.have.property( 'isOpened' ).and.equal( false ); + expect( emailData.email ).to.have.property( 'id' ).and.not.ok; + + expect( emailData.email ).to.have.property( 'dump' ); + + var dump = JSON.parse( emailData.email.dump ); + + expect( dump ).to.have.property( 'companyLogo' ).and.equal( data.accLogo ); + expect( dump ).to.have.property( 'companyName' ).and.equal( data.accName ); + expect( dump ).to.have.property( 'fromName' ).and.equal( [ data.userFirstName, data.userLastName ].join( ' ' ) ); + expect( dump ).to.have.property( 'fromMail' ).and.be.ok; + expect( dump ).to.have.property( 'toMail' ).and.equal( data.to.email ); + expect( dump ).to.have.property( 'usersCC' ).that.that.is.an( 'array' ); + expect( dump ).to.have.property( 'usersBCC' ).that.that.is.an( 'array' ); + expect( dump ).to.have.property( 'tplName' ).and.equal( 'default' ); + expect( dump ).to.have.property( 'tplTitle' ).and.equal( data.subject ); + expect( dump ).to.have.property( 'hasTemplate' ).and.equal( data.hasTemplate ); + + expect( dump.usersCC ).to.have.length( data.cc.length ); + expect( dump.usersCC[0] ).to.equal( data.cc[0].email ); + expect( dump.usersCC[1] ).to.equal( data.cc[1].email ); + + expect( dump.usersBCC ).to.have.length( data.bcc.length ); + expect( dump.usersBCC[0] ).to.equal( data.bcc[0].email ); + expect( dump.usersBCC[1] ).to.equal( data.bcc[1].email ); + + expect( emailData.usersCC ).to.be.an( 'array' ); + expect( emailData.usersCC ).to.have.length( data.cc.length ); + expect( emailData.usersCC[0] ).to.be.an( 'object' ); + expect( emailData.usersCC[0] ).to.have.property( 'email' ).and.equal( data.cc[0].email ); + + expect( emailData.usersBCC ).to.be.an( 'array' ); + expect( emailData.usersBCC ).to.have.length( data.bcc.length ); + expect( emailData.usersBCC[0] ).to.be.an( 'object' ); + expect( emailData.usersBCC[0] ).to.have.property( 'email' ).and.equal( data.bcc[0].email ); + + expect( emailData.attachments ).to.be.an( 'array' ); + expect( emailData.attachments ).to.be.empty; + + expect( emailData.sender ).to.be.an( 'object' ); + expect( emailData.sender ).to.have.property( 'fullName' ).and.equal( [ data.userFirstName, data.userLastName ].join( ' ' ) ); + expect( emailData.sender ).to.have.property( 'email' ).and.be.ok; + + expect( emailData.survey ).to.not.be.ok; + + expect( emailData.hasTemplate ).to.equal( data.hasTemplate ); + + done(); + } ); + + } ); + + describe( '.formatRepliedData( data )', function () { + it( 'should return an object with filtered data', function ( done ) { + + var data = { + emailToken: '25asd151s6dasd', + reply: 'test reply text', + emailId: 48, + subject: 'some subject', + replyHTML: 'html reply', + userEmail: 'user@mail.com', + from: 'Bob ', + to: 'BoltHR ' + }; + + var replyData = Service.formatRepliedData( data ); + + expect( replyData ).to.be.an( 'object' ); + expect( replyData ).to.contain.keys( 'id', 'reply', 'from', 'to', 'token', 'isDelivered', 'sentAttemps', 'isOpened', 'dump' ); + + expect( replyData ).to.have.property( 'id' ).and.is.not.ok; + expect( replyData ).to.have.property( 'reply' ).and.equal( data.reply ); + expect( replyData ).to.have.property( 'from' ).and.equal( data.userEmail ); + expect( replyData ).to.have.property( 'to' ).and.equal( data.to ); + expect( replyData ).to.have.property( 'token' ).and.is.ok; + expect( replyData ).to.have.property( 'isDelivered' ).and.equal( true ); + expect( replyData ).to.have.property( 'sentAttemps' ).and.equal( 1 ); + expect( replyData ).to.have.property( 'isOpened' ).and.equal( false ); + expect( replyData ).to.have.property( 'dump' ); + + var dump = JSON.parse( replyData.dump ); + + expect( dump ).to.have.property( 'subject' ).and.equal( data.subject ); + expect( dump ).to.have.property( 'html' ).and.equal( data.replyHTML ); + expect( dump ).to.have.property( 'replyAddress' ).and.is.ok; + expect( dump ).to.have.property( 'from' ).and.equal( data.from ); + expect( dump ).to.have.property( 'to' ).and.equal( data.to ); done(); } ); } ); -// describe( '.handleEmailTemplateUpdate', function () { -// -// it( 'should return code 401 and a message if email template does not exist ', function ( done ) { -// var data = { id: 1233444444444444444444 }; -// -// EmailTemplateService -// .handleEmailTemplateUpdate( user.AccountId, user.id, data ) -// .then( function ( msg ) { -// -// msg.should.be.a( 'object' ); -// msg.should.have.property( 'statuscode', 401 ); -// msg.should.have.property( 'message' ).and.not.be.empty; -// -// done(); -// -// } ) -// .fail( done ); -// -// } ); -// -// it( 'should call .updateEmailTemplate ', function ( done ) { -// -// -// var emailTemplateData = { -// title: 'schedule interview', subject: 'congradulations! we would love to meet you', body: 'Dear John Appleseed,
we are interest by your skill set your qualifications for the job', useDefault: 'false', permission: 'none', isActive: true, useDefault: false, isDefault: false, AccountId: user.AccountId, UserId: user.id -// }; -// -// var spyUpdateEmailTemplate = sinon.spy( EmailTemplateService, 'updateEmailTemplate' ); -// -// EmailTempalteModel -// .create( emailTemplateData ) -// .success( function ( emailTemplate ) { -// -// emailTemplate.should.have.property( 'id' ); -// emailTemplate.should.have.property( 'title', 'schedule interview' ); -// -// EmailTemplateService -// .handleEmailTemplateUpdate( user.AccountId, user.id, emailTemplate ) -// .then( function () { -// -// spyUpdateEmailTemplate.called.should.be.true; -// done(); -// -// } ) -// .fail( done ); -// } ) -// .error( done ); -// } ); -// } ); -// -// describe( '.updateEmailTemplate', function () { -// -// it( 'should update an email Template record', function ( done ) { -// -// var emailTemplateData = { -// title: 'schedule interview', subject: 'congradulations! we would love to meet you', body: 'Dear John Appleseed,
we are interest by your skill set your qualifications for the job', useDefault: 'false', permission: 'none', isActive: true, useDefault: false, isDefault: false, AccountId: user.AccountId, UserId: user.id -// }; -// -// var updatedData = { -// id: null, title: 'This is an updated title', subject: 'congradulations! we would love to meet you', body: 'Dear John Appleseed,
we are interest by your skill set your qualifications for the job', useDefault: 'false', permission: 'none', isActive: true, useDefault: false, isDefault: false -// }; -// -// -// EmailTempalteModel -// .create( emailTemplateData ) -// .success( function ( emailTemplate ) { -// -// emailTemplate.should.have.property( 'title', 'schedule interview' ); -// updatedData['id'] = emailTemplate.id; -// -// EmailTemplateService -// .updateEmailTemplate( emailTemplate, updatedData ) -// .then( function ( updatedEmailTpl ) { -// -// updatedEmailTpl.id.should.equal( emailTemplate.id ); -// updatedEmailTpl.title.should.not.equal( emailTemplateData.title ); -// updatedEmailTpl.title.should.equal( updatedData.title ); -// -// done(); -// } ) -// .fail( done ); -// } ) -// .error( done ); -// } ); -// } ); -// -// describe( '.removeEmailTemplate', function () { -// -// it( 'should remove an email template record ', function ( done ) { -// -// var emailTemplateData = { -// title: 'schedule interview', subject: 'congradulations! we would love to meet you', body: 'Dear John Appleseed,
we are interest by your skill set your qualifications for the job', useDefault: 'false', permission: 'none', isActive: true, useDefault: false, isDefault: false, AccountId: user.AccountId, UserId: user.id -// }; -// -// -// EmailTempalteModel -// .create( emailTemplateData ) -// .success( function ( emailTemplate ) { -// -// emailTemplate.should.have.property( 'id' ); -// emailTemplate.should.have.property( 'title', 'schedule interview' ); -// -// EmailTemplateService -// .removeEmailTemplate( user.id, emailTemplate.id ) -// .then( function ( msg ) { -// -// msg.should.be.a( 'object' ); -// msg.should.have.property( 'statuscode', 200 ); -// msg.should.have.property( 'message' ).and.not.be.empty; -// -// done(); -// -// } ) -// .fail( done ); -// } ) -// .error( done ); -// } ); -// -// it( 'should return code 400 and a message if email template does not exist ', function ( done ) { -// var tplId = '1233333333333333333333'; -// -// EmailTemplateService -// .removeEmailTemplate( user.id, tplId ) -// .then( function ( msg ) { -// -// msg.should.be.a( 'object' ); -// msg.should.have.property( 'statuscode', 400 ); -// msg.should.have.property( 'message' ).and.not.be.empty; -// -// done(); -// -// } ) -// .fail( done ); -// } ); -// } ); + describe( '.saveEmailAssociation( savedEmail, fData )', function () { + + before( function ( done ) { + var users = [] + , promise = []; + users[0] = { username: 'pavlick', email: 'pavlicl@mail.ru', password: '1234' }; + users[1] = { username: 'mishka', email: 'mishka@mail.ru', password: '1234' }; + users[2] = { username: 'vovka', email: 'vovka@mail.ru', password: '1234' }; + users[3] = { username: 'petka', email: 'petka@mail.ru', password: '1234' }; + + users.forEach( function( user ) { + promise.push( UsrModel.create( user ) ) + }); + + Q.all( promise ) + .then( function( users ) { + expect( users ).to.be.an( 'array' ).and.have.length( 4 ); + expect( users[0] ).to.be.an( 'object' ); + expect( users[0] ).to.have.property( 'id' ).and.be.ok; + expect( users[0] ).to.have.property( 'username' ).and.equal( users[0].username ); + expect( users[1] ).to.be.an( 'object' ); + expect( users[1] ).to.have.property( 'id' ).and.be.ok; + expect( users[1] ).to.have.property( 'username' ).and.equal( users[1].username ); + expect( users[2] ).to.be.an( 'object' ); + expect( users[2] ).to.have.property( 'id' ).and.be.ok; + expect( users[2] ).to.have.property( 'username' ).and.equal( users[2].username ); + expect( users[3] ).to.be.an( 'object' ); + expect( users[3] ).to.have.property( 'id' ).and.be.ok; + expect( users[3] ).to.have.property( 'username' ).and.equal( users[3].username ); + + userId_1 = users[0].id; + userId_2 = users[1].id; + userId_3 = users[2].id; + userId_4 = users[3].id; + + done(); + }) + .fail( done ); + + } ); + + it( 'should be able to save email associations', function ( done ) { + var data = { + title: 'some title', + subject: 'some subject', + body: 'some body', + userId: userId, + accId: 1, + to: { + id: 15, + email: 'to1@email.za' + }, + hasTemplate: false, + cc: [ { id: userId_1, email:'cc1@mail.ru' }, { id: userId_2, email: 'cc2@mail.ru' } ], + bcc: [ { id: userId_3, email: 'bcc1@mail.com' }, { id: userId_4, email: 'bcc2@mail.com' } ], + accLogo: 'LoGo', + accName: 'Default Name', + userFirstName: 'Dmitrij', + userLastName: 'Safronov', + EmailTemplateId: 45 + }; + + var emailData = Service.formatData( data ); + + Model + .create( emailData.email ) + .success( function( email ) { + + expect( email ).to.be.an( 'object' ); + expect( email ).to.have.property( 'id' ).and.be.ok; + expect( email ).to.have.property( 'subject' ).and.equal( data.subject ); + + emailId_1 = email.id; + + Service + .saveEmailAssociation( email, emailData ) + .then( function( res ) { + + EmailUsrModel + .findAll( { where: { EmailId: email.id, UserId: userId_1 } } ) + .success( function( res ) { + + expect( res ).to.be.an( 'array' ).and.have.length( 1 ); + expect( res[0] ).to.be.an( 'object' ); + expect( res[0] ).to.have.property( 'id' ).and.be.ok; + expect( res[0] ).to.have.property( 'status' ).and.equal( 'cc' ); + + EmailUsrModel + .findAll( { where: { EmailId: email.id, UserId: userId_4 } } ) + .success( function( res ) { + + expect( res ).to.be.an( 'array' ).and.have.length( 1 ); + expect( res[0] ).to.be.an( 'object' ); + expect( res[0] ).to.have.property( 'id' ).and.be.ok; + expect( res[0] ).to.have.property( 'status' ).and.equal( 'bcc' ); + + done(); + }) + .error( done ); + }) + .error( done ); + }) + .fail( done ) + + }) + } ); + + } ); + + describe( '.processEmailCreation( emailItem )', function () { + + it( 'should be able to create email with associations', function ( done ) { + var data = { + title: 'some title #1', + subject: 'some subject #1', + body: 'some body #1', + userId: userId, + accId: 1, + to: { + id: 15, + email: 'to1@email.za' + }, + hasTemplate: false, + cc: [ { id: userId_1, email:'cc1@mail.ru' }, { id: userId_2, email: 'cc2@mail.ru' } ], + bcc: [ { id: userId_3, email: 'bcc1@mail.com' }, { id: userId_4, email: 'bcc2@mail.com' } ], + accLogo: 'LoGo', + accName: 'Default Name', + userFirstName: 'Dmitrij', + userLastName: 'Safronov', + EmailTemplateId: 45 + }; + + Service + .processEmailCreation( data ) + .then( function() { + + Model + .find( { where: { subject: data.subject, body: data.body } } ) + .success( function( email ) { + + expect( email ).to.be.an( 'object' ); + expect( email ).to.have.property( 'id' ).and.be.ok; + expect( email ).to.have.property( 'subject' ).and.equal( data.subject ); + + EmailUsrModel + .findAll( { where: { EmailId: email.id, UserId: userId_1 } } ) + .success( function( res ) { + + expect( res ).to.be.an( 'array' ).and.have.length( 1 ); + expect( res[0] ).to.be.an( 'object' ); + expect( res[0] ).to.have.property( 'id' ).and.be.ok; + expect( res[0] ).to.have.property( 'status' ).and.equal( 'cc' ); + + EmailUsrModel + .findAll( { where: { EmailId: email.id, UserId: userId_4 } } ) + .success( function( res ) { + + expect( res ).to.be.an( 'array' ).and.have.length( 1 ); + expect( res[0] ).to.be.an( 'object' ); + expect( res[0] ).to.have.property( 'id' ).and.be.ok; + expect( res[0] ).to.have.property( 'status' ).and.equal( 'bcc' ); + + done(); + }) + .error( done ); + }) + .error( done ); + }) + .error( done ); + }, done ) + + } ); + + } ); + + describe( '.handleEmailCreation( data )', function () { + + it( 'should be able to create emails with associations', function ( done ) { + var email_1 = { + title: 'some title #2', + subject: 'some subject #2', + body: 'some body #2', + userId: userId, + accId: 1, + to: { + id: 15, + email: 'to1@email.za' + }, + hasTemplate: false, + cc: [ { id: userId_1, email:'cc1@mail.ru' }, { id: userId, email: 'cc2@mail.ru' } ], + bcc: [ { id: userId_3, email: 'bcc1@mail.com' }, { id: userId_4, email: 'bcc2@mail.com' } ], + accLogo: 'LoGo', + accName: 'Default Name', + userFirstName: 'Dmitrij', + userLastName: 'Safronov', + EmailTemplateId: 45 + }; + + var email_2 = { + title: 'some title #3', + subject: 'some subject #3', + body: 'some body #3', + userId: 3, + accId: 1, + to: { + id: 15, + email: 'to1@email.za' + }, + hasTemplate: false, + cc: [ { id: userId_1, email:'cc1@mail.ru' }, { id: userId_2, email: 'cc2@mail.ru' } ], + bcc: [ { id: userId_3, email: 'bcc1@mail.com' }, { id: userId_4, email: 'bcc2@mail.com' } ], + accLogo: 'LoGo', + accName: 'Default Name', + userFirstName: 'Dmitrij', + userLastName: 'Safronov', + EmailTemplateId: 45 + }; + + Service + .handleEmailCreation( [ email_1, email_2 ] ) + .then( function() { + + var promise = [ + Model.find( { where: { subject: email_1.subject, body: email_1.body } } ), + Model.find( { where: { subject: email_2.subject, body: email_2.body } } ) + ]; + + Q.all( promise ) + .then( function( emails ) { + + expect( emails ).to.be.an( 'array' ).and.have.length( 2 ); + + expect( emails[0] ).to.be.an( 'object' ); + expect( emails[0] ).to.have.property( 'id' ).and.be.ok; + expect( emails[0] ).to.have.property( 'subject' ).and.equal( email_1.subject ); + + expect( emails[1] ).to.be.an( 'object' ); + expect( emails[1] ).to.have.property( 'id' ).and.be.ok; + expect( emails[1] ).to.have.property( 'subject' ).and.equal( email_2.subject ); + + done(); + + }, done ); + }, done ) + } ); + + } ); + + describe( '.getEmailByIds( userId, emailId )', function () { + + it( 'should be able to find email bu emailId for user with userId', function ( done ) { + + Service + .getEmailByIds( userId, emailId_1 ) + .then( function( result ) { + + expect( result ).to.be.an( 'object' ); + expect( result ).to.contain.keys( 'emailReplies', 'emailAttachments' ); + + expect( result ).to.have.property( 'id' ).and.equal( emailId_1 ); + expect( result ).to.have.property( 'subject' ).and.equal( 'some subject' ); + expect( result ).to.have.property( 'body' ).and.equal( 'some body' ); + expect( result ).to.have.property( 'isDelivered' ).and.equal( false ); + expect( result ).to.have.property( 'sentAttemps' ).and.equal( 0 ); + expect( result ).to.have.property( 'isOpened' ).and.equal( false ); + expect( result ).to.have.property( 'token' ).and.be.ok; + expect( result ).to.have.property( 'EmailTemplateId' ).and.equal( 45 ); + expect( result ).to.have.property( 'UserId' ).and.equal( userId ); + expect( result ).to.have.property( 'AccountId' ).and.equal( 1 ); + expect( result ).to.have.property( 'dump' ).and.be.ok; + expect( result.emailReplies ).to.be.an( 'array' ).and.be.empty; + expect( result.emailAttachments ).to.be.an( 'array' ).and.be.empty; + + expect( result ).to.have.property( 'users' ).to.be.an( 'array' ).and.have.length( 4 ); + expect( result.users[0] ).to.have.property( 'id' ).and.be.ok; + expect( result.users[0] ).to.have.property( 'status' ).and.be.ok; + expect( result.users[0] ).to.have.property( 'UserId' ).and.be.ok; + expect( result.users[0] ).to.have.property( 'EmailId' ).and.equal( emailId_1 ); + expect( result.users[0].user ).to.be.an( 'object' ).and.not.be.empty; + + done(); + }, done ) + } ); + + it( 'should be able to get the error if the emailId does not exist', function ( done ) { + + Service + .getEmailByIds( userId, 100000 ) + .then( function( result ) { + + expect( result ).to.be.an( 'object' ); + expect( result ).to.have.property( 'statuscode' ).and.equal( 403 ); + expect( result ).to.have.property( 'message' ).and.be.ok; + + done(); + }, done ) + } ); + + it( 'should be able to get the error if email does not belong to the user', function ( done ) { + + Service + .getEmailByIds( userId_1, emailId_1 ) + .then( function( result ) { + + expect( result ).to.be.an( 'object' ); + expect( result ).to.have.property( 'statuscode' ).and.equal( 403 ); + expect( result ).to.have.property( 'message' ).and.be.ok; + + done(); + }, done ) + } ); + + } ); + + describe( '.listEmails( userId )', function () { + + it( 'should be able to find all emails by userId', function ( done ) { + + Service + .listEmails( userId ) + .then( function( result ) { + + expect( result ).to.be.an( 'array' ).and.not.empty; + expect( result ).to.have.length.above( 1 ); + + expect( result[0] ).to.be.an( 'object' ); + expect( result[0] ).to.have.property( 'id' ).and.be.ok; + expect( result[0] ).to.have.property( 'subject' ).and.be.ok; + expect( result[0] ).to.have.property( 'body' ).and.be.ok; + expect( result[0] ).to.have.property( 'UserId' ).and.equal( userId ); + expect( result[0] ).to.have.property( 'emailReplies' ).and.be.an( 'array' ).and.be.empty; + expect( result[0] ).to.have.property( 'emailAttachments' ).and.be.an( 'array' ).and.be.empty; + + done(); + }, done ) + } ); + + } ); + } ); \ No newline at end of file diff --git a/modules/orm/config/default.json b/modules/orm/config/default.json index 64dedb0..8d4759b 100644 --- a/modules/orm/config/default.json +++ b/modules/orm/config/default.json @@ -11,31 +11,31 @@ } }, "modelAssociations": { - "AccountModel": { - "hasMany": [ "UserModel", "EmailModel", "EmailTemplateModel" ] + "AccountModel": { + "hasMany": [ "EmailTemplateModel" ] }, "UserModel": { - "belongsTo": [ "AccountModel" ] + "belongsTo": [ "AccountModel" ], + "hasMany" : [ "EmailTemplateModel" ] }, - "ExampleModel": {}, "EmailTemplateModel": { - "hasMany": [ "UserModel" ], + "hasMany": [ "UserModel", "EmailModel" ], "belongsTo": [ "AccountModel", "UserModel" ] }, "EmailModel": { - "hasMany": [ "EmailAttachmentModel", "EmailReplyModel" ], - "belongsTo": [ "UserModel", "AccountModel" ] + "hasMany": [ "EmailAttachmentModel", "EmailReplyModel" ], + "belongsTo": [ "UserModel", "AccountModel", "EmailTemplateModel" ] }, - "EmailUserModel": { - "belongsTo": [ "EmailModel", "UserModel" ] + "EmailUserModel":{ + "belongsTo" : [ "EmailModel", "UserModel" ] }, "EmailAttachmentModel": { "belongsTo": [ "EmailModel" ] }, "EmailReplyModel": { "belongsTo": [ "EmailModel" ] - } - + }, + "ExampleModel": {} } } From 6d78a3628e62b404787729415e1a73a74c1d6bb0 Mon Sep 17 00:00:00 2001 From: Volodymyr Denshchykov Date: Thu, 23 Jan 2014 19:08:45 +0200 Subject: [PATCH 05/24] finished testing for clever-email module --- modules/clever-email/config/default.json | 5 +- .../controllers/EmailController.js | 45 +- .../controllers/EmailTemplateController.js | 154 ------ modules/clever-email/lib/ejsfilerender.js | 25 - modules/clever-email/lib/mailgun.js | 19 +- modules/clever-email/lib/mandrill.js | 25 +- modules/clever-email/lib/sendgrid.js | 15 +- .../mail-template/accountActivation.ejs | 15 - .../mail-template/passwordRecovery.ejs | 17 - .../models/orm/EmailReplyModel.js | 62 --- .../models/orm/EmailTemplateModel.js | 92 ---- modules/clever-email/routes.js | 29 +- modules/clever-email/schema/seedData.json | 265 +--------- modules/clever-email/services/EmailService.js | 211 ++------ .../services/EmailTemplateService.js | 332 ------------ .../test.controller.EmailAlertController.js | 461 ----------------- .../unit/test.controllers.EmailController.js | 481 ++++++++++++++++++ .../tests/unit/test.service.EmailService.js | 258 +++++++--- .../unit/test.service.EmailTemplateService.js | 192 ------- modules/orm/config/default.json | 18 +- 20 files changed, 798 insertions(+), 1923 deletions(-) delete mode 100644 modules/clever-email/controllers/EmailTemplateController.js delete mode 100644 modules/clever-email/lib/ejsfilerender.js delete mode 100644 modules/clever-email/mail-template/accountActivation.ejs delete mode 100644 modules/clever-email/mail-template/passwordRecovery.ejs delete mode 100644 modules/clever-email/models/orm/EmailReplyModel.js delete mode 100644 modules/clever-email/models/orm/EmailTemplateModel.js delete mode 100644 modules/clever-email/services/EmailTemplateService.js delete mode 100644 modules/clever-email/tests/temp/test.controller.EmailAlertController.js create mode 100644 modules/clever-email/tests/unit/test.controllers.EmailController.js delete mode 100644 modules/clever-email/tests/unit/test.service.EmailTemplateService.js diff --git a/modules/clever-email/config/default.json b/modules/clever-email/config/default.json index 436a679..d87484f 100644 --- a/modules/clever-email/config/default.json +++ b/modules/clever-email/config/default.json @@ -2,13 +2,12 @@ "clever-email": { "cc": true, "bcc": true, - "text": true, + "text": false, "systems": { "Mandrill": { "isActive": true, - "apiKey": "j8Gzc7vRpJ36FabmOsbk6g", - "ipPool": "Example Pool", + "apiKey": "anyaWGoRNAFaQQW79MwqAA", "async": false }, "SendGrid": { diff --git a/modules/clever-email/controllers/EmailController.js b/modules/clever-email/controllers/EmailController.js index cf7210f..133298f 100644 --- a/modules/clever-email/controllers/EmailController.js +++ b/modules/clever-email/controllers/EmailController.js @@ -1,23 +1,8 @@ module.exports = function ( EmailService ) { - return (require( 'classes' ).Controller).extend( { - service: null, - - checkEventMailData: function ( req, res, next ) { - var data = req.body - , fltData = []; - //console.log("\n *** SendGrid Event Data *** \n",data); - - var item; - while ( item = data.pop() ) { - if ( ( item.event == 'open' ) && item.email_id ) { - fltData.push( item ); - } - } - console.log( "\n\nReceived Event Notification POST from Sendgrid: " ); - req.body = fltData; - next(); - } + return (require( 'classes' ).Controller).extend( + { + service: null }, /* @Prototype */ { @@ -33,7 +18,7 @@ module.exports = function ( EmailService ) { getAction: function () { var userId = this.req.user.id - , emailId = this.req.params.id; + , emailId = this.req.params.id; EmailService .getEmailByIds( userId, emailId ) @@ -55,8 +40,8 @@ module.exports = function ( EmailService ) { x.accId = accId; x.userFirstName = userFirstName; x.userLastName = userLastName; - x['accLogo'] = accLogo; - x['accName'] = accName; + x.accLogo = accLogo; + x.accName = accName; return x; } ); @@ -67,18 +52,26 @@ module.exports = function ( EmailService ) { }, putAction: function () { - this.send( 403, 'invalid' ); + this.send( 'invalid', 403 ); }, deleteAction: function () { - this.send( 403, 'invalid' ); + var userId = this.req.user.id + , emailId = this.req.params.id; + + EmailService + .deleteEmail( userId, emailId ) + .then( this.proxy( 'handleServiceMessage' ) ) + .fail( this.proxy( 'handleException' ) ); }, - eventsMailAction: function () { - var data = this.req.body; + sendAction: function () { + var userId = this.req.user.id + , emailId = this.req.params.id + , type = this.req.body.type; EmailService - .processMailEvents( data ) + .handleEmailSending( userId, emailId, type ) .then( this.proxy( 'handleServiceMessage' ) ) .fail( this.proxy( 'handleException' ) ); diff --git a/modules/clever-email/controllers/EmailTemplateController.js b/modules/clever-email/controllers/EmailTemplateController.js deleted file mode 100644 index 2993346..0000000 --- a/modules/clever-email/controllers/EmailTemplateController.js +++ /dev/null @@ -1,154 +0,0 @@ -module.exports = function ( EmailTemplateService ) { - - return (require( 'classes' ).Controller).extend( - { - service: EmailTemplateService, - checkRequiredFields: function ( req, res, next ) { - var data = req.body; - - if ( !data || !data.title || !data.subject || !data.body ) { - res.json( 400, 'Please fill required fields' ); - return; - } - - next(); - } - }, - /* @Prototype */ - { - - listAction: function () { - var accId = this.req.user.account.id - , userId = this.req.user.id - , teamId = this.req.user.TeamId - , roleName = this.req.user.role.name; - - EmailTemplateService - .listTemplates( accId, userId, teamId, roleName ) - .then( this.proxy( 'handleServiceMessage' ) ) - .fail( this.proxy( 'handleException' ) ); - }, - - getAction: function () { - var userId = this.req.user.id - , accId = this.req.user.account.id - , teamId = this.req.user.TeamId - , tplId = this.req.params.id - , roleName = this.req.user.role.name; - - EmailTemplateService - .getTemplateById( accId, userId, teamId, tplId, roleName ) - .then( this.proxy( 'handleServiceMessage' ) ) - .fail( this.proxy( 'handleException' ) ); - }, - - postAction: function () { - var accId = this.req.user.account.id - , userId = this.req.user.id - , data = this.req.body; - - if ( data.id ) { - this.putAction(); - return; - } - - data['accId'] = accId; - data['userId'] = userId; - - EmailTemplateService - .createEmailTemplate( data ) - .then( this.proxy( 'handleEmailTemplateAssoc', data ) ) - .fail( this.proxy( 'handleException' ) ); - }, - - handleEmailTemplateAssoc: function ( dataWithAssocs, savedEmailTpl ) { - - if ( savedEmailTpl['statuscode'] != undefined ) { - this.handleServiceMessage( savedEmailTpl ); - return; - } - - EmailTemplateService - .processEmailTemplateAssoc( dataWithAssocs, savedEmailTpl ) - .then( this.proxy( 'handleServiceMessage' ) ) - .fail( this.proxy( 'handleException' ) ); - }, - - putAction: function () { - var accId = this.req.user.account.id - , userId = this.req.user.id - , data = this.req.body; - - if ( !data.id ) { - this.send( 'invalid id', 401 ); - return; - } - - data['accId'] = accId; - data['userId'] = userId; - - EmailTemplateService - .handleEmailTemplateUpdate( data ) - .then( this.proxy( 'handleEmailTemplateAssoc', data ) ) - .fail( this.proxy( 'handleException' ) ); - }, - - previewAction: function () { - var accId = this.req.user.account.id - , tplId = this.req.params.id - , data = {}; - - if ( !tplId ) { - this.send( 403, 'Invalid Template ID' ); - return; - } - - data.accId = accId; - data.template_id = tplId; - data.prospect_id = this.req.query.prospect_id || null; - data.job_id = this.req.query.job_id || null; - - EmailTemplateService - .getPlaceholderData( data ) - .then( this.proxy( 'handleTemplateInterpolation' ) ) - .fail( this.proxy( 'handleException' ) ); - - }, - - handleTemplateInterpolation: function ( data ) { - var user = this.req.user; - - if ( !data ) { - this.send( 403, 'Invalid Template ID' ); - return; - } - - EmailTemplateService - .processTemplateIntrpolation( data, user ) - .then( function ( html ) { - this.render( 'preview', { strHTML: html, tplTitle: 'Email Template Preview' } ); - }.bind( this ) ) - .fail( this.proxy( 'handleException' ) ); - }, - - deleteAction: function () { - var userId = this.req.user.id - , tplId = this.req.params.id; - - EmailTemplateService - .removeEmailTemplate( userId, tplId ) - .then( this.proxy( 'handleServiceMessage' ) ) - .fail( this.proxy( 'handleException' ) ); - }, - - handleServiceMessage: function ( obj ) { - - if ( obj.statuscode ) { - this.send( obj.message, obj.statuscode ); - return; - } - - this.send( obj, 200 ); - } - } ); -}; \ No newline at end of file diff --git a/modules/clever-email/lib/ejsfilerender.js b/modules/clever-email/lib/ejsfilerender.js deleted file mode 100644 index bb3bcfa..0000000 --- a/modules/clever-email/lib/ejsfilerender.js +++ /dev/null @@ -1,25 +0,0 @@ -var path = require( 'path' ) - , Q = require( 'q' ) - , ejs = require( 'ejs' ) - , approot = path.resolve( __dirname + '../../../' ); - -module.exports = function ( tpls ) { - - return function ( prop, data ) { - var deferred = Q.defer(); - - ejs.renderFile( approot + '/' + tpls[ prop ], data, function ( err, html ) { - - if ( err ) { - deferred.reject( err ); - return; - } - - deferred.resolve( html ); - - } ); - - return deferred.promise; - } - -}; \ No newline at end of file diff --git a/modules/clever-email/lib/mailgun.js b/modules/clever-email/lib/mailgun.js index 36f22c5..f91c1fe 100644 --- a/modules/clever-email/lib/mailgun.js +++ b/modules/clever-email/lib/mailgun.js @@ -1,19 +1,20 @@ module.exports = function ( config ) { var Q = require( 'q' ) + , _ = require( 'lodash' ) , defConf = config.default , confMailgun = config.systems.MailGun , mailgun = require( 'mailgun-js' )( confMailgun.apiKey, confMailgun.domain ); return { - send: function ( email, html, text ) { + send: function ( email, body, type ) { var deferred = Q.defer(); - var message = this.createMessage( email, html, text ); + var message = this.createMessage( email, body, type ); mailgun .messages - .send( message, function ( err, response, body ) { + .send( message, function ( err, res, body ) { if ( err ) { console.log( "MailGun Error: ", err.toString() ); @@ -21,17 +22,17 @@ module.exports = function ( config ) { return; } - deferred.resolve( response ); + deferred.resolve( _.map( res, function( x ) { return { status: x.message, id: x.id }; }) ); } ); return deferred.promise; }, - createMessage: function( email, html, text ){ + createMessage: function( email, body, type ){ var fromMail = defConf.from , fromName = defConf.fromName; - if ( email.dump.fromCompanyName ) { + if ( email.dump.companyName ) { fromName = email.dump.fromName; fromMail = email.dump.fromMail; } @@ -43,10 +44,10 @@ module.exports = function ( config ) { emailId: email.id }; - if ( config.text && !!text ){ - message.text = text; + if ( config.text && type === "text" ){ + message.text = body; } else { - message.html = html; + message.html = body; } if ( config.cc && email.dump.usersCC && email.dump.usersCC.length ) { diff --git a/modules/clever-email/lib/mandrill.js b/modules/clever-email/lib/mandrill.js index a7bb4a2..f56c2e6 100644 --- a/modules/clever-email/lib/mandrill.js +++ b/modules/clever-email/lib/mandrill.js @@ -1,27 +1,24 @@ module.exports = function ( config ) { var Q = require( 'q' ) + , _ = require( 'lodash' ) , mandrill = require( 'mandrill-api/mandrill' ) , defConf = config.default , confMandrill = config.systems.Mandrill , mandrill_client = new mandrill.Mandrill( confMandrill.apiKey ); return { - send: function ( email, html, text ) { + send: function ( email, body, type ) { var deferred = Q.defer(); - var message = this.createMessage( email, html, text ) - , async = confMandrill.async - , ip_pool = confMandrill.ipPool; + var message = this.createMessage( email, body, type ) + , async = confMandrill.async; mandrill_client .messages - .send( { - "message": message, - "async": async, - "ip_pool": ip_pool }, function ( result ) { + .send( { "message": message, "async": async }, function ( result ) { - deferred.resolve( result ); + deferred.resolve( _.map( result, function( x ) { return { status: x.status, id: x._id }; }) ); }, function ( err ) { @@ -34,11 +31,11 @@ module.exports = function ( config ) { return deferred.promise; }, - createMessage: function( email, html, text ){ + createMessage: function( email, body, type ){ var fromMail = defConf.from , fromName = defConf.fromName; - if ( email.dump.fromCompanyName ) { + if ( email.dump.companyName ) { fromName = email.dump.fromName; fromMail = email.dump.fromMail; } @@ -51,10 +48,10 @@ module.exports = function ( config ) { emailId: email.id }; - if ( config.text && !!text ){ - message.text = text; + if ( config.text && type === "text" ){ + message.text = body; } else { - message.html = html; + message.html = body; } if ( config.cc && email.dump.usersCC && email.dump.usersCC.length ) { diff --git a/modules/clever-email/lib/sendgrid.js b/modules/clever-email/lib/sendgrid.js index 63f50d6..cf25727 100644 --- a/modules/clever-email/lib/sendgrid.js +++ b/modules/clever-email/lib/sendgrid.js @@ -1,15 +1,16 @@ module.exports = function ( config ) { var Q = require( 'q' ) + , _ = require( 'lodash' ) , defConf = config.default , confSendGrid = config.systems.SendGrid , sendgrid = require( 'sendgrid' )( confSendGrid.apiUser, confSendGrid.apiKey ) , Email = sendgrid.Email; return { - send: function ( email, html, text ) { + send: function ( email, body, type ) { var deferred = Q.defer() - , message = this.createMessage( email, html, text ) + , message = this.createMessage( email, body, type ) , email = new Email( message ); if ( message.emailId ) { @@ -30,11 +31,11 @@ module.exports = function ( config ) { return deferred.promise; }, - createMessage: function( email, html, text ){ + createMessage: function( email, body, type ){ var fromMail = defConf.from , fromName = defConf.fromName; - if ( email.dump.fromCompanyName ) { + if ( email.dump.companyName ) { fromName = email.dump.fromName; fromMail = email.dump.fromMail; } @@ -47,10 +48,10 @@ module.exports = function ( config ) { emailId: emailId }; - if ( config.text && !!text ){ - message.text = text; + if ( config.text && type === "text" ){ + message.text = body; } else { - message.html = html; + message.html = body; } if ( config.cc && email.dump.usersCC && email.dump.usersCC.length ) { diff --git a/modules/clever-email/mail-template/accountActivation.ejs b/modules/clever-email/mail-template/accountActivation.ejs deleted file mode 100644 index 4d14b5d..0000000 --- a/modules/clever-email/mail-template/accountActivation.ejs +++ /dev/null @@ -1,15 +0,0 @@ - - - - Account Activation URL - - -
-
-

Account Activation

- -

Please click here to activate your account.

-
-
- - diff --git a/modules/clever-email/mail-template/passwordRecovery.ejs b/modules/clever-email/mail-template/passwordRecovery.ejs deleted file mode 100644 index 0bb0123..0000000 --- a/modules/clever-email/mail-template/passwordRecovery.ejs +++ /dev/null @@ -1,17 +0,0 @@ - - - - - Password Recovery - - - -
-
-

Password Recovery

- -

Please click here to reset your password.

-
-
- - diff --git a/modules/clever-email/models/orm/EmailReplyModel.js b/modules/clever-email/models/orm/EmailReplyModel.js deleted file mode 100644 index 595815c..0000000 --- a/modules/clever-email/models/orm/EmailReplyModel.js +++ /dev/null @@ -1,62 +0,0 @@ -module.exports = function ( sequelize, DataTypes ) { - return sequelize.define( "EmailReply", - { - id: { - type: DataTypes.INTEGER, - primaryKey: true, - autoIncrement: true - }, - reply: { - type: DataTypes.TEXT, - allowNull: true - }, - from: { - type: DataTypes.TEXT, - allowNull: true - }, - to: { - type: DataTypes.TEXT, - allowNull: true - }, - token: { - type: DataTypes.STRING, - allowNull: true - }, - isDelivered: { - type: DataTypes.BOOLEAN, - allowNull: false, - default: true - }, - sentAttemps: { - type: DataTypes.INTEGER, - allowNull: false, - default: 0 - }, - isOpened: { - type: DataTypes.BOOLEAN, - allowNull: false, - default: false - }, - dump: { - type: DataTypes.TEXT, - allowNull: true - }, - EmailId: { - type: DataTypes.INTEGER - } - }, - { - paranoid: true, - instanceMethods: { - toJSON: function () { - var values = this.values; - - if ( values.dump ) { - values.dump = JSON.parse( values.dump ); - } - - return value - } - } - } ); -}; \ No newline at end of file diff --git a/modules/clever-email/models/orm/EmailTemplateModel.js b/modules/clever-email/models/orm/EmailTemplateModel.js deleted file mode 100644 index bd4ef04..0000000 --- a/modules/clever-email/models/orm/EmailTemplateModel.js +++ /dev/null @@ -1,92 +0,0 @@ -module.exports = function ( sequelize, DataTypes ) { - return sequelize.define( "EmailTemplate", - { - id: { - type: DataTypes.INTEGER, - primaryKey: true, - autoIncrement: true - }, - title: { - type: DataTypes.STRING, - allowNull: false, - validate: { - len: [ 2, 256 ] - } - }, - subject: { - type: DataTypes.STRING, - allowNull: false, - validate: { - len: [ 2, 256 ] - } - }, - body: { - type: DataTypes.TEXT, - allowNull: false - }, - isActive: { - type: DataTypes.BOOLEAN, - allowNull: false, - default: false - }, - isDefault: { - type: DataTypes.BOOLEAN, - allowNull: false, - default: false - }, - useDefault: { - type: DataTypes.BOOLEAN, - allowNull: false, - default: false - }, - hasPermission: { - type: DataTypes.BOOLEAN, - allowNull: false, - default: true - }, - AccountId: { - type: DataTypes.INTEGER - }, - UserId: { - type: DataTypes.INTEGER - }, - EmailId: { - type: DataTypes.INTEGER - } - }, - { - instanceMethods: { - toJSON: function () { - var values = this.values; - values['permittedToUsers'] = []; - values['permittedToTeams'] = []; - - delete hasPermission; - - function uniqueValues( arr, attr ) { - var o = {}, i, l = arr.length, r = []; - for ( i = 0; i < l; i += 1 ) o[arr[i][attr]] = arr[i]; - for ( i in o ) r.push( o[i] ); - return r; - }; - - if ( values['users'] && values['users'].length ) { - values['permittedToUsers'] = uniqueValues( values['users'], 'id' ).map( function ( user ) { - return user.id; - } ); - delete values['users']; - } - ; - - if ( values['teams'] && values['teams'].length ) { - values['permittedToTeams'] = uniqueValues( values['teams'], 'id' ).map( function ( team ) { - return team.id; - } ); - delete values['teams']; - } - - return values; - } - } - } ); -}; \ No newline at end of file diff --git a/modules/clever-email/routes.js b/modules/clever-email/routes.js index b158444..e5dbbb9 100644 --- a/modules/clever-email/routes.js +++ b/modules/clever-email/routes.js @@ -1,34 +1,11 @@ module.exports = function ( app, - EmailController, - EmailTemplateController ) { - -// app.get( '/email_templates/:id/preview', UserController.requiresLogin, EmailTemplateController.attach() ); -// app.get( '/email_templates', UserController.requiresLogin, EmailTemplateController.attach() ); -// app.get( '/email_templates/:id', UserController.requiresLogin, EmailTemplateController.attach() ); -// app.post( '/email_templates', UserController.requiresLogin, EmailTemplateController.checkRequiredFields, EmailTemplateController.attach() ); -// app.post( '/email_templates/:id', UserController.requiresLogin, EmailTemplateController.checkRequiredFields, EmailTemplateController.attach() ); -// app['delete']( '/email_templates/:id', UserController.requiresLogin, EmailTemplateController.attach() ); -// //app.post('/emails/:id/send' , UserController.requiresLogin, EmailTemplateController.attach()); -// //app.get('/emails/:id/permission' , UserController.requiresLogin, EmailTemplateController.attach()); -// -// app.get( '/emails', UserController.requiresLogin, EmailController.attach() ); -// app.get( '/emails/:id', UserController.requiresLogin, EmailController.attach() ); -// app.post( '/emails', UserController.requiresLogin, EmailController.attach() ); -// -// app.post( '/emails/:pubkey/eventsMail', EmailController.checkEventMailData, EmailController.attach() ); - - app.get( '/email_templates/:id/preview', EmailTemplateController.attach() ); - app.get( '/email_templates', EmailTemplateController.attach() ); - app.get( '/email_templates/:id', EmailTemplateController.attach() ); - app.post( '/email_templates', EmailTemplateController.checkRequiredFields, EmailTemplateController.attach() ); - app.post( '/email_templates/:id', EmailTemplateController.checkRequiredFields, EmailTemplateController.attach() ); - app['delete']( '/email_templates/:id', EmailTemplateController.attach() ); + EmailController ) { app.get( '/emails', EmailController.attach() ); app.get( '/emails/:id', EmailController.attach() ); app.post( '/emails', EmailController.attach() ); - - app.post( '/emails/:pubkey/eventsMail', EmailController.checkEventMailData, EmailController.attach() ); + app['delete']( '/emails/:id', EmailController.attach() ); + app.post('/emails/:id/send' , EmailController.attach()); }; \ No newline at end of file diff --git a/modules/clever-email/schema/seedData.json b/modules/clever-email/schema/seedData.json index e84916b..26325bc 100644 --- a/modules/clever-email/schema/seedData.json +++ b/modules/clever-email/schema/seedData.json @@ -1,260 +1,13 @@ { - "AccountModel": [ - { - "name": "Default Account", - "subdomain": "dev", - "emailFwd": "dev23io@stage.bolthr.clevertech.biz", - "active": true - }, - { - "name": "Testing Account", - "subdomain": "testing", - "emailFwd": "dfg344dc@stage.bolthr.clevertech.biz", - "active": true - } - ], - - "UserModel": [ - { - "username": "dimitrios@clevertech.biz", - "email": "dimitrios@clevertech.biz", - "password": "9ac20922b054316be23842a5bca7d69f29f69d77", - "firstname": "dimitris", - "lastname": "ioakimidis", - "phone": "8900000", - "active": true, - "confirmed": true, - "hasAdminRight": true, - "associations": { - "AccountModel": { - "name": "Default Account" - } - } - }, - { - "username": "adnan@clevertech.biz", - "email": "adnan@clevertech.biz", - "password": "f10e2821bbbea527ea02200352313bc059445190", - "firstname": "Adnan", - "lastname": "Ibrisimbegovic", - "phone": "8900000", - "active": true, - "confirmed": true, - "hasAdminRight": true, - "associations": { - "AccountModel": { - "name": "Default Account" - } - } - }, - { - "username": "richard", - "email": "richard@clevertech.biz", - "password": "9ac20922b054316be23842a5bca7d69f29f69d77", - "firstname": "Richard", - "lastname": "Gustin", - "phone": "+61448048484", - "active": true, - "confirmed": true, - "hasAdminRight": true, - "associations": { - "AccountModel": { - "name": "Default Account" - } - } - }, - { - "username": "joeJunior", - "email": "joeJunior@clevertech2.biz", - "password": "9ac20922b054316be23842a5bca7d69f29f69d77", - "firstname": "joeJunior", - "lastname": "joeJunior", - "phone": "+3044444", - "active": true, - "confirmed": false, - "hasAdminRight": true, - "associations": { - "AccountModel": { - "name": "Default Account" - } - } - }, - { - "username": "joeJunior3", - "email": "joeJunior3@clevertech2.biz", - "password": "9ac20922b054316be23842a5bca7d69f29f69d77", - "firstname": "joeJunior3", - "lastname": "joeJunior3", - "phone": "+3044444", - "active": false, - "confirmed": false, - "hasAdminRight": true, - "associations": { - "AccountModel": { - "name": "Default Account" - } - } - }, - { - "username": "test", - "email": "bolt-hris@clevertech.biz", - "password": "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8", - "firstname": "Test", - "lastname": "Account", - "phone": "+61448048484", - "active": true, - "confirmed": true, - "hasAdminRight": true, - "associations": { - "AccountModel": { - "name": "Default Account" - } - } - }, - { - "username": "test_employee", - "email": "bolt-employee@clevertech.biz", - "password": "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8", - "firstname": "Test", - "lastname": "Employee", - "phone": "+61448048484", - "active": true, - "confirmed": true, - "hasAdminRight": false, - "associations": { - "AccountModel": { - "name": "Default Account" - } - } - } - ], - - "EmailTemplateModel": [ - { - "title": "Application submitted successfully", - "subject": "{{Company_Name}} :Application Submitted Successfully", - "body": "Dear Applicant First Name  Applicant Last Name ,

Thank you for your interest in Company Name . We received your application for the following position(s) submitted on {{submitted_date}}:
{{job_title}}
Our team is reviewing your qualifications and will contact you if there is a match with any of our open positions. Please note that new jobs are posted to the site often. Please check back regularly for future openings that may be of interest to you.
We appreciate your interest Company Name  and wish you the best of luck in your job search
Sincerely
Company Human Resources Team
{{career_link}}
Note: This message was automatically generated. Please do not respond to this email.

", - "hasPermission": true, - "isActive": true, - "useDefault": false, - "isDefault": true, - "associations": { - "UserModel": { - "username": "test_employee" - } - } - }, - { - "title": "Application status update", - "subject": "{{Company_Name}} :Application Status Update", - "body": "Dear Applicant First Name  Applicant Last Name ,

Thank you for applying for the position {{job_title}} with Company Name . We appreciate your interest in our organization.We received a significant number of applications for the position, and the hiring process has been a very competitive one. After a review of your application, we have decided not to move your application forward. However, we greatly appreciate your interest in working with us and wish you the best of luck with your job search. We welcome you to visit our careers page regularly for future openings that may be of interest to you
Sincerely
Company Human Resources Team
{{career_link}}

", - "hasPermission": true, - "isActive": true, - "useDefault": false, - "isDefault": true, - "associations": { - "UserModel": { - "username": "test_employee" - } - } - }, - { - "title": "new career opportunity", - "subject": "{{Company_Name}} :New career opportunity", - "body": "

Company Name  has just posted the following new jobs to our careers page on {{published_date}}
{{job_title_link}}
If you or anybody you know may be a good fit for this position, please encourage them to apply immediately. If passing along to others, please be sure to remind them to mention how they heard about the opportunity.
Thanks,
Company Human Resources Team
{{career_link}}

", - "hasPermission": true, - "isActive": true, - "useDefault": false, - "isDefault": true, - "associations": { - "UserModel": { - "username": "test_employee" - } - } - }, - { - "title": "Application submitted successfully", - "subject": "{{Company_Name}} :Application Submitted Successfully", - "body": "Dear Applicant First Name  Applicant Last Name ,

Thank you for your interest in Company Name . We received your application for the following position(s) submitted on {{submitted_date}}:
{{job_title}}
Our team is reviewing your qualifications and will contact you if there is a match with any of our open positions. Please note that new jobs are posted to the site often. Please check back regularly for future openings that may be of interest to you.
We appreciate your interest Company Name  and wish you the best of luck in your job search
Sincerely
Company Human Resources Team
{{career_link}}
Note: This message was automatically generated. Please do not respond to this email.

", - "hasPermission": true, - "isActive": true, - "useDefault": false, - "isDefault": true, - "associations": { - "UserModel": { - "id": 1 - } - } - }, - { - "title": "Application status update", - "subject": "{{Company_Name}} :Application Status Update", - "body": "Dear Applicant First Name  Applicant Last Name ,

Thank you for applying for the position {{job_title}} with Company Name . We appreciate your interest in our organization.We received a significant number of applications for the position, and the hiring process has been a very competitive one. After a review of your application, we have decided not to move your application forward. However, we greatly appreciate your interest in working with us and wish you the best of luck with your job search. We welcome you to visit our careers page regularly for future openings that may be of interest to you
Sincerely
Company Human Resources Team
{{career_link}}

", - "hasPermission": true, - "isActive": true, - "useDefault": false, - "isDefault": true, - "associations": { - "UserModel": { - "id": 1 - } - } - }, - { - "title": "new career opportunity", - "subject": "{{Company_Name}} :New career opportunity", - "body": "

Company Name  has just posted the following new jobs to our careers page on {{published_date}}
{{job_title_link}}
If you or anybody you know may be a good fit for this position, please encourage them to apply immediately. If passing along to others, please be sure to remind them to mention how they heard about the opportunity.
Thanks,
Company Human Resources Team
{{career_link}}

", - "hasPermission": true, - "isActive": true, - "useDefault": false, - "isDefault": true, - "associations": { - "UserModel": { - "id": 1 - } - } - }, - { - "title": "Application submitted successfully", - "subject": "{{Company_Name}} :Application Submitted Successfully", - "body": "Dear Applicant First Name  Applicant Last Name ,

Thank you for your interest in Company Name . We received your application for the following position(s) submitted on {{submitted_date}}:
{{job_title}}
Our team is reviewing your qualifications and will contact you if there is a match with any of our open positions. Please note that new jobs are posted to the site often. Please check back regularly for future openings that may be of interest to you.
We appreciate your interest Company Name  and wish you the best of luck in your job search
Sincerely
Company Human Resources Team
{{career_link}}
Note: This message was automatically generated. Please do not respond to this email.

", - "hasPermission": true, - "isActive": true, - "useDefault": false, - "isDefault": true, - "associations": { - "UserModel": { - "id": 1 - } - } - }, - { - "title": "Application status update", - "subject": "{{Company_Name}} :Application Status Update", - "body": "Dear Applicant First Name  Applicant Last Name ,

Thank you for applying for the position {{job_title}} with Company Name . We appreciate your interest in our organization.We received a significant number of applications for the position, and the hiring process has been a very competitive one. After a review of your application, we have decided not to move your application forward. However, we greatly appreciate your interest in working with us and wish you the best of luck with your job search. We welcome you to visit our careers page regularly for future openings that may be of interest to you
Sincerely
Company Human Resources Team
{{career_link}}

", - "hasPermission": true, - "isActive": true, - "useDefault": false, - "isDefault": true, - "associations": { - "UserModel": { - "id": 1 - } - } - }, - { - "title": "new career opportunity", - "subject": "{{Company_Name}} :New career opportunity", - "body": "

Company Name  has just posted the following new jobs to our careers page on {{published_date}}
{{job_title_link}}
If you or anybody you know may be a good fit for this position, please encourage them to apply immediately. If passing along to others, please be sure to remind them to mention how they heard about the opportunity.
Thanks,
Company Human Resources Team
{{career_link}}

", - "hasPermission": true, - "isActive": true, - "useDefault": false, - "isDefault": true, - "associations": { - "UserModel": { - "id": 1 - } - } + "EmailModel": [ + { + "subject": "custom subject", + "body": "custom body for testing", + "isDelivered": false, + "isOpened": false, + "token": "45sada45asd", + "dump": "{'companyLogo': 'LOGO','companyName': 'name','fromName': 'from name','fromMail': 'custom@mail.ru','toMail': 'denshikov_vovan@mail.ru'}", + "UserId": 1 } ] } \ No newline at end of file diff --git a/modules/clever-email/services/EmailService.js b/modules/clever-email/services/EmailService.js index 729d7e6..ea40175 100644 --- a/modules/clever-email/services/EmailService.js +++ b/modules/clever-email/services/EmailService.js @@ -1,20 +1,16 @@ var Q = require( 'q' ) , Sequelize = require( 'sequelize' ) - , ejsFileRender = require( '../lib/ejsfilerender' ) , shortid = require( 'shortid' ) , config = require( 'config' ) , mailer = require( '../lib/mailer' )( config['clever-email'] ) + , _ = require( 'lodash' ) , EmailService = null; module.exports = function ( sequelize, ORMEmailModel, ORMEmailAttachmentModel, - ORMEmailReplyModel, ORMUserModel, - ORMEmailUserModel, - EmailTemplateService ) { - - var bakeTemplate = ejsFileRender(); + ORMEmailUserModel ) { if ( EmailService && EmailService.instance ) { return EmailService.instance; @@ -39,7 +35,7 @@ module.exports = function ( sequelize, : 'reply_' + emailToken + '@app-mail.bolthr.com'; return addr; - }, /* tested */ + }, formatData: function ( data ) { var o = { @@ -102,47 +98,18 @@ module.exports = function ( sequelize, o.hasTemplate = hasTemplate; return o; - }, /* tested */ - - formatRepliedData: function ( data ) { - - var replyAddr = this.formatReplyAddress( data.emailToken ); - - var o = { - id: null, - reply: data.reply, - EmailId: data.emailId, - token: data.emailId + '_' + shortid.seed( 10000 ).generate(), - sentAttemps: 1, - isDelivered: true, - isOpened: false, - from: data.from.indexOf( data.userEmail ) != -1 ? data.userEmail : null, - to: data.to - }; - - var t = { - subject: data.subject, - html: data.replyHTML, - replyAddress: replyAddr, - from: data.from, - to: data.to - }; - - o.dump = JSON.stringify( t ); - - return o; - }, /* tested */ + }, listEmails: function ( userId ) { var deferred = Q.defer(); this - .find( { where: { UserId: userId }, include: [ ORMEmailAttachmentModel, ORMEmailReplyModel ] } ) + .find( { where: { UserId: userId }, include: [ ORMEmailAttachmentModel ] } ) .then( deferred.resolve ) .fail( deferred.reject ); return deferred.promise; - }, /* tested */ + }, getEmailByIds: function ( userId, emailId ) { var deferred = Q.defer() @@ -151,7 +118,7 @@ module.exports = function ( sequelize, chainer.add( ORMEmailModel.find( { - where: { id: emailId, UserId: userId, 'deletedAt': null }, include: [ ORMEmailAttachmentModel, ORMEmailReplyModel ] + where: { id: emailId, UserId: userId, 'deletedAt': null }, include: [ ORMEmailAttachmentModel ] } ) ); @@ -180,7 +147,7 @@ module.exports = function ( sequelize, .error( deferred.reject ); return deferred.promise; - }, /* tested */ + }, handleEmailCreation: function ( data ) { var deferred = Q.defer() @@ -193,12 +160,12 @@ module.exports = function ( sequelize, Q.all( promises ) .then( function() { - deferred.resolve(); + deferred.resolve( {statuscode: 200, message: 'email is created'} ); }) .fail( deferred.reject ); return deferred.promise; - }, /* tested */ + }, processEmailCreation: function ( emailItem ) { var deferred = Q.defer() @@ -219,7 +186,7 @@ module.exports = function ( sequelize, .fail( deferred.reject ); return deferred.promise; - }, /* tested */ + }, saveEmailAssociation: function ( savedEmail, fData ) { var deferred = Q.defer() @@ -286,143 +253,71 @@ module.exports = function ( sequelize, } ); return deferred.promise; - }, /* tested */ + }, - renderTemplate: function ( data ) { + handleEmailSending: function ( userId, emailId, type ) { var deferred = Q.defer() - , email = data.email - , user = data.user || null - , tplName = data.tplName || null - , tpl = {}; - - tpl.tplName = tplName || config['clever-email'].default.tplName; - tpl.tplTitle = email.subject || config['clever-email'].default.subject; - tpl.companyName = ( email.dump.companyName ) ? email.dump.companyName : config['clever-email'].default.fromName; - tpl.companyLogo = ( email.dump.companyLogo ) ? email.dump.companyLogo : config['clever-email'].default.logo; - - //Text has already being parsed from frontend - if ( !email.EmailTemplateId ) { - tpl.strHTML = email.body; - - bakeTemplate( tpl ) - .then( deferred.resolve ) - .fail( deferred.reject ); - - } else { - - EmailTemplateService - .getPlaceholderData( { - accId: email.AccountId, - EmailTemplateId: email.EmailTemplateId - } ) - .then( function ( emailTemplate ) { - return EmailTemplateService.processTemplateIntrpolation( user, emailTemplate ); - } ) - .then( function ( html ) { - tpl['strHTML'] = html; - - return bakeTemplate( tpl ); - } ) - .then( deferred.resolve ) - .fail( deferred.reject ); - } - - return deferred.promise; - }, - - sendEmail: function ( email, html ) { - var deferred = Q.defer(); - - mailer.send( email, html ) - .then( deferred.resolve ) - .fail( deferred.reject ); + , service = this; - return deferred.promise; - }, + service + .getEmailByIds( userId, emailId ) + .then( function( result ) { - processMailReply: function ( data ) { - var deferred = Q.defer() - , service = this; + if ( !!result && !!result.id && !!result.body ) { - this - .findOne( { where: { token: data.replyMailHash }, include: [ ORMUserModel ] } ) - .then( function ( email ) { + service + .sendEmail( result, result.body, type ) + .then( deferred.resolve ) + .fail( deferred.reject ); - if ( !email || !email.id ) { - console.log( "\n\n----- EMAIL REPLY TOKEN DOES NOT EXISTS ------\n" ); - deferred.resolve(); - return; + } else { + deferred.resolve( result ) } + }) + .fail( deferred.reject ); - console.log( "\n\n----- EMAIL REPLY TOKEN EXISTS ------\n" ); + return deferred.promise; + }, - data['emailId'] = email.id; - data['emailToken'] = email.token; - data['userEmail'] = email.user.email; - data['userName'] = email.user.firstname + ' ' + email.user.lastname; + sendEmail: function ( email, body, type ) { + var deferred = Q.defer(); - service - .saveMailReply( data ) - .then( deferred.resolve ) - .fail( deferred.reject ); + email.dump = _.isPlainObject( email.dump ) + ? email.dump + : JSON.parse( email.dump ); - } ) + mailer.send( email, body, type ) + .then( deferred.resolve ) .fail( deferred.reject ); return deferred.promise; - }, + }, - saveMailReply: function ( data ) { + deleteEmail: function( userId, emailId ) { var deferred = Q.defer() - , replyData = this.formatRepliedData( data ); - - ORMEmailReplyModel - .create( replyData ) - .success( deferred.resolve ) - .error( deferred.reject ); - - return deferred.promise; - }, + , service = this; - processMailReplyNotification: function ( savedReply ) { - var deferred = Q.defer() - , mail = JSON.parse( JSON.stringify( savedReply ) ) - , payload = { }; + service + .getEmailByIds( userId, emailId ) + .then( function( result ) { - payload[ 'from' ] = mail.dump.replyAddress; - payload[ 'fromname' ] = mail.dump.fromname; - payload[ 'to' ] = [ mail.to ]; - payload[ 'toname' ] = mail.dump.toname; + if ( !!result && !!result.id ) { - payload[ 'subject' ] = mail.dump.subject; - payload[ 'text' ] = mail.reply; - payload[ 'html' ] = mail.dump.html; + service + .destroy( emailId ) + .then( function() { + deferred.resolve( { statuscode: 200, message: 'email is deleted'} ) + }) + .fail( deferred.reject ); - mailer( payload ) - .then( deferred.resolve ) - .fail( deferred.resolve ); + } else { + deferred.resolve( result ) + } + }) + .fail( deferred.reject ); return deferred.promise; - }, - - processMailEvents: function ( evns ) { - var deferred = Q.defer() - , item = null - , chainer = new Sequelize.Utils.QueryChainer(); - - while ( item = evns.pop() ) { - console.log( "\nUPDATING: ", item.email_id ); - chainer.add( ORMEmailModel.update( { isOpened: true }, { id: item.email_id, 'deletedAt': null} ) ); - } - chainer - .run() - .success( function () { - deferred.resolve( {statuscode: 200, message: 'ok'} ); - } ) - .error( deferred.reject ); - - return deferred.promise; } } ); diff --git a/modules/clever-email/services/EmailTemplateService.js b/modules/clever-email/services/EmailTemplateService.js deleted file mode 100644 index be320e6..0000000 --- a/modules/clever-email/services/EmailTemplateService.js +++ /dev/null @@ -1,332 +0,0 @@ -var Q = require( 'q' ) - , moment = require( 'moment' ) - , Sequelize = require( 'sequelize' ) - , cheerio = require( 'cheerio' ) - , config = require( 'config' )['clever-email'] - , mailer = require( '../lib/mailer' )( config ) - , EmailTemplateService = null; - -module.exports = function ( sequelize, - EmailTemplateModel, - UserModel ) { - - if ( EmailTemplateService && EmailTemplateService.instance ) { - return EmailTemplateService.instance; - } - - EmailTemplateService = require( 'services' ).BaseService.extend( { - - formatData: function ( data ) { - var o = { - id: data.id || null, - title: data.title, - subject: data.subject, - body: data.body, - AccountId: data.accId, - UserId: data.userId || null, - isActive: (/false|true/.test( data.isActive )) ? data.isActive : false, - isDefault: (/false|true/.test( data.isDefault )) ? data.isDefault : false, - useDefault: (/false|true/.test( data.useDefault )) ? data.useDefault : false, - hasPermission: (/false|true/.test( data.hasPermission )) ? data.hasPermission : true - }; - - return o; - }, - - formatEmailTemplateUserForSave: function ( permittedToUsers ) { - var tu = [] - , arr = permittedToUsers - , item; - - if ( arr && arr.length ) { - while ( item = arr.pop() ) { - var team = UserModel.build( { id: item } ); - tu.push( team ); - } - } - - return tu; - }, - - listTemplates: function ( accId, userId, teamId, role ) { - var deferred = Q.defer() - , chainer = new Sequelize.Utils.QueryChainer() - , query = 'EmailTemplates.AccountId=' + accId; - - if ( !role || (role != 'Owner') ) { - query += ' AND ( '; - query += 'EmailTemplates.UserId = ' + userId + ' OR ' + - 'EmailTemplatesUsers.UserId =' + userId + ' OR ' + - 'EmailTemplates.hasPermission = false'; - - if ( teamId ) { - query += ' OR EmailTemplatesTeams.TeamId = ' + teamId; - } - - query += ' )'; - } - - this - .find( { where: [ query ], include: [ UserModel ] } ) - .then( function ( result ) { - if ( !result.length ) { - deferred.resolve( [] ); - return; - } - deferred.resolve( result ); - } ) - .fail( deferred.reject ); - - return deferred.promise; - }, - - getTemplateById: function ( accId, userId, teamId, tplId, role ) { - var deferred = Q.defer() - , query = '( EmailTemplates.id = ' + tplId + ' AND EmailTemplates.AccountId= ' + accId + ' )'; - - if ( !role || (role != 'Owner') ) { - - query += ' AND ( '; - query += '( EmailTemplates.UserId = ' + userId + ' AND EmailTemplates.AccountId= ' + accId + ' ) OR ' + - '( EmailTemplatesUsers.UserId = ' + userId + ' AND EmailTemplates.AccountId= ' + accId + ' ) OR ' + - '( EmailTemplates.hasPermission = false AND EmailTemplates.AccountId= ' + accId + ' )'; - - if ( teamId ) { - query += ' OR ( EmailTemplatesTeams.TeamId = ' + teamId + ' AND EmailTemplates.AccountId= ' + accId + ' )'; - } - - query += ' )'; - } - - this - .findOne( { where: [ query ], include: [ UserModel ] } ) - .then( function ( result ) { - - if ( !result ) { - deferred.resolve( {statuscode: 403, message: 'invalid'} ); - return; - } - deferred.resolve( result ); - } ) - .fail( deferred.reject ); - - return deferred.promise; - }, - - createEmailTemplate: function ( data ) { - var deferred = Q.defer() - , emailData = this.formatData( data ); - - this - .create( emailData ) - .then( deferred.resolve ) - .fail( deferred.reject ); - - return deferred.promise; - }, - - getPlaceholderData: function ( data ) { - var deferred = Q.defer() - , plData = { template: null } - , chainer = new Sequelize.Utils.QueryChainer(); - - var tplId = data.template_id || data.EmailTemplateId - , accId = data.accId || data.AccountId; - - chainer.add( - EmailTemplateModel.find( { - where: { id: tplId, AccountId: accId } - } ) - ); - - chainer - .run() - .success( function ( result ) { - - if ( !result[0] ) { // If template does not exist - deferred.resolve( null ); - return; - } - - plData['template'] = JSON.parse( JSON.stringify( result[0] ) ); - - deferred.resolve( plData ); - } ) - .error( deferred.reject ); - - return deferred.promise; - }, - - processTemplateIntrpolation: function ( data, user ) { - var deferred = Q.defer() - , service = this - , tpl = data.template.body; - - var $ = cheerio.load( tpl ); - - $( 'span[rel="placeholder"]' ).each( function ( i, elem ) { - text = service.getPlaceholderText( $( elem ), user ); - $( elem ).replaceWith( text ); - } ); - - deferred.resolve( $.root().html() ); - - return deferred.promise; - }, - - getPlaceholderText: function ( placeholderNode, user ) { - var id = placeholderNode.attr( 'id' ); - var parts = id.split( '-' ); - var domain = parts[0]; - var prop = parts[1]; - var propValue; - - //console.log("\n\nPLACEHOLDER: ",domain, prop); - - if ( domain === 'user' ) { - propValue = user && user[prop]; - } - - if ( domain === 'account' ) { - - if ( prop == 'url' ) { - var url = config.hosturl.replace( '://', '://' + user['account']['subdomain'] + '.' ); - url += '/careers'; - propValue = url; - } else { - propValue = user['account'] && user['account'][prop]; - } - } - - return propValue; - }, - - processEmailTemplateAssoc: function ( data, tpl ) { - var deferred = Q.defer() - , emailTplUsers = null - , emailTplTeams = null - , chainer = new Sequelize.Utils.QueryChainer(); - - chainer.add( this.query( 'delete from EmailTemplatesUsers where EmailTemplateId = ' + tpl.id ) ); - chainer.add( this.query( 'delete from EmailTemplatesTeams where EmailTemplateId = ' + tpl.id ) ); - - if ( (emailTplUsers = this.formatEmailTemplateUserForSave( data.permittedToUsers )).length ) { - chainer.add( tpl.setUsers( emailTplUsers ) ); - } - - if ( (emailTplTeams = this.formatEmailTemplateTeamForSave( data.permittedToTeams )).length ) { - chainer.add( tpl.setTeams( emailTplTeams ) ); - } - - chainer - .runSerially() - .success( function ( results ) { - - var tplJson = JSON.parse( JSON.stringify( tpl ) ); - tplJson['permittedToUsers'] = []; - tplJson['permittedToTeams'] = []; - - if ( ( emailTplUsers.length ) && ( emailTplTeams.length ) ) { - tplJson['permittedToUsers'] = results[ 2 ]; - tplJson['permittedToTeams'] = results[ 3 ]; - - } else if ( emailTplUsers.length ) { - - tplJson['permittedToUsers'] = results[ 2 ]; - } else { - - tplJson['permittedToTeams'] = results[ 2 ]; - } - - deferred.resolve( tplJson ); - } ) - .error( deferred.reject ); - - return deferred.promise; - }, - - handleEmailTemplateUpdate: function ( data ) { - var deferred = Q.defer() - , emailData = this.formatData( data ); - - this - .findOne( { where: { id: emailData.id, AccountId: data.accId } } ) - .then( function ( emailTpl ) { - - // If template is not Default and the User is not the creator send invalid - if ( !emailTpl || (!emailTpl.isDefault && ( emailTpl.UserId != data.userId )) ) { - deferred.resolve( { statuscode: 401, message: 'invalid' } ); - return; - } - - return this.updateEmailTemplate( emailTpl, emailData ); - - }.bind( this ) ) - .then( deferred.resolve ) - .fail( deferred.reject ); - - return deferred.promise; - }, - - updateEmailTemplate: function ( emailTemplate, data ) { - var deferred = Q.defer(); - - //If the EmailTemplate is default do not consider UserId - if ( emailTemplate.isDefault ) { - data['UserId'] = null; - } - - emailTemplate.updateAttributes( data ) - .success( function ( data ) { - deferred.resolve( data ); - } ) - .error( deferred.reject ); - - return deferred.promise; - }, - - removeEmailTemplate: function ( userId, tplId ) { - var deferred = Q.defer() - , chainer = new Sequelize.Utils.QueryChainer(); - - - this - .find( { where: { id: tplId, "UserId": userId } } ) - .then( function ( result ) { - - if ( !result.length ) { - deferred.resolve( { statuscode: 400, message: 'email tempplate does not exist'} ); - return; - } - - var tpl = result[0]; - - chainer.add( tpl.setTeams( [] ) ); - chainer.add( tpl.setUsers( [] ) ); - chainer.add( tpl.destroy() ); - chainer.run() - .success( function () { - deferred.resolve( { statuscode: 200, message: 'operation was successfull' } ); - } ) - .error( deferred.promise ); - } ); - - return deferred.promise; - }, - - sendEmail: function ( payload ) { - var deferred = Q.defer(); - - mailer( payload ) - .then( deferred.resolve ) - .fail( deferred.resolve ); - - return deferred.promise; - } - } ); - - EmailTemplateService.instance = new EmailTemplateService( sequelize ); - EmailTemplateService.Model = EmailTemplateModel; - - return EmailTemplateService.instance; -}; \ No newline at end of file diff --git a/modules/clever-email/tests/temp/test.controller.EmailAlertController.js b/modules/clever-email/tests/temp/test.controller.EmailAlertController.js deleted file mode 100644 index 5e6e141..0000000 --- a/modules/clever-email/tests/temp/test.controller.EmailAlertController.js +++ /dev/null @@ -1,461 +0,0 @@ -var should = require( 'should' ) - , sinon = require( 'sinon' ) - , testEnv = require( './utils' ).testEnv - , request = require( 'supertest' ) - , async = require( 'async' ) - , app = require( './../../../index' ); - -describe('controllers.EmailAlertController', function () { - this.timeout( 10000 ); - var env, EmailAlertController, ODMAttributeValueModel, ctrl, HRManagerSession, HRManager, EmployeeSession, Employee; - - before(function (done) { - var self = this; - - testEnv(function ( _ODMAttributeValueModel_ ) { - Model = _ODMAttributeValueModel_; - - async.parallel( [ - function loginAsHRManager ( next ) { - request( app ) - .post( '/user/login' ) - .set( 'Accept', 'application/json' ) - .send( { username: 'bolt-hris@clevertech.biz', password: 'password' }) - .expect( 'Content-Type' , /json/ ) - .expect( 200 ) - .end( function ( err, res ) { - HRManagerSession = res.headers[ 'set-cookie' ].pop().split( ';' )[ 0 ]; - HRManager = res.body; - next ( err ); - }); - }, - function loginAsEmployee ( next ) { - request( app ) - .post( '/user/login' ) - .set( 'Accept', 'application/json' ) - .send( { username: 'bolt-employee@clevertech.biz', password: 'password' } ) - .expect( 'Content-Type' , /json/ ) - .expect( 200 ) - .end( function ( err, res ) { - EmployeeSession = res.headers[ 'set-cookie' ].pop().split( ';' )[ 0 ]; - Employee = res.body; - next( err ); - }); - } - ], - done ); - }); - }); - - describe('.postAction()', function() { - it('should allow us to make a new e-mail alert with the correct permissions', function ( done ) { - var req = request( app ).post( '/alerts' ); - var date = ((new Date()).getTime() * 1000); - - req.cookies = HRManagerSession; - req.set( 'Accept','application/json' ) - .send ( { - alert: { - alertEmployee: true, - alertName: "New Benefit PTO", - category: "Benefit Eligibility", - customMessage: "My Message", - customSubject: "My Subject", - list: [], - otherFields: { - education: true - }, - reminders: [ - { - duration: "", - reminderDuration: "1", - reminderFrequency: "days", - reminderOffset: "before" - } - ], - tables: "Customized Education Table" - }, - submitted: date - } ) - .expect( 'Content-Type', /json/ ) - .expect( 200 ) - .end( function (err, res) { - res.body.should.be.instanceof( Object ); - res.body.should.have.properties( '_id', 'AttributeDocumentId', 'value' ); - res.body.value.should.be.instanceof( Object ); - res.body.value.should.have.properties( 'lastModifiedOn', 'lastModifiedBy', 'accountId', 'tables', 'reminders', 'otherFields', 'list', 'customSubject', 'customMessage', 'category', 'alertName', 'alertEmployee' ); - res.body.value.lastModifiedOn.value.should.equal( date ); - res.body.value.lastModifiedBy.value.should.equal( 9 ); - res.body.value.accountId.value.should.equal( 1 ); - res.body.value.tables.value.should.equal( 'Customized Education Table' ) - res.body.value.reminders.value.should.be.instanceOf( Array ); - res.body.value.reminders.value[0].duration.should.equal( '' ); - res.body.value.reminders.value[0].reminderDuration.should.equal( '1' ); - res.body.value.reminders.value[0].reminderFrequency.should.equal( 'days' ); - res.body.value.reminders.value[0].reminderOffset.should.equal( 'before' ); - res.body.value.otherFields.value.should.be.instanceof( Object ); - res.body.value.otherFields.value.should.have.properties( 'education' ); - res.body.value.otherFields.value.education.should.equal( true ); - res.body.value.list.value.should.be.instanceof( Array ); - res.body.value.list.value.length.should.equal( 0 ); - res.body.value.customSubject.value.should.equal( 'My Subject' ); - res.body.value.customMessage.value.should.equal( 'My Message' ); - res.body.value.category.value.should.equal( 'Benefit Eligibility' ); - res.body.value.alertName.value.should.equal( 'New Benefit PTO' ); - res.body.value.alertEmployee.value.should.equal( true ); - done(); - } ); - }); - - it.skip('shouldn\'t allow us to make a new e-mail alert if we don\'t have the correct permissions', function ( done ) { - var req = request( app ).post('/alerts'); - req.cookies = EmployeeSession; - req.set( 'Accept','application/json' ) - .send ( { - }) - .expect( 'Content-Type', /json/ ) - .expect( 403 ) - .end(function (err, res) { - done(); - }); - }); - - it.skip('shouldn\'t allow us to update an e-mail alert if we don\'t have the correct data with the correct permissions', function ( done ) { - var req = request( app ).post( '/alerts' ); - req.cookies = HRManagerSession; - req.set( 'Accept','application/json' ) - .send ( { - }) - .expect( 'Content-Type', /json/ ) - .expect( 400 ) - .end(function (err, res) { - done(); - }); - }); - - it.skip('shouldn\'t allow us to save/update am e-mail alert if we don\'t have any data with the correct permissions', function ( done ) { - var req = request( app ).post( '/alerts' ); - req.cookies = HRManagerSession; - req.set( 'Accept','application/json' ) - .send ( ) - .expect( 'Content-Type', /json/ ) - .expect( 400 ) - .end(function (err, res) { - done(); - }); - }); - }); - - describe('.putAction()', function() { - before( function ( done ) { - var self = this - , req = request( app ).post( '/alerts' ); - - var date = ((new Date()).getTime() * 1000); - - req.cookies = HRManagerSession; - req.set( 'Accept','application/json' ) - .send ( { - alert: { - alertEmployee: true, - alertName: "New Benefit PTO", - category: "Benefit Eligibility", - customMessage: "My Message", - customSubject: "My Subject", - list: [], - otherFields: { - education: true - }, - reminders: [ - { - duration: "", - reminderDuration: "1", - reminderFrequency: "days", - reminderOffset: "before" - } - ], - tables: "Customized Education Table" - }, - submitted: date - } ) - .expect( 'Content-Type', /json/ ) - .expect( 200 ) - .end(function (err, res) { - self.alert = res.body; - done(); - }); - }); - - it('should allow us to modify an existing alert', function ( done ) { - var self = this - , req = request( app ).put('/alerts'); - - var date = (new Date()).getTime() * 1000; - - req.cookies = HRManagerSession; - req.set( 'Accept','application/json' ) - .send({ - alert: { - _id: self.alert._id, - alertName: '123' - }, - submitted: date - }) - .expect( 'Content-Type', /json/ ) - .expect( 200 ) - .end(function (err, res) { - res.body.should.be.instanceof( Object ); - res.body.should.have.properties( '_id', 'AttributeDocumentId', 'value' ); - res.body.value.should.be.instanceof( Object ); - res.body.value.lastModifiedOn.value.should.equal( date ); - res.body.value.lastModifiedBy.value.should.equal( 9 ); - res.body.value.accountId.value.should.equal( 1 ); - res.body.value.alertName.value.should.equal( '123' ); - done(); - }); - }); - - it('should give us an error when we try to edit a non-existing alert', function ( done ) { - var self = this - , req = request( app ).put('/alerts'); - - req.cookies = HRManagerSession; - req.set( 'Accept','application/json' ) - .send( { - alert: { - _id: '1212121212121212', - alertName: 'Hello' - } - } ) - .expect( 'Content-Type', /json/ ) - .expect( 404 ) - .end(function (err, res) { - done(); - }); - }); - - it.skip('should give us an error when we don\'t have the correct permissions', function ( done ) { - var self = this - , req = request( app ).put('/alert'); - - req.cookies = EmployeeSession; - req.set( 'Accept','application/json' ) - .send({ - }) - .expect( 'Content-Type', /json/ ) - .expect( 403 ) - .end(function (err, res) { - done(); - }); - }); - }); - - describe('.deleteAction()', function() { - before(function ( done ) { - var self = this; - - async.parallel( [ - function ( next ) { - var date = (new Date()).getTime() * 1000; - var req = request( app ).post( '/alerts' ); - req.cookies = HRManagerSession; - req.set( 'Accept','application/json' ) - .send ( { - alert: { - alertEmployee: true, - alertName: "New Benefit PTO", - category: "Benefit Eligibility", - customMessage: "My Message", - customSubject: "My Subject", - list: [], - otherFields: { - education: true - }, - reminders: [ - { - duration: "", - reminderDuration: "1", - reminderFrequency: "days", - reminderOffset: "before" - } - ], - tables: "Customized Education Table" - }, - submitted: date - } ) - .expect( 'Content-Type', /json/ ) - .expect( 200 ) - .end(function (err, res) { - self.alert = res.body; - next(); - }); - } - ], done ); - }); - - it.skip('shouldn\'t be able to delete an alert if we don\'t have the permissions', function ( done ) { - var self = this - , req = request( app ).del('/alerts'); - - req.cookies = EmployeeSession; - req.set( 'Accept','application/json' ) - .send ( { - }) - .expect( 'Content-Type', /json/ ) - .expect( 403 ) - .end(function ( err, res ) { - done(); - }); - }); - - it('should be able to delete an alert', function ( done ) { - var self = this - , req = request( app ).del( '/alerts/' + self.alert._id ); - - req.cookies = HRManagerSession; - req.set( 'Accept','application/json' ) - .expect( 'Content-Type', /json/ ) - .expect( 200 ) - .end(function ( err, res ) { - done( err ); - }); - }); - - it('should give us a 404 if we try to delete a non-existant alert', function ( done ) { - var self = this - , req = request( app ).del('/alerts/' + self.alert._id ); - - req.cookies = HRManagerSession; - req.set( 'Accept','application/json' ) - .expect( 'Content-Type', /json/ ) - .expect( 404 ) - .end(function (err, res) { - done(); - }); - }); - }); - - describe('.listAction()', function() { - before(function ( done ) { - var self = this; - - async.parallel( [ - function ( next ) { - var date = (new Date()).getTime() * 1000; - var req = request( app ).post('/alerts'); - req.cookies = HRManagerSession; - req.set( 'Accept','application/json' ) - .send ( { - alert: { - alertEmployee: true, - alertName: "New Benefit PTO", - category: "Benefit Eligibility", - customMessage: "My Message", - customSubject: "My Subject", - list: [], - otherFields: { - education: true - }, - reminders: [ - { - duration: "", - reminderDuration: "1", - reminderFrequency: "days", - reminderOffset: "before" - } - ], - tables: "Customized Education Table" - }, - submitted: date - } ) - .expect( 'Content-Type', /json/ ) - .expect( 200 ) - .end(function (err, res) { - self.alert = res.body; - next(); - }); - } - ], done ); - }); - - it('should be able to get alerts with permissions', function ( done ) { - var self = this - , req = request( app ).get( '/alerts' ); - - req.cookies = HRManagerSession; - req.set( 'Accept','application/json' ) - .expect( 'Content-Type', /json/ ) - .expect( 200 ) - .end(function (err, res) { - res.body.should.be.instanceof( Array ); - res.body.length.should.be.above( 0 ); - res.body[0].should.have.properties( '_id', 'AttributeDocumentId', 'value' ); - res.body[0].value.should.be.instanceof( Object ); - res.body[0].value.should.have.properties( 'lastModifiedOn', 'lastModifiedBy', 'accountId', 'tables', 'reminders', 'otherFields', 'list', 'customSubject', 'customMessage', 'category', 'alertName', 'alertEmployee' ); - res.body[0].value.lastModifiedBy.value.should.equal( 9 ); - res.body[0].value.accountId.value.should.equal( 1 ); - res.body[0].value.tables.value.should.equal( 'Customized Education Table' ) - res.body[0].value.reminders.value.should.be.instanceOf( Array ); - res.body[0].value.reminders.value[0].duration.should.equal( '' ); - res.body[0].value.reminders.value[0].reminderDuration.should.equal( '1' ); - res.body[0].value.reminders.value[0].reminderFrequency.should.equal( 'days' ); - res.body[0].value.reminders.value[0].reminderOffset.should.equal( 'before' ); - res.body[0].value.otherFields.value.should.be.instanceof( Object ); - res.body[0].value.otherFields.value.should.have.properties( 'education' ); - res.body[0].value.otherFields.value.education.should.equal( true ); - res.body[0].value.list.value.should.be.instanceof( Array ); - res.body[0].value.list.value.length.should.equal( 0 ); - res.body[0].value.customSubject.value.should.equal( 'My Subject' ); - res.body[0].value.customMessage.value.should.equal( 'My Message' ); - res.body[0].value.category.value.should.equal( 'Benefit Eligibility' ); - res.body[0].value.alertName.value.should.equal( 'New Benefit PTO' ); - res.body[0].value.alertEmployee.value.should.equal( true ); - done(); - }); - }); - - it.skip('shouldn\'t be able to get a list of alerts without permissions', function ( done ) { - done(); - }); - - it('should be able to get a specific alert', function ( done ) { - var self = this - , req = request( app ).get( '/alerts/' + self.alert._id ); - - req.cookies = HRManagerSession; - req.set( 'Accept','application/json' ) - .expect( 'Content-Type', /json/ ) - .expect( 200 ) - .end(function (err, res) { - res.body.should.be.instanceof( Array ); - res.body.length.should.be.above( 0 ); - res.body[0].should.have.properties( '_id', 'AttributeDocumentId', 'value' ); - res.body[0].value.should.be.instanceof( Object ); - res.body[0].value.should.have.properties( 'lastModifiedOn', 'lastModifiedBy', 'accountId', 'tables', 'reminders', 'otherFields', 'list', 'customSubject', 'customMessage', 'category', 'alertName', 'alertEmployee' ); - res.body[0].value.lastModifiedBy.value.should.equal( 9 ); - res.body[0].value.accountId.value.should.equal( 1 ); - res.body[0].value.tables.value.should.equal( 'Customized Education Table' ) - res.body[0].value.reminders.value.should.be.instanceOf( Array ); - res.body[0].value.reminders.value[0].duration.should.equal( '' ); - res.body[0].value.reminders.value[0].reminderDuration.should.equal( '1' ); - res.body[0].value.reminders.value[0].reminderFrequency.should.equal( 'days' ); - res.body[0].value.reminders.value[0].reminderOffset.should.equal( 'before' ); - res.body[0].value.otherFields.value.should.be.instanceof( Object ); - res.body[0].value.otherFields.value.should.have.properties( 'education' ); - res.body[0].value.otherFields.value.education.should.equal( true ); - res.body[0].value.list.value.should.be.instanceof( Array ); - res.body[0].value.list.value.length.should.equal( 0 ); - res.body[0].value.customSubject.value.should.equal( 'My Subject' ); - res.body[0].value.customMessage.value.should.equal( 'My Message' ); - res.body[0].value.category.value.should.equal( 'Benefit Eligibility' ); - res.body[0].value.alertName.value.should.equal( 'New Benefit PTO' ); - res.body[0].value.alertEmployee.value.should.equal( true ); - done(); - }); - }); - - it('shouldn\'t be able to get a specific alert if we don\'t have the correct permissions', function ( done ) { - done(); - }); - }); -}); diff --git a/modules/clever-email/tests/unit/test.controllers.EmailController.js b/modules/clever-email/tests/unit/test.controllers.EmailController.js new file mode 100644 index 0000000..bffbef1 --- /dev/null +++ b/modules/clever-email/tests/unit/test.controllers.EmailController.js @@ -0,0 +1,481 @@ +// Bootstrap the testing environmen +var testEnv = require( 'utils' ).testEnv(); + +var expect = require( 'chai' ).expect + , Q = require ( 'q' ) + , Service; + +var userId = 1000, emailId; + +describe( 'controllers.EmailController', function () { + var ctrl; + + before( function ( done ) { + testEnv( function ( EmailController, EmailService ) { + var req = { + params: { action: 'fakeAction'}, + method: 'GET', + query: {} + }; + + var res = { + json: function () {} + }; + + var next = function () {}; + + ctrl = new EmailController( req, res, next ); + + Service = EmailService; + + done(); + } ); + } ); + + + describe( '.postAction()', function () { + + it( 'should be able to create emails with associations', function ( done ) { + + var email_1 = { + title: 'some title #2', + subject: 'some subject #2', + body: 'some body #2', + to: { + id: 15, + email: 'denshikov_vovan@mail.ru' + }, + hasTemplate: false, + cc: [ { id: 1500, email:'cc1@mail.ru' }, { id: userId, email: 'cc2@mail.ru' } ], + bcc: [ { id: 1600, email: 'bcc1@mail.com' }, { id: 1700, email: 'bcc2@mail.com' } ], + EmailTemplateId: 45 + }; + + var email_2 = { + title: 'some title #3', + subject: 'some subject #3', + body: 'some body #3', + to: { + id: 15, + email: 'to1@email.za' + }, + hasTemplate: false, + cc: [ { id: 1500, email:'cc1@mail.ru' }, { id: 1800, email: 'cc2@mail.ru' } ], + bcc: [ { id: 1600, email: 'bcc1@mail.com' }, { id: 1700, email: 'bcc2@mail.com' } ], + EmailTemplateId: 45 + }; + + + ctrl.send = function ( data, status ) { + + expect( status ).to.equal( 200 ); + + var promise = [ + Service.find( { where: { subject: email_1.subject, body: email_1.body } } ), + Service.find( { where: { subject: email_2.subject, body: email_2.body } } ) + ]; + + Q.all( promise ) + .then( function ( emails ) { + + emails[0] = emails[0][0]; + emails[1] = emails[1][0]; + + expect( emails ).to.be.an( 'array' ).and.have.length( 2 ); + + expect( emails[0] ).to.be.an( 'object' ); + expect( emails[0] ).to.have.property( 'id' ).and.be.ok; + expect( emails[0] ).to.have.property( 'subject' ).and.equal( email_1.subject ); + + expect( emails[1] ).to.be.an( 'object' ); + expect( emails[1] ).to.have.property( 'id' ).and.be.ok; + expect( emails[1] ).to.have.property( 'subject' ).and.equal( email_2.subject ); + + emailId = emails[0].id; + + done(); + + }, done ); + }; + ctrl.req.body = [ email_1, email_2 ]; + ctrl.req.user = { + id: userId, + firstname: 'Vitalis', + lastname: 'Popkin', + account: { + id:1, + logo: 'LOGO', + name: "AccountName" + } + }; + ctrl.postAction(); + } ); + + } ); + + describe( '.listAction()', function () { + + it( 'should be able to find all emails by userId', function ( done ) { + + ctrl.send = function ( result, status ) { + + expect( status ).to.equal( 200 ); + + expect( result ).to.be.an( 'array' ).and.not.empty; + expect( result ).to.have.length.above( 1 ); + + expect( result[0] ).to.be.an( 'object' ); + expect( result[0] ).to.have.property( 'id' ).and.be.ok; + expect( result[0] ).to.have.property( 'subject' ).and.be.ok; + expect( result[0] ).to.have.property( 'body' ).and.be.ok; + expect( result[0] ).to.have.property( 'UserId' ).and.equal( userId ); + expect( result[0] ).to.have.property( 'emailAttachments' ).and.be.an( 'array' ).and.be.empty; + + done(); + }; + + ctrl.req.user = { + id: userId, + firstname: 'Vitalis', + lastname: 'Popkin', + account: { + id:1, + logo: 'LOGO', + name: "AccountName" + } + }; + + ctrl.listAction(); + } ); + + it( 'should be able to give us an empty array if no emails for userId', function ( done ) { + + ctrl.send = function ( result, status ) { + + expect( status ).to.equal( 200 ); + + expect( result ).to.be.an( 'array' ).and.be.empty; + + done(); + }; + + ctrl.req.user = { + id: userId+1000, + firstname: 'Vitalis', + lastname: 'Popkin', + account: { + id:1, + logo: 'LOGO', + name: "AccountName" + } + }; + + ctrl.listAction(); + } ); + + } ); + + describe( '.getAction()', function () { + + it( 'should be able to find email bu emailId for user with userId', function ( done ) { + + ctrl.send = function ( result, status ) { + + expect( status ).to.equal( 200 ); + + expect( result ).to.be.an( 'object' ); + expect( result ).to.contain.keys( 'emailAttachments' ); + + expect( result ).to.have.property( 'id' ).and.equal( emailId ); + expect( result ).to.have.property( 'subject' ).and.equal( 'some subject #2' ); + expect( result ).to.have.property( 'body' ).and.equal( 'some body #2' ); + expect( result ).to.have.property( 'isDelivered' ).and.equal( false ); + expect( result ).to.have.property( 'sentAttemps' ).and.equal( 0 ); + expect( result ).to.have.property( 'isOpened' ).and.equal( false ); + expect( result ).to.have.property( 'token' ).and.be.ok; + expect( result ).to.have.property( 'EmailTemplateId' ).and.equal( 45 ); + expect( result ).to.have.property( 'UserId' ).and.equal( userId ); + expect( result ).to.have.property( 'AccountId' ).and.equal( 1 ); + expect( result ).to.have.property( 'dump' ).and.be.ok; + expect( result.emailAttachments ).to.be.an( 'array' ).and.be.empty; + + expect( result ).to.have.property( 'users' ).to.be.an( 'array' ).and.have.length( 4 ); + expect( result.users[0] ).to.have.property( 'id' ).and.be.ok; + expect( result.users[0] ).to.have.property( 'status' ).and.be.ok; + expect( result.users[0] ).to.have.property( 'UserId' ).and.be.ok; + expect( result.users[0] ).to.have.property( 'EmailId' ).and.equal( emailId ); + + done(); + }; + + ctrl.req.params = { id: emailId }; + + ctrl.req.user = { + id: userId, + firstname: 'Vitalis', + lastname: 'Popkin', + account: { + id:1, + logo: 'LOGO', + name: "AccountName" + } + }; + + ctrl.getAction(); + } ); + + it( 'should be able to get the error if the emailId does not exist', function ( done ) { + + ctrl.send = function ( result, status ) { + + expect( status ).to.equal( 403 ); + + expect( result ).to.be.an( 'string' ).and.be.ok; + + done(); + }; + + ctrl.req.params = { id: 151515 }; + + ctrl.req.user = { + id: userId, + firstname: 'Vitalis', + lastname: 'Popkin', + account: { + id:1, + logo: 'LOGO', + name: "AccountName" + } + }; + + ctrl.getAction(); + } ); + + it( 'should be able to get the error if email does not belong to the user', function ( done ) { + + ctrl.send = function ( result, status ) { + + expect( status ).to.equal( 403 ); + + expect( result ).to.be.an( 'string' ).and.be.ok; + + done(); + }; + + ctrl.req.params = { id: emailId }; + + ctrl.req.user = { + id: 1212, + firstname: 'Vitalis', + lastname: 'Popkin', + account: { + id:1, + logo: 'LOGO', + name: "AccountName" + } + }; + + ctrl.getAction(); + } ); + + } ); + + describe( '.putAction()', function () { + + it( 'should be able to get the error if we call the putAction', function ( done ) { + + ctrl.send = function ( result, status ) { + + expect( status ).to.equal( 403 ); + + expect( result ).to.be.an( 'string' ).and.be.ok; + + done(); + }; + + ctrl.putAction(); + } ); + + } ); + + describe( '.sendAction()', function () { + + it.skip( 'should be able to send email', function ( done ) { + + ctrl.send = function ( result, status ) { + + expect( status ).to.equal( 200 ); + + expect( result ).to.be.an( 'array' ).and.is.not.empty; + expect( result ).to.have.length.above( 1 ); + expect( result[0] ).to.have.property( 'status' ).and.equal( 'sent' ); + expect( result[0] ).to.have.property( 'id' ).and.be.ok; + + done(); + }; + + ctrl.req.params = { id: emailId }; + + ctrl.req.body = { type: 'html' }; + + ctrl.req.user = { + id: userId, + firstname: 'Vitalis', + lastname: 'Popkin', + account: { + id: 1, + logo: 'LOGO', + name: "AccountName" + } + }; + + ctrl.sendAction(); + } ); + + it( 'should be able to get the error if the emailId does not exist', function ( done ) { + + ctrl.send = function ( result, status ) { + + expect( status ).to.equal( 403 ); + + expect( result ).to.be.an( 'string' ).and.be.ok; + + done(); + }; + + ctrl.req.params = { id: 15151515151515 }; + + ctrl.req.body = { type: 'html' }; + + ctrl.req.user = { + id: userId, + firstname: 'Vitalis', + lastname: 'Popkin', + account: { + id: 1, + logo: 'LOGO', + name: "AccountName" + } + }; + + ctrl.sendAction(); + } ); + + it( 'should be able to give us an empty array if no emails for userId', function ( done ) { + + ctrl.send = function ( result, status ) { + + expect( status ).to.equal( 403 ); + + expect( result ).to.be.an( 'string' ).and.be.ok; + + done(); + }; + + ctrl.req.params = { id: emailId }; + + ctrl.req.body = { type: 'html' }; + + ctrl.req.user = { + id: userId + 10, + firstname: 'Vitalis', + lastname: 'Popkin', + account: { + id: 1, + logo: 'LOGO', + name: "AccountName" + } + }; + + ctrl.sendAction(); + } ); + + } ); + + describe( '.deleteAction()', function () { + + it( 'should be able to get the error if the emailId does not exist', function ( done ) { + + ctrl.send = function ( result, status ) { + + expect( status ).to.equal( 403 ); + + expect( result ).to.be.an( 'string' ).and.be.ok; + + done(); + }; + + ctrl.req.params = { id: 15151515515 }; + + ctrl.req.user = { + id: userId, + firstname: 'Vitalis', + lastname: 'Popkin', + account: { + id:1, + logo: 'LOGO', + name: "AccountName" + } + }; + + ctrl.deleteAction(); + } ); + + it( 'should be able to get the error if email does not belong to the user', function ( done ) { + + ctrl.send = function ( result, status ) { + + expect( status ).to.equal( 403 ); + + expect( result ).to.be.an( 'string' ).and.be.ok; + + done(); + }; + + ctrl.req.params = { id: emailId }; + + ctrl.req.user = { + id: userId + 10, + firstname: 'Vitalis', + lastname: 'Popkin', + account: { + id:1, + logo: 'LOGO', + name: "AccountName" + } + }; + + ctrl.deleteAction(); + } ); + + it( 'should be able to delete email by emailId for user with userId', function ( done ) { + + ctrl.send = function ( result, status ) { + + expect( status ).to.equal( 200 ); + + expect( result ).to.be.an( 'string' ).and.be.ok; + + done(); + }; + + ctrl.req.params = { id: emailId }; + + ctrl.req.user = { + id: userId, + firstname: 'Vitalis', + lastname: 'Popkin', + account: { + id:1, + logo: 'LOGO', + name: "AccountName" + } + }; + + ctrl.deleteAction(); + } ); + + } ); + + +} ); \ No newline at end of file diff --git a/modules/clever-email/tests/unit/test.service.EmailService.js b/modules/clever-email/tests/unit/test.service.EmailService.js index d119ac7..849cf0e 100644 --- a/modules/clever-email/tests/unit/test.service.EmailService.js +++ b/modules/clever-email/tests/unit/test.service.EmailService.js @@ -7,22 +7,22 @@ var expect = require ( 'chai' ).expect , Q = require ( 'q' ); describe( 'service.EmailService', function () { - var Service, Model, UsrModel, EmailUsrModel; + var Service, Model, UserModel, EmailUserModel; var userId, userId_1, userId_2, userId_3, userId_4 - , emailId_1; + , emailId_1, emailId_1_token, emailId_2; before( function ( done ) { - testEnv( function ( EmailService, EmailModel, UserModel, EmailUserModel ) { + testEnv( function ( _EmailService_, _EmailModel_, _UserModel_, _EmailUserModel_ ) { - Service = EmailService; - Model = EmailModel; - UsrModel = UserModel; - EmailUsrModel = EmailUserModel; + Service = _EmailService_; + Model = _EmailModel_; + UserModel = _UserModel_; + EmailUserModel = _EmailUserModel_; var user = { username: 'sender', email: 'sender@mail.ru', password: '1234' }; - UsrModel + UserModel .create( user ) .success( function( sender ) { @@ -41,6 +41,7 @@ describe( 'service.EmailService', function () { } ); describe( '.formatReplyAddress( emailToken )', function () { + it( 'should return an address according to environmentName', function ( done ) { var emailToken = '15da5AS15A1s'; @@ -63,6 +64,7 @@ describe( 'service.EmailService', function () { } ); describe( '.formatData( data )', function () { + it( 'should return an object with filtered data', function ( done ) { var data = { @@ -151,48 +153,6 @@ describe( 'service.EmailService', function () { } ); - describe( '.formatRepliedData( data )', function () { - it( 'should return an object with filtered data', function ( done ) { - - var data = { - emailToken: '25asd151s6dasd', - reply: 'test reply text', - emailId: 48, - subject: 'some subject', - replyHTML: 'html reply', - userEmail: 'user@mail.com', - from: 'Bob ', - to: 'BoltHR ' - }; - - var replyData = Service.formatRepliedData( data ); - - expect( replyData ).to.be.an( 'object' ); - expect( replyData ).to.contain.keys( 'id', 'reply', 'from', 'to', 'token', 'isDelivered', 'sentAttemps', 'isOpened', 'dump' ); - - expect( replyData ).to.have.property( 'id' ).and.is.not.ok; - expect( replyData ).to.have.property( 'reply' ).and.equal( data.reply ); - expect( replyData ).to.have.property( 'from' ).and.equal( data.userEmail ); - expect( replyData ).to.have.property( 'to' ).and.equal( data.to ); - expect( replyData ).to.have.property( 'token' ).and.is.ok; - expect( replyData ).to.have.property( 'isDelivered' ).and.equal( true ); - expect( replyData ).to.have.property( 'sentAttemps' ).and.equal( 1 ); - expect( replyData ).to.have.property( 'isOpened' ).and.equal( false ); - expect( replyData ).to.have.property( 'dump' ); - - var dump = JSON.parse( replyData.dump ); - - expect( dump ).to.have.property( 'subject' ).and.equal( data.subject ); - expect( dump ).to.have.property( 'html' ).and.equal( data.replyHTML ); - expect( dump ).to.have.property( 'replyAddress' ).and.is.ok; - expect( dump ).to.have.property( 'from' ).and.equal( data.from ); - expect( dump ).to.have.property( 'to' ).and.equal( data.to ); - - done(); - } ); - - } ); - describe( '.saveEmailAssociation( savedEmail, fData )', function () { before( function ( done ) { @@ -204,7 +164,7 @@ describe( 'service.EmailService', function () { users[3] = { username: 'petka', email: 'petka@mail.ru', password: '1234' }; users.forEach( function( user ) { - promise.push( UsrModel.create( user ) ) + promise.push( UserModel.create( user ) ) }); Q.all( promise ) @@ -266,12 +226,13 @@ describe( 'service.EmailService', function () { expect( email ).to.have.property( 'subject' ).and.equal( data.subject ); emailId_1 = email.id; + emailId_1_token = email.token Service .saveEmailAssociation( email, emailData ) .then( function( res ) { - EmailUsrModel + EmailUserModel .findAll( { where: { EmailId: email.id, UserId: userId_1 } } ) .success( function( res ) { @@ -280,7 +241,7 @@ describe( 'service.EmailService', function () { expect( res[0] ).to.have.property( 'id' ).and.be.ok; expect( res[0] ).to.have.property( 'status' ).and.equal( 'cc' ); - EmailUsrModel + EmailUserModel .findAll( { where: { EmailId: email.id, UserId: userId_4 } } ) .success( function( res ) { @@ -313,7 +274,7 @@ describe( 'service.EmailService', function () { accId: 1, to: { id: 15, - email: 'to1@email.za' + email: 'denshikov_vovan@mail.ru' }, hasTemplate: false, cc: [ { id: userId_1, email:'cc1@mail.ru' }, { id: userId_2, email: 'cc2@mail.ru' } ], @@ -337,7 +298,9 @@ describe( 'service.EmailService', function () { expect( email ).to.have.property( 'id' ).and.be.ok; expect( email ).to.have.property( 'subject' ).and.equal( data.subject ); - EmailUsrModel + emailId_2 = email.id; + + EmailUserModel .findAll( { where: { EmailId: email.id, UserId: userId_1 } } ) .success( function( res ) { @@ -346,7 +309,7 @@ describe( 'service.EmailService', function () { expect( res[0] ).to.have.property( 'id' ).and.be.ok; expect( res[0] ).to.have.property( 'status' ).and.equal( 'cc' ); - EmailUsrModel + EmailUserModel .findAll( { where: { EmailId: email.id, UserId: userId_4 } } ) .success( function( res ) { @@ -443,14 +406,14 @@ describe( 'service.EmailService', function () { describe( '.getEmailByIds( userId, emailId )', function () { - it( 'should be able to find email bu emailId for user with userId', function ( done ) { + it( 'should be able to find email by emailId for user with userId', function ( done ) { Service .getEmailByIds( userId, emailId_1 ) .then( function( result ) { expect( result ).to.be.an( 'object' ); - expect( result ).to.contain.keys( 'emailReplies', 'emailAttachments' ); + expect( result ).to.contain.keys( 'emailAttachments' ); expect( result ).to.have.property( 'id' ).and.equal( emailId_1 ); expect( result ).to.have.property( 'subject' ).and.equal( 'some subject' ); @@ -463,7 +426,6 @@ describe( 'service.EmailService', function () { expect( result ).to.have.property( 'UserId' ).and.equal( userId ); expect( result ).to.have.property( 'AccountId' ).and.equal( 1 ); expect( result ).to.have.property( 'dump' ).and.be.ok; - expect( result.emailReplies ).to.be.an( 'array' ).and.be.empty; expect( result.emailAttachments ).to.be.an( 'array' ).and.be.empty; expect( result ).to.have.property( 'users' ).to.be.an( 'array' ).and.have.length( 4 ); @@ -523,13 +485,189 @@ describe( 'service.EmailService', function () { expect( result[0] ).to.have.property( 'subject' ).and.be.ok; expect( result[0] ).to.have.property( 'body' ).and.be.ok; expect( result[0] ).to.have.property( 'UserId' ).and.equal( userId ); - expect( result[0] ).to.have.property( 'emailReplies' ).and.be.an( 'array' ).and.be.empty; expect( result[0] ).to.have.property( 'emailAttachments' ).and.be.an( 'array' ).and.be.empty; done(); }, done ) } ); + it( 'should be able to give us an empty array if no emails for userId', function ( done ) { + + Service + .listEmails( 10000 ) + .then( function( result ) { + + expect( result ).to.be.an( 'array' ).and.be.empty; + + done(); + }, done ) + } ); + + } ); + + describe.skip( '.sendEmail( email, body, type )', function () { + + it( 'should be able to send email', function ( done ) { + var data = { + title: 'some title', + subject: 'some subject', + body: 'this is the test mail', + userId: 1, + accId: 1, + to: { + id: 15, + email: 'denshikov_vovan@mail.ru' + }, + hasTemplate: false, + cc: [], + bcc: [], + accLogo: 'LoGo', + accName: 'Default Name', + userFirstName: 'Dmitrij', + userLastName: 'Safronov', + EmailTemplateId: 45 + }; + + var email = Service.formatData( data ).email; + + Service + .sendEmail( email, '
hi. it is the first email
', 'text' ) + .then( function( result ) { + + expect( result ).to.be.an( 'array' ).and.is.not.empty; + expect( result ).to.have.length( 1 ); + expect( result[0] ).to.have.property( 'status' ).and.equal( 'sent' ); + expect( result[0] ).to.have.property( 'id' ).and.be.ok; + + done(); + }, done ) + } ); + } ); + describe( '.handleEmailSending( userId, emailId, type )', function () { + + it.skip( 'should be able to send email', function ( done ) { + + var type = 'html'; + + Service + .handleEmailSending( userId, emailId_1, type ) + .then( function( result ) { + + expect( result ).to.be.an( 'array' ).and.is.not.empty; + expect( result ).to.have.length.above( 1 ); + expect( result[0] ).to.have.property( 'status' ).and.equal( 'sent' ); + expect( result[0] ).to.have.property( 'id' ).and.be.ok; + + done(); + }, done ) + } ); + + it( 'should be able to get the error if the emailId does not exist', function ( done ) { + + var type = 'html'; + + Service + .handleEmailSending( userId, 1000, type ) + .then( function( result ) { + + expect( result ).to.be.an( 'object' ); + expect( result ).to.have.property( 'statuscode' ).and.equal( 403 ); + expect( result ).to.have.property( 'message' ).and.be.ok; + + done(); + }, done ) + } ); + + it( 'should be able to give us an empty array if no emails for userId', function ( done ) { + + var type = 'html'; + + Service + .handleEmailSending( userId_1, emailId_1, type ) + .then( function( result ) { + + expect( result ).to.be.an( 'object' ); + expect( result ).to.have.property( 'statuscode' ).and.equal( 403 ); + expect( result ).to.have.property( 'message' ).and.be.ok; + + done(); + }, done ) + } ); + + } ); + + describe( '.deleteEmail( userId, emailId )', function () { + + it( 'should be able to get the error if the emailId does not exist', function ( done ) { + + Service + .deleteEmail( userId, 151515 ) + .then( function( result ) { + + expect( result ).to.be.an( 'object' ); + expect( result ).to.have.property( 'statuscode' ).and.equal( 403 ); + expect( result ).to.have.property( 'message' ).and.be.ok; + + Service + .findById( emailId_1 ) + .then( function( email ) { + + expect( email ).to.be.an( 'object' ).and.be.ok; + expect( email ).to.have.property( 'id' ).and.equal( emailId_1 ); + + done(); + }) + .fail( done ); + }, done ) + } ); + + it( 'should be able to get the error if email does not belong to the user', function ( done ) { + + Service + .deleteEmail( userId+15, emailId_1 ) + .then( function( result ) { + + expect( result ).to.be.an( 'object' ); + expect( result ).to.have.property( 'statuscode' ).and.equal( 403 ); + expect( result ).to.have.property( 'message' ).and.be.ok; + + Service + .findById( emailId_1 ) + .then( function( email ) { + + expect( email ).to.be.an( 'object' ).and.be.ok; + expect( email ).to.have.property( 'id' ).and.equal( emailId_1 ); + + done(); + }) + .fail( done ); + }, done ) + } ); + + it( 'should be able to delete email by emailId for user with userId', function ( done ) { + + Service + .deleteEmail( userId, emailId_1 ) + .then( function( result ) { + + expect( result ).to.be.an( 'object' ); + expect( result ).to.have.property( 'statuscode' ).and.equal( 200 ); + + Service + .findById( emailId_1 ) + .then( function( email ) { + + expect( email ).to.not.be.ok; + + done(); + }) + .fail( done ); + }, done ) + } ); + + } ); + + } ); \ No newline at end of file diff --git a/modules/clever-email/tests/unit/test.service.EmailTemplateService.js b/modules/clever-email/tests/unit/test.service.EmailTemplateService.js deleted file mode 100644 index 2285dd2..0000000 --- a/modules/clever-email/tests/unit/test.service.EmailTemplateService.js +++ /dev/null @@ -1,192 +0,0 @@ -var expect = require ( 'chai' ).expect - , request = require ( 'supertest' ) - , path = require( 'path' ) - , app = require ( path.resolve( __dirname + '/../../../../' ) + '/index.js' ) - , testEnv = require ( 'utils' ).testEnv(); - -describe( 'service.EmailTemplateService', function () { - var Service, Model, UserModel, user; - - before( function ( done ) { - testEnv( function ( EmailTemplateService, EmailTemplateModel, UserModel ) { - - Service = EmailTemplateService; - Model = EmailTemplateModel; - UserModel = UserModel; - - done(); - - }, done ); - } ); - - describe( '.formatData', function () { - it( 'should return an object with filtered data', function ( done ) { - - var data = { - title: 'some title', - subject: 'some subject', - body: 'some body', - accId: 15, - userId: 5, - useDefault: true, - permission: 'none', - someattr: 'this attribute is not part of the model' - }; - - var emailData = Service.formatData( data ); - - expect( emailData ).to.have.property( 'id' ).and.equal( null ); - expect( emailData ).to.have.property( 'title' ).and.equal( data.title ); - expect( emailData ).to.have.property( 'subject' ).and.equal( data.subject ); - expect( emailData ).to.have.property( 'body' ).and.equal( data.body ); - expect( emailData ).to.have.property( 'AccountId' ).and.equal( data.accId ); - expect( emailData ).to.have.property( 'UserId' ).and.equal( data.userId ); - expect( emailData ).to.have.property( 'isActive' ).and.equal( false ); - expect( emailData ).to.have.property( 'isDefault' ).and.equal( false ); - expect( emailData ).to.have.property( 'useDefault' ).and.equal( true ); - expect( emailData ).to.have.property( 'hasPermission' ).and.equal( true ); - - expect( emailData ).to.not.have.property( 'permission' ); - expect( emailData ).to.not.have.property( 'someattr' ); - - done(); - } ); - - } ); - -// describe( '.handleEmailTemplateUpdate', function () { -// -// it( 'should return code 401 and a message if email template does not exist ', function ( done ) { -// var data = { id: 1233444444444444444444 }; -// -// EmailTemplateService -// .handleEmailTemplateUpdate( user.AccountId, user.id, data ) -// .then( function ( msg ) { -// -// msg.should.be.a( 'object' ); -// msg.should.have.property( 'statuscode', 401 ); -// msg.should.have.property( 'message' ).and.not.be.empty; -// -// done(); -// -// } ) -// .fail( done ); -// -// } ); -// -// it( 'should call .updateEmailTemplate ', function ( done ) { -// -// -// var emailTemplateData = { -// title: 'schedule interview', subject: 'congradulations! we would love to meet you', body: 'Dear John Appleseed,
we are interest by your skill set your qualifications for the job', useDefault: 'false', permission: 'none', isActive: true, useDefault: false, isDefault: false, AccountId: user.AccountId, UserId: user.id -// }; -// -// var spyUpdateEmailTemplate = sinon.spy( EmailTemplateService, 'updateEmailTemplate' ); -// -// EmailTempalteModel -// .create( emailTemplateData ) -// .success( function ( emailTemplate ) { -// -// emailTemplate.should.have.property( 'id' ); -// emailTemplate.should.have.property( 'title', 'schedule interview' ); -// -// EmailTemplateService -// .handleEmailTemplateUpdate( user.AccountId, user.id, emailTemplate ) -// .then( function () { -// -// spyUpdateEmailTemplate.called.should.be.true; -// done(); -// -// } ) -// .fail( done ); -// } ) -// .error( done ); -// } ); -// } ); -// -// describe( '.updateEmailTemplate', function () { -// -// it( 'should update an email Template record', function ( done ) { -// -// var emailTemplateData = { -// title: 'schedule interview', subject: 'congradulations! we would love to meet you', body: 'Dear John Appleseed,
we are interest by your skill set your qualifications for the job', useDefault: 'false', permission: 'none', isActive: true, useDefault: false, isDefault: false, AccountId: user.AccountId, UserId: user.id -// }; -// -// var updatedData = { -// id: null, title: 'This is an updated title', subject: 'congradulations! we would love to meet you', body: 'Dear John Appleseed,
we are interest by your skill set your qualifications for the job', useDefault: 'false', permission: 'none', isActive: true, useDefault: false, isDefault: false -// }; -// -// -// EmailTempalteModel -// .create( emailTemplateData ) -// .success( function ( emailTemplate ) { -// -// emailTemplate.should.have.property( 'title', 'schedule interview' ); -// updatedData['id'] = emailTemplate.id; -// -// EmailTemplateService -// .updateEmailTemplate( emailTemplate, updatedData ) -// .then( function ( updatedEmailTpl ) { -// -// updatedEmailTpl.id.should.equal( emailTemplate.id ); -// updatedEmailTpl.title.should.not.equal( emailTemplateData.title ); -// updatedEmailTpl.title.should.equal( updatedData.title ); -// -// done(); -// } ) -// .fail( done ); -// } ) -// .error( done ); -// } ); -// } ); -// -// describe( '.removeEmailTemplate', function () { -// -// it( 'should remove an email template record ', function ( done ) { -// -// var emailTemplateData = { -// title: 'schedule interview', subject: 'congradulations! we would love to meet you', body: 'Dear John Appleseed,
we are interest by your skill set your qualifications for the job', useDefault: 'false', permission: 'none', isActive: true, useDefault: false, isDefault: false, AccountId: user.AccountId, UserId: user.id -// }; -// -// -// EmailTempalteModel -// .create( emailTemplateData ) -// .success( function ( emailTemplate ) { -// -// emailTemplate.should.have.property( 'id' ); -// emailTemplate.should.have.property( 'title', 'schedule interview' ); -// -// EmailTemplateService -// .removeEmailTemplate( user.id, emailTemplate.id ) -// .then( function ( msg ) { -// -// msg.should.be.a( 'object' ); -// msg.should.have.property( 'statuscode', 200 ); -// msg.should.have.property( 'message' ).and.not.be.empty; -// -// done(); -// -// } ) -// .fail( done ); -// } ) -// .error( done ); -// } ); -// -// it( 'should return code 400 and a message if email template does not exist ', function ( done ) { -// var tplId = '1233333333333333333333'; -// -// EmailTemplateService -// .removeEmailTemplate( user.id, tplId ) -// .then( function ( msg ) { -// -// msg.should.be.a( 'object' ); -// msg.should.have.property( 'statuscode', 400 ); -// msg.should.have.property( 'message' ).and.not.be.empty; -// -// done(); -// -// } ) -// .fail( done ); -// } ); -// } ); -} ); \ No newline at end of file diff --git a/modules/orm/config/default.json b/modules/orm/config/default.json index 8d4759b..ec3ec69 100644 --- a/modules/orm/config/default.json +++ b/modules/orm/config/default.json @@ -11,20 +11,13 @@ } }, "modelAssociations": { - "AccountModel": { - "hasMany": [ "EmailTemplateModel" ] - }, + "AccountModel": {}, "UserModel": { - "belongsTo": [ "AccountModel" ], - "hasMany" : [ "EmailTemplateModel" ] - }, - "EmailTemplateModel": { - "hasMany": [ "UserModel", "EmailModel" ], - "belongsTo": [ "AccountModel", "UserModel" ] + "belongsTo": [ "AccountModel" ] }, "EmailModel": { - "hasMany": [ "EmailAttachmentModel", "EmailReplyModel" ], - "belongsTo": [ "UserModel", "AccountModel", "EmailTemplateModel" ] + "hasMany": [ "EmailAttachmentModel" ], + "belongsTo": [ "UserModel", "AccountModel" ] }, "EmailUserModel":{ "belongsTo" : [ "EmailModel", "UserModel" ] @@ -32,9 +25,6 @@ "EmailAttachmentModel": { "belongsTo": [ "EmailModel" ] }, - "EmailReplyModel": { - "belongsTo": [ "EmailModel" ] - }, "ExampleModel": {} } From d8c06e418b323a31b76cadba4436a654b0f69874 Mon Sep 17 00:00:00 2001 From: Volodymyr Denshchykov Date: Thu, 23 Jan 2014 19:12:00 +0200 Subject: [PATCH 06/24] fix err in mailer --- modules/clever-email/lib/mailer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/clever-email/lib/mailer.js b/modules/clever-email/lib/mailer.js index dc50866..a35a7e4 100644 --- a/modules/clever-email/lib/mailer.js +++ b/modules/clever-email/lib/mailer.js @@ -11,7 +11,7 @@ module.exports = function ( config ) { ? sendgrid : confs.MailGun.isActive ? mailgun - : sendgrid; + : mandrill; return mailer( config ); }; \ No newline at end of file From 24a369a4e9ff47132884aeff255d046fb041c206 Mon Sep 17 00:00:00 2001 From: Volodymyr Denshchykov Date: Thu, 23 Jan 2014 19:18:34 +0200 Subject: [PATCH 07/24] added UserController.requiresLogin in routing --- modules/clever-email/routes.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/modules/clever-email/routes.js b/modules/clever-email/routes.js index e5dbbb9..7b61587 100644 --- a/modules/clever-email/routes.js +++ b/modules/clever-email/routes.js @@ -1,11 +1,13 @@ module.exports = function ( app, - EmailController ) { + EmailController, + UserController ) +{ - app.get( '/emails', EmailController.attach() ); - app.get( '/emails/:id', EmailController.attach() ); - app.post( '/emails', EmailController.attach() ); - app['delete']( '/emails/:id', EmailController.attach() ); - app.post('/emails/:id/send' , EmailController.attach()); + app.get( '/emails', UserController.requiresLogin, EmailController.attach() ); + app.get( '/emails/:id', UserController.requiresLogin, EmailController.attach() ); + app.post( '/emails', UserController.requiresLogin, EmailController.attach() ); + app['delete']( '/emails/:id', UserController.requiresLogin, EmailController.attach() ); + app.post('/emails/:id/send' , UserController.requiresLogin, EmailController.attach()); }; \ No newline at end of file From 2f19b2f49bfe6f3e3f1f000a13e4c77d9e64d221 Mon Sep 17 00:00:00 2001 From: Volodymyr Denshchykov Date: Fri, 24 Jan 2014 00:38:56 +0200 Subject: [PATCH 08/24] clear package.json --- modules/clever-email/package.json | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/modules/clever-email/package.json b/modules/clever-email/package.json index 6caa0ba..0e86feb 100644 --- a/modules/clever-email/package.json +++ b/modules/clever-email/package.json @@ -8,11 +8,8 @@ "mandrill-api": "*", "sendgrid": "~0.4.2", "mailgun-js": "*", - "ejs": "*", "shortid": "~2.0.0", - "cheerio": "~0.12.4", - "sequelize": "", - "moment": "" + "sequelize": "1.7.0-beta8" }, "author": { "name": "Clevertech", From bde7d2754cba8159914e8eb0cb839169a82f4634 Mon Sep 17 00:00:00 2001 From: Volodymyr Denshchykov Date: Wed, 29 Jan 2014 22:05:32 +0200 Subject: [PATCH 09/24] feature(module) created clever-auth module WIP --- modules/auth/models/orm/AccountModel.js | 118 --- modules/auth/package.json | 27 - .../{auth => clever-auth}/config/default.json | 6 +- .../controllers/AccountController.js | 251 ++++++ .../clever-auth/controllers/UserController.js | 430 +++++++++ .../clever-auth/models/orm/AccountModel.js | 87 ++ .../models/orm/SubscriptionModel.js | 35 + .../models/orm/UserModel.js | 45 +- modules/{auth => clever-auth}/module.js | 38 +- modules/clever-auth/package.json | 36 + modules/clever-auth/routes.js | 31 + modules/clever-auth/schema/seedData.json | 75 ++ .../clever-auth/services/AccountService.js | 531 +++++++++++ modules/clever-auth/services/UserService.js | 354 ++++++++ .../unit/test.controllers.UserController.js | 256 ++++++ .../tests/unit/test.service.UserService.js | 841 ++++++++++++++++++ modules/orm/config/default.json | 7 +- package.json | 2 +- 18 files changed, 2977 insertions(+), 193 deletions(-) delete mode 100644 modules/auth/models/orm/AccountModel.js delete mode 100644 modules/auth/package.json rename modules/{auth => clever-auth}/config/default.json (77%) create mode 100644 modules/clever-auth/controllers/AccountController.js create mode 100644 modules/clever-auth/controllers/UserController.js create mode 100644 modules/clever-auth/models/orm/AccountModel.js create mode 100644 modules/clever-auth/models/orm/SubscriptionModel.js rename modules/{auth => clever-auth}/models/orm/UserModel.js (68%) rename modules/{auth => clever-auth}/module.js (57%) create mode 100644 modules/clever-auth/package.json create mode 100644 modules/clever-auth/routes.js create mode 100644 modules/clever-auth/schema/seedData.json create mode 100644 modules/clever-auth/services/AccountService.js create mode 100644 modules/clever-auth/services/UserService.js create mode 100644 modules/clever-auth/tests/unit/test.controllers.UserController.js create mode 100644 modules/clever-auth/tests/unit/test.service.UserService.js diff --git a/modules/auth/models/orm/AccountModel.js b/modules/auth/models/orm/AccountModel.js deleted file mode 100644 index c3bc71d..0000000 --- a/modules/auth/models/orm/AccountModel.js +++ /dev/null @@ -1,118 +0,0 @@ -module.exports = function(sequelize, DataTypes) { - return sequelize.define("Account", { - id: { - type: DataTypes.INTEGER, - primaryKey: true, - autoIncrement: true - }, - name: { - type: DataTypes.STRING, - validate: { - len: [ 2, 50 ] - } - }, - subdomain: { - type: DataTypes.STRING, - allowNull: false, - unique: true, - validate: { - isAlphanumeric: true, - len: [ 3, 16 ] - } - }, - active: { - type: DataTypes.BOOLEAN, - allowNull: false, - defaultValue: false - }, - logo : { - type: DataTypes.STRING, - allowNull: true - }, - themeColor : { - type: DataTypes.STRING, - allowNull: true - }, - email : { - type: DataTypes.STRING, - allowNull: true, - unique: true, - validate: { - isEmail: true - } - }, - emailFwd : { - type: DataTypes.STRING, - allowNull: true - }, - info: { - type: DataTypes.TEXT, - allowNull: true - }, - benefits: { - type: DataTypes.BOOLEAN, - allowNull: false, - defaultValue: false - }, - dependents: { - type: DataTypes.BOOLEAN, - allowNull: false, - defaultValue: false - }, - documents: { - type: DataTypes.BOOLEAN, - allowNull: false, - defaultValue: false - }, - emergency: { - type: DataTypes.BOOLEAN, - allowNull: false, - defaultValue: false - }, - boarding: { - type: DataTypes.BOOLEAN, - allowNull: false, - defaultValue: false - }, - timeoff: { - type: DataTypes.BOOLEAN, - allowNull: false, - defaultValue: false - }, - training: { - type: DataTypes.BOOLEAN, - allowNull: false, - defaultValue: false - }, - date_format: { - type: DataTypes.STRING, - allowNull: false, - defaultValue: "yyyy-mm-dd" - }, - number_format: { - type: DataTypes.STRING, - allowNull: false, - defaultValue: "123'456.78" - }, - currency: { - type: DataTypes.STRING, - allowNull: false, - defaultValue: "USD" - }, - quickstart: { - type: DataTypes.BOOLEAN, - allowNull: false, - defaultValue: false - } - }, - { - instanceMethods: { - toJSON: function () { - var values = this.values; - delete values.createdAt; - delete values.updatedAt; - return values; - } - } - }); -}; diff --git a/modules/auth/package.json b/modules/auth/package.json deleted file mode 100644 index 76a2591..0000000 --- a/modules/auth/package.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "name": "auth", - "version": "0.0.1", - "private": true, - "dependencies": { - "passport": "~0.1.18", - "passport-facebook": "~1.0.2", - "passport-local": "~0.1.6", - "connect-redis": "~1.4.6", - "connect": "~2.12.0", - "redis": "~0.10.0" - }, - "author": { - "name": "Clevertech", - "email": "info@clevertech.biz", - "web": "http://www.clevertech.biz" - }, - "collaborators": [ - "Richard Gustin " - ], - "description": "Clevertech Authentication Module", - "keywords": [ - "" - ], - "main": "module.js", - "devDependencies": {} -} diff --git a/modules/auth/config/default.json b/modules/clever-auth/config/default.json similarity index 77% rename from modules/auth/config/default.json rename to modules/clever-auth/config/default.json index 55907c5..2efe392 100644 --- a/modules/auth/config/default.json +++ b/modules/clever-auth/config/default.json @@ -1,5 +1,5 @@ { - "auth": { + "clever-auth": { "secretKey": "youMustChangeMe", "redis": { @@ -8,5 +8,9 @@ "prefix": "", "key": "" } + }, + + "clever-auth-data": { + } } \ No newline at end of file diff --git a/modules/clever-auth/controllers/AccountController.js b/modules/clever-auth/controllers/AccountController.js new file mode 100644 index 0000000..fd46cd0 --- /dev/null +++ b/modules/clever-auth/controllers/AccountController.js @@ -0,0 +1,251 @@ +var config = require( 'config' ); + +module.exports = function ( AccountService, UserService ) { + + return (require( 'classes' ).Controller).extend( + { + service: AccountService, + + requiresUniqueSubdomain: function ( req, res, next ) { + var subdomain = req.body.subdomain + , company = req.body.company; + + if ( !company || !subdomain ) { + return res.json( 400, "Company name and URL are mandatory" ); + } + + AccountService + .find( { where: { subdomain: subdomain } } ) + .then( function ( result ) { + if ( result.length ) { + return res.json( 403, 'This URL "' + subdomain + '" is already taken' ); + } + + next(); + } ) + .fail( function () { + return res.send( 500 ); + next() + } ) + }, + + requiresUniqueUser: function ( req, res, next ) { + var email = req.body.email; + + UserService + .find( { where: { email: email } } ) + .then( function ( result ) { + + if ( result.length ) { + return res.json( 403, 'This email "' + email + '" is already taken' ); + } + + next(); + } ) + .fail( function ( err ) { + return res.send( 500 ); + next(); + } ); + }, + + formatData: function ( req, res, next ) { + var accData = req.user.account + , newData = { + name: req.body.name || accData.name, + logo: req.body.logo || accData.logo, + info: req.body.info || accData.info, + companyUrl: req.body.companyUrl || accData.companyUrl, + hrEmail: req.body.hrEmail || accData.hrEmail, + email: req.body.email || accData.email, + themeColor: req.body.themeColor || accData.themeColor, + showMap: req.body.showMap !== undefined ? req.body.showMap : accData.showMap + }; + + req.body = newData; + next(); + }, + + isValidEmailDomain: function ( req, res, next ) { + var data = req.body + , pattern = /@(gmail|mail|yahoo|aol|aim|hotmail|facebook|cox|verizon|icloud|apple|outlook|yandex|163|126|gmx)\./i; + + if ( !data.email ) { + res.send( 400, 'Email is mandatory' ); + return; + } + + if ( pattern.test( data.email ) && (/stag|prod/gi.test( config.environmentName ) ) ) { + res.send( 400, 'Please register for BoltHR with your corporate email address.' ); + return; + } + + next(); + } + + }, + /* @Prototype */ + { + //Private function + listAction: function () { + this.getAction(); + return; + }, + + //Public function + getAction: function () { + var accId = this.req.user.account.id; + + AccountService.find( { where: { id: accId, active: true } } ) + .then( this.proxy( 'handleAccountRetrieval' ) ) + .fail( this.proxy( 'handleException' ) ); + }, + + handleAccountRetrieval: function ( account ) { + var acc = ( !account || !account.length) ? {} : account[0]; + this.send( acc, 200 ); + }, + + postAction: function () { + var data = this.req.body; + + if ( data.id ) { + return this.putAction(); + } + + AccountService + .processRegistration( data ) + .then( this.proxy( "handleDefaultOptionalJobFields" ) ) + .fail( this.proxy( "handleException" ) ); + + }, + + handleDefaultOptionalJobFields: function ( ua ) { + AccountJobFieldService + .seedOptionalJobFields( ua, config.optionalJobFieldNames ) + .then( this.proxy( "handleRegistrationCode" ) ) + .fail( this.proxy( "handleException" ) ); + }, + + handleRegistrationCode: function ( ua ) { + AccountService + .generateRegistrationHash( ua, config.secretKey ) + .then( this.proxy( "handleMailVerificationCode", ua ) ) + .fail( this.proxy( "handleException" ) ); + }, + + handleMailVerificationCode: function ( ua, hash ) { + AccountService + .mailRegistrationCode( ua, hash ) + .then( this.proxy( "send", 200 ) ) + .fail( this.proxy( "handleException" ) ); + }, + + putAction: function () { + var accId = this.req.user.account.id + , data = this.req.body; + + AccountService + .update( accId, data ) + .then( this.proxy( "send", 200 ) ) + .fail( this.proxy( "handleException", 200 ) ); + }, + + confirmAction: function () { + var token = this.req.body.token + , accountId = this.req.body.accountId; + + AccountService + .findById( accountId ) + .then( this.proxy( "handleTokenConfirmation", token ) ) + .fail( this.proxy( "handleException" ) ); + + }, + + handleTokenConfirmation: function ( token, account ) { + + if ( !account ) { + this.send( {}, 400 ); + return; + } + + AccountService + .generateRegistrationHash( account, config.secretKey ) + .then( this.proxy( "varifyAccount", account, token ) ) + .fail( this.proxy( "handleException" ) ); + + }, + + varifyAccount: function ( account, token, hash ) { + if ( token === hash ) { + + account + .updateAttributes( { active: true } ) + .success( this.proxy( "handleSignInAfterConfirmation" ) ) + .error( this.proxy( "handleException" ) ); + + } else { + this.send( 'Unable to verify confirmation link', 400 ); + } + }, + + handleSignInAfterConfirmation: function ( account ) { + var self = this; + + UserService + .find( {where: {AccountId: account.id}} ) + .then( function ( users ) { + users[0] + .updateAttributes( {active: true, confirmed: true}, ['active', 'confirmed'] ) + .success( function () { + UserService + .getUserFullDataJson( { 'AccountId': account.id } ) + .then( self.proxy( "signInAfterConfirmation" ) ) + .fail( self.proxy( "handleException" ) ) + } ); + } ); + }, + + signInAfterConfirmation: function ( user ) { + this.req.login( user, this.proxy( 'createUserSession', user ) ); + }, + + createUserSession: function ( user, err ) { + if ( err ) return this.handleException( err ); + this.send( user, 200 ); + }, + + resendAction: function () { + var email = this.req.body.email; + + UserService + .getUserFullDataJson( { email: email } ) + .then( this.proxy( "verifyAccountOwnership" ) ) + .fail( this.proxy( "handleException" ) ); + + }, + + verifyAccountOwnership: function ( user ) { + if ( !user || (user.role.name.toLowerCase() != 'owner') ) { + this.send( {}, 403 ); + return; + } + + if ( user.account.active ) { + this.send( 'Your account is active. Try to recover your password instead.', 403 ); + return; + } + + this.handleRegistrationCode( user ); + }, + + handleServiceMessage: function ( obj ) { + + if ( obj.statuscode ) { + this.send( obj.message, obj.statuscode ); + return; + } + + this.send( obj, 200 ); + } + } ); +}; \ No newline at end of file diff --git a/modules/clever-auth/controllers/UserController.js b/modules/clever-auth/controllers/UserController.js new file mode 100644 index 0000000..4d1f26b --- /dev/null +++ b/modules/clever-auth/controllers/UserController.js @@ -0,0 +1,430 @@ +var crypto = require( 'crypto' ) + , moment = require( 'moment' ) + , passport = require( 'passport' ) + , LocalStrategy = require( 'passport-local' ).Strategy; + +module.exports = function ( UserService ) { + + passport.serializeUser( function ( user, done ) { + done( null, user ); + } ); + + passport.deserializeUser( function ( user, done ) { + done( null, user ) + } ); + + passport.use( new LocalStrategy( function ( username, password, done ) { + var credentials = { + email: username, + password: crypto.createHash( 'sha1' ).update( password ).digest( 'hex' ), + confirmed: true, + active: true + }; + + UserService.authenticate( credentials ) + .then( done.bind( null, null ) ) + .fail( done ); + } ) ); + + return (require( 'classes' ).Controller).extend( + { + service: UserService, + + requiresLogin: function ( req, res, next ) { + if ( !req.isAuthenticated() ) + return res.send( 401 ); + next(); + }, + + requiresRole: function ( roleName ) { + return function ( req, res, next ) { + if ( !req.isAuthenticated() || !req.session.passport.user.role || + req.session.passport.user.role.name.indexOf( roleName ) === -1 ) + return res.send( 403 ); + next(); + }; + }, + + requiresAdminRights: function ( req, res, next ) { + if ( !req.isAuthenticated() || !req.session.passport.user || !req.session.passport.user.hasAdminRight ) + return res.send( 403 ); + next(); + }, + + checkPasswordRecoveryData: function ( req, res, next ) { + var userId = req.body.userId || req.body.user, + password = req.body.password, + token = req.body.token; + //expTime = req.body.exp; + + + if ( !token || !userId ) { + return res.json( 400, 'Invalid Token.' ); + } + + //TODO: Here should be a regexp for password validation + if ( !password ) { + return res.json( 400, 'Password does not much the requirements' ); + } + + //Check timestamp + // var now = moment.utc().valueOf(); + // if( now > expTime ){ + // return res.json(400,'Token has been expired'); + // } + + next(); + }, + isUserInTheSameAccount: function ( req, res, next ) { + var uId = req.params.id, + myAccId = req.user.account.id; + + if ( !uId ) { + res.send( 401, {} ); + return; + } + + UserService.findById( uId ) + .then( function ( user ) { + if ( user.AccountId != myAccId ) { + res.send( 401, {} ); + return; + } + ; + + next(); + } ) + .fail( function () { + res.send( 500, {} ); + return; + } ); + + } + }, + { + listAction: function () { + var accId = this.req.user.account.id; + + UserService.find( { where: { "AccountId": accId } } ) + .then( this.proxy( 'send', 200 ) ) + .fail( this.proxy( 'handleException' ) ); + }, + + getAction: function () { + var uId = this.req.params.id; + + UserService.getUserFullDataJson( { id: uId } ) + .then( this.proxy( 'send', 200 ) ) + .fail( this.proxy( 'handleException' ) ); + }, + + postAction: function () { + var me = this.req.user + , data = this.req.body; + + //Its an update + if ( data.id || data.AccountId ) { + return this.putAction(); + } + ; + + if ( !data.email ) { + this.send( 'Email is mandatory', 400 ); + return; + } + ; + + data['AccountId'] = me.account.id; + + var tplData = { + firstName: this.req.user.firstname, accountSubdomain: this.req.user.account.subdomain, userFirstName: data.firstname, userEmail: data.email, tplTitle: 'BoltHR: Account Confirmation', subject: this.req.user.firstname + ' wants to add you to their recruiting team!' + }; + + UserService + .createUser( data, tplData ) + .then( this.proxy( 'handleServiceMessage' ) ) + .fail( this.proxy( 'handleException' ) ); + }, + + putAction: function () { + var meId = this.req.user.id + , accId = this.req.user.AccountId + , userId = this.req.params.id + , data = this.req.body; + + if ( !userId ) { + this.send( {}, 400 ); + return; + } + + UserService + .handleUpdateUser( accId, userId, data ) + .then( this.proxy( 'handleSessionUpdate', meId ) ) + .fail( this.proxy( 'handleException' ) ); + + }, + + handleSessionUpdate: function ( meId, user ) { + console.log( user ); + if ( user.id && (meId === user.id) ) { + this.loginUserJson( user ); + return; + } + + this.handleServiceMessage( user ); + }, + + deleteAction: function () { + var uId = this.req.params.id; + + UserService.destroy( uId ) + .then( this.proxy( 'handleDeletionData' ) ) + .fail( this.proxy( 'handleException' ) ); + + }, + + handleDeletionData: function ( obj ) { + if ( !obj.deletedAt ) { + this.send( {}, 500 ); + } + this.send( {}, 200 ); + }, + + handleUpdatePassword: function ( newPassword, user ) { + + if ( user.length ) { + user = user[0]; + user.updateAttributes( { + password: crypto.createHash( 'sha1' ).update( newPassword ).digest( 'hex' ) + } ).success( function ( user ) { + this.send( {status: 200, results: user} ); + }.bind( this ) + ).fail( this.proxy( 'handleException' ) ); + } else { + this.send( {status: 400, error: "Incorrect old password!"} ); + } + }, + + loginAction: function () { + + passport.authenticate( 'local', this.proxy( 'handleLocalUser' ) )( this.req, this.res, this.next ); + }, + + handleLocalUser: function ( err, user ) { + + if ( err ) return this.handleException( err ); + if ( !user ) return this.send( {}, 403 ); + this.loginUserJson( user ); + }, + + loginUserJson: function ( user ) { + + this.req.login( user, this.proxy( 'handleLoginJson', user ) ); + }, + + handleLoginJson: function ( user, err ) { + if ( err ) return this.handleException( err ); + this.send( user, 200 ); + }, + + currentAction: function () { + var user = this.req.user + , reload = this.req.query.reload || false; + + if ( !user ) { + this.send( {}, 404 ); + return; + } + + if ( !reload ) { + this.send( user, 200 ); + return; + } + + UserService + .getUserFullDataJson( { id: user.id } ) + .then( this.proxy( 'loginUserJson' ) ) + .fail( this.proxy( 'handleException' ) ); + + }, + + authorizedUser: function ( user ) { + console.dir( user ); + if ( user ) { + this.req.login( user ); + this.res.send( 200 ); + } else { + this.res.send( 403 ); + } + }, + + logoutAction: function () { + this.req.logout(); + this.res.send( {}, 200 ); + }, + + recoverAction: function () { + var email = this.req.body.email; + + if ( !email ) { + this.send( 'missing email', 400 ); + return; + } + + UserService + .find( { where: { email: email } } ) + .then( this.proxy( 'handlePasswordRecovery' ) ) + .fail( this.proxy( 'handleException' ) ); + }, + + handlePasswordRecovery: function ( user ) { + + if ( !user.length ) { + this.send( {}, 403 ); + return; + } + + UserService + .generatePasswordResetHash( user[0] ) + .then( this.proxy( 'handleMailRecoveryToken' ) ) + .fail( this.proxy( 'handleException' ) ); + + }, + + handleMailRecoveryToken: function ( recoverData ) { + + if ( !recoverData.hash && recoverData.statuscode ) { + this.handleServiceMessage( recoverData ); + return; + } + + UserService + .mailPasswordRecoveryToken( recoverData ) + .then( this.proxy( "handleServiceMessage" ) ) + .fail( this.proxy( "handleException" ) ); + }, + + resetAction: function () { + var userId = this.req.body.userId || this.req.body.user, + password = this.req.body.password, + token = this.req.body.token; + + UserService + .findById( userId ) + .then( this.proxy( 'handlePasswordReset', password, token ) ) + .fail( this.proxy( 'handleException' ) ); + + }, + + handlePasswordReset: function ( password, token, user ) { + + if ( !user ) { + this.send( {}, 403 ); + return; + } + + UserService + .generatePasswordResetHash( user ) + .then( this.proxy( 'verifyResetTokenValidity', user, password, token ) ) + .fail( this.proxy( 'handleException' ) ); + + }, + + verifyResetTokenValidity: function ( user, newPassword, token, resetData ) { + + if ( !resetData.hash && resetData.statuscode ) { + this.handleServiceMessage( resetData ); + return; + } + + var hash = resetData.hash; + if ( token != hash ) { + return this.send( 'Invalid token', 400 ); + } + ; + + this.handleUpdatePassword( newPassword, [user] ); + + }, + + confirmAction: function () { + var password = this.req.body.password + , token = this.req.body.token + , userId = this.req.body.userId; + + + UserService.findById( userId ) + .then( this.proxy( 'handleAccountConfirmation', password, token ) ) + .fail( this.proxy( 'handleException' ) ); + + }, + + handleAccountConfirmation: function ( pass, token, user ) { + if ( !user ) { + this.send( 'Invalid confirmation link', 403 ); + return; + } + if ( user.confirmed ) { + this.send( 'You have already activated your account', 400 ); + return; + } + + UserService.generatePasswordResetHash( user ) + .then( this.proxy( "confirmAccount", user, pass, token ) ) + .fail( this.proxy( "handleException" ) ); + + }, + + confirmAccount: function ( user, pass, token, hashobj ) { + + if ( !hashobj.hash && hashobj.statuscode ) { + this.handleServiceMessage( hashobj ); + return; + } + + if ( token !== hashobj.hash ) { + this.send( 'Invalid token', 400 ); + return; + } + + var newpass = crypto.createHash( 'sha1' ).update( pass ).digest( 'hex' ); + + user + .updateAttributes( { + active: true, + confirmed: true, + password: newpass + } ) + .success( this.proxy( 'send', 200 ) ) + .error( this.proxy( 'handleException' ) ); + + }, + + resendAction: function () { + var me = this.req.user + , userId = this.req.params.id + , data = this.req.body; + + var tplData = { + firstName: this.req.user.firstname, accountSubdomain: this.req.user.account.subdomain, userFirstName: '', userEmail: '', tplTitle: 'BoltHR: Account Confirmation', subject: this.req.user.firstname + ' wants to add you to their recruiting team!' + }; + + UserService + .resendAccountConfirmation( me.account.id, userId, tplData ) + .then( this.proxy( 'handleServiceMessage' ) ) + .then( this.proxy( 'handleException' ) ); + + }, + + handleServiceMessage: function ( obj ) { + + if ( obj.statuscode ) { + this.send( obj.message, obj.statuscode ); + return; + } + + this.send( obj, 200 ); + } + + } ); +}; \ No newline at end of file diff --git a/modules/clever-auth/models/orm/AccountModel.js b/modules/clever-auth/models/orm/AccountModel.js new file mode 100644 index 0000000..75d58b5 --- /dev/null +++ b/modules/clever-auth/models/orm/AccountModel.js @@ -0,0 +1,87 @@ +module.exports = function ( sequelize, DataTypes ) { + return sequelize.define( "Account", + { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true + }, + name: { + type: DataTypes.STRING, + validate: { + len: [ 2, 50 ] + } + }, + subdomain: { + type: DataTypes.STRING, + allowNull: false, + unique: true, + validate: { + isAlphanumeric: true, + len: [ 3, 16 ] + } + }, + active: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false + }, + logo: { + type: DataTypes.STRING, + allowNull: true + }, + themeColor: { + type: DataTypes.STRING, + allowNull: true + }, + email: { + type: DataTypes.STRING, + allowNull: true, + unique: true, + validate: { + isEmail: true + } + }, + emailFwd: { + type: DataTypes.STRING, + allowNull: true + }, + info: { + type: DataTypes.TEXT, + allowNull: true + }, + swfDomain: { + type: DataTypes.STRING, + allowNull: true, + defaultValue: null + }, + swfDomainAttempts: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: 0 + }, + companyUrl: { + type: DataTypes.STRING, + allowNull: true + }, + hrEmail: { + type: DataTypes.STRING, + allowNull: true + }, + showMap: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false + } + }, + { + instanceMethods: { + toJSON: function () { + var values = this.values; + delete values.createdAt; + delete values.updatedAt; + return values; + } + } + } ); +}; \ No newline at end of file diff --git a/modules/clever-auth/models/orm/SubscriptionModel.js b/modules/clever-auth/models/orm/SubscriptionModel.js new file mode 100644 index 0000000..2281bd5 --- /dev/null +++ b/modules/clever-auth/models/orm/SubscriptionModel.js @@ -0,0 +1,35 @@ +module.exports = function ( sequelize, DataTypes ) { + return sequelize.define( "Subscription", + { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true + }, + name: { + type: DataTypes.STRING, + validate: { + len: [ 2, 32 ] + } + }, + description: { + type: DataTypes.STRING, + validate: { + len: [ 10, 100 ] + } + + }, + price: { + type: DataTypes.DECIMAL( 10, 2 ), + allowNull: false + }, + period: { + type: DataTypes.STRING, + validate: { + len: [ 1, 20 ] + } + } + + }, + {} ); +}; \ No newline at end of file diff --git a/modules/auth/models/orm/UserModel.js b/modules/clever-auth/models/orm/UserModel.js similarity index 68% rename from modules/auth/models/orm/UserModel.js rename to modules/clever-auth/models/orm/UserModel.js index 6c20417..8462373 100644 --- a/modules/auth/models/orm/UserModel.js +++ b/modules/clever-auth/models/orm/UserModel.js @@ -1,10 +1,15 @@ -module.exports = function(sequelize, DataTypes) { - return sequelize.define("User", { +module.exports = function ( sequelize, DataTypes ) { + return sequelize.define( "User", + { id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true }, + title: { + type: DataTypes.STRING, + allowNull: true + }, username: { type: DataTypes.STRING, allowNull: false, @@ -25,53 +30,39 @@ module.exports = function(sequelize, DataTypes) { firstname: { type: DataTypes.STRING, allowNull: true - // validate: { - // isAlphanumeric: true - // } }, lastname: { type: DataTypes.STRING, allowNull: true - // validate: { - // isAlphanumeric: true - // } }, - phone : { - type:DataTypes.STRING, + phone: { + type: DataTypes.STRING, allowNull: true }, - confirmed : { + confirmed: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: false }, - active : { + active: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: true }, - hasAdminRight : { + hasAdminRight: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: false }, - accessedAt : { - type: DataTypes.DATE, - allowNull: true - }, - AccountId : { - type: DataTypes.INTEGER - }, - EmailTemplateId : { - type: DataTypes.INTEGER + accessedAt: { + type: DataTypes.DATE } - }, { paranoid: true, getterMethods: { - fullName: function(){ - return [ this.getDataValue('firstname'), this.getDataValue('lastname')].join(' '); + fullName: function () { + return [ this.getDataValue( 'firstname' ), this.getDataValue( 'lastname' )].join( ' ' ); } }, instanceMethods: { @@ -82,5 +73,5 @@ module.exports = function(sequelize, DataTypes) { return values; } } - }); -}; + } ); +}; \ No newline at end of file diff --git a/modules/auth/module.js b/modules/clever-auth/module.js similarity index 57% rename from modules/auth/module.js rename to modules/clever-auth/module.js index d7b740c..0df28ea 100644 --- a/modules/auth/module.js +++ b/modules/clever-auth/module.js @@ -4,29 +4,31 @@ var passport = require( 'passport' ) , RedisStore = require( 'connect-redis' )( injector.getInstance( 'express' ) ) , Module; -Module = ModuleClass.extend({ +Module = ModuleClass.extend( { + store: null, - preResources: function() { + preResources: function () { injector.instance( 'passport', passport ); + }, - configureApp: function( app, express ) { + configureApp: function ( app, express ) { // Setup the redis store - this.store = new RedisStore({ + this.store = new RedisStore( { host: this.config.redis.host, port: this.config.redis.port, prefix: this.config.redis.prefix + process.env.NODE_ENV + "_", password: this.config.redis.key - }); + } ); // Session management app.use( express.cookieParser() ); - app.use( express.session({ + app.use( express.session( { secret: this.config.secretKey, cookie: { secure: false, maxAge: 86400000 }, store: this.store - })); + } ) ); // Enable CORS app.use( this.proxy( 'enableCors' ) ); @@ -36,24 +38,24 @@ Module = ModuleClass.extend({ app.use( passport.session() ); }, - enableCors: function( req, res, next ) { - res.header("Access-Control-Allow-Origin", req.headers.origin); - res.header("Access-Control-Allow-Headers", "x-requested-with, content-type"); - res.header("Access-Control-Allow-Methods", "GET,PUT,POST,DELETE,OPTIONS"); - res.header("Access-Control-Allow-Credentials", "true"); - res.header("Access-Control-Max-Age", "1000000000"); + enableCors: function ( req, res, next ) { + res.header( "Access-Control-Allow-Origin", req.headers.origin ); + res.header( "Access-Control-Allow-Headers", "x-requested-with, content-type" ); + res.header( "Access-Control-Allow-Methods", "GET,PUT,POST,DELETE,OPTIONS" ); + res.header( "Access-Control-Allow-Credentials", "true" ); + res.header( "Access-Control-Max-Age", "1000000000" ); // intercept OPTIONS method - if ('OPTIONS' == req.method) { - res.send(200); + if ( 'OPTIONS' == req.method ) { + res.send( 200 ); } else { next(); } }, - preShutdown: function() { + preShutdown: function () { this.store.client.quit(); } -}); +} ); -module.exports = new Module( 'auth', injector ); \ No newline at end of file +module.exports = new Module( 'clever-auth', injector ); \ No newline at end of file diff --git a/modules/clever-auth/package.json b/modules/clever-auth/package.json new file mode 100644 index 0000000..0154c4d --- /dev/null +++ b/modules/clever-auth/package.json @@ -0,0 +1,36 @@ +{ + "name": "clever-auth", + "version": "0.0.1", + "private": true, + "dependencies": { + "passport": "~0.1.18", + "passport-facebook": "~1.0.2", + "passport-local": "~0.1.6", + "connect-redis": "~1.4.6", + "connect": "~2.12.0", + "redis": "~0.10.0", + "q": "", + "lodash": "2.4.1", + "sequelize": "1.7.0-beta8", + "shortid": "~2.0.0", + "moment": "", + "async": "" + }, + "author": { + "name": "Clevertech", + "email": "info@clevertech.biz", + "web": "http://www.clevertech.biz" + }, + "collaborators": [ + "Richard Gustin " + ], + "description": "Clevertech Authentication Module", + "keywords": [ + "" + ], + "main": "module.js", + "devDependencies": { + "chai": "*", + "mocha": "*" + } +} \ No newline at end of file diff --git a/modules/clever-auth/routes.js b/modules/clever-auth/routes.js new file mode 100644 index 0000000..21df16f --- /dev/null +++ b/modules/clever-auth/routes.js @@ -0,0 +1,31 @@ +module.exports = function ( + app, + AccountController, + UserController ) +{ + + app.post('/users/confirm', UserController.checkPasswordRecoveryData, UserController.attach()); + app.get('/users', UserController.requiresLogin, UserController.attach()); + app.post('/users', UserController.requiresLogin, UserController.attach()); + app.get('/users/:id', UserController.requiresLogin, UserController.isUserInTheSameAccount, UserController.attach()); + app.put('/users/:id', UserController.requiresLogin, UserController.isUserInTheSameAccount, UserController.attach()); + app.post('/users/:id', UserController.requiresLogin, UserController.isUserInTheSameAccount, UserController.attach()); + + app.post('/users/:id/resend', UserController.requiresAdminRights, UserController.attach()); + app.get('/user/current', UserController.attach()); + app.get('/user/logout', UserController.requiresLogin, UserController.attach()); + app.post('/user/login', UserController.attach()); + app.post('/user/recover', UserController.attach()); + app.post('/user/reset', UserController.checkPasswordRecoveryData, UserController.attach()); + + app['delete']('/users/:id', UserController.requiresLogin, UserController.isUserInTheSameAccount, UserController.attach()); + app.all('/user/:action/:id?', UserController.requiresLogin, UserController.attach()); + app.all('/user/?:action?', UserController.requiresLogin, UserController.attach()); + + app.get('/account', UserController.requiresLogin, AccountController.attach()); + app.put('/account', UserController.requiresLogin, AccountController.formatData, AccountController.attach()); + app.post('/account', AccountController.isValidEmailDomain, AccountController.requiresUniqueSubdomain, AccountController.requiresUniqueUser, AccountController.attach()); + app.post('/account/confirm', AccountController.attach()); + app.post('/account/resend', AccountController.attach()); + +}; \ No newline at end of file diff --git a/modules/clever-auth/schema/seedData.json b/modules/clever-auth/schema/seedData.json new file mode 100644 index 0000000..f37333d --- /dev/null +++ b/modules/clever-auth/schema/seedData.json @@ -0,0 +1,75 @@ +{ + "Subscription": [ + { + "name": "Trial", + "description": "This is a 30 days trial subscription", + "price": 0.00, + "period": "30days" + } + ], + + "Account": [ + { + "name": "Default Account", + "subdomain": "dev", + "emailFwd": "dev23io@stage.bolthr.clevertech.biz", + "active": true, + "associations": { + "Subscription": { + "name": "Trial" + } + } + } + ], + + "User": [ + { + "username": "dimitrios@clevertech.biz", + "email": "dimitrios@clevertech.biz", + "password": "9ac20922b054316be23842a5bca7d69f29f69d77", + "firstname": "dimitris", + "lastname": "ioakimidis", + "phone": "8900000", + "active": true, + "confirmed": true, + "hasAdminRight": true, + "associations": { + "Account": { + "name": "Default Account" + } + } + }, + { + "username": "adnan@clevertech.biz", + "email": "adnan@clevertech.biz", + "password": "f10e2821bbbea527ea02200352313bc059445190", + "firstname": "Adnan", + "lastname": "Ibrisimbegovic", + "phone": "8900000", + "active": true, + "confirmed": true, + "hasAdminRight": true, + "associations": { + "Account": { + "name": "Default Account" + } + } + }, + { + "username": "richard", + "email": "richard@clevertech.biz", + "password": "9ac20922b054316be23842a5bca7d69f29f69d77", + "firstname": "Richard", + "lastname": "Gustin", + "phone": "+61448048484", + "active": true, + "confirmed": true, + "hasAdminRight": true, + "associations": { + "Account": { + "name": "Default Account" + } + } + } + ] +} \ No newline at end of file diff --git a/modules/clever-auth/services/AccountService.js b/modules/clever-auth/services/AccountService.js new file mode 100644 index 0000000..43bdfdf --- /dev/null +++ b/modules/clever-auth/services/AccountService.js @@ -0,0 +1,531 @@ +var Q = require( 'q' ) + , async = require( 'async' ) + , Sequelize = require( 'sequelize' ) + , crypto = require( 'crypto' ) + , sendgrid = require( 'utils' ).sendgrid + , ejsFileRender = require( 'utils' ).ejsfilerender + , shortId = require( 'shortid' ) + , config = require( 'config' ) +// , appSeed = require( './../../config/appData' ) + , AccountService = null; + + +module.exports = function ( sequelize, + ORMAccountModel, + ORMUserModel, + ORMRoleModel, + ORMEmailTemplateModel, + ORMSubscriptionModel, + ORMPermissionModel, + ORMWorkflowModel, + ORMWorkflowStepsModel ) { + + if ( AccountService && AccountService.instance ) { + return AccountService.instance; + } + + AccountService = require( 'services' ).BaseService.extend( { + + formatRegistrationData: function ( data, operation ) { + var d = { user: {}, account: {}, roles: [] } + , emailURL = '' + , hashedId = shortId.seed( 1000 ).generate() + , envName = ( config.environmentName == 'DEV' ) ? 'dev' : ( config.environmentName == 'PROD' ) ? 'prod' : 'stage'; + + emailURL = ( envName != 'prod' ) + ? hashedId + '@' + envName + '.bolthr.clevertech.biz' + : hashedId + '@app-mail.bolthr.com'; + +// d['roles'] = appSeed['DefaultRoles']; +// d['rolePermissions'] = appSeed['RolePermissions']; +// d['templates'] = appSeed['DefaultEmailTemplates']; + + d['account']['name'] = data['company']; + d['account']['subdomain'] = data['subdomain']; + d['account']['emailFwd'] = emailURL; + d['account']['active'] = false; + d['account']['SubscriptionId'] = null; + + d['user']['title'] = data['title'] || null; + d['user']['username'] = ( data['username'] ) ? data['username'] : data['email']; + d['user']['email'] = data['email']; + d['user']['firstname'] = data['firstname']; + d['user']['lastname'] = data['lastname']; + d['user']['password'] = crypto.createHash( 'sha1' ).update( data['password'] ).digest( 'hex' ); + d['user']['phone'] = data['phone'] || null; + d['user']['active'] = false; + d['user']['confirmed'] = false; + d['user']['hasAdminRight'] = true; + d['user']['RoleId'] = null; + d['user']['AccountId'] = null; + + return d; + }, + + processRegistration: function ( data ) { + var deferred = Q.defer() + , fData = this.formatRegistrationData( data ); + + this + .handleRegistrationStep1( fData ) + .then( deferred.resolve ) + .fail( deferred.reject ); + + return deferred.promise; + }, + + handleRegistrationStep1: function ( data ) { + var deferred = Q.defer() + , chainer = new Sequelize.Utils.QueryChainer(); + + //Perform all seeds + chainer.add( ORMAccountModel.create( data['account'] ) ); + chainer.add( ORMUserModel.create( data['user'] ) ); + chainer.add( ORMSubscriptionModel.find( { where: { name: 'Trial' } } ) ); + chainer.add( ORMPermissionModel.findAll() ); + + chainer + .run() + .success( function ( results ) { + var account = results[0] + , user = results[1] + , subsciption = results[2]; + + this + .handleRegistrationStep2( account, user, subsciption.id, data ) + .then( deferred.resolve ) + .fail( deferred.reject ); + + }.bind( this ) ) + .error( deferred.reject ); + + return deferred.promise; + }, + + handleRegistrationStep2: function ( account, user, subId, data ) { + var deferred = Q.defer() + , chainer = new Sequelize.Utils.QueryChainer(); + + data['roles'] = data['roles'].map( function ( x ) { + x['AccountId'] = account.id; + return x; + } ); + + data['templates'] = data['templates'].map( function ( x ) { + x['UserId'] = user.id; + x['AccountId'] = account.id; + return x; + } ); + + chainer.add( account.updateAttributes( { SubscriptionId: subId } ) ); + chainer.add( ORMRoleModel.bulkCreate( data['roles'] ) ); + chainer.add( ORMEmailTemplateModel.bulkCreate( data['templates'] ) ); + + + chainer + .run() + .success( function ( results ) { + + this + .handleRegistrationStep3( account, user, data ) + .then( deferred.resolve ) + .fail( deferred.reject ); + + }.bind( this ) ) + .error( deferred.reject ); + + return deferred.promise; + }, + + handleRegistrationStep3: function ( account, user, data ) { + var deferred = Q.defer() + , service = this + , roleOwnerId = null + , chainer = new Sequelize.Utils.QueryChainer(); + + + ORMRoleModel + .findAll( { where: { AccountId: account.id } } ) + .success( function ( roles ) { + + var role = null; + while ( role = roles.pop() ) { + + if ( role.name == 'Owner' ) { + roleOwnerId = role.id; + } + + rolePerms = data['rolePermissions'][role.name].map( function ( x ) { + return ORMPermissionModel.build( {id: x.id} ) + } ); + chainer.add( role.setPermissions( rolePerms ) ); + } + + chainer.add( user.updateAttributes( {RoleId: roleOwnerId, AccountId: account.id } ) ); + + chainer + .run() + .success( function ( results ) { + var userJSON = results[results.length - 1]; + userJSON['account'] = account; + + //We have to send userJSON back to the controller + service + .handleRegistrationStep4( account ) + .then( function () { + deferred.resolve( userJSON ); + } ) + .fail( deferred.reject ); + } ) + .error( deferred.reject ); + } ) + .error( deferred.reject ); + + return deferred.promise; + }, + + handleRegistrationStep4: function ( account ) { + var deferred = Q.defer(); + var self = this; + + async.series( [ + function ( cb ) { + self + .generateAdvancedWorkflow( account ) + .then( cb ) + .fail( cb ); + }, + function ( cb ) { + self + .generateSimpleWorkflow( account ) + .then( cb ) + .fail( cb ); + } + ], + function ( err, results ) { + if ( err ) { + deferred.reject(); + } else { + deferred.resolve(); + } + } ); + + return deferred.promise; + }, + + generateAdvancedWorkflow: function ( account ) { + var deferred = Q.defer(); + + ORMWorkflowModel + .create( { + name: 'Advanced Workflow', + type: 'Applicant', + defaultWorkflow: true, + isEditable: false, + AccountId: account.id + } ) + .success( this.proxy( 'generateAdvancedWorkflowSteps', account, deferred ) ) + .error( deferred.reject ); + + return deferred.promise; + }, + + generateSimpleWorkflow: function ( account ) { + var deferred = Q.defer(); + + ORMWorkflowModel + .create( { + name: 'Simple Workflow', + type: 'Applicant', + defaultWorkflow: false, + isEditable: false, + AccountId: account.id + } ) + .success( this.proxy( 'generateSimpleWorkflowSteps', account, deferred ) ) + .error( deferred.reject ); + + return deferred.promise; + }, + + generateAdvancedWorkflowSteps: function ( account, deferred, workflow ) { + var defaultSteps = []; + + defaultSteps.push( { + name: 'Filed for Later', + statusType: 'Active' + } ); + + defaultSteps.push( { + name: 'Reviewed', + statusType: 'Active' + } ); + + defaultSteps.push( { + name: 'Phone Screened', + statusType: 'Active' + } ); + + defaultSteps.push( { + name: 'Scheduling Interview', + statusType: 'Active' + } ); + + defaultSteps.push( { + name: 'Interview Scheduled', + statusType: 'Active' + } ); + + defaultSteps.push( { + name: 'Interviewed', + statusType: 'Active' + } ); + + defaultSteps.push( { + name: 'Checking References', + statusType: 'Active' + } ); + + defaultSteps.push( { + name: 'Put On Hold', + statusType: 'Active' + } ); + + defaultSteps.push( { + name: 'Made Offer', + statusType: 'Active' + } ); + + defaultSteps.push( { + name: 'Hired Full Time', + statusType: 'Hired' + } ); + + defaultSteps.push( { + name: 'Hired Part Time', + statusType: 'Hired' + } ); + + defaultSteps.push( { + name: 'Interned', + statusType: 'Hired' + } ); + + defaultSteps.push( { + name: 'Contracted', + statusType: 'Hired' + } ); + + defaultSteps.push( { + name: 'Not a Fit', + statusType: 'Not-Hired' + } ); + + defaultSteps.push( { + name: 'Declined Offer', + statusType: 'Not-Hired' + } ); + + defaultSteps.push( { + name: 'Not Qualified', + statusType: 'Not-Hired' + } ); + + defaultSteps.push( { + name: 'Over Qualified', + statusType: 'Not-Hired' + } ); + + defaultSteps.push( { + name: 'Location', + statusType: 'Not-Hired' + } ); + + defaultSteps.push( { + name: 'Hired Elsewhere', + statusType: 'Not-Hired' + } ); + + async.forEach( + defaultSteps, + this.proxy( 'createWorkflowStep', workflow ), + function ( err ) { + deferred.resolve(); + } + ); + }, + + generateSimpleWorkflowSteps: function ( account, deferred, workflow ) { + var defaultSteps = []; + + defaultSteps.push( { + name: 'Scheduling Interview', + statusType: 'Active' + } ); + + defaultSteps.push( { + name: 'Interview Scheduled', + statusType: 'Active' + } ); + + defaultSteps.push( { + name: 'Interviewed', + statusType: 'Active' + } ); + + defaultSteps.push( { + name: 'Checking References', + statusType: 'Active' + } ); + + defaultSteps.push( { + name: 'Made Offer', + statusType: 'Active' + } ); + + defaultSteps.push( { + name: 'Hired Full Time', + statusType: 'Hired' + } ); + + defaultSteps.push( { + name: 'Hired Part Time', + statusType: 'Hired' + } ); + + defaultSteps.push( { + name: 'Interned', + statusType: 'Hired' + } ); + + defaultSteps.push( { + name: 'Contracted', + statusType: 'Hired' + } ); + + defaultSteps.push( { + name: 'Not a Fit', + statusType: 'Not-Hired' + } ); + + defaultSteps.push( { + name: 'Declined Offer', + statusType: 'Not-Hired' + } ); + + defaultSteps.push( { + name: 'Not Qualified', + statusType: 'Not-Hired' + } ); + + defaultSteps.push( { + name: 'Over Qualified', + statusType: 'Not-Hired' + } ); + + defaultSteps.push( { + name: 'Location', + statusType: 'Not-Hired' + } ); + + defaultSteps.push( { + name: 'Hired Elsewhere', + statusType: 'Not-Hired' + } ); + + async.forEach( + defaultSteps, + this.proxy( 'createWorkflowStep', workflow ), + function ( err ) { + deferred.resolve(); + } + ); + }, + + createWorkflowStep: function ( workflow, step, callback ) { + + ORMWorkflowStepsModel.create( { + name: step.name, + statusType: step.statusType, + WorkflowId: workflow.id + } ) + .success( callback ) + .error( callback ); + }, + + generateRegistrationHash: function ( ua, secretKey ) { + + var deferred = Q.defer() + , hash, md5 + , createdAt = (ua.account) ? ua.account.createdAt : ua.createdAt + , updatedAt = (ua.account) ? ua.account.updatedAt : ua.updatedAt + , subdomain = (ua.account) ? ua.account.subdomain : ua.subdomain; + + console.log( 'Generating hash with secretKey:', secretKey, 'createdAt: ', createdAt, subdomain, 'account_verify' ); + + md5 = crypto.createHash( 'md5' ); + md5.update( secretKey + createdAt + subdomain + 'account_verify', 'utf8' ); + hash = md5.digest( 'hex' ); + + deferred.resolve( hash ); + + return deferred.promise; + }, + + mailRegistrationCode: function ( ua, token ) { + var mailer = sendgrid( config.sendgrid ) + , bakeTemplate = ejsFileRender() + , link = config.hosturl + '/registration_confirm/' + ua.AccountId + '/' + token + + var payload = { + to: ua.email, + from: 'no-reply@bolthr.com', + subject: 'Account Activation Action', + link: link, + text: 'Please click on the link below to activate your account\n ' + link + }; + + var info = { + link: link, + firstname: ua.firstname, + companyLogo: 'http://app.bolthr.com/images/logo.png', + companyName: 'BoltHR', + tplTitle: 'BoltHR: Account Activation', + tplName: 'accountActivation' + }; + + return bakeTemplate( info ) + .then( function ( html ) { + + payload['html'] = html; + + return mailer( payload ); + } ) + .then( function () { + return 'Account created succefully'; + } ); + }, + + //Public Function + getAccountById: function ( accId ) { + var deferred = Q.defer(); + + this + .findOne( { where: { id: accId, Active: true }, attributes: ['id', 'info', 'name', 'subdomain', 'logo', 'themeColor', 'emailFwd', 'hrEmail', 'companyUrl', 'showMap'] } ) + .then( function ( account ) { + if ( !account ) { + return deferred.resolve( {} ); + } + + deferred.resolve( account ); + } ) + .fail( deferred.resolve ); + + return deferred.promise; + } + } ); + + AccountService.instance = new AccountService( sequelize ); + AccountService.Model = ORMAccountModel; + + return AccountService.instance; +}; \ No newline at end of file diff --git a/modules/clever-auth/services/UserService.js b/modules/clever-auth/services/UserService.js new file mode 100644 index 0000000..37dba9a --- /dev/null +++ b/modules/clever-auth/services/UserService.js @@ -0,0 +1,354 @@ +var Q = require( 'q' ) + , crypto = require( 'crypto' ) + , moment = require( 'moment' ) + , Sequelize = require( 'sequelize' ) + , sendgrid = require( 'utils' ).sendgrid + , ejsFileRender = require( 'utils' ).ejsfilerender + , config = require( 'config' ) + , UserService = null; + +module.exports = function ( sequelize, + ORMUserModel, + ORMAccountModel, + ORMRoleModel ) { + + var mailer = sendgrid( config.sendgrid ); + + if ( UserService && UserService.instance ) { + return UserService.instance; + } + + UserService = require( 'services' ).BaseService.extend( { + + authenticate: function ( credentials ) { + var deferred = Q.defer() + , service = this + , chainer = new Sequelize.Utils.QueryChainer(); + + service + .findOne( { where: credentials, include: [ ORMAccountModel, ORMRoleModel ] } ) + .then( function ( user ) { + if ( !user || !user.active ) { + return deferred.resolve(); + } + + chainer.add( user.updateAttributes( { accessedAt: moment.utc().format( 'YYYY-MM-DD HH:ss:mm' ) } ) ); + chainer.add( user.role.getPermissions() ); + + chainer + .runSerially() + .success( function ( result ) { + + var userJson = ( result[0] ) ? JSON.parse( JSON.stringify( result[0] ) ) : {}; + userJson.role.permissions = ( result[1] ) ? result[1] : []; + + deferred.resolve( userJson ); + } ) + .error( deferred.reject ); + } ) + .fail( deferred.reject ); + + return deferred.promise; + }, + + getUserFullDataJson: function ( options ) { + var deferred = Q.defer() + , service = this; + + service + .findOne( { where: options, include: [ ORMAccountModel, ORMRoleModel ] } ) + .then( function ( user ) { + + if ( !user ) { + return deferred.resolve(); + } + + user.role + .getPermissions() + .success( function ( perms ) { + + var userJson = JSON.parse( JSON.stringify( user ) ); + userJson.role.permissions = perms; + + deferred.resolve( userJson ); + } ) + .error( deferred.reject ); + } ) + .fail( deferred.reject ); + + return deferred.promise; + }, + + generatePasswordResetHash: function ( user, tplData ) { + var deferred = Q.defer() + , md5 = null + , hash = null + , expTime = null + , actionpath = ( !user.confirmed ) ? 'account_confirm' : 'password_reset_submit' + , mailsubject = ( !user.confirmed ) ? 'Account Confirmation' : 'Password Recovery'; + + + if ( !user || !user.createdAt || !user.updatedAt || !user.password || !user.email || !user.AccountId ) { + deferred.resolve( { statuscode: 403, message: 'Unauthorized' } ); + } else { + + md5 = crypto.createHash( 'md5' ); + md5.update( user.createdAt + user.updatedAt + user.password + user.email + user.AccountId + 'recover', 'utf8' ); + hash = md5.digest( 'hex' ); + + expTime = moment.utc().add( 'hours', 8 ).valueOf(); + + deferred.resolve( { + hash: hash, + expTime: expTime, + user: user, + action: actionpath, + mailsubject: mailsubject, + tplData: tplData || null + } ); + } + return deferred.promise; + }, + + mailPasswordRecoveryToken: function ( obj ) { + var mailer = sendgrid( config.sendgrid ) + , bakeTemplate = ejsFileRender() + , link = config.hosturl + '/' + obj.action + '?u=' + obj.user.id + '&t=' + obj.hash + '&n=' + encodeURIComponent( obj.user.fullName ); + + var payload = { to: obj.user.email, from: 'no-reply@bolthr.com' }; + + payload.text = (obj.action === 'account_confirm') + ? "Please click on the link below to activate your account\n " + link + : "Please click on the link below to enter a new password\n " + link; + + var info = { link: link, companyLogo: 'http://app.bolthr.com/images/logo.png', companyName: 'BoltHR' }; + + info.tplName = (obj.action === 'account_confirm') + ? 'userNew' + : 'passwordRecovery'; + + if ( !obj.tplData ) { + payload.subject = 'BoltHR: ' + obj.mailsubject; + + info.firstname = obj.user.firstname; + info.username = obj.user.username; + info.user = obj.user; + info.tplTitle = 'BoltHR: Password Recovery'; + + } else { + payload.subject = obj.tplData.subject; + + info.tplTitle = obj.tplData.tplTitle; + info.firstName = obj.tplData.firstName; + info.accountSubdomain = obj.tplData.accountSubdomain; + info.userFirstName = obj.tplData.userFirstName; + info.userEmail = obj.tplData.userEmail; + } + + return Q.resolve( 'Init Promise Chaining' ) + .then( function () { + return bakeTemplate( info ); + } ) + .then( function ( html ) { + payload.html = html; + + return mailer( payload ); + } ) + .then( function () { + return { statuscode: 200, message: 'Message successfully sent' }; + } ) + .fail( function ( err ) { + console.log( "\n\nERRR: ", err ); + return { statuscode: 500, message: err }; + } ); + }, + + createUser: function ( data, tplData ) { + var deferred = Q.defer() + , service = this + , usr; + + service + .findOne( { where: { email: data.email } } ) + .then( function ( user ) { + + if ( user !== null ) { + deferred.resolve( { statuscode: 400, message: 'Email already exist' } ); + return; + } + + service + .saveNewUser( data ) + .then( function ( user ) { + usr = user; + return service.generatePasswordResetHash( user, tplData ); + } ) + .then( service.mailPasswordRecoveryToken ) + .then( function () { + deferred.resolve( usr ); + } ) + .fail( function ( er ) { + console.log( er ); + deferred.reject(); + } ); + } ) + .fail( deferred.reject ); + + return deferred.promise; + }, + + saveNewUser: function ( data ) { + var deferred = Q.defer(); + + if ( !data.AccountId ) { + deferred.resolve( { statuscode: 403, message: 'Unauthorized' } ); + + } else { + data.username = data.username || data.email; + data.RoleId = data.RoleId || 7; //Default into general user + data.confirmed = false; + data.active = true; + data.password = ( data.password ) + ? crypto.createHash( 'sha1' ).update( data.password ).digest( 'hex' ) + : Math.random().toString( 36 ).slice( -14 ); + + this.create( data ) + .then( deferred.resolve ) + .fail( deferred.reject ); + } + + return deferred.promise; + }, + + resendAccountConfirmation: function ( accId, userId, tplData ) { + var deferred = Q.defer() + , service = this; + + service + .findOne( userId ) + .then( function ( user ) { + + if ( !user || ( user.AccountId !== accId ) ) { + deferred.resolve( { statuscode: 403, message: 'User doesn\'t exist or invalid account' } ); + return; + } + + if ( user.confirmed ) { + deferred.resolve( { statuscode: 403, message: user.email + ' , has already confirmed the account' } ); + return; + } + + tplData.userFirstName = user.firstname; + + tplData.userEmail = user.email; + + service.generatePasswordResetHash( user, tplData ) + .then( service.mailPasswordRecoveryToken ) + .then( function () { + deferred.resolve( { statuscode: 200, message: 'A confirmation link has been resent' } ); + } ) + .fail( deferred.reject ); + + } ) + .fail( deferred.resolve ); + + return deferred.promise; + }, + + handleUpdateUser: function ( accId, userId, data ) { + var deferred = Q.defer(); + + ORMUserModel + .find( { where: { id: userId, AccountId: accId } } ) + .success( function ( user ) { + + if ( !user ) { + deferred.resolve( { statuscode: 403, message: 'invalid id' } ); + return; + } + + if ( data.password && data.new_password ) { + if ( crypto.createHash( 'sha1' ).update( data.password ).digest( 'hex' ) !== user.password ) { + deferred.resolve( {statuscode: 403, message: 'Invalid password'} ); + return; + } + + data.hashedPassword = crypto.createHash( 'sha1' ).update( data.new_password ).digest( 'hex' ); + } + + this + .checkEmailAndUpdate( user, data ) + .then( deferred.resolve ) + .fail( deferred.reject ); + + }.bind( this ) ) + .error( deferred.reject ); + + return deferred.promise; + }, + + checkEmailAndUpdate: function ( user, data ) { + var deferred = Q.defer(); + + if ( data.email && ( user.email != data.email ) ) { + + ORMUserModel + .find( { where: { email: data.email } } ) + .success( function ( chkUser ) { + + if ( chkUser ) { + deferred.resolve( { statuscode: 400, message: "email already exists" } ); + return; + } + + this + .updateUser( user, data ) + .then( deferred.resolve ) + .fail( deferred.reject ); + + }.bind( this ) ) + .error( deferred.reject ); + } else { + + this + .updateUser( user, data ) + .then( deferred.resolve ) + .fail( deferred.reject ); + } + + return deferred.promise; + }, + + updateUser: function ( user, data ) { + var deferred = Q.defer(); + + user.firstname = data.firstname || user.firstname; + user.lastname = data.lastname || user.lastname; + user.email = data.email || user.email; + user.phone = data.phone || user.phone; + + if ( data.hashedPassword ) { + user.password = data.hashedPassword; + } + + user.save() + .success( function ( user ) { + + this + .getUserFullDataJson( { id: user.id } ) + .then( deferred.resolve ) + .fail( deferred.reject ); + + }.bind( this ) ) + .error( deferred.reject ); + + return deferred.promise; + } + + } ); + + UserService.instance = new UserService( sequelize ); + UserService.Model = ORMUserModel; + + return UserService.instance; +}; \ No newline at end of file diff --git a/modules/clever-auth/tests/unit/test.controllers.UserController.js b/modules/clever-auth/tests/unit/test.controllers.UserController.js new file mode 100644 index 0000000..70aa683 --- /dev/null +++ b/modules/clever-auth/tests/unit/test.controllers.UserController.js @@ -0,0 +1,256 @@ +var should = require( 'should' ), + sinon = require( 'sinon' ), + testEnv = require( './utils' ).testEnv; + +describe.skip( 'controllers.UserController', function () { + var UserService, UserController, ctrl, users = []; + + beforeEach( function ( done ) { + testEnv( function ( _UserService_, _UserController_ ) { + UserService = _UserService_; + UserController = _UserController_; + UserController.prototype.fakeAction = function () { + }; + + var req = { + params: {}, + method: 'GET', + query: {} + }; + var res = { + json: function () { + } + }; + var next = function () { + }; + + ctrl = new UserController( req, res, next ); + + UserService.create( { + // firstName: 'Joe', + username: 'joe@example.com', + email: 'joe@example.com', + password: '7110eda4d09e062aa5e4a390b0a572ac0d2c0220' + } ) + .then( function ( user ) { + users.push( user ); + return UserService.create( { + // firstName: 'Rachel', + username: 'rachel@example.com', + email: 'rachel@example.com', + password: '7110eda4d09e062aa5e4a390b0a572ac0d2c0220' + } ); + } ) + .then( function ( user ) { + users.push( user ); + done(); + } ) + .fail( done ); + } ); + } ); + + afterEach( function () { + UserService.constructor.instance = null; + } ); + + describe( 'static members', function () { + describe( '.requiresLogin(req, res, next)', function () { + it( 'should call next if req.isAuthenticated() returns true', function () { + var req = { + isAuthenticated: function () { + return true; + } + }, + res = {}, + next = sinon.spy(); + UserController.requiresLogin( req, res, next ); + next.called.should.be.true; + } ); + + it( 'should send 401 if req.isAuthenticated() returns false', function () { + var req = { + isAuthenticated: function () { + return false; + } + }, + res = { + send: sinon.spy() + }, + next = function () { + }; + UserController.requiresLogin( req, res, next ); + res.send.calledWith( 401 ).should.be.true; + } ); + } ); + + describe( '.requiresRole(roleName) -> function(req, res, next)', function () { + it( 'should call next() if req.session.user has specified role', function () { + var req = { + isAuthenticated: function () { + return true; + }, + session: { + user: { + roles: ['Trainer'] + } + } + }, + res = {}, + next = sinon.spy(); + UserController.requiresRole( 'Trainer' )( req, res, next ); + next.called.should.be.true; + } ); + + it( 'should call send(401) if user hasnt specified role', function () { + var req = { + isAuthenticated: function () { + return true; + }, + session: { + user: { + roles: ['Client'] + } + } + }, + res = { + send: sinon.spy() + }, + next = function () { + }; + UserController.requiresRole( 'Trainer' )( req, res, next ); + res.send.calledWith( 401 ).should.be.true; + } ); + } ); + } ); + + describe( '.postAction()', function () { + it( 'should hash password and save user', function ( done ) { + ctrl.send = function ( result ) { + UserService.findAll() + .then( function ( users ) { + users.should.have.length( 3 ); + users[2].password.should.equal( '2394a9661a9089208c1c9c65ccac85a91da6a859' ); + done(); + } ) + .fail( done ); + }; + + ctrl.req.body = { + username: 'admin', + email: 'admin@example.com', + password: 'secret_password' + }; + ctrl.postAction(); + } ); + + it( 'should call .send() with new user', function ( done ) { + ctrl.send = function ( result ) { + console.log( result ); + result.username.should.equal( 'admin' ); + result.id.should.be.ok; + result.password.should.equal( '2394a9661a9089208c1c9c65ccac85a91da6a859' ); + done(); + }; + ctrl.req.body = { + username: 'admin', + email: 'admin@example.com', + password: 'secret_password' + }; + ctrl.postAction(); + } ); + } ); + + describe.skip( '.putAction()', function () { + it( 'should hash password and update user', function ( done ) { + ctrl.send = function ( result ) { + UserService.findById( users[0].id ) + .then( function ( user ) { + user.username.should.equal( 'admin' ); + user.email.should.equal( 'admin@example.com' ); + user.password.should.equal( '2394a9661a9089208c1c9c65ccac85a91da6a859' ); + done(); + } ) + .fail( done ); + }; + + ctrl.req.body = { + username: 'admin', + email: 'admin@example.com', + password: 'secret_password' + }; + ctrl.req.params.id = users[0].id; + ctrl.putAction(); + } ); + + it( 'should call .send() with updated user data', function ( done ) { + ctrl.send = function ( result ) { + result.username.should.equal( 'admin' ); + result.email.should.equal( 'admin@example.com' ); + result.password.should.equal( '2394a9661a9089208c1c9c65ccac85a91da6a859' ); + result.id.should.be.ok; + done(); + }; + ctrl.req.body = { + username: 'admin', + email: 'admin@example.com', + password: 'secret_password' + }; + ctrl.req.params.id = users[0].id; + ctrl.putAction(); + } ); + } ); + + describe( '.loginAction()', function () { + it( 'should call req.login(user) if user with such credentials found', function ( done ) { + ctrl.req.login = function ( user ) { + user.id.should.eql( users[0].id ); + done(); + }; + ctrl.req.body = { + username: users[0].username, + password: '1234' + }; + ctrl.loginAction(); + } ); + + it( 'should call .send(200) if user if such credentials found', function ( done ) { + ctrl.req.login = function ( data, done ) { + done( null ); + }; + ctrl.send = function ( user, code ) { + user.username.should.equal( users[0].username ); + code.should.equal( 200 ); + done(); + }; + ctrl.req.body = { + username: users[0].username, + password: '1234' + }; + ctrl.loginAction(); + } ); + + it( 'should call .send(403) if user is not found', function ( done ) { + ctrl.send = function ( data, code ) { + data.should.eql( {} ); + code.should.equal( 403 ); + done(); + }; + ctrl.req.body = { + username: users[0].username, + password: '12345' + }; + ctrl.loginAction(); + } ); + } ); + + describe( '.logoutAction()', function () { + it( 'should call req.logout() and .send(200)', function () { + ctrl.req.logout = sinon.spy(); + ctrl.res.send = sinon.spy(); + ctrl.logoutAction(); + + ctrl.req.logout.called.should.be.true; + ctrl.res.send.calledWith( 200 ).should.be.true; + } ); + } ); +} ); \ No newline at end of file diff --git a/modules/clever-auth/tests/unit/test.service.UserService.js b/modules/clever-auth/tests/unit/test.service.UserService.js new file mode 100644 index 0000000..13a222c --- /dev/null +++ b/modules/clever-auth/tests/unit/test.service.UserService.js @@ -0,0 +1,841 @@ +var should = require( 'should' ) + , sinon = require( 'sinon' ) + , Q = require( 'q' ) + , testEnv = require( './utils' ).testEnv; + +describe( 'service.UserService', function () { + var UserService; + + before( function ( done ) { + this.timeout( 15000 ); + testEnv( function ( _UserService_ ) { + UserService = _UserService_; + done(); + }, done ); + } ); + + describe( '.authenticate(credentials)', function () { + it( 'should return User with specified credentials', function ( done ) { + var data1 = { + username: 'Joe', + email: 'joe@example.com', + password: '1234' + }; + var data2 = { + username: 'Rachel', + email: 'rachel@example.com', + password: '1234' + }; + + UserService.create( data1 ) + .then( function () { + return UserService.create( data2 ); + } ) + .then( function () { + return UserService.authenticate( { + email: 'rachel@example.com', + password: '1234' + } ) + .then( function ( user ) { + user.username.should.equal( data2.username ); + done(); + } ); + } ) + .fail( done ); + } ); + + it( 'should return User with "account", "role" and "team" properties', function ( done ) { + var data = { + username: 'Rachel2', + email: 'rachel2@example.com', + password: '1234' + }; + + UserService + .create( data ) + .then( function () { + return UserService.authenticate( { + email: data.email, + password: data.password + } ); + } ) + .then( function ( user ) { + user.username.should.equal( data.username ); + + user.should.have.property( 'account' ); + user.should.have.property( 'role' ); + user.should.have.property( 'team' ); + + done(); + } ) + .fail( done ); + } ); + + it( 'should not return user when he is not active', function ( done ) { + var data = { + username: 'Joe3', + email: 'joe3@example.com', + password: '1234', + active: false + }; + + UserService + .create( data ) + .then( function () { + return UserService.authenticate( { + email: data.email, + password: data.password + } ); + + } ) + .then( function ( user ) { + should.not.exist( user ); + + done(); + } ) + .fail( done ); + } ); + + it( 'should not return user when he is not active', function ( done ) { + var data = { + username: 'Joe4', + email: 'joe4@example.com', + password: '1234', + active: false + }; + + UserService + .create( data ) + .then( function () { + return UserService.authenticate( { + email: 'noneExistedEmail@somemail.com', + password: data.password + } ); + + } ) + .then( function ( user ) { + should.not.exist( user ); + + done(); + } ) + .fail( done ); + } ); + + it( 'should set "accessedAt" property after successfull login', function ( done ) { + var lastLogin = null + , data = { + username: 'Joe5', + email: 'joe5@example.com', + password: '1234', + active: true + }; + + UserService + .create( data ) + .then( function () { + + return UserService.authenticate( { + email: data.email, + password: data.password + } ); + } ) + .then( function ( user ) { + user.should.have.property( 'accessedAt' ); + lastLogin = user.accessedAt; + + return lastLogin; + + + } ) + .delay( 1000 ) + .then( function () { + return UserService.authenticate( { + email: data.email, + password: data.password + } ); + } ) + .then( function ( user ) { + user.should.have.property( 'accessedAt' ); + user.accessedAt.should.not.equal( lastLogin ); + + done(); + } ) + .fail( done ); + } ); + } ); + + describe( '.getUserFullDataJson( options )', function () { + it( 'should return User with specified options', function ( done ) { + var data = { + username: 'Rachel8', + email: 'rachel8@example.com', + password: '1234' + }; + + UserService.create( data ) + .then( function () { + return UserService.getUserFullDataJson( { email: data.email } ); + } ) + .then( function ( user ) { + user.email.should.equal( data.email ); + done(); + } ) + .fail( done ); + } ); + + it( 'should return User with "account", "role" and "team" properties', function ( done ) { + var data = { + username: 'Rachel9', + email: 'rachel9@example.com', + password: '1234', + "AccountId": null, + "RoleId": null, + "TeamId": null + }; + + UserService.create( data ) + .then( function () { + return UserService.getUserFullDataJson( { email: data.email } ); + } ) + .then( function ( user ) { + user.email.should.equal( data.email ); + + user.should.have.property( 'account' ); + should.not.exist( user.account ); + + user.should.have.property( 'role' ); + should.not.exist( user.role ); + + user.should.have.property( 'team' ); + should.not.exist( user.team ); + + done(); + } ) + .fail( done ); + } ); + + it( 'should return null when options does not match in the db', function ( done ) { + var data = { + username: 'Rachel10', + email: 'rachel10@example.com', + password: '1234', + "AccountId": null, + "RoleId": null, + "TeamId": null + }; + + UserService.create( data ) + .then( function () { + return UserService.getUserFullDataJson( { email: 'noneExistedEmail2@somemail.com' } ); + } ) + .then( function ( user ) { + should.not.exist( user ); + + done(); + } ) + .fail( done ); + } ); + } ); + + describe( '.generatePasswordResetHash( user )', function () { + it( 'should return data for account confirmation', function ( done ) { + + var data = { + username: 'Rachel12', + email: 'rachel12@example.com', + password: '1234', + confirmed: false, + "AccountId": 1 + }; + + UserService + .create( data ) + .then( function ( user ) { + should.exist( user ); + //Properties needed for creating hash value + user.should.have.property( 'createdAt' ); + user.should.have.property( 'updatedAt' ); + user.should.have.property( 'password' ); + user.should.have.property( 'email' ); + user.should.have.property( 'AccountId' ); + + return UserService.generatePasswordResetHash( user ); + } ) + .then( function ( recoverydata ) { + recoverydata.should.be.a( 'object' ); + + recoverydata.should.have.property( 'hash' ); + should.exist( recoverydata.hash ); + + recoverydata.should.have.property( 'expTime' ); + should.exist( recoverydata.expTime ); + + recoverydata.should.have.property( 'user' ); + recoverydata.user.should.be.a( 'object' ); + should.exist( recoverydata.user.id ); + should.exist( recoverydata.user.fullName ); + + recoverydata.should.have.property( 'action' ); + recoverydata.action.should.be.a( 'string' ).and.include( 'confirm' ); + + recoverydata.should.have.property( 'mailsubject' ); + recoverydata.mailsubject.should.be.a( 'string' ).and.include( 'Confirmation' ); + + done(); + } ) + .fail( done ); + } ); + + it( 'should return data for password recovery', function ( done ) { + var data = { + username: 'Rachel13', + email: 'rachel13@example.com', + password: '1234', + confirmed: true, + "AccountId": 1 + }; + + UserService + .create( data ) + .then( function ( user ) { + should.exist( user ); + //Properties needed for creating hash value + user.should.have.property( 'createdAt' ); + user.should.have.property( 'updatedAt' ); + user.should.have.property( 'password' ); + user.should.have.property( 'email' ); + user.should.have.property( 'AccountId' ); + + return UserService.generatePasswordResetHash( user ); + } ) + .then( function ( recoverydata ) { + recoverydata.should.be.a( 'object' ); + + recoverydata.should.have.property( 'hash' ); + should.exist( recoverydata.hash ); + + recoverydata.should.have.property( 'expTime' ); + should.exist( recoverydata.expTime ); + + recoverydata.should.have.property( 'user' ); + recoverydata.user.should.be.a( 'object' ); + should.exist( recoverydata.user.id ); + should.exist( recoverydata.user.fullName ); + + recoverydata.should.have.property( 'action' ); + recoverydata.action.should.be.a( 'string' ).and.include( 'reset' ); + + recoverydata.should.have.property( 'mailsubject' ); + recoverydata.mailsubject.should.be.a( 'string' ).and.include( 'Recovery' ); + + done(); + } ) + .fail( done ); + } ); + + it( 'should return status 403 and message when user missing fields', function ( done ) { + var data = { + username: 'Rachel13', + email: 'rachel13@example.com', + password: '1234', + }; + + UserService + .generatePasswordResetHash( data ) + .then( function ( data ) { + + data.should.have.property( 'statuscode' ); + data.statuscode.should.equal( 403 ); + + data.should.have.property( 'message' ); + data.message.should.be.a( 'string' ).and.not.be.empty; + + done(); + } ) + .fail( done ); + } ); + } ); + + describe( '.mailPasswordRecoveryToken( obj )', function () { + it( 'should return status 200 and a message for account confirmation action ', function ( done ) { + var data = { + user: { email: "email@mail.com", id: "id", fullName: "Jim Ioak" }, hash: "some_valid_hash", mailsubject: "some_valid_subject", action: "account_confirm" + }; + + UserService + .mailPasswordRecoveryToken( data ) + .then( function ( result ) { + + should.exist( result ); + result.should.be.a( 'object' ); + + result.should.have.property( 'statuscode', 200 ); + result.should.have.property( 'message' ); + done(); + } ) + .fail( done ); + + } ); + + it( 'should return status 200 and a message for password recovery action ', function ( done ) { + var data = { + user: { email: "email@mail.com", id: "id", fullName: "Jim Ioak" }, hash: "some_valid_hash", mailsubject: "some_valid_subject", action: "password_reset_submit" + }; + + UserService + .mailPasswordRecoveryToken( data ) + .then( function ( result ) { + + should.exist( result ); + result.should.be.a( 'object' ); + + result.should.have.property( 'statuscode', 200 ); + result.should.have.property( 'message' ); + done(); + } ) + .fail( done ); + } ); + + it( 'should return status 500 and a message for unrecognized action ', function ( done ) { + var data = { + user: { email: "email@mail.com", id: "id", fullName: "Jim Ioak" }, hash: "some_valid_hash", mailsubject: "some_valid_subject", action: "unrecognized_action" + }; + + UserService + .mailPasswordRecoveryToken( data ) + .then( function ( result ) { + should.exist( result ); + result.should.be.a( 'object' ); + + result.should.have.property( 'statuscode', 500 ); + result.should.have.property( 'message' ); + + done(); + } ) + .fail( done ); + } ); + } ); + + describe( '.createUser( data )', function () { + it( 'should return status 400 and a message when user with email exists', function ( done ) { + + var data = { + username: 'Rachel18', + email: 'rachel18@example.com', + password: '1234' + }; + + UserService + .create( data ) + .then( function () { + return UserService.createUser( data ); + } ) + .then( function ( result ) { + + result.should.be.a( 'object' ); + + result.should.have.property( 'statuscode', 400 ); + result.should.have.property( 'message' ); + + done(); + } ) + .fail( done ); + } ); + + it( 'should call .savedNewUser method ', function ( done ) { + sinon.spy( UserService, "saveNewUser" ); + + var data = { + username: 'Rachel20', + email: 'rachel20@example.com', + password: '1234', + "AccountId": 1 + }; + + UserService + .createUser( data ) + .then( function () { + + UserService + .saveNewUser + .calledOnce + .should + .be + .true; + + done(); + } ) + .fail( done ); + } ); + + + it( 'should call .generatePasswordResetHash method ', function ( done ) { + sinon.spy( UserService, "generatePasswordResetHash" ); + + var data = { + username: 'Rachel21', + email: 'rachel21@example.com', + password: '1234', + "AccountId": 1 + }; + + UserService + .createUser( data ) + .then( function () { + + UserService + .generatePasswordResetHash + .calledOnce + .should + .be + .true; + + UserService + .generatePasswordResetHash + .restore(); + + done(); + } ) + .fail( done ); + } ); + + it( 'should call .mailPasswordRecoveryToken method ', function ( done ) { + this.timeout( 5000 ); + sinon.spy( UserService, "mailPasswordRecoveryToken" ); + + var data = { + username: 'Rachel22', + email: 'rachel22@example.com', + password: '1234', + "AccountId": 1 + }; + + UserService + .createUser( data ) + .then( function () { + + UserService + .mailPasswordRecoveryToken + .calledOnce + .should + .be + .true; + + UserService + .mailPasswordRecoveryToken + .restore(); + + done(); + } ) + .fail( done ); + } ); + + it( 'should return status 200 and user object', function ( done ) { + + var data = { + username: 'Rachel23', + email: 'rachel23@example.com', + password: '1234', + "AccountId": 1 + }; + + UserService + .createUser( data ) + .then( function ( data ) { + + should.exist( data ); + data.should.be.a( 'object' ); + + data.should.have.property( 'id' ); + data.should.have.property( 'username' ).and.equal( data.username ); + data.should.have.property( 'AccountId' ).and.equal( data.AccountId ); + + done(); + } ) + .fail( done ); + } ); + } ); + + describe( '.saveNewUser( data )', function () { + it( 'should return statuscode 403 and message when account id is missing', function ( done ) { + + var data = { + username: 'rachel31@example.com', + email: 'rachel31@example.com', + password: '1234' + }; + + UserService + .saveNewUser( data ) + .then( function ( result ) { + + result.should.be.a( 'object' ); + + result.should.have.property( 'statuscode', 403 ); + result.should.have.property( 'message' ); + + done(); + } ) + .fail( done ); + } ); + + it( 'should auto generate random password when password is not given', function ( done ) { + var data = { + username: 'rachel32@example.com', + email: 'rachel32@example.com', + "AccountId": 1 + }; + + UserService + .saveNewUser( data ) + .then( function () { + return UserService.find( { + where: { email: data.email } + } ); + } ) + .then( function ( user ) { + user.should.be.instanceOf( Array ); + user[0].should.be.a( 'object' ); + user[0].should.have.property( 'email', data.email ); + user[0].should.have.property( 'id' ); + + user[0].should.have.property( 'password' ).and.not.be.empty; + done(); + } ) + .fail( done ); + } ); + + it( 'should hash password when password is given', function ( done ) { + + var data = { + username: 'rachel33@example.com', + email: 'rachel33@example.com', + password: '123', + "AccountId": 1 + }; + + UserService + .saveNewUser( data ) + .then( function () { + return UserService.find( { + where: { email: data.email } + } ); + } ) + .then( function ( user ) { + user.should.be.instanceOf( Array ); + user[0].should.be.a( 'object' ); + user[0].should.have.property( 'email', data.email ); + user[0].should.have.property( 'id' ); + + user[0].should.have.property( 'password' ); + user[0].password.should.not.equal( '123' ); + user[0].password.length.should.be.above( '123'.length ); + done(); + } ) + .fail( done ); + } ); + + it( 'should return a new user object', function ( done ) { + var data = { + username: 'rachel34@example.com', + email: 'rachel34@example.com', + password: '123', + "AccountId": 1 + }; + + UserService + .saveNewUser( data ) + .then( function () { + return UserService.find( { + where: { email: data.email } + } ); + } ) + .then( function ( user ) { + user.should.be.instanceOf( Array ); + user[0].should.be.a( 'object' ); + user[0].should.have.property( 'email', data.email ); + user[0].should.have.property( 'id' ); + done(); + } ) + .fail( done ); + + } ); + } ); + + describe( '.resendAccountConfirmation( me, userId )', function () { + it( 'should return status code 403 and message when user id does not exist', function ( done ) { + var userId = 'noneExistedId' + , accId = 1; + + UserService + .resendAccountConfirmation( accId, userId ) + .then( function ( data ) { + should.exist( data ); + + data.should.be.a( 'object' ); + data.should.have.property( 'statuscode', 403 ); + data.should.have.property( 'message' ).and.not.be.empty; + done(); + } ) + + } ); + + it( 'should return status code 403 and message when account ids do not match', function ( done ) { + var newuser = { + username: 'rachel35@example.com', + email: 'rachel35@example.com', + password: '123', + "AccountId": 1 + } + , accId = 2; + + + UserService + .create( newuser ) + .then( function ( user ) { + return UserService.resendAccountConfirmation( accId, user ); + } ) + .then( function ( data ) { + should.exist( data ); + + data.should.be.a( 'object' ); + data.should.have.property( 'statuscode', 403 ); + data.should.have.property( 'message' ).and.not.be.empty; + done(); + } ) + .fail( done ); + } ); + + it( 'should return status code 403 and message when account has been confirmed', function ( done ) { + var newuser = { + username: 'rachel36@example.com', + email: 'rachel36@example.com', + password: '123', + confirmed: true, + "AccountId": 1 + } + , accId = 1; + + + UserService + .create( newuser ) + .then( function ( user ) { + return UserService.resendAccountConfirmation( accId, user ); + } ) + .then( function ( data ) { + should.exist( data ); + + data.should.be.a( 'object' ); + data.should.have.property( 'statuscode', 403 ); + data.should.have.property( 'message' ).and.not.be.empty; + done(); + } ) + .fail( done ); + } ); + + it( 'should call .generatePasswordResetHash method ', function ( done ) { + sinon.spy( UserService, "generatePasswordResetHash" ); + + var newuser = { + username: 'rachel363@example.com', + email: 'rachel363@example.com', + password: '123', + confirmed: false, + "AccountId": 1 + } + , accId = 1; + + + UserService + .create( newuser ) + .then( function ( user ) { + return UserService.resendAccountConfirmation( accId, user.id ); + } ) + .then( function ( data ) { + + UserService + .generatePasswordResetHash + .calledOnce + .should + .be + .true; + + UserService + .generatePasswordResetHash + .restore(); + + done(); + } ) + .fail( done ); + } ); + + it( 'should call .mailPasswordRecoveryToken method ', function ( done ) { + sinon.spy( UserService, "mailPasswordRecoveryToken" ); + + var newuser = { + username: 'rachel37@example.com', + email: 'rachel37@example.com', + password: '123', + confirmed: false, + "AccountId": 1 + } + , accId = 1; + + + UserService + .create( newuser ) + .then( function ( user ) { + return UserService.resendAccountConfirmation( accId, user.id ); + } ) + .then( function ( data ) { + + UserService + .mailPasswordRecoveryToken + .calledOnce + .should + .be + .true; + + UserService + .mailPasswordRecoveryToken + .restore(); + + done(); + } ) + .fail( done ); + } ); + + + it( 'should return statuscode 200 and a message', function ( done ) { + + var newuser = { + username: 'rachel38@example.com', + email: 'rachel38@example.com', + password: '123', + confirmed: false, + "AccountId": 1 + } + , accId = 1; + + + UserService + .create( newuser ) + .then( function ( user ) { + return UserService.resendAccountConfirmation( accId, user.id ); + } ) + .then( function ( data ) { + + should.exist( data ); + + data.should.have.property( 'statuscode', 200 ); + data.should.have.property( 'message' ).and.not.be.empty; + + done(); + } ) + .fail( done ); + + + } ); + + } ); +} ); \ No newline at end of file diff --git a/modules/orm/config/default.json b/modules/orm/config/default.json index ec3ec69..5da4591 100644 --- a/modules/orm/config/default.json +++ b/modules/orm/config/default.json @@ -11,7 +11,12 @@ } }, "modelAssociations": { - "AccountModel": {}, + "SubscriptionModel": { + "hasMany": [ "AccountModel" ] + }, + "AccountModel": { + "belongsTo": [ "SubscriptionModel" ] + }, "UserModel": { "belongsTo": [ "AccountModel" ] }, diff --git a/package.json b/package.json index 44bb901..b788756 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,7 @@ "orm", "odm", "background-tasks", - "auth", + "clever-auth", "example-module", "clever-email" ] From 6b8f1dff69cf849c84fdddb5afd3a8bcbd8506f1 Mon Sep 17 00:00:00 2001 From: Daniel Durante Date: Wed, 29 Jan 2014 19:26:30 -0500 Subject: [PATCH 10/24] fix(auth): Strips auth down to what we need and fixes for ORM. --- .../controllers/AccountController.js | 251 --------- .../clever-auth/controllers/UserController.js | 40 +- .../clever-auth/models/orm/AccountModel.js | 87 --- .../models/orm/SubscriptionModel.js | 35 -- modules/clever-auth/module.js | 69 ++- modules/clever-auth/schema/seedData.json | 74 +-- .../clever-auth/services/AccountService.js | 531 ------------------ modules/clever-auth/services/UserService.js | 130 ++--- 8 files changed, 135 insertions(+), 1082 deletions(-) delete mode 100644 modules/clever-auth/controllers/AccountController.js delete mode 100644 modules/clever-auth/models/orm/AccountModel.js delete mode 100644 modules/clever-auth/models/orm/SubscriptionModel.js delete mode 100644 modules/clever-auth/services/AccountService.js diff --git a/modules/clever-auth/controllers/AccountController.js b/modules/clever-auth/controllers/AccountController.js deleted file mode 100644 index fd46cd0..0000000 --- a/modules/clever-auth/controllers/AccountController.js +++ /dev/null @@ -1,251 +0,0 @@ -var config = require( 'config' ); - -module.exports = function ( AccountService, UserService ) { - - return (require( 'classes' ).Controller).extend( - { - service: AccountService, - - requiresUniqueSubdomain: function ( req, res, next ) { - var subdomain = req.body.subdomain - , company = req.body.company; - - if ( !company || !subdomain ) { - return res.json( 400, "Company name and URL are mandatory" ); - } - - AccountService - .find( { where: { subdomain: subdomain } } ) - .then( function ( result ) { - if ( result.length ) { - return res.json( 403, 'This URL "' + subdomain + '" is already taken' ); - } - - next(); - } ) - .fail( function () { - return res.send( 500 ); - next() - } ) - }, - - requiresUniqueUser: function ( req, res, next ) { - var email = req.body.email; - - UserService - .find( { where: { email: email } } ) - .then( function ( result ) { - - if ( result.length ) { - return res.json( 403, 'This email "' + email + '" is already taken' ); - } - - next(); - } ) - .fail( function ( err ) { - return res.send( 500 ); - next(); - } ); - }, - - formatData: function ( req, res, next ) { - var accData = req.user.account - , newData = { - name: req.body.name || accData.name, - logo: req.body.logo || accData.logo, - info: req.body.info || accData.info, - companyUrl: req.body.companyUrl || accData.companyUrl, - hrEmail: req.body.hrEmail || accData.hrEmail, - email: req.body.email || accData.email, - themeColor: req.body.themeColor || accData.themeColor, - showMap: req.body.showMap !== undefined ? req.body.showMap : accData.showMap - }; - - req.body = newData; - next(); - }, - - isValidEmailDomain: function ( req, res, next ) { - var data = req.body - , pattern = /@(gmail|mail|yahoo|aol|aim|hotmail|facebook|cox|verizon|icloud|apple|outlook|yandex|163|126|gmx)\./i; - - if ( !data.email ) { - res.send( 400, 'Email is mandatory' ); - return; - } - - if ( pattern.test( data.email ) && (/stag|prod/gi.test( config.environmentName ) ) ) { - res.send( 400, 'Please register for BoltHR with your corporate email address.' ); - return; - } - - next(); - } - - }, - /* @Prototype */ - { - //Private function - listAction: function () { - this.getAction(); - return; - }, - - //Public function - getAction: function () { - var accId = this.req.user.account.id; - - AccountService.find( { where: { id: accId, active: true } } ) - .then( this.proxy( 'handleAccountRetrieval' ) ) - .fail( this.proxy( 'handleException' ) ); - }, - - handleAccountRetrieval: function ( account ) { - var acc = ( !account || !account.length) ? {} : account[0]; - this.send( acc, 200 ); - }, - - postAction: function () { - var data = this.req.body; - - if ( data.id ) { - return this.putAction(); - } - - AccountService - .processRegistration( data ) - .then( this.proxy( "handleDefaultOptionalJobFields" ) ) - .fail( this.proxy( "handleException" ) ); - - }, - - handleDefaultOptionalJobFields: function ( ua ) { - AccountJobFieldService - .seedOptionalJobFields( ua, config.optionalJobFieldNames ) - .then( this.proxy( "handleRegistrationCode" ) ) - .fail( this.proxy( "handleException" ) ); - }, - - handleRegistrationCode: function ( ua ) { - AccountService - .generateRegistrationHash( ua, config.secretKey ) - .then( this.proxy( "handleMailVerificationCode", ua ) ) - .fail( this.proxy( "handleException" ) ); - }, - - handleMailVerificationCode: function ( ua, hash ) { - AccountService - .mailRegistrationCode( ua, hash ) - .then( this.proxy( "send", 200 ) ) - .fail( this.proxy( "handleException" ) ); - }, - - putAction: function () { - var accId = this.req.user.account.id - , data = this.req.body; - - AccountService - .update( accId, data ) - .then( this.proxy( "send", 200 ) ) - .fail( this.proxy( "handleException", 200 ) ); - }, - - confirmAction: function () { - var token = this.req.body.token - , accountId = this.req.body.accountId; - - AccountService - .findById( accountId ) - .then( this.proxy( "handleTokenConfirmation", token ) ) - .fail( this.proxy( "handleException" ) ); - - }, - - handleTokenConfirmation: function ( token, account ) { - - if ( !account ) { - this.send( {}, 400 ); - return; - } - - AccountService - .generateRegistrationHash( account, config.secretKey ) - .then( this.proxy( "varifyAccount", account, token ) ) - .fail( this.proxy( "handleException" ) ); - - }, - - varifyAccount: function ( account, token, hash ) { - if ( token === hash ) { - - account - .updateAttributes( { active: true } ) - .success( this.proxy( "handleSignInAfterConfirmation" ) ) - .error( this.proxy( "handleException" ) ); - - } else { - this.send( 'Unable to verify confirmation link', 400 ); - } - }, - - handleSignInAfterConfirmation: function ( account ) { - var self = this; - - UserService - .find( {where: {AccountId: account.id}} ) - .then( function ( users ) { - users[0] - .updateAttributes( {active: true, confirmed: true}, ['active', 'confirmed'] ) - .success( function () { - UserService - .getUserFullDataJson( { 'AccountId': account.id } ) - .then( self.proxy( "signInAfterConfirmation" ) ) - .fail( self.proxy( "handleException" ) ) - } ); - } ); - }, - - signInAfterConfirmation: function ( user ) { - this.req.login( user, this.proxy( 'createUserSession', user ) ); - }, - - createUserSession: function ( user, err ) { - if ( err ) return this.handleException( err ); - this.send( user, 200 ); - }, - - resendAction: function () { - var email = this.req.body.email; - - UserService - .getUserFullDataJson( { email: email } ) - .then( this.proxy( "verifyAccountOwnership" ) ) - .fail( this.proxy( "handleException" ) ); - - }, - - verifyAccountOwnership: function ( user ) { - if ( !user || (user.role.name.toLowerCase() != 'owner') ) { - this.send( {}, 403 ); - return; - } - - if ( user.account.active ) { - this.send( 'Your account is active. Try to recover your password instead.', 403 ); - return; - } - - this.handleRegistrationCode( user ); - }, - - handleServiceMessage: function ( obj ) { - - if ( obj.statuscode ) { - this.send( obj.message, obj.statuscode ); - return; - } - - this.send( obj, 200 ); - } - } ); -}; \ No newline at end of file diff --git a/modules/clever-auth/controllers/UserController.js b/modules/clever-auth/controllers/UserController.js index 4d1f26b..106f975 100644 --- a/modules/clever-auth/controllers/UserController.js +++ b/modules/clever-auth/controllers/UserController.js @@ -74,38 +74,11 @@ module.exports = function ( UserService ) { // } next(); - }, - isUserInTheSameAccount: function ( req, res, next ) { - var uId = req.params.id, - myAccId = req.user.account.id; - - if ( !uId ) { - res.send( 401, {} ); - return; - } - - UserService.findById( uId ) - .then( function ( user ) { - if ( user.AccountId != myAccId ) { - res.send( 401, {} ); - return; - } - ; - - next(); - } ) - .fail( function () { - res.send( 500, {} ); - return; - } ); - } }, { listAction: function () { - var accId = this.req.user.account.id; - - UserService.find( { where: { "AccountId": accId } } ) + UserService.find( ) .then( this.proxy( 'send', 200 ) ) .fail( this.proxy( 'handleException' ) ); }, @@ -123,18 +96,14 @@ module.exports = function ( UserService ) { , data = this.req.body; //Its an update - if ( data.id || data.AccountId ) { + if ( data.id ) { return this.putAction(); } - ; if ( !data.email ) { this.send( 'Email is mandatory', 400 ); return; } - ; - - data['AccountId'] = me.account.id; var tplData = { firstName: this.req.user.firstname, accountSubdomain: this.req.user.account.subdomain, userFirstName: data.firstname, userEmail: data.email, tplTitle: 'BoltHR: Account Confirmation', subject: this.req.user.firstname + ' wants to add you to their recruiting team!' @@ -148,7 +117,6 @@ module.exports = function ( UserService ) { putAction: function () { var meId = this.req.user.id - , accId = this.req.user.AccountId , userId = this.req.params.id , data = this.req.body; @@ -158,14 +126,13 @@ module.exports = function ( UserService ) { } UserService - .handleUpdateUser( accId, userId, data ) + .handleUpdateUser( userId, data ) .then( this.proxy( 'handleSessionUpdate', meId ) ) .fail( this.proxy( 'handleException' ) ); }, handleSessionUpdate: function ( meId, user ) { - console.log( user ); if ( user.id && (meId === user.id) ) { this.loginUserJson( user ); return; @@ -249,7 +216,6 @@ module.exports = function ( UserService ) { }, authorizedUser: function ( user ) { - console.dir( user ); if ( user ) { this.req.login( user ); this.res.send( 200 ); diff --git a/modules/clever-auth/models/orm/AccountModel.js b/modules/clever-auth/models/orm/AccountModel.js deleted file mode 100644 index 75d58b5..0000000 --- a/modules/clever-auth/models/orm/AccountModel.js +++ /dev/null @@ -1,87 +0,0 @@ -module.exports = function ( sequelize, DataTypes ) { - return sequelize.define( "Account", - { - id: { - type: DataTypes.INTEGER, - primaryKey: true, - autoIncrement: true - }, - name: { - type: DataTypes.STRING, - validate: { - len: [ 2, 50 ] - } - }, - subdomain: { - type: DataTypes.STRING, - allowNull: false, - unique: true, - validate: { - isAlphanumeric: true, - len: [ 3, 16 ] - } - }, - active: { - type: DataTypes.BOOLEAN, - allowNull: false, - defaultValue: false - }, - logo: { - type: DataTypes.STRING, - allowNull: true - }, - themeColor: { - type: DataTypes.STRING, - allowNull: true - }, - email: { - type: DataTypes.STRING, - allowNull: true, - unique: true, - validate: { - isEmail: true - } - }, - emailFwd: { - type: DataTypes.STRING, - allowNull: true - }, - info: { - type: DataTypes.TEXT, - allowNull: true - }, - swfDomain: { - type: DataTypes.STRING, - allowNull: true, - defaultValue: null - }, - swfDomainAttempts: { - type: DataTypes.INTEGER, - allowNull: false, - defaultValue: 0 - }, - companyUrl: { - type: DataTypes.STRING, - allowNull: true - }, - hrEmail: { - type: DataTypes.STRING, - allowNull: true - }, - showMap: { - type: DataTypes.BOOLEAN, - allowNull: false, - defaultValue: false - } - }, - { - instanceMethods: { - toJSON: function () { - var values = this.values; - delete values.createdAt; - delete values.updatedAt; - return values; - } - } - } ); -}; \ No newline at end of file diff --git a/modules/clever-auth/models/orm/SubscriptionModel.js b/modules/clever-auth/models/orm/SubscriptionModel.js deleted file mode 100644 index 2281bd5..0000000 --- a/modules/clever-auth/models/orm/SubscriptionModel.js +++ /dev/null @@ -1,35 +0,0 @@ -module.exports = function ( sequelize, DataTypes ) { - return sequelize.define( "Subscription", - { - id: { - type: DataTypes.INTEGER, - primaryKey: true, - autoIncrement: true - }, - name: { - type: DataTypes.STRING, - validate: { - len: [ 2, 32 ] - } - }, - description: { - type: DataTypes.STRING, - validate: { - len: [ 10, 100 ] - } - - }, - price: { - type: DataTypes.DECIMAL( 10, 2 ), - allowNull: false - }, - period: { - type: DataTypes.STRING, - validate: { - len: [ 1, 20 ] - } - } - - }, - {} ); -}; \ No newline at end of file diff --git a/modules/clever-auth/module.js b/modules/clever-auth/module.js index 0df28ea..33955bf 100644 --- a/modules/clever-auth/module.js +++ b/modules/clever-auth/module.js @@ -1,16 +1,83 @@ var passport = require( 'passport' ) , debug = require( 'debug' )( 'AuthModule' ) + , fs = require( 'fs' ) + , path = require( 'path' ) , ModuleClass = require( 'classes' ).ModuleClass , RedisStore = require( 'connect-redis' )( injector.getInstance( 'express' ) ) , Module; Module = ModuleClass.extend( { - + models: null, store: null, + sequelize: null, + + preSetup: function() { + var self = this; + this.models = {}; + this.sequelize = injector.getInstance( 'sequelize' ); + var dir = fs.readdirSync( path.join( __dirname, 'models', 'orm' ) ); + dir.forEach( function ( model ) { + self.getModel( path.join( __dirname, 'models', 'orm', model ) ); + } ); + }, preResources: function () { injector.instance( 'passport', passport ); + }, + + modulesLoaded: function() { + this.defineModelsAssociations(); + }, + + defineModelsAssociations: function() { + if (!this.config.hasOwnProperty( 'modelAssociations' )) { + return true; + } + + debug( 'Defining model assocations' ); + + Object.keys( this.config.modelAssociations ).forEach( this.proxy( function( modelName ) { + Object.keys( this.config.modelAssociations[ modelName ] ).forEach( this.proxy( 'defineModelAssociations', modelName ) ); + })); + }, + + defineModelAssociations: function( modelName, assocType ) { + var associatedWith = this.config.modelAssociations[ modelName ][ assocType ]; + if ( ! associatedWith instanceof Array ) { + associatedWith = [ associatedWith ]; + } + + associatedWith.forEach( this.proxy( 'associateModels', modelName, assocType ) ); + }, + + associateModels: function( modelName, assocType, assocTo ) { + // Support second argument + if ( assocTo instanceof Array ) { + debug( '%s %s %s with second argument of ', modelName, assocType, assocTo[0], assocTo[1] ); + this.models[ modelName ][ assocType ]( this.models[ assocTo[0] ], assocTo[1] ); + } else { + debug( '%s %s %s', modelName, assocType, assocTo ); + this.models[ modelName ][ assocType ]( this.models[assocTo] ); + } + }, + + getModel: function( modelPath ) { + var modelName = modelPath.split( '/' ).pop().split( '.' ).shift(); + + if ( typeof this.models[ modelName ] === 'undefined' ) { + debug( [ 'Loading model', modelName, 'from', modelPath ].join( ' ' ) ); + + // Call on sequelizejs to load the model + this.models[ modelName ] = this.sequelize.import( modelPath ); + + // Set a flat for tracking + this.models[ modelName ].ORM = true; + + // Add the model to the injector + injector.instance( 'ORM' + modelName, this.models[ modelName ] ); + } + return this.models[ modelName ]; }, configureApp: function ( app, express ) { diff --git a/modules/clever-auth/schema/seedData.json b/modules/clever-auth/schema/seedData.json index f37333d..623b6c5 100644 --- a/modules/clever-auth/schema/seedData.json +++ b/modules/clever-auth/schema/seedData.json @@ -1,75 +1,15 @@ { - "Subscription": [ - { - "name": "Trial", - "description": "This is a 30 days trial subscription", - "price": 0.00, - "period": "30days" - } - ], - - "Account": [ - { - "name": "Default Account", - "subdomain": "dev", - "emailFwd": "dev23io@stage.bolthr.clevertech.biz", - "active": true, - "associations": { - "Subscription": { - "name": "Trial" - } - } - } - ], - "User": [ { - "username": "dimitrios@clevertech.biz", - "email": "dimitrios@clevertech.biz", - "password": "9ac20922b054316be23842a5bca7d69f29f69d77", - "firstname": "dimitris", - "lastname": "ioakimidis", - "phone": "8900000", - "active": true, - "confirmed": true, - "hasAdminRight": true, - "associations": { - "Account": { - "name": "Default Account" - } - } - }, - { - "username": "adnan@clevertech.biz", - "email": "adnan@clevertech.biz", - "password": "f10e2821bbbea527ea02200352313bc059445190", - "firstname": "Adnan", - "lastname": "Ibrisimbegovic", - "phone": "8900000", - "active": true, - "confirmed": true, - "hasAdminRight": true, - "associations": { - "Account": { - "name": "Default Account" - } - } - }, - { - "username": "richard", - "email": "richard@clevertech.biz", - "password": "9ac20922b054316be23842a5bca7d69f29f69d77", - "firstname": "Richard", - "lastname": "Gustin", - "phone": "+61448048484", + "username": "test", + "email": "test@clevertech.biz", + "password": "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8", + "firstname": "Test", + "lastname": "Account", + "phone": "+15551234", "active": true, "confirmed": true, - "hasAdminRight": true, - "associations": { - "Account": { - "name": "Default Account" - } - } + "hasAdminRight": true } ] } \ No newline at end of file diff --git a/modules/clever-auth/services/AccountService.js b/modules/clever-auth/services/AccountService.js deleted file mode 100644 index 43bdfdf..0000000 --- a/modules/clever-auth/services/AccountService.js +++ /dev/null @@ -1,531 +0,0 @@ -var Q = require( 'q' ) - , async = require( 'async' ) - , Sequelize = require( 'sequelize' ) - , crypto = require( 'crypto' ) - , sendgrid = require( 'utils' ).sendgrid - , ejsFileRender = require( 'utils' ).ejsfilerender - , shortId = require( 'shortid' ) - , config = require( 'config' ) -// , appSeed = require( './../../config/appData' ) - , AccountService = null; - - -module.exports = function ( sequelize, - ORMAccountModel, - ORMUserModel, - ORMRoleModel, - ORMEmailTemplateModel, - ORMSubscriptionModel, - ORMPermissionModel, - ORMWorkflowModel, - ORMWorkflowStepsModel ) { - - if ( AccountService && AccountService.instance ) { - return AccountService.instance; - } - - AccountService = require( 'services' ).BaseService.extend( { - - formatRegistrationData: function ( data, operation ) { - var d = { user: {}, account: {}, roles: [] } - , emailURL = '' - , hashedId = shortId.seed( 1000 ).generate() - , envName = ( config.environmentName == 'DEV' ) ? 'dev' : ( config.environmentName == 'PROD' ) ? 'prod' : 'stage'; - - emailURL = ( envName != 'prod' ) - ? hashedId + '@' + envName + '.bolthr.clevertech.biz' - : hashedId + '@app-mail.bolthr.com'; - -// d['roles'] = appSeed['DefaultRoles']; -// d['rolePermissions'] = appSeed['RolePermissions']; -// d['templates'] = appSeed['DefaultEmailTemplates']; - - d['account']['name'] = data['company']; - d['account']['subdomain'] = data['subdomain']; - d['account']['emailFwd'] = emailURL; - d['account']['active'] = false; - d['account']['SubscriptionId'] = null; - - d['user']['title'] = data['title'] || null; - d['user']['username'] = ( data['username'] ) ? data['username'] : data['email']; - d['user']['email'] = data['email']; - d['user']['firstname'] = data['firstname']; - d['user']['lastname'] = data['lastname']; - d['user']['password'] = crypto.createHash( 'sha1' ).update( data['password'] ).digest( 'hex' ); - d['user']['phone'] = data['phone'] || null; - d['user']['active'] = false; - d['user']['confirmed'] = false; - d['user']['hasAdminRight'] = true; - d['user']['RoleId'] = null; - d['user']['AccountId'] = null; - - return d; - }, - - processRegistration: function ( data ) { - var deferred = Q.defer() - , fData = this.formatRegistrationData( data ); - - this - .handleRegistrationStep1( fData ) - .then( deferred.resolve ) - .fail( deferred.reject ); - - return deferred.promise; - }, - - handleRegistrationStep1: function ( data ) { - var deferred = Q.defer() - , chainer = new Sequelize.Utils.QueryChainer(); - - //Perform all seeds - chainer.add( ORMAccountModel.create( data['account'] ) ); - chainer.add( ORMUserModel.create( data['user'] ) ); - chainer.add( ORMSubscriptionModel.find( { where: { name: 'Trial' } } ) ); - chainer.add( ORMPermissionModel.findAll() ); - - chainer - .run() - .success( function ( results ) { - var account = results[0] - , user = results[1] - , subsciption = results[2]; - - this - .handleRegistrationStep2( account, user, subsciption.id, data ) - .then( deferred.resolve ) - .fail( deferred.reject ); - - }.bind( this ) ) - .error( deferred.reject ); - - return deferred.promise; - }, - - handleRegistrationStep2: function ( account, user, subId, data ) { - var deferred = Q.defer() - , chainer = new Sequelize.Utils.QueryChainer(); - - data['roles'] = data['roles'].map( function ( x ) { - x['AccountId'] = account.id; - return x; - } ); - - data['templates'] = data['templates'].map( function ( x ) { - x['UserId'] = user.id; - x['AccountId'] = account.id; - return x; - } ); - - chainer.add( account.updateAttributes( { SubscriptionId: subId } ) ); - chainer.add( ORMRoleModel.bulkCreate( data['roles'] ) ); - chainer.add( ORMEmailTemplateModel.bulkCreate( data['templates'] ) ); - - - chainer - .run() - .success( function ( results ) { - - this - .handleRegistrationStep3( account, user, data ) - .then( deferred.resolve ) - .fail( deferred.reject ); - - }.bind( this ) ) - .error( deferred.reject ); - - return deferred.promise; - }, - - handleRegistrationStep3: function ( account, user, data ) { - var deferred = Q.defer() - , service = this - , roleOwnerId = null - , chainer = new Sequelize.Utils.QueryChainer(); - - - ORMRoleModel - .findAll( { where: { AccountId: account.id } } ) - .success( function ( roles ) { - - var role = null; - while ( role = roles.pop() ) { - - if ( role.name == 'Owner' ) { - roleOwnerId = role.id; - } - - rolePerms = data['rolePermissions'][role.name].map( function ( x ) { - return ORMPermissionModel.build( {id: x.id} ) - } ); - chainer.add( role.setPermissions( rolePerms ) ); - } - - chainer.add( user.updateAttributes( {RoleId: roleOwnerId, AccountId: account.id } ) ); - - chainer - .run() - .success( function ( results ) { - var userJSON = results[results.length - 1]; - userJSON['account'] = account; - - //We have to send userJSON back to the controller - service - .handleRegistrationStep4( account ) - .then( function () { - deferred.resolve( userJSON ); - } ) - .fail( deferred.reject ); - } ) - .error( deferred.reject ); - } ) - .error( deferred.reject ); - - return deferred.promise; - }, - - handleRegistrationStep4: function ( account ) { - var deferred = Q.defer(); - var self = this; - - async.series( [ - function ( cb ) { - self - .generateAdvancedWorkflow( account ) - .then( cb ) - .fail( cb ); - }, - function ( cb ) { - self - .generateSimpleWorkflow( account ) - .then( cb ) - .fail( cb ); - } - ], - function ( err, results ) { - if ( err ) { - deferred.reject(); - } else { - deferred.resolve(); - } - } ); - - return deferred.promise; - }, - - generateAdvancedWorkflow: function ( account ) { - var deferred = Q.defer(); - - ORMWorkflowModel - .create( { - name: 'Advanced Workflow', - type: 'Applicant', - defaultWorkflow: true, - isEditable: false, - AccountId: account.id - } ) - .success( this.proxy( 'generateAdvancedWorkflowSteps', account, deferred ) ) - .error( deferred.reject ); - - return deferred.promise; - }, - - generateSimpleWorkflow: function ( account ) { - var deferred = Q.defer(); - - ORMWorkflowModel - .create( { - name: 'Simple Workflow', - type: 'Applicant', - defaultWorkflow: false, - isEditable: false, - AccountId: account.id - } ) - .success( this.proxy( 'generateSimpleWorkflowSteps', account, deferred ) ) - .error( deferred.reject ); - - return deferred.promise; - }, - - generateAdvancedWorkflowSteps: function ( account, deferred, workflow ) { - var defaultSteps = []; - - defaultSteps.push( { - name: 'Filed for Later', - statusType: 'Active' - } ); - - defaultSteps.push( { - name: 'Reviewed', - statusType: 'Active' - } ); - - defaultSteps.push( { - name: 'Phone Screened', - statusType: 'Active' - } ); - - defaultSteps.push( { - name: 'Scheduling Interview', - statusType: 'Active' - } ); - - defaultSteps.push( { - name: 'Interview Scheduled', - statusType: 'Active' - } ); - - defaultSteps.push( { - name: 'Interviewed', - statusType: 'Active' - } ); - - defaultSteps.push( { - name: 'Checking References', - statusType: 'Active' - } ); - - defaultSteps.push( { - name: 'Put On Hold', - statusType: 'Active' - } ); - - defaultSteps.push( { - name: 'Made Offer', - statusType: 'Active' - } ); - - defaultSteps.push( { - name: 'Hired Full Time', - statusType: 'Hired' - } ); - - defaultSteps.push( { - name: 'Hired Part Time', - statusType: 'Hired' - } ); - - defaultSteps.push( { - name: 'Interned', - statusType: 'Hired' - } ); - - defaultSteps.push( { - name: 'Contracted', - statusType: 'Hired' - } ); - - defaultSteps.push( { - name: 'Not a Fit', - statusType: 'Not-Hired' - } ); - - defaultSteps.push( { - name: 'Declined Offer', - statusType: 'Not-Hired' - } ); - - defaultSteps.push( { - name: 'Not Qualified', - statusType: 'Not-Hired' - } ); - - defaultSteps.push( { - name: 'Over Qualified', - statusType: 'Not-Hired' - } ); - - defaultSteps.push( { - name: 'Location', - statusType: 'Not-Hired' - } ); - - defaultSteps.push( { - name: 'Hired Elsewhere', - statusType: 'Not-Hired' - } ); - - async.forEach( - defaultSteps, - this.proxy( 'createWorkflowStep', workflow ), - function ( err ) { - deferred.resolve(); - } - ); - }, - - generateSimpleWorkflowSteps: function ( account, deferred, workflow ) { - var defaultSteps = []; - - defaultSteps.push( { - name: 'Scheduling Interview', - statusType: 'Active' - } ); - - defaultSteps.push( { - name: 'Interview Scheduled', - statusType: 'Active' - } ); - - defaultSteps.push( { - name: 'Interviewed', - statusType: 'Active' - } ); - - defaultSteps.push( { - name: 'Checking References', - statusType: 'Active' - } ); - - defaultSteps.push( { - name: 'Made Offer', - statusType: 'Active' - } ); - - defaultSteps.push( { - name: 'Hired Full Time', - statusType: 'Hired' - } ); - - defaultSteps.push( { - name: 'Hired Part Time', - statusType: 'Hired' - } ); - - defaultSteps.push( { - name: 'Interned', - statusType: 'Hired' - } ); - - defaultSteps.push( { - name: 'Contracted', - statusType: 'Hired' - } ); - - defaultSteps.push( { - name: 'Not a Fit', - statusType: 'Not-Hired' - } ); - - defaultSteps.push( { - name: 'Declined Offer', - statusType: 'Not-Hired' - } ); - - defaultSteps.push( { - name: 'Not Qualified', - statusType: 'Not-Hired' - } ); - - defaultSteps.push( { - name: 'Over Qualified', - statusType: 'Not-Hired' - } ); - - defaultSteps.push( { - name: 'Location', - statusType: 'Not-Hired' - } ); - - defaultSteps.push( { - name: 'Hired Elsewhere', - statusType: 'Not-Hired' - } ); - - async.forEach( - defaultSteps, - this.proxy( 'createWorkflowStep', workflow ), - function ( err ) { - deferred.resolve(); - } - ); - }, - - createWorkflowStep: function ( workflow, step, callback ) { - - ORMWorkflowStepsModel.create( { - name: step.name, - statusType: step.statusType, - WorkflowId: workflow.id - } ) - .success( callback ) - .error( callback ); - }, - - generateRegistrationHash: function ( ua, secretKey ) { - - var deferred = Q.defer() - , hash, md5 - , createdAt = (ua.account) ? ua.account.createdAt : ua.createdAt - , updatedAt = (ua.account) ? ua.account.updatedAt : ua.updatedAt - , subdomain = (ua.account) ? ua.account.subdomain : ua.subdomain; - - console.log( 'Generating hash with secretKey:', secretKey, 'createdAt: ', createdAt, subdomain, 'account_verify' ); - - md5 = crypto.createHash( 'md5' ); - md5.update( secretKey + createdAt + subdomain + 'account_verify', 'utf8' ); - hash = md5.digest( 'hex' ); - - deferred.resolve( hash ); - - return deferred.promise; - }, - - mailRegistrationCode: function ( ua, token ) { - var mailer = sendgrid( config.sendgrid ) - , bakeTemplate = ejsFileRender() - , link = config.hosturl + '/registration_confirm/' + ua.AccountId + '/' + token - - var payload = { - to: ua.email, - from: 'no-reply@bolthr.com', - subject: 'Account Activation Action', - link: link, - text: 'Please click on the link below to activate your account\n ' + link - }; - - var info = { - link: link, - firstname: ua.firstname, - companyLogo: 'http://app.bolthr.com/images/logo.png', - companyName: 'BoltHR', - tplTitle: 'BoltHR: Account Activation', - tplName: 'accountActivation' - }; - - return bakeTemplate( info ) - .then( function ( html ) { - - payload['html'] = html; - - return mailer( payload ); - } ) - .then( function () { - return 'Account created succefully'; - } ); - }, - - //Public Function - getAccountById: function ( accId ) { - var deferred = Q.defer(); - - this - .findOne( { where: { id: accId, Active: true }, attributes: ['id', 'info', 'name', 'subdomain', 'logo', 'themeColor', 'emailFwd', 'hrEmail', 'companyUrl', 'showMap'] } ) - .then( function ( account ) { - if ( !account ) { - return deferred.resolve( {} ); - } - - deferred.resolve( account ); - } ) - .fail( deferred.resolve ); - - return deferred.promise; - } - } ); - - AccountService.instance = new AccountService( sequelize ); - AccountService.Model = ORMAccountModel; - - return AccountService.instance; -}; \ No newline at end of file diff --git a/modules/clever-auth/services/UserService.js b/modules/clever-auth/services/UserService.js index 37dba9a..e944762 100644 --- a/modules/clever-auth/services/UserService.js +++ b/modules/clever-auth/services/UserService.js @@ -2,17 +2,12 @@ var Q = require( 'q' ) , crypto = require( 'crypto' ) , moment = require( 'moment' ) , Sequelize = require( 'sequelize' ) - , sendgrid = require( 'utils' ).sendgrid - , ejsFileRender = require( 'utils' ).ejsfilerender , config = require( 'config' ) , UserService = null; -module.exports = function ( sequelize, - ORMUserModel, - ORMAccountModel, - ORMRoleModel ) { - - var mailer = sendgrid( config.sendgrid ); +module.exports = function ( sequelize, + ORMUserModel + ) { if ( UserService && UserService.instance ) { return UserService.instance; @@ -26,21 +21,19 @@ module.exports = function ( sequelize, , chainer = new Sequelize.Utils.QueryChainer(); service - .findOne( { where: credentials, include: [ ORMAccountModel, ORMRoleModel ] } ) + .findOne( { where: credentials } ) .then( function ( user ) { if ( !user || !user.active ) { return deferred.resolve(); } chainer.add( user.updateAttributes( { accessedAt: moment.utc().format( 'YYYY-MM-DD HH:ss:mm' ) } ) ); - chainer.add( user.role.getPermissions() ); chainer .runSerially() .success( function ( result ) { var userJson = ( result[0] ) ? JSON.parse( JSON.stringify( result[0] ) ) : {}; - userJson.role.permissions = ( result[1] ) ? result[1] : []; deferred.resolve( userJson ); } ) @@ -56,23 +49,16 @@ module.exports = function ( sequelize, , service = this; service - .findOne( { where: options, include: [ ORMAccountModel, ORMRoleModel ] } ) + .findOne( { where: options } ) .then( function ( user ) { if ( !user ) { return deferred.resolve(); } - user.role - .getPermissions() - .success( function ( perms ) { - - var userJson = JSON.parse( JSON.stringify( user ) ); - userJson.role.permissions = perms; + var userJson = JSON.parse( JSON.stringify( user ) ); - deferred.resolve( userJson ); - } ) - .error( deferred.reject ); + deferred.resolve( userJson ); } ) .fail( deferred.reject ); @@ -111,52 +97,56 @@ module.exports = function ( sequelize, }, mailPasswordRecoveryToken: function ( obj ) { - var mailer = sendgrid( config.sendgrid ) - , bakeTemplate = ejsFileRender() - , link = config.hosturl + '/' + obj.action + '?u=' + obj.user.id + '&t=' + obj.hash + '&n=' + encodeURIComponent( obj.user.fullName ); - var payload = { to: obj.user.email, from: 'no-reply@bolthr.com' }; + // var mailer = sendgrid( config.sendgrid ) + // , bakeTemplate = ejsFileRender() + // , link = config.hosturl + '/' + obj.action + '?u=' + obj.user.id + '&t=' + obj.hash + '&n=' + encodeURIComponent( obj.user.fullName ); - payload.text = (obj.action === 'account_confirm') - ? "Please click on the link below to activate your account\n " + link - : "Please click on the link below to enter a new password\n " + link; + // var payload = { to: obj.user.email, from: 'no-reply@CleverTech.biz' }; - var info = { link: link, companyLogo: 'http://app.bolthr.com/images/logo.png', companyName: 'BoltHR' }; + // payload.text = (obj.action === 'account_confirm') + // ? "Please click on the link below to activate your account\n " + link + // : "Please click on the link below to enter a new password\n " + link; - info.tplName = (obj.action === 'account_confirm') - ? 'userNew' - : 'passwordRecovery'; + // var info = { link: link, companyLogo: 'http://app.CleverTech.biz/images/logo.png', companyName: 'CleverTech' }; - if ( !obj.tplData ) { - payload.subject = 'BoltHR: ' + obj.mailsubject; + // info.tplName = (obj.action === 'account_confirm') + // ? 'userNew' + // : 'passwordRecovery'; - info.firstname = obj.user.firstname; - info.username = obj.user.username; - info.user = obj.user; - info.tplTitle = 'BoltHR: Password Recovery'; + // if ( !obj.tplData ) { + // payload.subject = 'CleverTech: ' + obj.mailsubject; - } else { - payload.subject = obj.tplData.subject; + // info.firstname = obj.user.firstname; + // info.username = obj.user.username; + // info.user = obj.user; + // info.tplTitle = 'CleverTech: Password Recovery'; - info.tplTitle = obj.tplData.tplTitle; - info.firstName = obj.tplData.firstName; - info.accountSubdomain = obj.tplData.accountSubdomain; - info.userFirstName = obj.tplData.userFirstName; - info.userEmail = obj.tplData.userEmail; - } + // } else { + // payload.subject = obj.tplData.subject; - return Q.resolve( 'Init Promise Chaining' ) - .then( function () { - return bakeTemplate( info ); - } ) - .then( function ( html ) { - payload.html = html; + // info.tplTitle = obj.tplData.tplTitle; + // info.firstName = obj.tplData.firstName; + // info.accountSubdomain = obj.tplData.accountSubdomain; + // info.userFirstName = obj.tplData.userFirstName; + // info.userEmail = obj.tplData.userEmail; + // } - return mailer( payload ); - } ) + return Q.resolve( 'Init Promise Chaining' ) .then( function () { return { statuscode: 200, message: 'Message successfully sent' }; } ) + // .then( function() { + // return bakeTemplate( info ); + // } ) + // .then( function ( html ) { + // payload.html = html; + + // return mailer( payload ); + // } ) + // .then( function () { + // return { statuscode: 200, message: 'Message successfully sent' }; + // } ) .fail( function ( err ) { console.log( "\n\nERRR: ", err ); return { statuscode: 500, message: err }; @@ -200,22 +190,16 @@ module.exports = function ( sequelize, saveNewUser: function ( data ) { var deferred = Q.defer(); - if ( !data.AccountId ) { - deferred.resolve( { statuscode: 403, message: 'Unauthorized' } ); + data.username = data.username || data.email; + data.confirmed = false; + data.active = true; + data.password = ( data.password ) + ? crypto.createHash( 'sha1' ).update( data.password ).digest( 'hex' ) + : Math.random().toString( 36 ).slice( -14 ); - } else { - data.username = data.username || data.email; - data.RoleId = data.RoleId || 7; //Default into general user - data.confirmed = false; - data.active = true; - data.password = ( data.password ) - ? crypto.createHash( 'sha1' ).update( data.password ).digest( 'hex' ) - : Math.random().toString( 36 ).slice( -14 ); - - this.create( data ) - .then( deferred.resolve ) - .fail( deferred.reject ); - } + this.create( data ) + .then( deferred.resolve ) + .fail( deferred.reject ); return deferred.promise; }, @@ -228,8 +212,8 @@ module.exports = function ( sequelize, .findOne( userId ) .then( function ( user ) { - if ( !user || ( user.AccountId !== accId ) ) { - deferred.resolve( { statuscode: 403, message: 'User doesn\'t exist or invalid account' } ); + if ( !user ) { + deferred.resolve( { statuscode: 403, message: 'User doesn\'t exist' } ); return; } @@ -255,11 +239,11 @@ module.exports = function ( sequelize, return deferred.promise; }, - handleUpdateUser: function ( accId, userId, data ) { + handleUpdateUser: function ( userId, data ) { var deferred = Q.defer(); ORMUserModel - .find( { where: { id: userId, AccountId: accId } } ) + .find( { where: { id: userId } } ) .success( function ( user ) { if ( !user ) { From 33030e3747dc8ece538c7b672aeb43fe1ee4d825 Mon Sep 17 00:00:00 2001 From: Daniel Durante Date: Wed, 29 Jan 2014 19:27:17 -0500 Subject: [PATCH 11/24] fix(orm): Adds ORM fix. --- lib/models/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/models/index.js b/lib/models/index.js index dfdb2e1..463ef34 100644 --- a/lib/models/index.js +++ b/lib/models/index.js @@ -4,7 +4,7 @@ var path = require( 'path' ) , models = {}; dbModules.forEach( function( type ) { - if ( moduleLoader.moduleIsEnabled( type ) ) { + if ( moduleLoader.moduleIsEnabled( 'clever-' + type ) ) { models[ type ] = {}; moduleLoader.modules.forEach( function( theModule ) { Object.keys( theModule.models[ type ] ).forEach( function( key ) { From 9f0396bb00fb405dcd76a4e5f9fabb804bcc5c10 Mon Sep 17 00:00:00 2001 From: Volodymyr Denshchykov Date: Fri, 31 Jan 2014 03:13:16 +0200 Subject: [PATCH 12/24] update service and controller and test for it WIP --- lib/models/index.js | 2 +- modules/clever-auth/config/default.json | 6 +- .../clever-auth/controllers/UserController.js | 79 +- modules/clever-auth/package.json | 3 +- modules/clever-auth/routes.js | 60 +- modules/clever-auth/schema/seedData.json | 2 +- modules/clever-auth/services/UserService.js | 133 ++-- .../unit/test.controllers.UserController.js | 484 +++++++++--- .../tests/unit/test.service.UserService.js | 721 +++++++++++------- modules/orm/config/default.json | 15 +- package.json | 1 - 11 files changed, 997 insertions(+), 509 deletions(-) diff --git a/lib/models/index.js b/lib/models/index.js index 463ef34..dfdb2e1 100644 --- a/lib/models/index.js +++ b/lib/models/index.js @@ -4,7 +4,7 @@ var path = require( 'path' ) , models = {}; dbModules.forEach( function( type ) { - if ( moduleLoader.moduleIsEnabled( 'clever-' + type ) ) { + if ( moduleLoader.moduleIsEnabled( type ) ) { models[ type ] = {}; moduleLoader.modules.forEach( function( theModule ) { Object.keys( theModule.models[ type ] ).forEach( function( key ) { diff --git a/modules/clever-auth/config/default.json b/modules/clever-auth/config/default.json index 2efe392..730999f 100644 --- a/modules/clever-auth/config/default.json +++ b/modules/clever-auth/config/default.json @@ -7,10 +7,8 @@ "port": "6379", "prefix": "", "key": "" - } - }, - - "clever-auth-data": { + }, + "hostUrl": "http://localhost:" } } \ No newline at end of file diff --git a/modules/clever-auth/controllers/UserController.js b/modules/clever-auth/controllers/UserController.js index 106f975..4c8a996 100644 --- a/modules/clever-auth/controllers/UserController.js +++ b/modules/clever-auth/controllers/UserController.js @@ -31,50 +31,42 @@ module.exports = function ( UserService ) { service: UserService, requiresLogin: function ( req, res, next ) { - if ( !req.isAuthenticated() ) + + if ( !req.isAuthenticated() ) { return res.send( 401 ); - next(); - }, + } - requiresRole: function ( roleName ) { - return function ( req, res, next ) { - if ( !req.isAuthenticated() || !req.session.passport.user.role || - req.session.passport.user.role.name.indexOf( roleName ) === -1 ) - return res.send( 403 ); - next(); - }; - }, + next(); + }, //tested requiresAdminRights: function ( req, res, next ) { - if ( !req.isAuthenticated() || !req.session.passport.user || !req.session.passport.user.hasAdminRight ) + + if ( !req.isAuthenticated() || !req.session.passport.user || !req.session.passport.user.hasAdminRight ) { return res.send( 403 ); + } + next(); - }, + }, //tested checkPasswordRecoveryData: function ( req, res, next ) { - var userId = req.body.userId || req.body.user, - password = req.body.password, - token = req.body.token; - //expTime = req.body.exp; + var userId = req.body.userId + , password = req.body.password + , token = req.body.token + if ( !userId ) { + return res.send( 400, 'Invalid user Id.' ); + } - if ( !token || !userId ) { - return res.json( 400, 'Invalid Token.' ); + if ( !token ) { + return res.send( 400, 'Invalid Token.' ); } - //TODO: Here should be a regexp for password validation if ( !password ) { - return res.json( 400, 'Password does not much the requirements' ); + return res.send( 400, 'Password does not much the requirements' ); } - //Check timestamp - // var now = moment.utc().valueOf(); - // if( now > expTime ){ - // return res.json(400,'Token has been expired'); - // } - next(); - } + } //tested }, { listAction: function () { @@ -92,10 +84,8 @@ module.exports = function ( UserService ) { }, postAction: function () { - var me = this.req.user - , data = this.req.body; + var data = this.req.body; - //Its an update if ( data.id ) { return this.putAction(); } @@ -106,22 +96,26 @@ module.exports = function ( UserService ) { } var tplData = { - firstName: this.req.user.firstname, accountSubdomain: this.req.user.account.subdomain, userFirstName: data.firstname, userEmail: data.email, tplTitle: 'BoltHR: Account Confirmation', subject: this.req.user.firstname + ' wants to add you to their recruiting team!' + firstName: data.firstname, + userEmail: data.email, + tplTitle: 'BoltHR: User Confirmation', + subject: data.firstname || data.email + ' wants to add you to their recruiting team!' }; + UserService .createUser( data, tplData ) .then( this.proxy( 'handleServiceMessage' ) ) .fail( this.proxy( 'handleException' ) ); - }, + }, // tested without email confirmation putAction: function () { var meId = this.req.user.id - , userId = this.req.params.id - , data = this.req.body; - + , userId = this.req.params.id + , data = this.req.body; +console.log(data) if ( !userId ) { - this.send( {}, 400 ); + this.send( 'Bad Request', 400 ); return; } @@ -173,12 +167,10 @@ module.exports = function ( UserService ) { }, loginAction: function () { - passport.authenticate( 'local', this.proxy( 'handleLocalUser' ) )( this.req, this.res, this.next ); }, handleLocalUser: function ( err, user ) { - if ( err ) return this.handleException( err ); if ( !user ) return this.send( {}, 403 ); this.loginUserJson( user ); @@ -314,9 +306,10 @@ module.exports = function ( UserService ) { }, confirmAction: function () { + console.log(this.req.query) var password = this.req.body.password - , token = this.req.body.token - , userId = this.req.body.userId; + , token = this.req.body.token + , userId = this.req.body.userId; UserService.findById( userId ) @@ -368,8 +361,8 @@ module.exports = function ( UserService ) { resendAction: function () { var me = this.req.user - , userId = this.req.params.id - , data = this.req.body; + , userId = this.req.params.id + , data = this.req.body; var tplData = { firstName: this.req.user.firstname, accountSubdomain: this.req.user.account.subdomain, userFirstName: '', userEmail: '', tplTitle: 'BoltHR: Account Confirmation', subject: this.req.user.firstname + ' wants to add you to their recruiting team!' diff --git a/modules/clever-auth/package.json b/modules/clever-auth/package.json index 0154c4d..62eedd2 100644 --- a/modules/clever-auth/package.json +++ b/modules/clever-auth/package.json @@ -31,6 +31,7 @@ "main": "module.js", "devDependencies": { "chai": "*", - "mocha": "*" + "mocha": "*", + "sinon": "" } } \ No newline at end of file diff --git a/modules/clever-auth/routes.js b/modules/clever-auth/routes.js index 21df16f..ae3cb91 100644 --- a/modules/clever-auth/routes.js +++ b/modules/clever-auth/routes.js @@ -1,31 +1,45 @@ module.exports = function ( app, - AccountController, +// AccountController, UserController ) { + app.all('/user/?:action?', UserController.attach()); + app.post('/users/confirm', UserController.checkPasswordRecoveryData, UserController.attach()); - app.get('/users', UserController.requiresLogin, UserController.attach()); - app.post('/users', UserController.requiresLogin, UserController.attach()); - app.get('/users/:id', UserController.requiresLogin, UserController.isUserInTheSameAccount, UserController.attach()); - app.put('/users/:id', UserController.requiresLogin, UserController.isUserInTheSameAccount, UserController.attach()); - app.post('/users/:id', UserController.requiresLogin, UserController.isUserInTheSameAccount, UserController.attach()); - - app.post('/users/:id/resend', UserController.requiresAdminRights, UserController.attach()); - app.get('/user/current', UserController.attach()); - app.get('/user/logout', UserController.requiresLogin, UserController.attach()); - app.post('/user/login', UserController.attach()); - app.post('/user/recover', UserController.attach()); - app.post('/user/reset', UserController.checkPasswordRecoveryData, UserController.attach()); - - app['delete']('/users/:id', UserController.requiresLogin, UserController.isUserInTheSameAccount, UserController.attach()); - app.all('/user/:action/:id?', UserController.requiresLogin, UserController.attach()); - app.all('/user/?:action?', UserController.requiresLogin, UserController.attach()); - - app.get('/account', UserController.requiresLogin, AccountController.attach()); - app.put('/account', UserController.requiresLogin, AccountController.formatData, AccountController.attach()); - app.post('/account', AccountController.isValidEmailDomain, AccountController.requiresUniqueSubdomain, AccountController.requiresUniqueUser, AccountController.attach()); - app.post('/account/confirm', AccountController.attach()); - app.post('/account/resend', AccountController.attach()); + +// app.post('/users/confirm', UserController.attach()); +// app.get('/users', UserController.attach()); +// app.post('/users', UserController.attach()); +// app.get('/users/:id', UserController.attach()); +// app.put('/users/:id', UserController.attach()); +// app.post('/users/:id', UserController.attach()); + +// app.post('/users/confirm', UserController.checkPasswordRecoveryData, UserController.attach()); +// app.get('/users', UserController.requiresLogin, UserController.attach()); +// app.post('/users', UserController.requiresLogin, UserController.attach()); +// app.get('/users/:id', UserController.requiresLogin, UserController.isUserInTheSameAccount, UserController.attach()); +// app.put('/users/:id', UserController.requiresLogin, UserController.isUserInTheSameAccount, UserController.attach()); +// app.post('/users/:id', UserController.requiresLogin, UserController.isUserInTheSameAccount, UserController.attach()); + + + + +// app.post('/users/:id/resend', UserController.requiresAdminRights, UserController.attach()); +// app.get('/user/current', UserController.attach()); +// app.get('/user/logout', UserController.requiresLogin, UserController.attach()); +// app.post('/user/login', UserController.attach()); +// app.post('/user/recover', UserController.attach()); +// app.post('/user/reset', UserController.checkPasswordRecoveryData, UserController.attach()); +// +// app['delete']('/users/:id', UserController.requiresLogin, UserController.isUserInTheSameAccount, UserController.attach()); +// app.all('/user/:action/:id?', UserController.requiresLogin, UserController.attach()); +// app.all('/user/?:action?', UserController.requiresLogin, UserController.attach()); + +// app.get('/account', UserController.requiresLogin, AccountController.attach()); +// app.put('/account', UserController.requiresLogin, AccountController.formatData, AccountController.attach()); +// app.post('/account', AccountController.isValidEmailDomain, AccountController.requiresUniqueSubdomain, AccountController.requiresUniqueUser, AccountController.attach()); +// app.post('/account/confirm', AccountController.attach()); +// app.post('/account/resend', AccountController.attach()); }; \ No newline at end of file diff --git a/modules/clever-auth/schema/seedData.json b/modules/clever-auth/schema/seedData.json index 623b6c5..b990861 100644 --- a/modules/clever-auth/schema/seedData.json +++ b/modules/clever-auth/schema/seedData.json @@ -1,5 +1,5 @@ { - "User": [ + "UserModel": [ { "username": "test", "email": "test@clevertech.biz", diff --git a/modules/clever-auth/services/UserService.js b/modules/clever-auth/services/UserService.js index e944762..856492f 100644 --- a/modules/clever-auth/services/UserService.js +++ b/modules/clever-auth/services/UserService.js @@ -6,13 +6,14 @@ var Q = require( 'q' ) , UserService = null; module.exports = function ( sequelize, - ORMUserModel - ) { + ORMUserModel ) { if ( UserService && UserService.instance ) { return UserService.instance; } + var EmailService = null; + UserService = require( 'services' ).BaseService.extend( { authenticate: function ( credentials ) { @@ -20,9 +21,10 @@ module.exports = function ( sequelize, , service = this , chainer = new Sequelize.Utils.QueryChainer(); - service - .findOne( { where: credentials } ) - .then( function ( user ) { + ORMUserModel + .find( { where: credentials } ) + .success( function ( user ) { + if ( !user || !user.active ) { return deferred.resolve(); } @@ -39,18 +41,18 @@ module.exports = function ( sequelize, } ) .error( deferred.reject ); } ) - .fail( deferred.reject ); + .error( deferred.reject ); return deferred.promise; - }, + }, //tested getUserFullDataJson: function ( options ) { var deferred = Q.defer() , service = this; - service - .findOne( { where: options } ) - .then( function ( user ) { + ORMUserModel + .find( { where: options } ) + .success( function ( user ) { if ( !user ) { return deferred.resolve(); @@ -60,26 +62,25 @@ module.exports = function ( sequelize, deferred.resolve( userJson ); } ) - .fail( deferred.reject ); + .error( deferred.reject ); return deferred.promise; - }, + }, //tested generatePasswordResetHash: function ( user, tplData ) { var deferred = Q.defer() , md5 = null , hash = null , expTime = null - , actionpath = ( !user.confirmed ) ? 'account_confirm' : 'password_reset_submit' - , mailsubject = ( !user.confirmed ) ? 'Account Confirmation' : 'Password Recovery'; + , actionpath = ( !user.confirmed ) ? 'user/confirm' : 'password_reset_submit' + , mailsubject = ( !user.confirmed ) ? 'User Confirmation' : 'Password Recovery'; - - if ( !user || !user.createdAt || !user.updatedAt || !user.password || !user.email || !user.AccountId ) { + if ( !user || !user.createdAt || !user.updatedAt || !user.password || !user.email ) { deferred.resolve( { statuscode: 403, message: 'Unauthorized' } ); } else { md5 = crypto.createHash( 'md5' ); - md5.update( user.createdAt + user.updatedAt + user.password + user.email + user.AccountId + 'recover', 'utf8' ); + md5.update( user.createdAt + user.updatedAt + user.password + user.email + 'recover', 'utf8' ); hash = md5.digest( 'hex' ); expTime = moment.utc().add( 'hours', 8 ).valueOf(); @@ -94,13 +95,16 @@ module.exports = function ( sequelize, } ); } return deferred.promise; - }, + }, //tested mailPasswordRecoveryToken: function ( obj ) { - // var mailer = sendgrid( config.sendgrid ) - // , bakeTemplate = ejsFileRender() - // , link = config.hosturl + '/' + obj.action + '?u=' + obj.user.id + '&t=' + obj.hash + '&n=' + encodeURIComponent( obj.user.fullName ); +// var hosturl = !!config.hosturl +// ? config.hosturl +// : [ config['clever-auth'].hostUrl, config.webPort ].join(''); +// +// var link = hosturl + '/' + obj.action + '?u=' + obj.user.id + '&t=' + obj.hash + '&n=' + encodeURIComponent( obj.user.fullName ); + // var payload = { to: obj.user.email, from: 'no-reply@CleverTech.biz' }; @@ -158,59 +162,79 @@ module.exports = function ( sequelize, , service = this , usr; - service - .findOne( { where: { email: data.email } } ) - .then( function ( user ) { + ORMUserModel + .find( { where: { email: data.email } } ) + .success( function ( user ) { if ( user !== null ) { deferred.resolve( { statuscode: 400, message: 'Email already exist' } ); return; } - service - .saveNewUser( data ) - .then( function ( user ) { - usr = user; - return service.generatePasswordResetHash( user, tplData ); - } ) - .then( service.mailPasswordRecoveryToken ) - .then( function () { - deferred.resolve( usr ); - } ) - .fail( function ( er ) { - console.log( er ); - deferred.reject(); - } ); + try { + EmailService = require( 'services' )['EmailService']; + } catch ( err ) { + console.log( err ); + } + + if ( EmailService === null ) { + + data.confirmed = true; + + service + .saveNewUser( data ) + .then( deferred.resolve ) + .fail( deferred.reject ); + + } else { + + data.confirmed = false; + + service + .saveNewUser( data ) + .then( function ( user ) { + usr = user; + return service.generatePasswordResetHash( user, tplData ); + } ) + .then( service.mailPasswordRecoveryToken ) + .then( function () { + deferred.resolve( usr ); + } ) + .fail( function ( err ) { + console.log( err ); + deferred.reject(); + } ); + } } ) - .fail( deferred.reject ); + .error( deferred.reject ); return deferred.promise; - }, + }, //tested saveNewUser: function ( data ) { var deferred = Q.defer(); data.username = data.username || data.email; - data.confirmed = false; data.active = true; - data.password = ( data.password ) + data.password = data.password ? crypto.createHash( 'sha1' ).update( data.password ).digest( 'hex' ) : Math.random().toString( 36 ).slice( -14 ); - this.create( data ) - .then( deferred.resolve ) - .fail( deferred.reject ); + ORMUserModel + .create( data ) + .success( deferred.resolve ) + .error( deferred.reject ); return deferred.promise; - }, + }, //tested - resendAccountConfirmation: function ( accId, userId, tplData ) { + resendAccountConfirmation: function ( userId, tplData ) { var deferred = Q.defer() , service = this; - service - .findOne( userId ) - .then( function ( user ) { + ORMUserModel + .find( userId ) + .success( function ( user ) { if ( !user ) { deferred.resolve( { statuscode: 403, message: 'User doesn\'t exist' } ); @@ -234,7 +258,7 @@ module.exports = function ( sequelize, .fail( deferred.reject ); } ) - .fail( deferred.resolve ); + .error( deferred.resolve ); return deferred.promise; }, @@ -252,6 +276,7 @@ module.exports = function ( sequelize, } if ( data.password && data.new_password ) { + if ( crypto.createHash( 'sha1' ).update( data.password ).digest( 'hex' ) !== user.password ) { deferred.resolve( {statuscode: 403, message: 'Invalid password'} ); return; @@ -269,7 +294,7 @@ module.exports = function ( sequelize, .error( deferred.reject ); return deferred.promise; - }, + }, //tested checkEmailAndUpdate: function ( user, data ) { var deferred = Q.defer(); @@ -301,7 +326,7 @@ module.exports = function ( sequelize, } return deferred.promise; - }, + }, //tested updateUser: function ( user, data ) { var deferred = Q.defer(); @@ -327,7 +352,7 @@ module.exports = function ( sequelize, .error( deferred.reject ); return deferred.promise; - } + } //tested } ); diff --git a/modules/clever-auth/tests/unit/test.controllers.UserController.js b/modules/clever-auth/tests/unit/test.controllers.UserController.js index 70aa683..4348ab6 100644 --- a/modules/clever-auth/tests/unit/test.controllers.UserController.js +++ b/modules/clever-auth/tests/unit/test.controllers.UserController.js @@ -1,48 +1,59 @@ -var should = require( 'should' ), - sinon = require( 'sinon' ), - testEnv = require( './utils' ).testEnv; +// Bootstrap the testing environmen +var testEnv = require( 'utils' ).testEnv(); -describe.skip( 'controllers.UserController', function () { - var UserService, UserController, ctrl, users = []; +var expect = require( 'chai' ).expect + , Q = require ( 'q' ) + , sinon = require( 'sinon' ) + , Service; + +describe( 'controllers.UserController', function () { + var Service, UserController, ctrl, users = []; beforeEach( function ( done ) { testEnv( function ( _UserService_, _UserController_ ) { - UserService = _UserService_; - UserController = _UserController_; - UserController.prototype.fakeAction = function () { - }; - var req = { - params: {}, + params: { action: 'fakeAction'}, method: 'GET', query: {} }; + var res = { - json: function () { - } - }; - var next = function () { + json: function () {} }; + var next = function () {}; + + UserController = _UserController_; + Service = _UserService_; ctrl = new UserController( req, res, next ); - UserService.create( { - // firstName: 'Joe', + Service.create( { + firstName: 'Joeqwer', username: 'joe@example.com', email: 'joe@example.com', password: '7110eda4d09e062aa5e4a390b0a572ac0d2c0220' } ) .then( function ( user ) { + + expect( user ).to.be.an( 'object' ).and.be.ok; + expect( user ).to.have.property( 'id' ).and.be.ok; + users.push( user ); - return UserService.create( { - // firstName: 'Rachel', + + return Service.create( { + firstName: 'Racheller', username: 'rachel@example.com', email: 'rachel@example.com', password: '7110eda4d09e062aa5e4a390b0a572ac0d2c0220' } ); } ) .then( function ( user ) { + + expect( user ).to.be.an( 'object' ).and.be.ok; + expect( user ).to.have.property( 'id' ).and.be.ok; + users.push( user ); + done(); } ) .fail( done ); @@ -50,157 +61,418 @@ describe.skip( 'controllers.UserController', function () { } ); afterEach( function () { - UserService.constructor.instance = null; + Service.constructor.instance = null; } ); describe( 'static members', function () { - describe( '.requiresLogin(req, res, next)', function () { - it( 'should call next if req.isAuthenticated() returns true', function () { + + describe( ' requiresLogin( req, res, next )', function () { + + it( 'should call next if req.isAuthenticated() returns true', function ( done ) { var req = { - isAuthenticated: function () { - return true; - } - }, - res = {}, - next = sinon.spy(); + isAuthenticated: function () { return true; } + } + , res = {} + , next = sinon.spy(); + UserController.requiresLogin( req, res, next ); - next.called.should.be.true; + + expect( req.isAuthenticated() ).to.be.true; + + expect( next.called ).to.be.true; + + done(); } ); - it( 'should send 401 if req.isAuthenticated() returns false', function () { + it( 'should send 401 if req.isAuthenticated() returns false', function ( done ) { var req = { - isAuthenticated: function () { - return false; - } - }, - res = { + isAuthenticated: function () { return false; } + } + , res = { send: sinon.spy() - }, - next = function () { - }; + } + , next = function () {}; + UserController.requiresLogin( req, res, next ); - res.send.calledWith( 401 ).should.be.true; + + expect( req.isAuthenticated() ).to.be.false; + + expect( res.send.called ).to.be.true; + expect( res.send.calledWith( 401 ) ).to.be.true; + + done(); } ); + } ); - describe( '.requiresRole(roleName) -> function(req, res, next)', function () { - it( 'should call next() if req.session.user has specified role', function () { + describe( ' requiresAdminRights( req, res, next )', function () { + + it( 'should call next if req.isAuthenticated() returns true and hasAdminRight is true', function ( done ) { var req = { - isAuthenticated: function () { - return true; - }, + isAuthenticated: function () { return true; }, session: { - user: { - roles: ['Trainer'] + passport: { + user: { + hasAdminRight: true + } } } - }, - res = {}, - next = sinon.spy(); - UserController.requiresRole( 'Trainer' )( req, res, next ); - next.called.should.be.true; + } + , res = {} + , next = sinon.spy(); + + UserController.requiresAdminRights( req, res, next ); + + expect( req.isAuthenticated() ).to.be.true; + expect( req.session.passport.user.hasAdminRight ).to.be.true; + + expect( next.called ).to.be.true; + + done(); } ); - it( 'should call send(401) if user hasnt specified role', function () { + it( 'should send 403 if req.isAuthenticated() returns false and hasAdminRight is true', function ( done ) { var req = { - isAuthenticated: function () { - return true; - }, + isAuthenticated: function () { return false; }, session: { - user: { - roles: ['Client'] + passport: { + user: { + hasAdminRight: true + } } } - }, - res = { + } + , res = { send: sinon.spy() - }, - next = function () { - }; - UserController.requiresRole( 'Trainer' )( req, res, next ); - res.send.calledWith( 401 ).should.be.true; + } + , next = function () {}; + + UserController.requiresAdminRights( req, res, next ); + + expect( req.isAuthenticated() ).to.be.false; + expect( req.session.passport.user.hasAdminRight ).to.be.true; + + expect( res.send.called ).to.be.true; + expect( res.send.calledWith( 403 ) ).to.be.true; + + done(); + } ); + + it( 'should send 403 if req.isAuthenticated() returns true and hasAdminRight is false', function ( done ) { + var req = { + isAuthenticated: function () { return true; }, + session: { + passport: { + user: { + hasAdminRight: false + } + } + } + } + , res = { + send: sinon.spy() + } + , next = function () {}; + + UserController.requiresAdminRights( req, res, next ); + + expect( req.isAuthenticated() ).to.be.true; + expect( req.session.passport.user.hasAdminRight ).to.be.false; + + expect( res.send.called ).to.be.true; + expect( res.send.calledWith( 403 ) ).to.be.true; + + done(); + } ); + + it( 'should send 403 if req.isAuthenticated() returns false and hasAdminRight is false', function ( done ) { + var req = { + isAuthenticated: function () { return false; }, + session: { + passport: { + user: { + hasAdminRight: false + } + } + } + } + , res = { + send: sinon.spy() + } + , next = function () {}; + + UserController.requiresAdminRights( req, res, next ); + + expect( req.isAuthenticated() ).to.be.false; + expect( req.session.passport.user.hasAdminRight ).to.be.false; + + expect( res.send.called ).to.be.true; + expect( res.send.calledWith( 403 ) ).to.be.true; + + done(); + } ); + + } ); + + describe( ' checkPasswordRecoveryData( req, res, next )', function () { + + it( 'should call next if is right', function ( done ) { + var req = { + body: { + userId: 1, + password: 'asasasasa', + token: '15151saAS5A1S51A51S' + } + } + , res = {} + , next = sinon.spy(); + + UserController.checkPasswordRecoveryData( req, res, next ); + + expect( req.body.userId ).to.be.ok; + expect( req.body.password ).to.be.ok; + expect( req.body.token ).to.be.ok; + + expect( next.called ).to.be.true; + + done(); + } ); + + it( 'should send 400 if insufficiently UserId', function ( done ) { + var req = { + body: { + password: 'asasasasa', + token: '15151saAS5A1S51A51S' + } + } + , res = { + send: sinon.spy() + } + , next = function () {}; + + UserController.checkPasswordRecoveryData( req, res, next ); + + expect( req.body.userId ).to.not.be.ok; + expect( req.body.password ).to.be.ok; + expect( req.body.token ).to.be.ok; + + expect( res.send.called ).to.be.true; + expect( res.send.calledWith( 400, 'Invalid user Id.' ) ).to.be.true; + + done(); + } ); + + it( 'should send 400 if insufficiently password', function ( done ) { + var req = { + body: { + userId: 1, + token: '15151saAS5A1S51A51S' + } + } + , res = { + send: sinon.spy() + } + , next = function () {}; + + UserController.checkPasswordRecoveryData( req, res, next ); + + expect( req.body.userId ).to.be.ok; + expect( req.body.password ).to.not.be.ok; + expect( req.body.token ).to.be.ok; + + expect( res.send.called ).to.be.true; + expect( res.send.calledWith( 400, 'Password does not much the requirements' ) ).to.be.true; + + done(); + } ); + + it( 'should send 400 if insufficiently token', function ( done ) { + var req = { + body: { + userId: 1, + password: '151515' + } + } + , res = { + send: sinon.spy() + } + , next = function () {}; + + UserController.checkPasswordRecoveryData( req, res, next ); + + expect( req.body.userId ).to.be.ok; + expect( req.body.password ).to.be.ok; + expect( req.body.token ).to.not.be.ok; + + expect( res.send.called ).to.be.true; + expect( res.send.calledWith( 400, 'Invalid Token.' ) ).to.be.true; + + done(); } ); + } ); + } ); - describe( '.postAction()', function () { + describe( 'postAction()', function () { + it( 'should hash password and save user', function ( done ) { - ctrl.send = function ( result ) { - UserService.findAll() + var data = { + username: 'admin', + email: 'admin@example.com', + password: 'secret_password' + }; + + ctrl.send = function ( user, status ) { + + expect( status ).to.equal( 200 ); + + expect( user ).to.be.an( 'object' ).and.be.ok; + expect( user ).to.have.property( 'id' ).and.be.ok; + + Service.find( { where: { email: 'admin@example.com' } } ) .then( function ( users ) { - users.should.have.length( 3 ); - users[2].password.should.equal( '2394a9661a9089208c1c9c65ccac85a91da6a859' ); + + expect( users ).to.be.an( 'array' ).and.have.length( 1 ); + + user = users[0]; + + expect( user ).to.be.an( 'object' ).and.be.ok; + expect( user ).to.have.property( 'id' ).and.be.ok; + expect( user ).to.have.property( 'username' ).and.equal( data.username ); + expect( user ).to.have.property( 'email' ).and.equal( data.email ); + expect( user ).to.have.property( 'password' ).and.equal( '2394a9661a9089208c1c9c65ccac85a91da6a859' ); + done(); } ) .fail( done ); }; - ctrl.req.body = { + ctrl.req.body = data; + + ctrl.postAction(); + } ); + + it( 'should be able to get the error if user with such email already exist', function ( done ) { + var data = { username: 'admin', email: 'admin@example.com', password: 'secret_password' }; - ctrl.postAction(); + + Service + .create( data ) + .then( function( user ) { + + expect( user ).to.be.an( 'object' ).and.be.ok; + expect( user ).to.have.property( 'id' ).and.be.ok; + + ctrl.send = function ( result, status ) { + + expect( status ).to.equal( 400 ); + + expect( result ).to.be.an( 'string' ).and.be.ok; + + done(); + }; + + ctrl.req.body = data; + + ctrl.postAction(); + + }) + .fail( done ); } ); - it( 'should call .send() with new user', function ( done ) { - ctrl.send = function ( result ) { - console.log( result ); - result.username.should.equal( 'admin' ); - result.id.should.be.ok; - result.password.should.equal( '2394a9661a9089208c1c9c65ccac85a91da6a859' ); - done(); - }; - ctrl.req.body = { + it( 'should be able to get the error if insufficiently email', function ( done ) { + var data = { username: 'admin', - email: 'admin@example.com', password: 'secret_password' }; + + ctrl.send = function ( result, status ) { + + expect( status ).to.equal( 400 ); + + expect( result ).to.be.an( 'string' ).and.be.ok; + + done(); + }; + + ctrl.req.body = data; + ctrl.postAction(); + } ); + + //TODO - email confirmation + } ); - describe.skip( '.putAction()', function () { + describe( 'putAction()', function () { + + it( 'should be able to get the error if insufficiently userId', function ( done ) { + var data = { + username: 'admin', + password: 'secret_password' + }; + + ctrl.send = function ( result, status ) { + + expect( status ).to.equal( 400 ); + + expect( result ).to.be.an( 'string' ).and.be.ok; + + done(); + }; + + ctrl.req = { + user: { id: users[0].id }, + params: { id: null } + }; + + ctrl.req.body = data; + + ctrl.putAction(); + + } ); + it( 'should hash password and update user', function ( done ) { - ctrl.send = function ( result ) { + var data = { + username: 'admin', + email: 'admin@example.com', + password: 'secret_password' + }; + + ctrl.send = function ( result, status ) { + + expect( status ).to.equal( 200 ); + UserService.findById( users[0].id ) .then( function ( user ) { + user.username.should.equal( 'admin' ); user.email.should.equal( 'admin@example.com' ); user.password.should.equal( '2394a9661a9089208c1c9c65ccac85a91da6a859' ); + done(); } ) .fail( done ); }; - ctrl.req.body = { - username: 'admin', - email: 'admin@example.com', - password: 'secret_password' + ctrl.req = { + body: data, + user: { id: users[0].id }, + params: { id: users[0].id } }; - ctrl.req.params.id = users[0].id; - ctrl.putAction(); - } ); - it( 'should call .send() with updated user data', function ( done ) { - ctrl.send = function ( result ) { - result.username.should.equal( 'admin' ); - result.email.should.equal( 'admin@example.com' ); - result.password.should.equal( '2394a9661a9089208c1c9c65ccac85a91da6a859' ); - result.id.should.be.ok; - done(); - }; - ctrl.req.body = { - username: 'admin', - email: 'admin@example.com', - password: 'secret_password' - }; - ctrl.req.params.id = users[0].id; ctrl.putAction(); } ); + + } ); - describe( '.loginAction()', function () { + describe.skip( 'loginAction()', function () { it( 'should call req.login(user) if user with such credentials found', function ( done ) { ctrl.req.login = function ( user ) { user.id.should.eql( users[0].id ); @@ -243,7 +515,7 @@ describe.skip( 'controllers.UserController', function () { } ); } ); - describe( '.logoutAction()', function () { + describe.skip( 'logoutAction()', function () { it( 'should call req.logout() and .send(200)', function () { ctrl.req.logout = sinon.spy(); ctrl.res.send = sinon.spy(); diff --git a/modules/clever-auth/tests/unit/test.service.UserService.js b/modules/clever-auth/tests/unit/test.service.UserService.js index 13a222c..8f50f0b 100644 --- a/modules/clever-auth/tests/unit/test.service.UserService.js +++ b/modules/clever-auth/tests/unit/test.service.UserService.js @@ -1,20 +1,32 @@ -var should = require( 'should' ) +var expect = require ( 'chai' ).expect + , request = require ( 'supertest' ) + , path = require( 'path' ) + , app = require ( path.resolve( __dirname + '/../../../../' ) + '/index.js' ) + , config = require( 'config' ) + , testEnv = require ( 'utils' ).testEnv() , sinon = require( 'sinon' ) - , Q = require( 'q' ) - , testEnv = require( './utils' ).testEnv; + , Q = require ( 'q' ); + +var EmailService = null; + +var user_1, user_1_json, old_password; describe( 'service.UserService', function () { var UserService; before( function ( done ) { this.timeout( 15000 ); - testEnv( function ( _UserService_ ) { + testEnv( function ( _UserService_, _ORMUserModel_ ) { + UserService = _UserService_; + UserModel = _ORMUserModel_; + done(); }, done ); } ); - describe( '.authenticate(credentials)', function () { + describe( '.authenticate( credentials )', function () { + it( 'should return User with specified credentials', function ( done ) { var data1 = { username: 'Joe', @@ -37,36 +49,14 @@ describe( 'service.UserService', function () { password: '1234' } ) .then( function ( user ) { - user.username.should.equal( data2.username ); - done(); - } ); - } ) - .fail( done ); - } ); - it( 'should return User with "account", "role" and "team" properties', function ( done ) { - var data = { - username: 'Rachel2', - email: 'rachel2@example.com', - password: '1234' - }; + expect( user ).to.be.an( 'object' ).and.be.ok; + expect( user ).to.have.property( 'username' ).and.equal( data2.username ); + expect( user ).to.have.property( 'email' ).and.equal( data2.email ); + expect( user ).to.not.have.property( 'password' ); - UserService - .create( data ) - .then( function () { - return UserService.authenticate( { - email: data.email, - password: data.password - } ); - } ) - .then( function ( user ) { - user.username.should.equal( data.username ); - - user.should.have.property( 'account' ); - user.should.have.property( 'role' ); - user.should.have.property( 'team' ); - - done(); + done(); + } ); } ) .fail( done ); } ); @@ -89,32 +79,8 @@ describe( 'service.UserService', function () { } ) .then( function ( user ) { - should.not.exist( user ); - - done(); - } ) - .fail( done ); - } ); - it( 'should not return user when he is not active', function ( done ) { - var data = { - username: 'Joe4', - email: 'joe4@example.com', - password: '1234', - active: false - }; - - UserService - .create( data ) - .then( function () { - return UserService.authenticate( { - email: 'noneExistedEmail@somemail.com', - password: data.password - } ); - - } ) - .then( function ( user ) { - should.not.exist( user ); + expect( user ).to.not.be.ok; done(); } ) @@ -122,8 +88,9 @@ describe( 'service.UserService', function () { } ); it( 'should set "accessedAt" property after successfull login', function ( done ) { + var lastLogin = null - , data = { + , data = { username: 'Joe5', email: 'joe5@example.com', password: '1234', @@ -140,12 +107,15 @@ describe( 'service.UserService', function () { } ); } ) .then( function ( user ) { - user.should.have.property( 'accessedAt' ); - lastLogin = user.accessedAt; - return lastLogin; + expect( user ).to.be.an( 'object' ).and.be.ok; + expect( user ).to.have.property( 'username' ).and.equal( data.username ); + expect( user ).to.have.property( 'email' ).and.equal( data.email ); + expect( user ).to.have.property( 'accessedAt' ).and.be.ok; + lastLogin = user.accessedAt; + return lastLogin; } ) .delay( 1000 ) .then( function () { @@ -155,16 +125,20 @@ describe( 'service.UserService', function () { } ); } ) .then( function ( user ) { - user.should.have.property( 'accessedAt' ); - user.accessedAt.should.not.equal( lastLogin ); + expect( user ).to.be.an( 'object' ).and.be.ok; + expect( user ).to.have.property( 'username' ).and.equal( data.username ); + expect( user ).to.have.property( 'email' ).and.equal( data.email ); + expect( user ).to.have.property( 'accessedAt' ).and.not.equal( lastLogin ); done(); } ) .fail( done ); } ); + } ); describe( '.getUserFullDataJson( options )', function () { + it( 'should return User with specified options', function ( done ) { var data = { username: 'Rachel8', @@ -177,37 +151,10 @@ describe( 'service.UserService', function () { return UserService.getUserFullDataJson( { email: data.email } ); } ) .then( function ( user ) { - user.email.should.equal( data.email ); - done(); - } ) - .fail( done ); - } ); - - it( 'should return User with "account", "role" and "team" properties', function ( done ) { - var data = { - username: 'Rachel9', - email: 'rachel9@example.com', - password: '1234', - "AccountId": null, - "RoleId": null, - "TeamId": null - }; - - UserService.create( data ) - .then( function () { - return UserService.getUserFullDataJson( { email: data.email } ); - } ) - .then( function ( user ) { - user.email.should.equal( data.email ); - - user.should.have.property( 'account' ); - should.not.exist( user.account ); - user.should.have.property( 'role' ); - should.not.exist( user.role ); - - user.should.have.property( 'team' ); - should.not.exist( user.team ); + expect( user ).to.be.an( 'object' ).and.be.ok; + expect( user ).to.have.property( 'username' ).and.equal( data.username ); + expect( user ).to.have.property( 'email' ).and.equal( data.email ); done(); } ) @@ -218,10 +165,7 @@ describe( 'service.UserService', function () { var data = { username: 'Rachel10', email: 'rachel10@example.com', - password: '1234', - "AccountId": null, - "RoleId": null, - "TeamId": null + password: '1234' }; UserService.create( data ) @@ -229,57 +173,55 @@ describe( 'service.UserService', function () { return UserService.getUserFullDataJson( { email: 'noneExistedEmail2@somemail.com' } ); } ) .then( function ( user ) { - should.not.exist( user ); + + expect( user ).to.not.be.ok; done(); } ) .fail( done ); } ); + } ); describe( '.generatePasswordResetHash( user )', function () { - it( 'should return data for account confirmation', function ( done ) { + + it( 'should return data for user confirmation', function ( done ) { var data = { username: 'Rachel12', email: 'rachel12@example.com', password: '1234', - confirmed: false, - "AccountId": 1 + confirmed: false }; UserService .create( data ) .then( function ( user ) { - should.exist( user ); + + expect( user ).to.be.an( 'object' ).and.be.ok; + //Properties needed for creating hash value - user.should.have.property( 'createdAt' ); - user.should.have.property( 'updatedAt' ); - user.should.have.property( 'password' ); - user.should.have.property( 'email' ); - user.should.have.property( 'AccountId' ); + expect( user ).to.have.property( 'createdAt' ).and.be.ok; + expect( user ).to.have.property( 'updatedAt' ).and.be.ok; + expect( user ).to.have.property( 'password' ).and.be.ok; + expect( user ).to.have.property( 'email' ).and.equal( data.email ); + expect( user ).to.have.property( 'confirmed' ).and.equal( data.confirmed ); return UserService.generatePasswordResetHash( user ); } ) .then( function ( recoverydata ) { - recoverydata.should.be.a( 'object' ); - - recoverydata.should.have.property( 'hash' ); - should.exist( recoverydata.hash ); - - recoverydata.should.have.property( 'expTime' ); - should.exist( recoverydata.expTime ); - - recoverydata.should.have.property( 'user' ); - recoverydata.user.should.be.a( 'object' ); - should.exist( recoverydata.user.id ); - should.exist( recoverydata.user.fullName ); - recoverydata.should.have.property( 'action' ); - recoverydata.action.should.be.a( 'string' ).and.include( 'confirm' ); - - recoverydata.should.have.property( 'mailsubject' ); - recoverydata.mailsubject.should.be.a( 'string' ).and.include( 'Confirmation' ); + expect( recoverydata ).to.be.an( 'object' ).and.be.ok; + expect( recoverydata ).to.have.property( 'hash' ).and.be.ok; + expect( recoverydata ).to.have.property( 'expTime' ).and.be.ok; + expect( recoverydata ).to.have.property( 'user' ).and.be.ok; + expect( recoverydata.user ).to.be.an( 'object' ).and.be.ok; + expect( recoverydata.user ).to.have.property( 'id' ).and.be.ok; + expect( recoverydata.user ).to.have.property( 'fullName' ).and.be.ok; + expect( recoverydata ).to.have.property( 'action' ).and.be.ok; + expect( recoverydata.action ).to.be.an( 'string' ).and.include( 'confirm' ); + expect( recoverydata ).to.have.property( 'mailsubject' ).and.be.ok; + expect( recoverydata.mailsubject ).to.be.an( 'string' ).and.include( 'Confirmation' ); done(); } ) @@ -287,6 +229,7 @@ describe( 'service.UserService', function () { } ); it( 'should return data for password recovery', function ( done ) { + var data = { username: 'Rachel13', email: 'rachel13@example.com', @@ -298,35 +241,31 @@ describe( 'service.UserService', function () { UserService .create( data ) .then( function ( user ) { - should.exist( user ); + + expect( user ).to.be.an( 'object' ).and.be.ok; + //Properties needed for creating hash value - user.should.have.property( 'createdAt' ); - user.should.have.property( 'updatedAt' ); - user.should.have.property( 'password' ); - user.should.have.property( 'email' ); - user.should.have.property( 'AccountId' ); + expect( user ).to.have.property( 'createdAt' ).and.be.ok; + expect( user ).to.have.property( 'updatedAt' ).and.be.ok; + expect( user ).to.have.property( 'password' ).and.be.ok; + expect( user ).to.have.property( 'email' ).and.equal( data.email ); + expect( user ).to.have.property( 'confirmed' ).and.equal( data.confirmed ); return UserService.generatePasswordResetHash( user ); } ) .then( function ( recoverydata ) { - recoverydata.should.be.a( 'object' ); - - recoverydata.should.have.property( 'hash' ); - should.exist( recoverydata.hash ); - - recoverydata.should.have.property( 'expTime' ); - should.exist( recoverydata.expTime ); - recoverydata.should.have.property( 'user' ); - recoverydata.user.should.be.a( 'object' ); - should.exist( recoverydata.user.id ); - should.exist( recoverydata.user.fullName ); - - recoverydata.should.have.property( 'action' ); - recoverydata.action.should.be.a( 'string' ).and.include( 'reset' ); - - recoverydata.should.have.property( 'mailsubject' ); - recoverydata.mailsubject.should.be.a( 'string' ).and.include( 'Recovery' ); + expect( recoverydata ).to.be.an( 'object' ).and.be.ok; + expect( recoverydata ).to.have.property( 'hash' ).and.be.ok; + expect( recoverydata ).to.have.property( 'expTime' ).and.be.ok; + expect( recoverydata ).to.have.property( 'user' ).and.be.ok; + expect( recoverydata.user ).to.be.an( 'object' ).and.be.ok; + expect( recoverydata.user ).to.have.property( 'id' ).and.be.ok; + expect( recoverydata.user ).to.have.property( 'fullName' ).and.be.ok; + expect( recoverydata ).to.have.property( 'action' ).and.be.ok; + expect( recoverydata.action ).to.be.an( 'string' ).and.include( 'reset' ); + expect( recoverydata ).to.have.property( 'mailsubject' ).and.be.ok; + expect( recoverydata.mailsubject ).to.be.an( 'string' ).and.include( 'Recovery' ); done(); } ) @@ -337,30 +276,38 @@ describe( 'service.UserService', function () { var data = { username: 'Rachel13', email: 'rachel13@example.com', - password: '1234', + password: '1234' }; UserService .generatePasswordResetHash( data ) - .then( function ( data ) { - - data.should.have.property( 'statuscode' ); - data.statuscode.should.equal( 403 ); + .then( function ( result ) { - data.should.have.property( 'message' ); - data.message.should.be.a( 'string' ).and.not.be.empty; + expect( result ).to.be.an( 'object' ).and.be.ok; + expect( result ).to.have.property( 'statuscode' ).and.equal( 403 ); + expect( result ).to.have.property( 'message' ).and.be.an( 'string' ).and.be.ok; done(); } ) .fail( done ); } ); + } ); - describe( '.mailPasswordRecoveryToken( obj )', function () { + describe.skip( '.mailPasswordRecoveryToken( obj )', function () { + it( 'should return status 200 and a message for account confirmation action ', function ( done ) { - var data = { - user: { email: "email@mail.com", id: "id", fullName: "Jim Ioak" }, hash: "some_valid_hash", mailsubject: "some_valid_subject", action: "account_confirm" - }; + var data = + { + user: { + email: "email@mail.com", + id: "id", + fullName: "Jim Ioak" + }, + hash: "some_valid_hash", + mailsubject: "some_valid_subject", + action: "account_confirm" + }; UserService .mailPasswordRecoveryToken( data ) @@ -377,7 +324,7 @@ describe( 'service.UserService', function () { } ); - it( 'should return status 200 and a message for password recovery action ', function ( done ) { + it.skip( 'should return status 200 and a message for password recovery action ', function ( done ) { var data = { user: { email: "email@mail.com", id: "id", fullName: "Jim Ioak" }, hash: "some_valid_hash", mailsubject: "some_valid_subject", action: "password_reset_submit" }; @@ -396,7 +343,7 @@ describe( 'service.UserService', function () { .fail( done ); } ); - it( 'should return status 500 and a message for unrecognized action ', function ( done ) { + it.skip( 'should return status 500 and a message for unrecognized action ', function ( done ) { var data = { user: { email: "email@mail.com", id: "id", fullName: "Jim Ioak" }, hash: "some_valid_hash", mailsubject: "some_valid_subject", action: "unrecognized_action" }; @@ -417,6 +364,23 @@ describe( 'service.UserService', function () { } ); describe( '.createUser( data )', function () { + + before( function( done ) { + try { + + EmailService = require( 'services' )['EmailService']; + + expect( EmailService ).to.be.ok; + + } catch ( err ) { + + expect( EmailService ).to.not.be.ok; + + } + + done(); + }); + it( 'should return status 400 and a message when user with email exists', function ( done ) { var data = { @@ -432,17 +396,16 @@ describe( 'service.UserService', function () { } ) .then( function ( result ) { - result.should.be.a( 'object' ); - - result.should.have.property( 'statuscode', 400 ); - result.should.have.property( 'message' ); + expect( result ).to.be.an( 'object' ).and.be.ok; + expect( result ).to.have.property( 'statuscode' ).and.equal( 400 ); + expect( result ).to.have.property( 'message' ).and.be.an( 'string' ).and.be.ok; done(); } ) .fail( done ); } ); - it( 'should call .savedNewUser method ', function ( done ) { + it( 'should call .savedNewUser method anytime', function ( done ) { sinon.spy( UserService, "saveNewUser" ); var data = { @@ -456,216 +419,449 @@ describe( 'service.UserService', function () { .createUser( data ) .then( function () { - UserService - .saveNewUser - .calledOnce - .should - .be - .true; + expect( UserService.saveNewUser.calledOnce ).to.be.true; done(); } ) .fail( done ); } ); - - it( 'should call .generatePasswordResetHash method ', function ( done ) { + it( 'should call .generatePasswordResetHash method if EmailService exist and do not call otherwise', function ( done ) { sinon.spy( UserService, "generatePasswordResetHash" ); var data = { username: 'Rachel21', email: 'rachel21@example.com', - password: '1234', - "AccountId": 1 + password: '1234' }; UserService .createUser( data ) .then( function () { - UserService - .generatePasswordResetHash - .calledOnce - .should - .be - .true; + if ( EmailService !== null ) { - UserService - .generatePasswordResetHash - .restore(); + expect( UserService.generatePasswordResetHash.calledOnce ).to.be.true; + + UserService + .generatePasswordResetHash + .restore(); + } else { + + expect( UserService.generatePasswordResetHash.calledOnce ).to.be.false; + } done(); } ) .fail( done ); } ); - it( 'should call .mailPasswordRecoveryToken method ', function ( done ) { + it( 'should call .mailPasswordRecoveryToken method if EmailService exist and do not call otherwise', function ( done ) { this.timeout( 5000 ); sinon.spy( UserService, "mailPasswordRecoveryToken" ); var data = { username: 'Rachel22', email: 'rachel22@example.com', - password: '1234', - "AccountId": 1 + password: '1234' }; UserService .createUser( data ) .then( function () { - UserService - .mailPasswordRecoveryToken - .calledOnce - .should - .be - .true; + if ( EmailService !== null ) { - UserService - .mailPasswordRecoveryToken - .restore(); + expect( UserService.mailPasswordRecoveryToken.calledOnce ).to.be.true; + + UserService + .mailPasswordRecoveryToken + .restore(); + } else { + + expect( UserService.mailPasswordRecoveryToken.calledOnce ).to.be.false; + } done(); } ) .fail( done ); } ); - it( 'should return status 200 and user object', function ( done ) { + it( 'should return user object', function ( done ) { var data = { username: 'Rachel23', email: 'rachel23@example.com', - password: '1234', - "AccountId": 1 + password: '1234' }; UserService .createUser( data ) - .then( function ( data ) { - - should.exist( data ); - data.should.be.a( 'object' ); + .then( function ( user ) { - data.should.have.property( 'id' ); - data.should.have.property( 'username' ).and.equal( data.username ); - data.should.have.property( 'AccountId' ).and.equal( data.AccountId ); + expect( user ).to.be.an( 'object' ).and.be.ok; + expect( user ).to.have.property( 'id' ).and.be.ok; + expect( user ).to.have.property( 'username' ).and.equal( data.username ); + expect( user ).to.have.property( 'email' ).and.equal( data.email ); + expect( user ).to.have.property( 'password' ).and.be.ok; done(); } ) .fail( done ); } ); + } ); describe( '.saveNewUser( data )', function () { - it( 'should return statuscode 403 and message when account id is missing', function ( done ) { + it( 'should auto generate random password when password is not given', function ( done ) { var data = { - username: 'rachel31@example.com', - email: 'rachel31@example.com', - password: '1234' + username: 'rachel32@example.com', + email: 'rachel32@example.com' }; UserService .saveNewUser( data ) - .then( function ( result ) { + .then( function ( newUser ) { - result.should.be.a( 'object' ); + expect( newUser ).to.be.an( 'object' ).and.be.ok; + expect( newUser ).to.have.property( 'id' ).and.be.ok; + expect( newUser ).to.have.property( 'username' ).and.equal( data.username ); + expect( newUser ).to.have.property( 'email' ).and.equal( data.email ); + expect( newUser ).to.have.property( 'password' ).and.be.ok; - result.should.have.property( 'statuscode', 403 ); - result.should.have.property( 'message' ); + return UserModel.find( { where: { email: data.email } } ); + } ) + .then( function ( user ) { + + expect( user ).to.be.an( 'object' ).and.be.ok; + expect( user ).to.have.property( 'id' ).and.be.ok; + expect( user ).to.have.property( 'username' ).and.equal( data.username ); + expect( user ).to.have.property( 'email' ).and.equal( data.email ); + expect( user ).to.have.property( 'password' ).and.be.ok; done(); } ) .fail( done ); } ); - it( 'should auto generate random password when password is not given', function ( done ) { + it( 'should hash password when password is given', function ( done ) { + var data = { - username: 'rachel32@example.com', - email: 'rachel32@example.com', - "AccountId": 1 + username: 'rachel33@example.com', + email: 'rachel33@example.com', + password: '123' }; UserService .saveNewUser( data ) - .then( function () { - return UserService.find( { - where: { email: data.email } - } ); + .then( function ( newUser ) { + + expect( newUser ).to.be.an( 'object' ).and.be.ok; + expect( newUser ).to.have.property( 'id' ).and.be.ok; + expect( newUser ).to.have.property( 'username' ).and.equal( data.username ); + expect( newUser ).to.have.property( 'email' ).and.equal( data.email ); + expect( newUser ).to.have.property( 'password' ).and.be.ok; + + return UserModel.find( { where: { email: data.email } } ); } ) .then( function ( user ) { - user.should.be.instanceOf( Array ); - user[0].should.be.a( 'object' ); - user[0].should.have.property( 'email', data.email ); - user[0].should.have.property( 'id' ); - user[0].should.have.property( 'password' ).and.not.be.empty; + expect( user ).to.be.an( 'object' ).and.be.ok; + expect( user ).to.have.property( 'id' ).and.be.ok; + expect( user ).to.have.property( 'username' ).and.equal( data.username ); + expect( user ).to.have.property( 'email' ).and.equal( data.email ); + expect( user ).to.have.property( 'password' ).and.not.equal( '123' ); + expect( user.password.length ).to.be.ok.and.be.above( '123'.length ); + done(); } ) .fail( done ); } ); - it( 'should hash password when password is given', function ( done ) { - + it( 'should return a new user object', function ( done ) { var data = { - username: 'rachel33@example.com', - email: 'rachel33@example.com', - password: '123', - "AccountId": 1 + username: 'rachel34@example.com', + email: 'rachel34@example.com', + password: '123' }; UserService .saveNewUser( data ) - .then( function () { - return UserService.find( { - where: { email: data.email } - } ); + .then( function ( newUser ) { + + expect( newUser ).to.be.an( 'object' ).and.be.ok; + expect( newUser ).to.have.property( 'id' ).and.be.ok; + expect( newUser ).to.have.property( 'username' ).and.equal( data.username ); + expect( newUser ).to.have.property( 'email' ).and.equal( data.email ); + expect( newUser ).to.have.property( 'password' ).and.be.ok; + + return UserModel.find( { where: { email: data.email } } ); } ) .then( function ( user ) { - user.should.be.instanceOf( Array ); - user[0].should.be.a( 'object' ); - user[0].should.have.property( 'email', data.email ); - user[0].should.have.property( 'id' ); - - user[0].should.have.property( 'password' ); - user[0].password.should.not.equal( '123' ); - user[0].password.length.should.be.above( '123'.length ); + + expect( user ).to.be.an( 'object' ).and.be.ok; + expect( user ).to.have.property( 'id' ).and.be.ok; + expect( user ).to.have.property( 'username' ).and.equal( data.username ); + expect( user ).to.have.property( 'email' ).and.equal( data.email ); + expect( user ).to.have.property( 'password' ).and.be.ok; + + user_1 = user; + user_1_json = user.toJSON(); + done(); } ) .fail( done ); + } ); - it( 'should return a new user object', function ( done ) { + } ); + + describe( '.updateUser( user, data )', function () { + + it( 'should be able to update firstname, lastname, email, phone and do not update other', function ( done ) { var data = { - username: 'rachel34@example.com', - email: 'rachel34@example.com', - password: '123', - "AccountId": 1 + firstname: 'mishka', + lastname: 'mikhajlov', + email: 'qwqwqw@mail.ru', + phone: '845848485', + + username: 'vasjok', + confirmed: true, + active: false }; UserService - .saveNewUser( data ) - .then( function () { - return UserService.find( { - where: { email: data.email } - } ); + .updateUser( user_1, data ) + .then( function ( result ) { + + expect( result ).to.be.an( 'object' ).and.be.ok; + expect( result ).to.have.property( 'id' ).and.equal( user_1_json.id ); + + return UserModel.find( user_1.id ); + } ) + .then( function ( user ) { + + expect( user ).to.be.an( 'object' ).and.be.ok; + expect( user ).to.have.property( 'id' ).and.equal( user_1_json.id ); + expect( user ).to.have.property( 'email' ).and.equal( data.email ); + expect( user ).to.have.property( 'firstname' ).and.equal( data.firstname ); + expect( user ).to.have.property( 'lastname' ).and.equal( data.lastname ); + expect( user ).to.have.property( 'phone' ).and.equal( data.phone ); + + expect( user ).to.have.property( 'username' ).and.not.equal( data.username ); + expect( user.username ).to.equal( user_1_json.username ); + expect( user ).to.have.property( 'confirmed' ).and.not.equal( data.confirmed ); + expect( user.confirmed ).to.equal( user_1_json.confirmed ); + expect( user ).to.have.property( 'active' ).and.not.equal( data.active ); + expect( user.active ).to.equal( user_1_json.active ); + + user_1_json = user.toJSON(); + + done(); + } ) + .fail( done ); + } ); + + } ); + + describe( '.checkEmailAndUpdate( user, data )', function () { + + it( 'should return status 400 and a message when user with email exists', function ( done ) { + var data = { + email: 'rachel32@example.com' + }; + + UserService + .checkEmailAndUpdate( user_1, data ) + .then( function ( result ) { + + expect( result ).to.be.an( 'object' ).and.be.ok; + expect( result ).to.have.property( 'statuscode' ).and.equal( 400 ); + expect( result ).to.have.property( 'message' ).and.be.an( 'string' ).and.be.ok; + + return UserModel.find( user_1.id ); + } ) + .then( function ( user ) { + + expect( user ).to.be.an( 'object' ).and.be.ok; + expect( user ).to.have.property( 'id' ).and.equal( user_1.id ); + expect( user ).to.have.property( 'email' ).and.equal( user_1.email ); + + done(); + } ) + .fail( done ); + } ); + + it( 'should be able to update email ', function ( done ) { + var data = { + email: 'rachel152@example.com' + }; + + UserService + .checkEmailAndUpdate( user_1, data ) + .then( function ( result ) { + + expect( result ).to.be.an( 'object' ).and.be.ok; + expect( result ).to.have.property( 'id' ).and.equal( user_1.id ); + + return UserModel.find( user_1.id ); } ) .then( function ( user ) { - user.should.be.instanceOf( Array ); - user[0].should.be.a( 'object' ); - user[0].should.have.property( 'email', data.email ); - user[0].should.have.property( 'id' ); + + expect( user ).to.be.an( 'object' ).and.be.ok; + expect( user ).to.have.property( 'id' ).and.equal( user_1_json.id ); + expect( user ).to.have.property( 'username' ).and.equal( user_1_json.username ); + expect( user ).to.have.property( 'email' ).and.not.equal( user_1_json.email ); + expect( user.email ).to.equal( data.email ); + + user_1_json = user.toJSON(); + done(); } ) .fail( done ); + } ); + it( 'should be able to update firstname, lastname, email, phone and do not update other', function ( done ) { + var data = { + firstname: 'firstname', + lastname: 'lastname', + email: 'qqq@mail.ru', + phone: '09548848', + + username: 'vasjok', + confirmed: true, + active: false + }; + + UserService + .checkEmailAndUpdate( user_1, data ) + .then( function ( result ) { + + expect( result ).to.be.an( 'object' ).and.be.ok; + expect( result ).to.have.property( 'id' ).and.equal( user_1_json.id ); + + return UserModel.find( user_1.id ); + } ) + .then( function ( user ) { + + expect( user ).to.be.an( 'object' ).and.be.ok; + expect( user ).to.have.property( 'id' ).and.equal( user_1_json.id ); + expect( user ).to.have.property( 'email' ).and.equal( data.email ); + expect( user ).to.have.property( 'firstname' ).and.equal( data.firstname ); + expect( user ).to.have.property( 'lastname' ).and.equal( data.lastname ); + expect( user ).to.have.property( 'phone' ).and.equal( data.phone ); + + expect( user ).to.have.property( 'username' ).and.not.equal( data.username ); + expect( user ).to.have.property( 'confirmed' ).and.not.equal( data.confirmed ); + expect( user ).to.have.property( 'active' ).and.not.equal( data.active ); + + done(); + } ) + .fail( done ); } ); + } ); - describe( '.resendAccountConfirmation( me, userId )', function () { + describe( '.handleUpdateUser( userId, data )', function () { + + it( 'should return status 403 and a message when user with userId do not exists', function ( done ) { + var data = { + firstname: 'qwqwqw', + lastname: 'ererere' + }; + + UserService + .handleUpdateUser( 1515151515, data ) + .then( function ( result ) { + + expect( result ).to.be.an( 'object' ).and.be.ok; + expect( result ).to.have.property( 'statuscode' ).and.equal( 403 ); + expect( result ).to.have.property( 'message' ).and.be.an( 'string' ).and.be.ok; + + done(); + } ) + .fail( done ); + } ); + + it( 'should return status 403 and a message if old password incorrect', function ( done ) { + var data = { + firstname: 'qwqwqw', + lastname: 'ererere', + email: 'xcxcxcxcx@mail.ru', + phone: '545454545', + password: '1223345', + new_password: '15151515' + }; + + UserService + .handleUpdateUser( user_1.id, data ) + .then( function ( result ) { + + expect( result ).to.be.an( 'object' ).and.be.ok; + expect( result ).to.have.property( 'statuscode' ).and.equal( 403 ); + expect( result ).to.have.property( 'message' ).and.be.an( 'string' ).and.be.ok; + + return UserModel.find( user_1.id ); + } ) + .then( function ( user ) { + + expect( user ).to.be.an( 'object' ).and.be.ok; + expect( user ).to.have.property( 'id' ).and.equal( user_1.id ); + expect( user ).to.have.property( 'firstname' ).and.not.equal( data.firstname ); + expect( user ).to.have.property( 'lastname' ).and.not.equal( data.lastname ); + expect( user ).to.have.property( 'email' ).and.not.equal( data.email ); + expect( user ).to.have.property( 'phone' ).and.not.equal( data.phone ); + + old_password = user.password; + + done(); + } ) + .fail( done ); + } ); + + it( 'should hash password and update it', function ( done ) { + var data = { + firstname: 'qwqwqw', + lastname: 'ererere', + email: 'xcxcxcxcx@mail.ru', + phone: '545454545', + password: '123', + new_password: '321' + }; + + expect( user_1.password ).to.equal( old_password ); + + UserService + .handleUpdateUser( user_1.id, data ) + .then( function ( result ) { + + expect( result ).to.be.an( 'object' ).and.be.ok; + expect( result ).to.have.property( 'id' ).and.equal( user_1_json.id ); + + return UserModel.find( user_1.id ); + } ) + .then( function ( user ) { + + expect( user ).to.be.an( 'object' ).and.be.ok; + expect( user ).to.have.property( 'id' ).and.equal( user_1.id ); + expect( user ).to.have.property( 'firstname' ).and.equal( data.firstname ); + expect( user ).to.have.property( 'lastname' ).and.equal( data.lastname ); + expect( user ).to.have.property( 'email' ).and.equal( data.email ); + expect( user ).to.have.property( 'phone' ).and.equal( data.phone ); + expect( user ).to.have.property( 'password' ).and.not.equal( old_password ); + + done(); + } ) + .fail( done ); + } ); + + } ); + + describe.skip( '.resendAccountConfirmation( me, userId )', function () { it( 'should return status code 403 and message when user id does not exist', function ( done ) { var userId = 'noneExistedId' - , accId = 1; + , accId = 1; UserService .resendAccountConfirmation( accId, userId ) @@ -805,7 +1001,6 @@ describe( 'service.UserService', function () { .fail( done ); } ); - it( 'should return statuscode 200 and a message', function ( done ) { var newuser = { diff --git a/modules/orm/config/default.json b/modules/orm/config/default.json index 5da4591..ec9fbcf 100644 --- a/modules/orm/config/default.json +++ b/modules/orm/config/default.json @@ -11,26 +11,17 @@ } }, "modelAssociations": { - "SubscriptionModel": { - "hasMany": [ "AccountModel" ] - }, - "AccountModel": { - "belongsTo": [ "SubscriptionModel" ] - }, - "UserModel": { - "belongsTo": [ "AccountModel" ] - }, + "UserModel": {}, "EmailModel": { "hasMany": [ "EmailAttachmentModel" ], - "belongsTo": [ "UserModel", "AccountModel" ] + "belongsTo": [ "UserModel" ] }, "EmailUserModel":{ "belongsTo" : [ "EmailModel", "UserModel" ] }, "EmailAttachmentModel": { "belongsTo": [ "EmailModel" ] - }, - "ExampleModel": {} + } } } diff --git a/package.json b/package.json index b788756..a9efd36 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,6 @@ "odm", "background-tasks", "clever-auth", - "example-module", "clever-email" ] } From 25fcd81d0a23d838ff0f45fd5e91ebe14eeca262 Mon Sep 17 00:00:00 2001 From: Volodymyr Denshchykov Date: Fri, 31 Jan 2014 17:15:00 +0200 Subject: [PATCH 13/24] updated test for controller WIP --- config/locac.json | 31 +++ modules/clever-auth/config/default.json | 4 +- .../clever-auth/controllers/UserController.js | 23 +- modules/clever-auth/services/UserService.js | 2 +- .../unit/test.controllers.UserController.js | 242 ++++++++++++++---- .../tests/unit/test.service.UserService.js | 4 +- 6 files changed, 235 insertions(+), 71 deletions(-) create mode 100644 config/locac.json diff --git a/config/locac.json b/config/locac.json new file mode 100644 index 0000000..a9f3b60 --- /dev/null +++ b/config/locac.json @@ -0,0 +1,31 @@ +{ + "environmentName": "LOCAL", + + "memcacheHost": "localhost:11211", + + "db": { + "username": "root", + "password": "qqq", + "database": "clevertech", + "options": { + "host": "localhost", + "dialect": "mysql", + "port": 3306 + } + }, + + "testDb": { + "username": "root", + "password": "qqq", + "database": "clTest", + "options": { + "host": "localhost", + "dialect": "mysql", + "port": 3306 + } + }, + + "mongoose": { + "uri": "mongodb://localhost/database" + } +} \ No newline at end of file diff --git a/modules/clever-auth/config/default.json b/modules/clever-auth/config/default.json index 730999f..a99f475 100644 --- a/modules/clever-auth/config/default.json +++ b/modules/clever-auth/config/default.json @@ -9,6 +9,8 @@ "key": "" }, - "hostUrl": "http://localhost:" + "hostUrl": "http://localhost:", + + "email_confirmation": false } } \ No newline at end of file diff --git a/modules/clever-auth/controllers/UserController.js b/modules/clever-auth/controllers/UserController.js index 4c8a996..feb6340 100644 --- a/modules/clever-auth/controllers/UserController.js +++ b/modules/clever-auth/controllers/UserController.js @@ -98,7 +98,7 @@ module.exports = function ( UserService ) { var tplData = { firstName: data.firstname, userEmail: data.email, - tplTitle: 'BoltHR: User Confirmation', + tplTitle: 'User Confirmation', subject: data.firstname || data.email + ' wants to add you to their recruiting team!' }; @@ -107,13 +107,13 @@ module.exports = function ( UserService ) { .createUser( data, tplData ) .then( this.proxy( 'handleServiceMessage' ) ) .fail( this.proxy( 'handleException' ) ); - }, // tested without email confirmation + }, //tested without email confirmation putAction: function () { var meId = this.req.user.id , userId = this.req.params.id , data = this.req.body; -console.log(data) + if ( !userId ) { this.send( 'Bad Request', 400 ); return; @@ -124,16 +124,16 @@ console.log(data) .then( this.proxy( 'handleSessionUpdate', meId ) ) .fail( this.proxy( 'handleException' ) ); - }, + }, //tested handleSessionUpdate: function ( meId, user ) { - if ( user.id && (meId === user.id) ) { - this.loginUserJson( user ); + if ( user.id && ( meId === user.id ) ) { + this.loginUserJson ( user ); return; } this.handleServiceMessage( user ); - }, + }, //tested --> putAction deleteAction: function () { var uId = this.req.params.id; @@ -177,14 +177,14 @@ console.log(data) }, loginUserJson: function ( user ) { - + console.log(user) this.req.login( user, this.proxy( 'handleLoginJson', user ) ); - }, + }, //tested --> putAction, loginAction handleLoginJson: function ( user, err ) { if ( err ) return this.handleException( err ); this.send( user, 200 ); - }, + }, //tested --> putAction, loginAction currentAction: function () { var user = this.req.user @@ -299,7 +299,6 @@ console.log(data) if ( token != hash ) { return this.send( 'Invalid token', 400 ); } - ; this.handleUpdatePassword( newPassword, [user] ); @@ -365,7 +364,7 @@ console.log(data) , data = this.req.body; var tplData = { - firstName: this.req.user.firstname, accountSubdomain: this.req.user.account.subdomain, userFirstName: '', userEmail: '', tplTitle: 'BoltHR: Account Confirmation', subject: this.req.user.firstname + ' wants to add you to their recruiting team!' + firstName: this.req.user.firstname, accountSubdomain: this.req.user.account.subdomain, userFirstName: '', userEmail: '', tplTitle: 'Account Confirmation', subject: this.req.user.firstname + ' wants to add you to their recruiting team!' }; UserService diff --git a/modules/clever-auth/services/UserService.js b/modules/clever-auth/services/UserService.js index 856492f..3589e85 100644 --- a/modules/clever-auth/services/UserService.js +++ b/modules/clever-auth/services/UserService.js @@ -177,7 +177,7 @@ module.exports = function ( sequelize, console.log( err ); } - if ( EmailService === null ) { + if ( EmailService === null || !config['clever-auth'].email_confirmation ) { data.confirmed = true; diff --git a/modules/clever-auth/tests/unit/test.controllers.UserController.js b/modules/clever-auth/tests/unit/test.controllers.UserController.js index 4348ab6..5c44d9b 100644 --- a/modules/clever-auth/tests/unit/test.controllers.UserController.js +++ b/modules/clever-auth/tests/unit/test.controllers.UserController.js @@ -6,10 +6,12 @@ var expect = require( 'chai' ).expect , sinon = require( 'sinon' ) , Service; +var new_user; + describe( 'controllers.UserController', function () { var Service, UserController, ctrl, users = []; - beforeEach( function ( done ) { + before( function ( done ) { testEnv( function ( _UserService_, _UserController_ ) { var req = { params: { action: 'fakeAction'}, @@ -60,9 +62,21 @@ describe( 'controllers.UserController', function () { } ); } ); - afterEach( function () { - Service.constructor.instance = null; - } ); + afterEach( function ( done ) { + + ctrl.req = { + params: { action: 'fakeAction'}, + method: 'GET', + query: {}, + body: {} + }; + + ctrl.res = { + json: function () {} + }; + + done(); + }); describe( 'static members', function () { @@ -356,32 +370,23 @@ describe( 'controllers.UserController', function () { it( 'should be able to get the error if user with such email already exist', function ( done ) { var data = { username: 'admin', - email: 'admin@example.com', + email: users[0].email, password: 'secret_password' }; - Service - .create( data ) - .then( function( user ) { - - expect( user ).to.be.an( 'object' ).and.be.ok; - expect( user ).to.have.property( 'id' ).and.be.ok; - - ctrl.send = function ( result, status ) { + ctrl.send = function ( result, status ) { - expect( status ).to.equal( 400 ); + expect ( status ).to.equal ( 400 ); - expect( result ).to.be.an( 'string' ).and.be.ok; + expect ( result ).to.be.an ( 'string' ).and.be.ok; - done(); - }; + done (); + }; - ctrl.req.body = data; + ctrl.req.body = data; - ctrl.postAction(); + ctrl.postAction (); - }) - .fail( done ); } ); it( 'should be able to get the error if insufficiently email', function ( done ) { @@ -411,6 +416,26 @@ describe( 'controllers.UserController', function () { describe( 'putAction()', function () { + before( function( done ) { + + Service.saveNewUser( { + firstName: 'cdxsasdf', + username: 'xcxcxc@example.com', + email: 'sasasas@example.com', + password: 'secret_password' + } ) + .then( function ( user ) { + + expect( user ).to.be.an( 'object' ).and.be.ok; + expect( user ).to.have.property( 'id' ).and.be.ok; + + new_user = user; + + done(); + }) + .fail( done ); + }); + it( 'should be able to get the error if insufficiently userId', function ( done ) { var data = { username: 'admin', @@ -421,108 +446,215 @@ describe( 'controllers.UserController', function () { expect( status ).to.equal( 400 ); - expect( result ).to.be.an( 'string' ).and.be.ok; + expect( result ).to.be.an( 'string' ).and.equal( 'Bad Request' ); done(); }; - ctrl.req = { - user: { id: users[0].id }, - params: { id: null } - }; - + ctrl.req.user = { id: users[0].id }; + ctrl.req.params = { id: null }; ctrl.req.body = data; ctrl.putAction(); } ); - it( 'should hash password and update user', function ( done ) { + it( 'should be able to get the error if new email already exist', function ( done ) { var data = { + email: users[0].email, username: 'admin', - email: 'admin@example.com', password: 'secret_password' }; ctrl.send = function ( result, status ) { + expect( status ).to.equal( 400 ); + + expect( result ).to.be.an( 'string' ).and.equal( 'email already exists' ); + + done(); + }; + + ctrl.req.user = { id: new_user.id }; + ctrl.req.params = { id: new_user.id }; + ctrl.req.body = data; + + ctrl.putAction(); + + } ); + + it( 'should be able to get the error if old password incorrect', function ( done ) { + var data = { + password: 'secret_password_incorrect', + new_password: 'secret_password_new' + }; + + ctrl.send = function ( result, status ) { + + expect( status ).to.equal( 403 ); + + expect( result ).to.be.an( 'string' ).and.equal( 'Invalid password' ); + + done(); + }; + + ctrl.req.user = { id: new_user.id }; + ctrl.req.params = { id: new_user.id }; + ctrl.req.body = data; + + ctrl.putAction(); + + } ); + + it.skip( 'should hash password and to update firstname, lastname, email, phone, password and do not update other', function ( done ) { + var data = { + password: 'secret_password', + new_password: 'secret_password_new', + + firstname: 'mishka', + lastname: 'mikhajlov', + email: 'qwqwqw@mail.ru', + phone: '845848485', + + username: 'vasjok', + confirmed: true, + active: false + }; + + var old_password = new_user.password; + + ctrl.send = function ( user, status ) { + expect( status ).to.equal( 200 ); - UserService.findById( users[0].id ) - .then( function ( user ) { + expect( user ).to.be.an( 'object' ).and.be.ok; + expect( user ).to.have.property( 'id' ).and.equal( new_ser.id ); + + UserService.findById( new_user.id ) + .then( function ( newUser ) { + + expect( newUser ).to.be.an( 'object' ).and.be.ok; + expect( newUser ).to.have.property( 'id' ).and.equal( new_user.id ); + + expect( newUser ).to.have.property( 'email' ).and.equal( data.email ); + expect( newUser ).to.have.property( 'password' ).and.not.equal( old_password ); + expect( newUser ).to.have.property( 'firstname' ).and.equal( data.firstname ); + expect( newUser ).to.have.property( 'lastname' ).and.equal( data.lastname ); + expect( newUser ).to.have.property( 'email' ).and.equal( data.email ); + expect( newUser ).to.have.property( 'phone' ).and.equal( data.phone ); - user.username.should.equal( 'admin' ); - user.email.should.equal( 'admin@example.com' ); - user.password.should.equal( '2394a9661a9089208c1c9c65ccac85a91da6a859' ); + expect( newUser ).to.have.property( 'username' ).and.not.equal( data.username ); + expect( newUser ).to.have.property( 'confirmed' ).and.not.equal( data.confirmed ); + expect( newUser ).to.have.property( 'active' ).and.not.equal( data.active ); done(); } ) .fail( done ); }; - ctrl.req = { - body: data, - user: { id: users[0].id }, - params: { id: users[0].id } - }; + ctrl.req.user = { id: new_user.id }; + ctrl.req.params = { id: new_user.id }; + ctrl.req.body = data; ctrl.putAction(); } ); - } ); - describe.skip( 'loginAction()', function () { + describe( 'loginAction()', function () { + it( 'should call req.login(user) if user with such credentials found', function ( done ) { - ctrl.req.login = function ( user ) { - user.id.should.eql( users[0].id ); + + before( function( done ) { + + Service + .findById( new_user.id ) + .then( function( user ) { + + user + .updateAttributes( { confirmed: true, active: true } ) + .success( done ) + .error( done ); + + done(); + }, done ); + }); + + ctrl.send = function ( user, status ) { + +console.log(user) +console.log(status) + + expect( user ).to.be.an( 'object' ).and.be.ok; + expect( user ).to.have.property( 'id' ).and.equal( users[0].id ); + done(); }; + ctrl.req.body = { - username: users[0].username, - password: '1234' + username: new_user.username, + password: 'secret_password' }; + ctrl.loginAction(); } ); - it( 'should call .send(200) if user if such credentials found', function ( done ) { + it( 'should call .send( 200 ) if user if such credentials found', function ( done ) { + ctrl.req.login = function ( data, done ) { done( null ); }; + ctrl.send = function ( user, code ) { - user.username.should.equal( users[0].username ); - code.should.equal( 200 ); + + expect( user ).to.be.an( 'object' ).and.be.ok; + expect( user ).to.have.property( 'id' ).and.equal( users[0].id ); + expect( user ).to.have.property( 'username' ).and.equal( users[0].username ); + + expect( code ).to.equal( 200 ); + done(); }; + ctrl.req.body = { username: users[0].username, password: '1234' }; + ctrl.loginAction(); } ); - it( 'should call .send(403) if user is not found', function ( done ) { + it( 'should call .send( 403 ) if user is not found', function ( done ) { ctrl.send = function ( data, code ) { - data.should.eql( {} ); - code.should.equal( 403 ); + + expect( data ).to.be.an( 'object' ).and.be.empty; + + expect( code ).to.equal( 200 ); + done(); }; + ctrl.req.body = { username: users[0].username, password: '12345' }; + ctrl.loginAction(); } ); } ); - describe.skip( 'logoutAction()', function () { - it( 'should call req.logout() and .send(200)', function () { + describe( 'logoutAction()', function () { + + it( 'should call req.logout() and .send(200)', function ( done ) { ctrl.req.logout = sinon.spy(); ctrl.res.send = sinon.spy(); ctrl.logoutAction(); - ctrl.req.logout.called.should.be.true; - ctrl.res.send.calledWith( 200 ).should.be.true; + expect( ctrl.req.logout.called ).to.be.true; + expect( ctrl.res.send.calledWith( 200 ) ).to.be.true; + + done(); } ); + } ); } ); \ No newline at end of file diff --git a/modules/clever-auth/tests/unit/test.service.UserService.js b/modules/clever-auth/tests/unit/test.service.UserService.js index 8f50f0b..946d2a9 100644 --- a/modules/clever-auth/tests/unit/test.service.UserService.js +++ b/modules/clever-auth/tests/unit/test.service.UserService.js @@ -439,7 +439,7 @@ describe( 'service.UserService', function () { .createUser( data ) .then( function () { - if ( EmailService !== null ) { + if ( EmailService !== null && config['clever-auth'].email_confirmation ) { expect( UserService.generatePasswordResetHash.calledOnce ).to.be.true; @@ -470,7 +470,7 @@ describe( 'service.UserService', function () { .createUser( data ) .then( function () { - if ( EmailService !== null ) { + if ( EmailService !== null && config['clever-auth'].email_confirmation ) { expect( UserService.mailPasswordRecoveryToken.calledOnce ).to.be.true; From d2d02dae37dc2598d7571d8cec272ec6d4cc4a1e Mon Sep 17 00:00:00 2001 From: Volodymyr Denshchykov Date: Fri, 31 Jan 2014 17:20:35 +0200 Subject: [PATCH 14/24] fix console.log --- config/locac.json | 31 ------------------- .../clever-auth/controllers/UserController.js | 2 -- .../unit/test.controllers.UserController.js | 3 -- 3 files changed, 36 deletions(-) delete mode 100644 config/locac.json diff --git a/config/locac.json b/config/locac.json deleted file mode 100644 index a9f3b60..0000000 --- a/config/locac.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "environmentName": "LOCAL", - - "memcacheHost": "localhost:11211", - - "db": { - "username": "root", - "password": "qqq", - "database": "clevertech", - "options": { - "host": "localhost", - "dialect": "mysql", - "port": 3306 - } - }, - - "testDb": { - "username": "root", - "password": "qqq", - "database": "clTest", - "options": { - "host": "localhost", - "dialect": "mysql", - "port": 3306 - } - }, - - "mongoose": { - "uri": "mongodb://localhost/database" - } -} \ No newline at end of file diff --git a/modules/clever-auth/controllers/UserController.js b/modules/clever-auth/controllers/UserController.js index feb6340..96fe207 100644 --- a/modules/clever-auth/controllers/UserController.js +++ b/modules/clever-auth/controllers/UserController.js @@ -177,7 +177,6 @@ module.exports = function ( UserService ) { }, loginUserJson: function ( user ) { - console.log(user) this.req.login( user, this.proxy( 'handleLoginJson', user ) ); }, //tested --> putAction, loginAction @@ -305,7 +304,6 @@ module.exports = function ( UserService ) { }, confirmAction: function () { - console.log(this.req.query) var password = this.req.body.password , token = this.req.body.token , userId = this.req.body.userId; diff --git a/modules/clever-auth/tests/unit/test.controllers.UserController.js b/modules/clever-auth/tests/unit/test.controllers.UserController.js index 5c44d9b..8912471 100644 --- a/modules/clever-auth/tests/unit/test.controllers.UserController.js +++ b/modules/clever-auth/tests/unit/test.controllers.UserController.js @@ -582,9 +582,6 @@ describe( 'controllers.UserController', function () { ctrl.send = function ( user, status ) { -console.log(user) -console.log(status) - expect( user ).to.be.an( 'object' ).and.be.ok; expect( user ).to.have.property( 'id' ).and.equal( users[0].id ); From 1357c84391f806c8c0f7c00171162212c05cf3b8 Mon Sep 17 00:00:00 2001 From: Volodymyr Denshchykov Date: Sun, 2 Feb 2014 23:21:07 +0200 Subject: [PATCH 15/24] updated tests for controller and service --- config/locac.json | 31 + .../clever-auth/controllers/UserController.js | 78 +- modules/clever-auth/routes.js | 4 +- modules/clever-auth/services/UserService.js | 52 +- .../unit/test.controllers.UserController.js | 1198 +++++++++++++++-- .../tests/unit/test.service.UserService.js | 60 +- 6 files changed, 1272 insertions(+), 151 deletions(-) create mode 100644 config/locac.json diff --git a/config/locac.json b/config/locac.json new file mode 100644 index 0000000..a9f3b60 --- /dev/null +++ b/config/locac.json @@ -0,0 +1,31 @@ +{ + "environmentName": "LOCAL", + + "memcacheHost": "localhost:11211", + + "db": { + "username": "root", + "password": "qqq", + "database": "clevertech", + "options": { + "host": "localhost", + "dialect": "mysql", + "port": 3306 + } + }, + + "testDb": { + "username": "root", + "password": "qqq", + "database": "clTest", + "options": { + "host": "localhost", + "dialect": "mysql", + "port": 3306 + } + }, + + "mongoose": { + "uri": "mongodb://localhost/database" + } +} \ No newline at end of file diff --git a/modules/clever-auth/controllers/UserController.js b/modules/clever-auth/controllers/UserController.js index 96fe207..cc4d8ab 100644 --- a/modules/clever-auth/controllers/UserController.js +++ b/modules/clever-auth/controllers/UserController.js @@ -70,18 +70,18 @@ module.exports = function ( UserService ) { }, { listAction: function () { - UserService.find( ) - .then( this.proxy( 'send', 200 ) ) + UserService.listUsers() + .then( this.proxy( 'handleServiceMessage' ) ) .fail( this.proxy( 'handleException' ) ); - }, + }, //tested getAction: function () { var uId = this.req.params.id; UserService.getUserFullDataJson( { id: uId } ) - .then( this.proxy( 'send', 200 ) ) + .then( this.proxy( 'handleServiceMessage' ) ) .fail( this.proxy( 'handleException' ) ); - }, + }, //tested postAction: function () { var data = this.req.body; @@ -133,61 +133,39 @@ module.exports = function ( UserService ) { } this.handleServiceMessage( user ); - }, //tested --> putAction + }, //tested through putAction deleteAction: function () { var uId = this.req.params.id; - UserService.destroy( uId ) - .then( this.proxy( 'handleDeletionData' ) ) + UserService.deleteUser( uId ) + .then( this.proxy( 'handleServiceMessage' ) ) .fail( this.proxy( 'handleException' ) ); - }, - - handleDeletionData: function ( obj ) { - if ( !obj.deletedAt ) { - this.send( {}, 500 ); - } - this.send( {}, 200 ); - }, - - handleUpdatePassword: function ( newPassword, user ) { - - if ( user.length ) { - user = user[0]; - user.updateAttributes( { - password: crypto.createHash( 'sha1' ).update( newPassword ).digest( 'hex' ) - } ).success( function ( user ) { - this.send( {status: 200, results: user} ); - }.bind( this ) - ).fail( this.proxy( 'handleException' ) ); - } else { - this.send( {status: 400, error: "Incorrect old password!"} ); - } - }, + }, //tested loginAction: function () { passport.authenticate( 'local', this.proxy( 'handleLocalUser' ) )( this.req, this.res, this.next ); - }, + }, //tested handleLocalUser: function ( err, user ) { if ( err ) return this.handleException( err ); if ( !user ) return this.send( {}, 403 ); this.loginUserJson( user ); - }, + }, //tested through loginAction loginUserJson: function ( user ) { this.req.login( user, this.proxy( 'handleLoginJson', user ) ); - }, //tested --> putAction, loginAction + }, //tested through loginAction, putAction, currentAction handleLoginJson: function ( user, err ) { if ( err ) return this.handleException( err ); this.send( user, 200 ); - }, //tested --> putAction, loginAction + }, //tested through loginAction, putAction currentAction: function () { var user = this.req.user - , reload = this.req.query.reload || false; + , reload = this.req.query.reload || false; if ( !user ) { this.send( {}, 404 ); @@ -204,21 +182,12 @@ module.exports = function ( UserService ) { .then( this.proxy( 'loginUserJson' ) ) .fail( this.proxy( 'handleException' ) ); - }, - - authorizedUser: function ( user ) { - if ( user ) { - this.req.login( user ); - this.res.send( 200 ); - } else { - this.res.send( 403 ); - } - }, + }, //tested logoutAction: function () { this.req.logout(); this.res.send( {}, 200 ); - }, + }, //tested recoverAction: function () { var email = this.req.body.email; @@ -303,6 +272,21 @@ module.exports = function ( UserService ) { }, + handleUpdatePassword: function ( newPassword, user ) { + + if ( user.length ) { + user = user[0]; + user.updateAttributes( { + password: crypto.createHash( 'sha1' ).update( newPassword ).digest( 'hex' ) + } ).success( function ( user ) { + this.send( {status: 200, results: user} ); + }.bind( this ) + ).fail( this.proxy( 'handleException' ) ); + } else { + this.send( {status: 400, error: "Incorrect old password!"} ); + } + }, + confirmAction: function () { var password = this.req.body.password , token = this.req.body.token diff --git a/modules/clever-auth/routes.js b/modules/clever-auth/routes.js index ae3cb91..d19ec56 100644 --- a/modules/clever-auth/routes.js +++ b/modules/clever-auth/routes.js @@ -4,9 +4,11 @@ module.exports = function ( UserController ) { + app.get('/users', UserController.requiresLogin, UserController.attach()); + app.all('/users/?:id?', UserController.requiresLogin, UserController.attach()); + app.all('/user/?:action?', UserController.attach()); - app.post('/users/confirm', UserController.checkPasswordRecoveryData, UserController.attach()); // app.post('/users/confirm', UserController.attach()); // app.get('/users', UserController.attach()); diff --git a/modules/clever-auth/services/UserService.js b/modules/clever-auth/services/UserService.js index 3589e85..3648a23 100644 --- a/modules/clever-auth/services/UserService.js +++ b/modules/clever-auth/services/UserService.js @@ -55,7 +55,7 @@ module.exports = function ( sequelize, .success( function ( user ) { if ( !user ) { - return deferred.resolve(); + return deferred.resolve( {} ); } var userJson = JSON.parse( JSON.stringify( user ) ); @@ -352,7 +352,55 @@ module.exports = function ( sequelize, .error( deferred.reject ); return deferred.promise; - } //tested + }, //tested + + listUsers: function() { + var deferred = Q.defer(); + + ORMUserModel + .findAll( { where: { deletedAt: null } } ) + .success( function( users ) { + if ( !!users && !!users.length ) { + deferred.resolve( users.map( function( u ) { return u.toJSON(); } ) ); + } else { + deferred.resolve( {} ); + } + }) + .error( deferred.reject ); + + return deferred.promise; + }, //tested + + deleteUser: function( userId ) { + var deferred = Q.defer(); + + ORMUserModel + .find( userId ) + .success( function( user ) { + + if ( !!user && !!user.id ) { + + user + .destroy() + .success( function( result ) { + + if ( !result.deletedAt ) { + deferred.resolve( { statuscode: 500, message: 'error' } ); + } else { + deferred.resolve( { statuscode: 200, message: 'user is deleted' } ); + } + + }) + .error( deferred.reject ); + + } else { + deferred.resolve( { statuscode: 403, message: 'user do not exist' } ) + } + }) + .error( deferred.reject ); + + return deferred.promise; + } } ); diff --git a/modules/clever-auth/tests/unit/test.controllers.UserController.js b/modules/clever-auth/tests/unit/test.controllers.UserController.js index 8912471..d70e0a7 100644 --- a/modules/clever-auth/tests/unit/test.controllers.UserController.js +++ b/modules/clever-auth/tests/unit/test.controllers.UserController.js @@ -418,12 +418,12 @@ describe( 'controllers.UserController', function () { before( function( done ) { - Service.saveNewUser( { + Service.createUser( { firstName: 'cdxsasdf', username: 'xcxcxc@example.com', email: 'sasasas@example.com', password: 'secret_password' - } ) + }, {} ) .then( function ( user ) { expect( user ).to.be.an( 'object' ).and.be.ok; @@ -436,41 +436,113 @@ describe( 'controllers.UserController', function () { .fail( done ); }); - it( 'should be able to get the error if insufficiently userId', function ( done ) { + it( 'should call UserService.handleUpdateUser( userId, data ) if the data is complete', function ( done ) { var data = { - username: 'admin', - password: 'secret_password' + firstname: 'petrushka' + }; + + var spy = sinon.spy( Service, 'handleUpdateUser' ); + + ctrl.send = function ( result, status ) { + + expect( status ).to.equal( 200 ); + + expect( result ).to.be.an( 'object' ).and.be.ok; + expect( result ).to.have.property( 'id' ).and.equal( new_user.id ); + expect( result ).to.have.property( 'firstname' ).and.equal( data.firstname ); + + expect( spy.called ).to.be.true; + expect( spy.calledOnce ).to.be.true; + + var spyCall = spy.getCall ( 0 ).args; + + expect( spyCall ).to.be.an( 'array' ); + + expect( spyCall[0] ).to.be.an( 'number' ).and.equal( new_user.id ); + + expect( spyCall[1] ).to.be.an( 'object' ).and.be.ok; + expect( spyCall[1] ).to.have.property( 'firstname' ).and.equal( data.firstname ); + + spy.restore(); + + done(); + }; + + ctrl.req.user = { id: new_user.id }; + ctrl.req.params = { id: new_user.id }; + ctrl.req.body = data; + + ctrl.req.login = function( user, callback ) { + if ( !!user && !!user.id ) { + callback( null, user ); + } + }; + + ctrl.putAction(); + } ); + + it( 'should not call UserService.handleUpdateUser if insufficiently userId', function ( done ) { + var data = { + firstname: 'petrushka' }; + var spy = sinon.spy( Service, 'handleUpdateUser' ); + ctrl.send = function ( result, status ) { expect( status ).to.equal( 400 ); expect( result ).to.be.an( 'string' ).and.equal( 'Bad Request' ); + expect( spy.called ).to.be.false; + + spy.restore(); + done(); }; - ctrl.req.user = { id: users[0].id }; + ctrl.req.user = { id: new_user.id }; ctrl.req.params = { id: null }; ctrl.req.body = data; - ctrl.putAction(); + ctrl.req.login = function( user, callback ) { + if ( !!user && !!user.id ) { + callback( null, user ); + } + }; + ctrl.putAction(); } ); - it( 'should be able to get the error if new email already exist', function ( done ) { + it( 'should call UserService.checkEmailAndUpdate( user, data ) if the data is complete', function ( done ) { var data = { - email: users[0].email, - username: 'admin', - password: 'secret_password' + firstname: 'petrush' }; + var spy = sinon.spy( Service, 'checkEmailAndUpdate' ); + ctrl.send = function ( result, status ) { - expect( status ).to.equal( 400 ); + expect( status ).to.equal( 200 ); - expect( result ).to.be.an( 'string' ).and.equal( 'email already exists' ); + expect( result ).to.be.an( 'object' ).and.be.ok; + expect( result ).to.have.property( 'id' ).and.equal( new_user.id ); + expect( result ).to.have.property( 'firstname' ).and.equal( data.firstname ); + + expect( spy.called ).to.be.true; + expect( spy.calledOnce ).to.be.true; + + var spyCall = spy.getCall ( 0 ).args; + + expect( spyCall ).to.be.an( 'array' ); + + expect( spyCall[0] ).to.be.an( 'object' ).and.be.ok; + expect( spyCall[0] ).to.have.property( 'id' ).and.equal( new_user.id ); + + expect( spyCall[1] ).to.be.an( 'object' ).and.be.ok; + expect( spyCall[1] ).to.have.property( 'firstname' ).and.equal( data.firstname ); + + spy.restore(); done(); }; @@ -479,21 +551,78 @@ describe( 'controllers.UserController', function () { ctrl.req.params = { id: new_user.id }; ctrl.req.body = data; + ctrl.req.login = function( user, callback ) { + if ( !!user && !!user.id ) { + callback( null, user ); + } + }; + ctrl.putAction(); + } ); + + it( 'should not call UserService.checkEmailAndUpdate if user with such id do not exist', function ( done ) { + var data = { + firstname: 'petrushka' + }; + + var spy = sinon.spy( Service, 'checkEmailAndUpdate' ); + + ctrl.send = function ( result, status ) { + + expect( status ).to.equal( 403 ); + expect( result ).to.be.an( 'string' ).and.equal( 'invalid id' ); + + expect( spy.called ).to.be.false; + + spy.restore(); + + done(); + }; + + ctrl.req.user = { id: new_user.id }; + ctrl.req.params = { id: new_user.id + 10000000 }; + ctrl.req.body = data; + + ctrl.req.login = function( user, callback ) { + if ( !!user && !!user.id ) { + callback( null, user ); + } + }; + + ctrl.putAction(); } ); - it( 'should be able to get the error if old password incorrect', function ( done ) { + it( 'should call UserService.checkEmailAndUpdate( user, data ) if old password correct', function ( done ) { var data = { - password: 'secret_password_incorrect', + password: 'secret_password', new_password: 'secret_password_new' }; + var spy = sinon.spy( Service, 'checkEmailAndUpdate' ); + ctrl.send = function ( result, status ) { - expect( status ).to.equal( 403 ); + expect( status ).to.equal( 200 ); - expect( result ).to.be.an( 'string' ).and.equal( 'Invalid password' ); + expect( result ).to.be.an( 'object' ).and.be.ok; + expect( result ).to.have.property( 'id' ).and.equal( new_user.id ); + + expect( spy.called ).to.be.true; + expect( spy.calledOnce ).to.be.true; + + var spyCall = spy.getCall ( 0 ).args; + + expect( spyCall ).to.be.an( 'array' ); + + expect( spyCall[0] ).to.be.an( 'object' ).and.be.ok; + expect( spyCall[0] ).to.have.property( 'id' ).and.equal( new_user.id ); + + expect( spyCall[1] ).to.be.an( 'object' ).and.be.ok; + expect( spyCall[1] ).to.have.property( 'password' ).and.equal( data.password ); + expect( spyCall[1] ).to.have.property( 'new_password' ).and.equal( data.new_password ); + + spy.restore(); done(); }; @@ -502,156 +631,1027 @@ describe( 'controllers.UserController', function () { ctrl.req.params = { id: new_user.id }; ctrl.req.body = data; - ctrl.putAction(); + ctrl.req.login = function( user, callback ) { + if ( !!user && !!user.id ) { + callback( null, user ); + } + }; + ctrl.putAction(); } ); - it.skip( 'should hash password and to update firstname, lastname, email, phone, password and do not update other', function ( done ) { + it( 'should not call UserService.checkEmailAndUpdate if old password incorrect', function ( done ) { var data = { password: 'secret_password', - new_password: 'secret_password_new', + new_password: 'secret_password_new' + }; - firstname: 'mishka', - lastname: 'mikhajlov', - email: 'qwqwqw@mail.ru', - phone: '845848485', + var spy = sinon.spy( Service, 'checkEmailAndUpdate' ); - username: 'vasjok', - confirmed: true, - active: false + ctrl.send = function ( result, status ) { + + expect( status ).to.equal( 403 ); + + expect( result ).to.be.an( 'string' ).and.equal( 'Invalid password' ); + + expect( spy.called ).to.be.false; + + spy.restore(); + + done(); }; - var old_password = new_user.password; + ctrl.req.user = { id: new_user.id }; + ctrl.req.params = { id: new_user.id }; + ctrl.req.body = data; - ctrl.send = function ( user, status ) { + ctrl.req.login = function( user, callback ) { + if ( !!user && !!user.id ) { + callback( null, user ); + } + }; + + ctrl.putAction(); + } ); + + it( 'should call UserService.updateUser( user, data ) if the email does not change', function ( done ) { + var data = { + password: 'secret_password_new', + new_password: 'secret_password' + }; + + var spy = sinon.spy( Service, 'updateUser' ); + + ctrl.send = function ( result, status ) { expect( status ).to.equal( 200 ); - expect( user ).to.be.an( 'object' ).and.be.ok; - expect( user ).to.have.property( 'id' ).and.equal( new_ser.id ); + expect( result ).to.be.an( 'object' ).and.be.ok; + expect( result ).to.have.property( 'id' ).and.equal( new_user.id ); - UserService.findById( new_user.id ) - .then( function ( newUser ) { + expect( spy.called ).to.be.true; + expect( spy.calledOnce ).to.be.true; - expect( newUser ).to.be.an( 'object' ).and.be.ok; - expect( newUser ).to.have.property( 'id' ).and.equal( new_user.id ); + var spyCall = spy.getCall ( 0 ).args; - expect( newUser ).to.have.property( 'email' ).and.equal( data.email ); - expect( newUser ).to.have.property( 'password' ).and.not.equal( old_password ); - expect( newUser ).to.have.property( 'firstname' ).and.equal( data.firstname ); - expect( newUser ).to.have.property( 'lastname' ).and.equal( data.lastname ); - expect( newUser ).to.have.property( 'email' ).and.equal( data.email ); - expect( newUser ).to.have.property( 'phone' ).and.equal( data.phone ); + expect( spyCall ).to.be.an( 'array' ); - expect( newUser ).to.have.property( 'username' ).and.not.equal( data.username ); - expect( newUser ).to.have.property( 'confirmed' ).and.not.equal( data.confirmed ); - expect( newUser ).to.have.property( 'active' ).and.not.equal( data.active ); + expect( spyCall[0] ).to.be.an( 'object' ).and.be.ok; + expect( spyCall[0] ).to.have.property( 'id' ).and.equal( new_user.id ); - done(); - } ) - .fail( done ); + expect( spyCall[1] ).to.be.an( 'object' ).and.be.ok; + expect( spyCall[1] ).to.have.property( 'password' ).and.equal( data.password ); + expect( spyCall[1] ).to.have.property( 'new_password' ).and.equal( data.new_password ); + + spy.restore(); + + done(); }; ctrl.req.user = { id: new_user.id }; ctrl.req.params = { id: new_user.id }; ctrl.req.body = data; + ctrl.req.login = function( user, callback ) { + if ( !!user && !!user.id ) { + callback( null, user ); + } + }; + ctrl.putAction(); } ); - } ); + it( 'should call UserService.updateUser( user, data ) if the email change and valid', function ( done ) { + var data = { + email: 'qqq@mail.ru' + }; - describe( 'loginAction()', function () { + var spy = sinon.spy( Service, 'updateUser' ); - it( 'should call req.login(user) if user with such credentials found', function ( done ) { + ctrl.send = function ( result, status ) { - before( function( done ) { + expect( status ).to.equal( 200 ); - Service - .findById( new_user.id ) - .then( function( user ) { + expect( result ).to.be.an( 'object' ).and.be.ok; + expect( result ).to.have.property( 'id' ).and.equal( new_user.id ); - user - .updateAttributes( { confirmed: true, active: true } ) - .success( done ) - .error( done ); + expect( spy.called ).to.be.true; + expect( spy.calledOnce ).to.be.true; - done(); - }, done ); - }); + var spyCall = spy.getCall ( 0 ).args; - ctrl.send = function ( user, status ) { + expect( spyCall ).to.be.an( 'array' ); - expect( user ).to.be.an( 'object' ).and.be.ok; - expect( user ).to.have.property( 'id' ).and.equal( users[0].id ); + expect( spyCall[0] ).to.be.an( 'object' ).and.be.ok; + expect( spyCall[0] ).to.have.property( 'id' ).and.equal( new_user.id ); + + expect( spyCall[1] ).to.be.an( 'object' ).and.be.ok; + expect( spyCall[1] ).to.have.property( 'email' ).and.equal( data.email ); + + spy.restore(); done(); }; - ctrl.req.body = { - username: new_user.username, - password: 'secret_password' + ctrl.req.user = { id: new_user.id }; + ctrl.req.params = { id: new_user.id }; + ctrl.req.body = data; + + ctrl.req.login = function( user, callback ) { + if ( !!user && !!user.id ) { + callback( null, user ); + } }; - ctrl.loginAction(); + ctrl.putAction(); } ); - it( 'should call .send( 200 ) if user if such credentials found', function ( done ) { - - ctrl.req.login = function ( data, done ) { - done( null ); + it( 'should not call UserService.updateUser if the email change and already exist', function ( done ) { + var data = { + email: 'admin@example.com' }; - ctrl.send = function ( user, code ) { + var spy = sinon.spy( Service, 'updateUser' ); - expect( user ).to.be.an( 'object' ).and.be.ok; - expect( user ).to.have.property( 'id' ).and.equal( users[0].id ); - expect( user ).to.have.property( 'username' ).and.equal( users[0].username ); + ctrl.send = function ( result, status ) { - expect( code ).to.equal( 200 ); + expect( status ).to.equal( 400 ); + + expect( result ).to.be.an( 'string' ).and.equal( 'email already exists' ); + + expect( spy.called ).to.be.false; + + spy.restore(); done(); }; - ctrl.req.body = { - username: users[0].username, - password: '1234' + ctrl.req.user = { id: new_user.id }; + ctrl.req.params = { id: new_user.id }; + ctrl.req.body = data; + + ctrl.req.login = function( user, callback ) { + if ( !!user && !!user.id ) { + callback( null, user ); + } }; - ctrl.loginAction(); + ctrl.putAction(); } ); - it( 'should call .send( 403 ) if user is not found', function ( done ) { - ctrl.send = function ( data, code ) { + it( 'should call UserService.getUserFullDataJson( options ) If all previous conditions have been met', function ( done ) { + var data = { + email: 'qqq_new@mail.ru' + }; + + var spy = sinon.spy( Service, 'getUserFullDataJson' ); + + ctrl.send = function ( result, status ) { + + expect( status ).to.equal( 200 ); + + expect( result ).to.be.an( 'object' ).and.be.ok; + expect( result ).to.have.property( 'id' ).and.equal( new_user.id ); - expect( data ).to.be.an( 'object' ).and.be.empty; + expect( spy.called ).to.be.true; + expect( spy.calledOnce ).to.be.true; - expect( code ).to.equal( 200 ); + var spyCall = spy.getCall ( 0 ).args; + + expect( spyCall ).to.be.an( 'array' ).and.have.length( 1 ); + + expect( spyCall[0] ).to.be.an( 'object' ).and.be.ok; + expect( spyCall[0] ).to.have.property( 'id' ).and.equal( new_user.id ); + + spy.restore(); done(); }; - ctrl.req.body = { - username: users[0].username, - password: '12345' + ctrl.req.user = { id: new_user.id }; + ctrl.req.params = { id: new_user.id }; + ctrl.req.body = data; + + ctrl.req.login = function( user, callback ) { + if ( !!user && !!user.id ) { + callback( null, user ); + } }; - ctrl.loginAction(); + ctrl.putAction(); } ); - } ); - describe( 'logoutAction()', function () { + it( 'should be able to get the error if insufficiently userId', function ( done ) { + var data = { + username: 'admin', + password: 'secret_password' + }; - it( 'should call req.logout() and .send(200)', function ( done ) { - ctrl.req.logout = sinon.spy(); - ctrl.res.send = sinon.spy(); - ctrl.logoutAction(); + ctrl.send = function ( result, status ) { - expect( ctrl.req.logout.called ).to.be.true; - expect( ctrl.res.send.calledWith( 200 ) ).to.be.true; + expect( status ).to.equal( 400 ); + + expect( result ).to.be.an( 'string' ).and.equal( 'Bad Request' ); + + done(); + }; + + ctrl.req.user = { id: users[0].id }; + ctrl.req.params = { id: null }; + ctrl.req.body = data; + + ctrl.putAction(); - done(); } ); - } ); + it( 'should be able to get the error if new email already exist', function ( done ) { + var data = { + email: users[0].email, + username: 'admin', + password: 'secret_password' + }; + + ctrl.send = function ( result, status ) { + + expect( status ).to.equal( 400 ); + + expect( result ).to.be.an( 'string' ).and.equal( 'email already exists' ); + + done(); + }; + + ctrl.req.user = { id: new_user.id }; + ctrl.req.params = { id: new_user.id }; + ctrl.req.body = data; + + ctrl.putAction(); + + } ); + + it( 'should be able to get the error if old password incorrect', function ( done ) { + var data = { + password: 'secret_password_incorrect', + new_password: 'secret_password_new' + }; + + ctrl.send = function ( result, status ) { + + expect( status ).to.equal( 403 ); + + expect( result ).to.be.an( 'string' ).and.equal( 'Invalid password' ); + + done(); + }; + + ctrl.req.user = { id: new_user.id }; + ctrl.req.params = { id: new_user.id }; + ctrl.req.body = data; + + ctrl.putAction(); + + } ); + + it( 'should hash password and to update firstname, lastname, email, phone, password and do not update other', function ( done ) { + var data = { + password: 'secret_password', + new_password: 'secret_password_updated', + + firstname: 'mishka', + lastname: 'mikhajlov', + email: 'qwqwqw@mail.ru', + phone: '845848485', + + username: 'vasjok', + confirmed: false, + active: false + }; + + var old_password = new_user.password; + + ctrl.send = function ( user, status ) { + + expect( status ).to.equal( 200 ); + + expect( user ).to.be.an( 'object' ).and.be.ok; + expect( user ).to.have.property( 'id' ).and.equal( new_user.id ); + + Service.findById( new_user.id ) + .then( function ( newUser ) { + + expect( newUser ).to.be.an( 'object' ).and.be.ok; + expect( newUser ).to.have.property( 'id' ).and.equal( new_user.id ); + + expect( newUser ).to.have.property( 'email' ).and.equal( data.email ); + expect( newUser ).to.have.property( 'password' ).and.not.equal( old_password ); + expect( newUser ).to.have.property( 'firstname' ).and.equal( data.firstname ); + expect( newUser ).to.have.property( 'lastname' ).and.equal( data.lastname ); + expect( newUser ).to.have.property( 'email' ).and.equal( data.email ); + expect( newUser ).to.have.property( 'phone' ).and.equal( data.phone ); + + expect( newUser ).to.have.property( 'username' ).and.not.equal( data.username ); + expect( newUser ).to.have.property( 'confirmed' ).and.not.equal( data.confirmed ); + expect( newUser ).to.have.property( 'active' ).and.not.equal( data.active ); + + new_user = newUser; + + done(); + } ) + .fail( done ); + }; + + ctrl.req.user = { id: new_user.id }; + ctrl.req.params = { id: new_user.id }; + ctrl.req.body = data; + + ctrl.req.login = function( user, callback ) { + if ( !!user && !!user.id ) { + callback( null, user ); + } + }; + + ctrl.putAction(); + } ); + + } ); + + describe( 'loginAction()', function () { + + it( 'should call UserService.authenticate() anytime', function ( done ) { + + var spy = sinon.spy( Service, 'authenticate' ); + + ctrl.send = function ( result, status ) { + + expect( status ).to.equal( 200 ); + + expect( result ).to.be.an( 'object' ).and.be.ok; + expect( result ).to.have.property( 'id' ).and.equal( new_user.id ); + + expect( spy.called ).to.be.true; + expect( spy.calledOnce ).to.be.true; + + var spyCall = spy.getCall ( 0 ).args[0]; + + expect( spyCall ).to.be.an( 'object' ).and.be.ok; + expect( spyCall ).to.have.property( 'email' ).and.equal( new_user.email ); + expect( spyCall ).to.have.property( 'password' ).and.equal( '46c8df75e98d0eea89d8414b9bd997b13f33caae' ); + expect( spyCall ).to.have.property( 'confirmed' ).and.equal( true ); + expect( spyCall ).to.have.property( 'active' ).and.equal( true ); + + spy.restore(); + + done(); + }; + + ctrl.req.body = { + username: new_user.email, + password: 'secret_password_updated' + }; + + ctrl.req.login = function( user, callback ) { + if ( !!user && !!user.id ) { + callback( null, user ); + } + }; + + ctrl.loginAction(); + } ); + + it( 'should call this.handleLocalUser( err, user )', function ( done ) { + + var spy = sinon.spy( ctrl, 'handleLocalUser' ); + + ctrl.send = function ( result, status ) { + + expect( status ).to.equal( 200 ); + + expect( result ).to.be.an( 'object' ).and.be.ok; + expect( result ).to.have.property( 'id' ).and.equal( new_user.id ); + + expect( spy.called ).to.be.true; + expect( spy.calledOnce ).to.be.true; + + var spyCall_err = spy.getCall ( 0 ).args[0]; + var spyCall_user = spy.getCall ( 0 ).args[1]; + + expect( spyCall_err ).to.be.not.ok; + + expect( spyCall_user ).to.be.an( 'object' ).and.be.ok; + expect( spyCall_user ).to.have.property( 'id' ).and.equal( new_user.id ); + expect( spyCall_user ).to.have.property( 'email' ).and.equal( new_user.email ); + expect( spyCall_user ).to.not.have.property( 'password' ); + expect( spyCall_user ).to.have.property( 'confirmed' ).and.equal( true ); + expect( spyCall_user ).to.have.property( 'active' ).and.equal( true ); + + spy.restore(); + + done(); + }; + + ctrl.req.body = { + username: new_user.email, + password: 'secret_password_updated' + }; + + ctrl.req.login = function( user, callback ) { + if ( !!user && !!user.id ) { + callback( null, user ); + } + }; + + ctrl.loginAction(); + } ); + + it( 'should call this.loginUserJson( user ) if user was found', function ( done ) { + + var spy = sinon.spy( ctrl, 'loginUserJson' ); + + ctrl.send = function ( result, status ) { + + expect( status ).to.equal( 200 ); + + expect( result ).to.be.an( 'object' ).and.be.ok; + expect( result ).to.have.property( 'id' ).and.equal( new_user.id ); + + expect( spy.called ).to.be.true; + expect( spy.calledOnce ).to.be.true; + + var spyCall = spy.getCall ( 0 ).args[0]; + + expect( spyCall ).to.be.an( 'object' ).and.be.ok; + expect( spyCall ).to.have.property( 'id' ).and.equal( new_user.id ); + expect( spyCall ).to.have.property( 'email' ).and.equal( new_user.email ); + expect( spyCall ).to.not.have.property( 'password' ); + expect( spyCall ).to.have.property( 'confirmed' ).and.equal( true ); + expect( spyCall ).to.have.property( 'active' ).and.equal( true ); + + spy.restore(); + + done(); + }; + + ctrl.req.body = { + username: new_user.email, + password: 'secret_password_updated' + }; + + ctrl.req.login = function( user, callback ) { + if ( !!user && !!user.id ) { + callback( null, user ); + } + }; + + ctrl.loginAction(); + } ); + + it( 'should not call this.loginUserJson( user ) if user is not found', function ( done ) { + + var spy = sinon.spy( ctrl, 'loginUserJson' ); + + ctrl.send = function ( result, status ) { + + expect( status ).to.equal( 403 ); + + expect( result ).to.be.an( 'object' ).and.be.empty; + + expect( spy.called ).to.be.false; + + spy.restore(); + + done(); + }; + + ctrl.req.body = { + username: new_user.email + '1', + password: 'secret_password_updated' + }; + + ctrl.req.login = function( user, callback ) { + if ( !!user && !!user.id ) { + callback( null, user ); + } + }; + + ctrl.loginAction(); + } ); + + it( 'should call this.req.login( user, cb ) if user was found', function ( done ) { + + ctrl.send = function ( result, status ) { + + expect( status ).to.equal( 200 ); + + expect( result ).to.be.an( 'object' ).and.be.ok; + expect( result ).to.have.property( 'id' ).and.equal( new_user.id ); + + expect( spy.called ).to.be.true; + expect( spy.calledOnce ).to.be.true; + + var spyCall = spy.getCall ( 0 ).args; + + expect( spyCall ).to.be.an( 'array' ).and.have.length( 2 ); + + expect( spyCall[0] ).to.be.an( 'object' ).and.be.ok; + expect( spyCall[0] ).to.have.property( 'id' ).and.equal( new_user.id ); + expect( spyCall[0] ).to.have.property( 'email' ).and.equal( new_user.email ); + expect( spyCall[0] ).to.not.have.property( 'password' ); + expect( spyCall[0] ).to.have.property( 'confirmed' ).and.equal( true ); + expect( spyCall[0] ).to.have.property( 'active' ).and.equal( true ); + + expect( spyCall[1] ).to.be.an( 'function' ).and.be.ok; + + spy.restore(); + + done(); + }; + + ctrl.req.body = { + username: new_user.email, + password: 'secret_password_updated' + }; + + ctrl.req.login = function( user, callback ) { + if ( !!user && !!user.id ) { + callback( null, user ); + } + }; + + var spy = sinon.spy( ctrl.req, 'login' ); + + ctrl.loginAction(); + } ); + + it( 'should not call this.req.login( user, cb ) if user is not found', function ( done ) { + + ctrl.send = function ( result, status ) { + + expect( status ).to.equal( 403 ); + + expect( result ).to.be.an( 'object' ).and.be.empty; + + expect( spy.called ).to.be.false; + + spy.restore(); + + done(); + }; + + ctrl.req.body = { + username: new_user.email + '1', + password: 'secret_password_updated' + }; + + ctrl.req.login = function( user, callback ) { + if ( !!user && !!user.id ) { + callback( null, user ); + } + }; + + var spy = sinon.spy( ctrl.req, 'login' ); + + ctrl.loginAction(); + } ); + + it( 'should call this.handleLoginJson( user, err ) if user was found', function ( done ) { + + var spy = sinon.spy( ctrl, 'handleLoginJson' ); + + ctrl.send = function ( result, status ) { + + expect( status ).to.equal( 200 ); + + expect( result ).to.be.an( 'object' ).and.be.ok; + expect( result ).to.have.property( 'id' ).and.equal( new_user.id ); + + expect( spy.called ).to.be.true; + expect( spy.calledOnce ).to.be.true; + + var spyCall = spy.getCall ( 0 ).args; + + expect( spyCall ).to.be.an( 'array' ); + + expect( spyCall[0] ).to.be.an( 'object' ).and.be.ok; + expect( spyCall[0] ).to.have.property( 'id' ).and.equal( new_user.id ); + expect( spyCall[0] ).to.have.property( 'email' ).and.equal( new_user.email ); + expect( spyCall[0] ).to.not.have.property( 'password' ); + expect( spyCall[0] ).to.have.property( 'confirmed' ).and.equal( true ); + expect( spyCall[0] ).to.have.property( 'active' ).and.equal( true ); + + expect( spyCall[1] ).to.not.be.ok; + + spy.restore(); + + done(); + }; + + ctrl.req.body = { + username: new_user.email, + password: 'secret_password_updated' + }; + + ctrl.req.login = function( user, callback ) { + if ( !!user && !!user.id ) { + callback( null, user ); + } + }; + + ctrl.loginAction(); + } ); + + it( 'should not call this.handleLoginJson( user, err ) if user is not found', function ( done ) { + + var spy = sinon.spy( ctrl, 'handleLoginJson' ); + + ctrl.send = function ( result, status ) { + + expect( status ).to.equal( 403 ); + + expect( result ).to.be.an( 'object' ).and.be.empty; + + expect( spy.called ).to.be.false; + + spy.restore(); + + done(); + }; + + ctrl.req.body = { + username: new_user.email + '1', + password: 'secret_password_updated' + }; + + ctrl.req.login = function( user, callback ) { + if ( !!user && !!user.id ) { + callback( null, user ); + } + }; + + ctrl.loginAction(); + } ); + + it( 'should be able to authenticate user', function ( done ) { + + ctrl.send = function ( result, status ) { + + expect( status ).to.equal( 200 ); + + expect( result ).to.be.an( 'object' ).and.be.ok; + expect( result ).to.have.property( 'id' ).and.equal( new_user.id ); + + done(); + }; + + ctrl.req.body = { + username: new_user.email, + password: 'secret_password_updated' + }; + + ctrl.req.login = function( user, callback ) { + if ( !!user && !!user.id ) { + callback( null, user ); + } + }; + + ctrl.loginAction(); + } ); + + it( 'should be able to get the error and empty object as result if user is not found', function ( done ) { + + ctrl.send = function ( result, status ) { + + expect( status ).to.equal( 403 ); + + expect( result ).to.be.an( 'object' ).and.be.empty; + + done(); + }; + + ctrl.req.body = { + username: new_user.email, + password: 'secret_password_updated' + '15' + }; + + ctrl.req.login = function( user, callback ) { + if ( !!user && !!user.id ) { + callback( null, user ); + } + }; + + ctrl.loginAction(); + } ); + + } ); + + describe( 'logoutAction()', function () { + + it( 'should call req.logout() and res.send( {}, 200 )', function ( done ) { + ctrl.req.logout = sinon.spy(); + ctrl.res.send = sinon.spy(); + ctrl.logoutAction(); + + expect( ctrl.req.logout.called ).to.be.true; + expect( ctrl.req.logout.calledOnce ).to.be.true; + + expect( ctrl.res.send.called ).to.be.true; + expect( ctrl.res.send.calledOnce ).to.be.true; + + var spyCall = ctrl.res.send.getCall ( 0 ).args; + + expect( spyCall ).to.be.an( 'array' ).and.have.length( 2 ) + expect( spyCall[0] ).to.be.an( 'object' ).and.be.empty; + expect( spyCall[1] ).to.be.a( 'number' ).and.equal( 200 ); + + done(); + } ); + + } ); + + describe( 'currentAction()', function () { + + it( 'should not call UserService.getUserFullDataJson( options ) if user is not login', function ( done ) { + + var spy = sinon.spy( Service, 'getUserFullDataJson' ); + + ctrl.send = function ( result, status ) { + + expect( status ).to.equal( 404 ); + + expect( result ).to.be.an( 'object' ).and.be.empty; + + expect( spy.called ).to.be.false; + + spy.restore(); + + done(); + }; + + ctrl.req.user = null; + + ctrl.currentAction(); + } ); + + it( 'should not call UserService.getUserFullDataJson( options ) if user is login and do not require reload', function ( done ) { + + var spy = sinon.spy( Service, 'getUserFullDataJson' ); + + ctrl.send = function ( result, status ) { + + expect( status ).to.equal( 200 ); + + expect( result ).to.be.an( 'object' ).and.be.ok; + expect( result ).to.have.property( 'id' ).and.be.ok; + + expect( spy.called ).to.be.false; + + spy.restore(); + + done(); + }; + + ctrl.req.user = { id: 4 }; + ctrl.req.query = {}; + + ctrl.currentAction(); + } ); + + it( 'should call UserService.getUserFullDataJson( options ) if user is login and require reload', function ( done ) { + var spy = sinon.spy( Service, 'getUserFullDataJson' ); + + ctrl.send = function ( result, status ) { + + expect( status ).to.equal( 200 ); + + expect( result ).to.be.an( 'object' ).and.be.ok; + expect( result ).to.have.property( 'id' ).and.equal( new_user.id ); + + expect( spy.called ).to.be.true; + expect( spy.calledOnce ).to.be.true; + + var spyCall = spy.getCall ( 0 ).args; + + expect( spyCall ).to.be.an( 'array' ).and.have.length( 1 ); + + expect( spyCall[0] ).to.be.an( 'object' ).and.be.ok; + expect( spyCall[0] ).to.have.property( 'id' ).and.equal( new_user.id ); + + spy.restore(); + + done(); + }; + + ctrl.req.user = { id: new_user.id }; + + ctrl.req.query = { + reload: true + }; + + ctrl.req.login = function( user, callback ) { + if ( !!user && !!user.id ) { + callback( null, user ); + } + }; + + ctrl.currentAction(); + } ); + + it( 'should call this.loginUserJson( user ) if user is login and require reload', function ( done ) { + + var spy = sinon.spy( ctrl, 'loginUserJson' ); + + ctrl.send = function ( result, status ) { + + expect( status ).to.equal( 200 ); + + expect( result ).to.be.an( 'object' ).and.be.ok; + expect( result ).to.have.property( 'id' ).and.equal( new_user.id ); + + expect( spy.called ).to.be.true; + expect( spy.calledOnce ).to.be.true; + + var spyCall = spy.getCall ( 0 ).args[0]; + + expect( spyCall ).to.be.an( 'object' ).and.be.ok; + expect( spyCall ).to.have.property( 'id' ).and.equal( new_user.id ); + expect( spyCall ).to.have.property( 'email' ).and.equal( new_user.email ); + expect( spyCall ).to.not.have.property( 'password' ); + expect( spyCall ).to.have.property( 'confirmed' ).and.equal( true ); + expect( spyCall ).to.have.property( 'active' ).and.equal( true ); + + spy.restore(); + + done(); + }; + + ctrl.req.user = { id: new_user.id }; + + ctrl.req.query = { + reload: true + }; + + ctrl.req.login = function( user, callback ) { + if ( !!user && !!user.id ) { + callback( null, user ); + } + }; + + ctrl.currentAction(); + } ); + + it( 'should be able to get user if user is login and require reload', function ( done ) { + + var user = new_user.toJSON(); + + ctrl.send = function ( result, status ) { + + expect( status ).to.equal( 200 ); + + expect( result ).to.be.an( 'object' ).and.be.ok; + expect( result ).to.have.property( 'id' ).and.equal( user.id ); + expect( result ).to.have.property( 'username' ).and.equal( user.username ); + expect( result ).to.have.property( 'email' ).and.equal( user.email ); + expect( result ).to.have.property( 'firstname' ).and.equal( user.firstname ); + expect( result ).to.have.property( 'phone' ).and.equal( user.phone ); + expect( result ).to.have.property( 'confirmed' ).and.equal( true ); + expect( result ).to.have.property( 'active' ).and.equal( true ); + + done(); + }; + + ctrl.req.user = { id: new_user.id }; + + ctrl.req.query = { + reload: true + }; + + ctrl.req.login = function( user, callback ) { + if ( !!user && !!user.id ) { + callback( null, user ); + } + }; + + ctrl.currentAction(); + } ); + + it( 'should be able to get user if user is login and not require reload', function ( done ) { + + var user = new_user.toJSON(); + + ctrl.send = function ( result, status ) { + + expect( status ).to.equal( 200 ); + + expect( result ).to.be.an( 'object' ).and.be.ok; + expect( result ).to.have.property( 'id' ).and.equal( user.id ); + expect( result ).to.have.property( 'username' ).and.equal( user.username ); + expect( result ).to.have.property( 'email' ).and.equal( user.email ); + expect( result ).to.have.property( 'firstname' ).and.equal( user.firstname ); + expect( result ).to.have.property( 'phone' ).and.equal( user.phone ); + expect( result ).to.have.property( 'confirmed' ).and.equal( true ); + expect( result ).to.have.property( 'active' ).and.equal( true ); + + done(); + }; + + ctrl.req.user = user; + + ctrl.req.query = { + reload: false + }; + + ctrl.req.login = function( user, callback ) { + if ( !!user && !!user.id ) { + callback( null, user ); + } + }; + + ctrl.currentAction(); + } ); + + it( 'should be able to get the error if user is not login', function ( done ) { + + ctrl.send = function ( result, status ) { + + expect ( status ).to.equal ( 404 ); + + expect ( result ).to.be.an ( 'object' ).and.be.empty; + + done (); + }; + + ctrl.req.body = {}; + + ctrl.req.user = null; + + ctrl.currentAction(); + } ); + + } ); + + describe( 'listAction()', function () { + + it( 'should be able to get list of users', function ( done ) { + + ctrl.send = function ( result, status ) { + + expect( status ).to.equal( 200 ); + + expect( result ).to.be.an( 'array' ).and.have.length.above( 1 ); + expect( result[0] ).to.be.an( 'object' ).and.be.ok; + expect( result[0] ).to.have.property( 'id' ).and.be.ok; + expect( result[0] ).to.have.property( 'username' ).and.be.ok; + expect( result[0] ).to.have.property( 'email' ).and.be.ok; + expect( result[0] ).to.have.property( 'active' ).and.equal( true ); + expect( result[0] ).to.not.have.property( 'password' ); + + done(); + }; + + ctrl.listAction(); + } ); + + } ); + + describe( 'getAction()', function () { + + it( 'should be able to get user by id', function ( done ) { + + var user = new_user.toJSON(); + + ctrl.send = function ( result, status ) { + + expect( status ).to.equal( 200 ); + + expect( result ).to.be.an( 'object' ).and.be.ok; + expect( result ).to.have.property( 'id' ).and.equal( user.id ); + expect( result ).to.have.property( 'username' ).and.equal( user.username ); + expect( result ).to.have.property( 'email' ).and.equal( user.email ); + expect( result ).to.have.property( 'active' ).and.equal( user.active ); + expect( result ).to.not.have.property( 'password' ); + + done(); + }; + + ctrl.req.params = { id: new_user.id }; + + ctrl.getAction(); + } ); + + it( 'should be able to get empty object if user do not exist', function ( done ) { + + ctrl.send = function ( result, status ) { + + expect( status ).to.equal( 200 ); + + expect( result ).to.be.an( 'object' ).and. be.empty; + + done(); + }; + + ctrl.req.params = { id: 15151515151151515 }; + + ctrl.getAction(); + } ); + + } ); + } ); \ No newline at end of file diff --git a/modules/clever-auth/tests/unit/test.service.UserService.js b/modules/clever-auth/tests/unit/test.service.UserService.js index 946d2a9..f4dd9f0 100644 --- a/modules/clever-auth/tests/unit/test.service.UserService.js +++ b/modules/clever-auth/tests/unit/test.service.UserService.js @@ -161,7 +161,7 @@ describe( 'service.UserService', function () { .fail( done ); } ); - it( 'should return null when options does not match in the db', function ( done ) { + it( 'should return empty object when options does not match in the db', function ( done ) { var data = { username: 'Rachel10', email: 'rachel10@example.com', @@ -174,7 +174,7 @@ describe( 'service.UserService', function () { } ) .then( function ( user ) { - expect( user ).to.not.be.ok; + expect( user ).to.be.empty; done(); } ) @@ -858,6 +858,62 @@ describe( 'service.UserService', function () { } ); + describe( '.listUsers()', function () { + + it( 'should be able to get list of all users', function ( done ) { + + UserService + .listUsers() + .then( function ( users ) { + + expect( users ).to.be.an( 'array' ).and.have.length.above( 1 ); + expect( users[0] ).to.be.an( 'object' ).and.be.ok; + expect( users[0] ).to.have.property( 'id' ).and.be.ok; + expect( users[0] ).to.have.property( 'username' ).and.be.ok; + expect( users[0] ).to.have.property( 'email' ).and.be.ok; + expect( users[0] ).to.have.property( 'active' ).and.equal( true ); + expect( users[0] ).to.not.have.property( 'password' ); + + done(); + } ) + .fail( done ); + } ); + + } ); + + describe( '.deleteUser( userId )', function () { + + it( 'should be able to get the error if the user does not exist', function ( done ) { + + UserService + .deleteUser( 151515115151515151 ) + .then( function( result ) { + + expect( result ).to.be.an( 'object' ); + expect( result ).to.have.property( 'statuscode' ).and.equal( 403 ); + expect( result ).to.have.property( 'message' ).and.be.ok; + + done(); + }, done ) + } ); + + it( 'should be able to delete user', function ( done ) { + + UserService + .deleteUser( user_1.id ) + .then( function ( result ) { + + expect( result ).to.be.an( 'object' ); + expect( result ).to.have.property( 'statuscode' ).and.equal( 200 ); + expect( result ).to.have.property( 'message' ).and.be.ok; + + done(); + } ) + .fail( done ); + } ); + + } ); + describe.skip( '.resendAccountConfirmation( me, userId )', function () { it( 'should return status code 403 and message when user id does not exist', function ( done ) { var userId = 'noneExistedId' From 573a74f56be7c9417294dbc351ea0cb2c1037d7d Mon Sep 17 00:00:00 2001 From: Volodymyr Denshchykov Date: Sun, 2 Feb 2014 23:23:18 +0200 Subject: [PATCH 16/24] deleted unnecessary file --- config/{locac.json => localjson} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename config/{locac.json => localjson} (100%) diff --git a/config/locac.json b/config/localjson similarity index 100% rename from config/locac.json rename to config/localjson From ee86167f8238ee245d0d4be4d0b3999a9d8f2c99 Mon Sep 17 00:00:00 2001 From: Volodymyr Denshchykov Date: Sun, 2 Feb 2014 23:30:16 +0200 Subject: [PATCH 17/24] deleted unnecessary file --- config/localjson | 31 ------------------------------- 1 file changed, 31 deletions(-) delete mode 100644 config/localjson diff --git a/config/localjson b/config/localjson deleted file mode 100644 index a9f3b60..0000000 --- a/config/localjson +++ /dev/null @@ -1,31 +0,0 @@ -{ - "environmentName": "LOCAL", - - "memcacheHost": "localhost:11211", - - "db": { - "username": "root", - "password": "qqq", - "database": "clevertech", - "options": { - "host": "localhost", - "dialect": "mysql", - "port": 3306 - } - }, - - "testDb": { - "username": "root", - "password": "qqq", - "database": "clTest", - "options": { - "host": "localhost", - "dialect": "mysql", - "port": 3306 - } - }, - - "mongoose": { - "uri": "mongodb://localhost/database" - } -} \ No newline at end of file From 82ef841a3a85bad0fede6fa918bd0e07c00dc6e8 Mon Sep 17 00:00:00 2001 From: Volodymyr Denshchykov Date: Tue, 4 Feb 2014 18:16:27 +0200 Subject: [PATCH 18/24] feature(module) create clever-auth-google module --- .../clever-auth-google/config/default.json | 23 +++ .../controllers/UserGoogleController.js | 97 +++++++++ .../models/orm/UserGoogleModel.js | 77 +++++++ modules/clever-auth-google/module.js | 129 ++++++++++++ modules/clever-auth-google/package.json | 38 ++++ modules/clever-auth-google/routes.js | 8 + .../services/UserGoogleService.js | 191 ++++++++++++++++++ 7 files changed, 563 insertions(+) create mode 100644 modules/clever-auth-google/config/default.json create mode 100644 modules/clever-auth-google/controllers/UserGoogleController.js create mode 100644 modules/clever-auth-google/models/orm/UserGoogleModel.js create mode 100644 modules/clever-auth-google/module.js create mode 100644 modules/clever-auth-google/package.json create mode 100644 modules/clever-auth-google/routes.js create mode 100644 modules/clever-auth-google/services/UserGoogleService.js diff --git a/modules/clever-auth-google/config/default.json b/modules/clever-auth-google/config/default.json new file mode 100644 index 0000000..295df9c --- /dev/null +++ b/modules/clever-auth-google/config/default.json @@ -0,0 +1,23 @@ +{ + "clever-auth-google": { + "secretKey": "youMustChangeMe", + + "redis": { + "host": "localhost", + "port": "6379", + "prefix": "", + "key": "" + }, + + "google": { + "clientId": "117613345915-5ammdhvej2pkgbfuso2u2u1mo2akmfg7.apps.googleusercontent.com", + "clientSecret": "YkcZxpWEKr_Jijb_RaL0nNlC", + "clientEmail": "117613345915-5ammdhvej2pkgbfuso2u2u1mo2akmfg7@developer.gserviceaccount.com", + "redirectURIs": "http://localhost:8080/auth/google/return", + "javascriptOrigins": "http://localhost:8080", + "authURI": "http://accounts.google.com/o/oauth2/auth", + "tokenURI": "http://accounts.google.com/o/oauth2/token" + } + + } +} \ No newline at end of file diff --git a/modules/clever-auth-google/controllers/UserGoogleController.js b/modules/clever-auth-google/controllers/UserGoogleController.js new file mode 100644 index 0000000..78434d9 --- /dev/null +++ b/modules/clever-auth-google/controllers/UserGoogleController.js @@ -0,0 +1,97 @@ +var config = require ( 'config' )[ 'clever-auth-google' ] + , passport = require ( 'passport' ) + , qs = require ( 'qs' ) + , GoogleStrategy = require ( 'passport-google-oauth' ).OAuth2Strategy; + +module.exports = function ( UserGoogleService ) { + + passport.serializeUser( function ( user, done ) { + done( null, user ); + } ); + + passport.deserializeUser( function ( user, done ) { + done( null, user ) + } ); + + passport.use( new GoogleStrategy( + { + clientID: config.google.clientId, + clientSecret: config.google.clientSecret, + callbackURL: config.google.redirectURIs + }, + function ( accessToken, refreshToken, profile, done ) { + + UserGoogleService + .authenticate( profile, accessToken ) + .then( function( gUser ) { + return UserGoogleService.associate ( gUser, profile ) + }) + .then( done.bind( null, null ) ) + .fail( done ); + } + )); + + + return (require( 'classes' ).Controller).extend( + { + service: UserGoogleService + }, + { + listAction: function () { + UserGoogleService.listUsers() + .then( this.proxy( 'handleServiceMessage' ) ) + .fail( this.proxy( 'handleException' ) ); + }, + + deleteAction: function () { + var uId = this.req.params.id; + + UserGoogleService.deleteUser( { id: uId } ) + .then( this.proxy( 'handleServiceMessage' ) ) + .fail( this.proxy( 'handleException' ) ); + }, + + loginAction: function () { + var params = { + response_type: "code", + client_id: config.google.clientId, + redirect_uri: config.google.redirectURIs, + display: "popup", + scope: "https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email" + }; + + this.send(200, { url: 'https://accounts.google.com/o/oauth2/auth?' + qs.stringify(params) } ); + + }, + + returnAction: function () { + passport.authenticate( 'google', this.proxy( 'handleLocalUser' ) )( this.req, this.res, this.next ); + }, + + handleLocalUser: function ( err, user ) { + if ( err ) return this.handleException( err ); + if ( !user ) return this.send( {}, 403 ); + this.loginUserJson( user ); + }, + + loginUserJson: function ( user ) { + this.req.login( user, this.proxy( 'handleLoginJson', user ) ); + }, + + handleLoginJson: function ( user, err ) { + if ( err ) return this.handleException( err ); + this.send( user, 200 ); + }, + + handleServiceMessage: function ( obj ) { + + if ( obj.statuscode ) { + this.send( obj.message, obj.statuscode ); + return; + } + + this.send( obj, 200 ); + } + + } ); +}; \ No newline at end of file diff --git a/modules/clever-auth-google/models/orm/UserGoogleModel.js b/modules/clever-auth-google/models/orm/UserGoogleModel.js new file mode 100644 index 0000000..60bcd24 --- /dev/null +++ b/modules/clever-auth-google/models/orm/UserGoogleModel.js @@ -0,0 +1,77 @@ +module.exports = function ( sequelize, DataTypes ) { + return sequelize.define( "UserGoogle", + { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true + }, + token: { + type: DataTypes.STRING, + allowNull: false + }, + firstname: { + type: DataTypes.STRING, + allowNull: true + }, + lastname: { + type: DataTypes.STRING, + allowNull: true + }, + email: { + type: DataTypes.STRING, + allowNull: false, + unique: true, + validate: { + isEmail: true + } + }, + googleId: { + type: DataTypes.STRING, + allowNull: false + }, + picture: { + type: DataTypes.STRING, + allowNull: true + }, + link: { + type: DataTypes.STRING, + allowNull: true + }, + gender: { + type: DataTypes.STRING, + allowNull: true, + values: [ 'male', 'female' ] + }, + locale: { + type: DataTypes.STRING, + allowNull: true + }, + verified: { + type: DataTypes.STRING, + allowNull: false, + defaultValue: false + }, + accessedAt: { + type: DataTypes.DATE + } + }, + { + paranoid: true, + + getterMethods: { + fullName: function () { + return [ this.getDataValue( 'firstname' ), this.getDataValue( 'lastname' )].join( ' ' ); + } + }, + + instanceMethods: { + toJSON: function () { + var values = this.values; + values.fullName = this.fullName; + delete values.token; + return values; + } + } + } ); +}; \ No newline at end of file diff --git a/modules/clever-auth-google/module.js b/modules/clever-auth-google/module.js new file mode 100644 index 0000000..164c70d --- /dev/null +++ b/modules/clever-auth-google/module.js @@ -0,0 +1,129 @@ +var passport = require( 'passport' ) + , debug = require( 'debug' )( 'CleverAuthGoogleModule' ) + , fs = require( 'fs' ) + , path = require( 'path' ) + , ModuleClass = require( 'classes' ).ModuleClass + , RedisStore = require( 'connect-redis' )( injector.getInstance( 'express' ) ) + , Module; + +Module = ModuleClass.extend( { + models: null, + store: null, + sequelize: null, + + preSetup: function () { + var self = this; + this.models = {}; + this.sequelize = injector.getInstance ( 'sequelize' ); + var dir = fs.readdirSync ( path.join ( __dirname, 'models', 'orm' ) ); + dir.forEach ( function ( model ) { + self.getModel ( path.join ( __dirname, 'models', 'orm', model ) ); + } ); + }, + + preResources: function () { + injector.instance ( 'passport', passport ); + }, + + modulesLoaded: function () { + this.defineModelsAssociations (); + }, + + defineModelsAssociations: function () { + if ( !this.config.hasOwnProperty ( 'modelAssociations' ) ) { + return true; + } + + debug ( 'Defining model assocations' ); + + Object.keys ( this.config.modelAssociations ).forEach ( this.proxy ( function ( modelName ) { + Object.keys ( this.config.modelAssociations[ modelName ] ).forEach ( this.proxy ( 'defineModelAssociations', modelName ) ); + } ) ); + }, + + defineModelAssociations: function ( modelName, assocType ) { + var associatedWith = this.config.modelAssociations[ modelName ][ assocType ]; + if ( !associatedWith instanceof Array ) { + associatedWith = [ associatedWith ]; + } + + associatedWith.forEach ( this.proxy ( 'associateModels', modelName, assocType ) ); + }, + + associateModels: function ( modelName, assocType, assocTo ) { + // Support second argument + if ( assocTo instanceof Array ) { + debug ( '%s %s %s with second argument of ', modelName, assocType, assocTo[0], assocTo[1] ); + this.models[ modelName ][ assocType ] ( this.models[ assocTo[0] ], assocTo[1] ); + } else { + debug ( '%s %s %s', modelName, assocType, assocTo ); + this.models[ modelName ][ assocType ] ( this.models[assocTo] ); + } + }, + + getModel: function ( modelPath ) { + var modelName = modelPath.split ( '/' ).pop ().split ( '.' ).shift (); + + if ( typeof this.models[ modelName ] === 'undefined' ) { + debug ( [ 'Loading model', modelName, 'from', modelPath ].join ( ' ' ) ); + + // Call on sequelizejs to load the model + this.models[ modelName ] = this.sequelize.import ( modelPath ); + + // Set a flat for tracking + this.models[ modelName ].ORM = true; + + // Add the model to the injector + injector.instance ( 'ORM' + modelName, this.models[ modelName ] ); + } + + return this.models[ modelName ]; + }, + + configureApp: function ( app, express ) { +// Setup the redis store + this.store = new RedisStore ( { + host: this.config.redis.host, + port: this.config.redis.port, + prefix: this.config.redis.prefix + process.env.NODE_ENV + "_", + password: this.config.redis.key + } ); + +// Session management + app.use ( express.cookieParser () ); + app.use ( express.session ( { + secret: this.config.secretKey, + cookie: { secure: false, maxAge: 86400000 }, + store: this.store + } ) ); + + // Enable CORS + app.use ( this.proxy ( 'enableCors' ) ); + + // Initialize passport + app.use ( passport.initialize () ); + app.use ( passport.session () ); + }, + + enableCors: function ( req, res, next ) { + + res.header ( 'Access-Control-Allow-Origin', req.headers.origin ); + res.header ( 'Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE' ); + res.header ( 'Access-Control-Allow-Headers', 'Content-Type, Authorization, x-requested-with' ); + res.header( "Access-Control-Allow-Credentials", "true" ); + res.header( "Access-Control-Max-Age", "1000000000" ); + // intercept OPTIONS method + if ( 'OPTIONS' == req.method ) { + res.send ( 200 ); + } + else { + next (); + } + }, + + preShutdown: function () { + this.store.client.quit (); + } +} ); + +module.exports = new Module( 'clever-auth-google', injector ); \ No newline at end of file diff --git a/modules/clever-auth-google/package.json b/modules/clever-auth-google/package.json new file mode 100644 index 0000000..01db8b2 --- /dev/null +++ b/modules/clever-auth-google/package.json @@ -0,0 +1,38 @@ +{ + "name": "clever-auth-google", + "version": "0.0.1", + "private": true, + "dependencies": { + "passport": "~0.1.18", + "connect-redis": "~1.4.6", + "connect": "~2.12.0", + "redis": "~0.10.0", + "q": "", + "lodash": "2.4.1", + "sequelize": "1.7.0-beta8", + "shortid": "~2.0.0", + "moment": "", + "async": "", + "passport-google-oauth": "~0.1.5", + "qs": "*", + "request": "latest" + }, + "author": { + "name": "Clevertech", + "email": "info@clevertech.biz", + "web": "http://www.clevertech.biz" + }, + "collaborators": [ + "" + ], + "description": "", + "keywords": [ + "" + ], + "main": "module.js", + "devDependencies": { + "chai": "*", + "mocha": "*", + "sinon": "" + } +} \ No newline at end of file diff --git a/modules/clever-auth-google/routes.js b/modules/clever-auth-google/routes.js new file mode 100644 index 0000000..7231a73 --- /dev/null +++ b/modules/clever-auth-google/routes.js @@ -0,0 +1,8 @@ +module.exports = function ( + app, + UserGoogleController ) +{ + + app.all('/auth/google/?:action?', UserGoogleController.attach() ); + +}; \ No newline at end of file diff --git a/modules/clever-auth-google/services/UserGoogleService.js b/modules/clever-auth-google/services/UserGoogleService.js new file mode 100644 index 0000000..81cf60b --- /dev/null +++ b/modules/clever-auth-google/services/UserGoogleService.js @@ -0,0 +1,191 @@ +var Q = require( 'q' ) + , crypto = require( 'crypto' ) + , moment = require( 'moment' ) + , Sequelize = require( 'sequelize' ) + , UserGoogleService = null; + +module.exports = function ( sequelize, + ORMUserGoogleModel ) { + + if ( UserGoogleService && UserGoogleService.instance ) { + return UserGoogleService.instance; + } + + var UserService = null; + + UserGoogleService = require( 'services' ).BaseService.extend( { + + formatData: function ( profile, accessToken ) { + return { + googleid: profile._json.id, + email: profile._json.email, + firstname: profile._json.given_name, + lastname: profile._json.family_name, + picture: profile._json.picture, + verified: profile._json.verified_email, + link: profile._json.link, + gender: profile._json.gender, + locale: profile._json.locale, + token: accessToken + } + }, + + authenticate: function ( profile, accessToken ) { + var deferred = Q.defer() + , data = this.formatData ( profile, accessToken ); + + if ( data.verified ) { + + ORMUserGoogleModel + .find( { where: { email: data.email } } ) + .success( function ( gUser ) { + + if ( !gUser ) { + + data.accessedAt = moment.utc().format( 'YYYY-MM-DD HH:ss:mm' ); + + ORMUserGoogleModel + .create( data ) + .success( deferred.resolve ) + .error( deferred.reject ); + + } else { + + gUser + .updateAttributes( { accessedAt: moment.utc().format( 'YYYY-MM-DD HH:ss:mm' ), token: data.token } ) + .success( deferred.resolve ) + .error( deferred.reject ); + } + } ) + .error( deferred.reject ); + } else { + deferred.resolve(); + } + + return deferred.promise; + }, + + associate: function( gUser, profile ) { + var deferred = Q.defer() + , data = this.formatData ( profile ); + + try { + UserService = injector.getInstance( 'UserService' ); + } catch ( err ) { + console.log( err ); + } + + if ( !!gUser && !!UserService ) { + + if ( gUser.userId ) { + + UserService + .find( { where: { id: gUser.userId, email: gUser.email } } ) + .then( function( user ) { + deferred.resolve( user.toJSON() ); + }) + .fail( deferred.reject ); + + } else { + + UserService + .find( { where: { email: gUser.email } } ) + .then( function( user ) { + + user = user[0]; + + if ( !!user && !!user.id ) { + + gUser + .updateAttributes( { userId: user.id } ) + .success( function() { + deferred.resolve( user.toJSON() ); + }) + .error( deferred.reject ); + + } else { + + data.confirmed = true; + data.username = data.email; + data.accessedAt = moment.utc().format( 'YYYY-MM-DD HH:ss:mm' ); + data.password = crypto.createHash( 'sha1' ).update( Math.floor(Math.random() * 1e18) ).digest( 'hex' ) + + UserService + .create( data ) + .then( function( user ) { + + gUser + .updateAttributes ( { userId: user.id } ) + .success ( function () { + deferred.resolve ( user.toJSON () ); + } ) + .error ( deferred.reject ); + }) + .fail( deferred.reject ); + } + }) + .fail( deferred.reject ); + } + + } else { + deferred.resolve( gUser.toJSON() ); + } + + return deferred.promise; + }, + + listUsers: function() { + var deferred = Q.defer(); + + ORMUserGoogleModel + .findAll( { where: { deletedAt: null } } ) + .success( function( gUsers ) { + if ( !!gUsers && !!gUsers.length ) { + deferred.resolve( gUsers.map( function( u ) { return u.toJSON(); } ) ); + } else { + deferred.resolve( {} ); + } + }) + .error( deferred.reject ); + + return deferred.promise; + }, + + deleteUser: function( gUserId ) { + var deferred = Q.defer(); + + ORMUserGoogleModel + .find( gUserId ) + .success( function( gUser ) { + + if ( !!gUser && !!gUser.id ) { + + gUser + .destroy() + .success( function( result ) { + + if ( !result.deletedAt ) { + deferred.resolve( { statuscode: 500, message: 'error' } ); + } else { + deferred.resolve( { statuscode: 200, message: 'user is deleted' } ); + } + + }) + .error( deferred.reject ); + + } else { + deferred.resolve( { statuscode: 403, message: 'user do not exist' } ) + } + }) + .error( deferred.reject ); + + return deferred.promise; + } + + } ); + + UserGoogleService.instance = new UserGoogleService( sequelize ); + UserGoogleService.Model = ORMUserGoogleModel; + + return UserGoogleService.instance; +}; \ No newline at end of file From 9c61be4dee07dc41163b41aa08c89bf065dbf2f5 Mon Sep 17 00:00:00 2001 From: Volodymyr Denshchykov Date: Tue, 4 Feb 2014 23:40:55 +0200 Subject: [PATCH 19/24] added tests and updated cervise, model and controller --- .../test.controllers.UserGoogleController.js | 221 ++++++ .../unit/test.service.UserGoogleService.js | 683 ++++++++++++++++++ 2 files changed, 904 insertions(+) create mode 100644 modules/clever-auth-google/tests/unit/test.controllers.UserGoogleController.js create mode 100644 modules/clever-auth-google/tests/unit/test.service.UserGoogleService.js diff --git a/modules/clever-auth-google/tests/unit/test.controllers.UserGoogleController.js b/modules/clever-auth-google/tests/unit/test.controllers.UserGoogleController.js new file mode 100644 index 0000000..798b45f --- /dev/null +++ b/modules/clever-auth-google/tests/unit/test.controllers.UserGoogleController.js @@ -0,0 +1,221 @@ +// Bootstrap the testing environmen +var testEnv = require( 'utils' ).testEnv(); + +var expect = require( 'chai' ).expect + , Q = require ( 'q' ) + , sinon = require( 'sinon' ) + , Service; + +var new_user; + +describe( 'controllers.UserGoogleController', function () { + var Service, UserGoogleController, ctrl, gUsers = []; + + before( function ( done ) { + testEnv( function ( _UserGoogleService_, _UserGoogleController_ ) { + var req = { + params: { action: 'fakeAction'}, + method: 'GET', + query: {} + }; + + var res = { + json: function () {} + }; + + var next = function () {}; + + Controller = _UserGoogleController_; + Service = _UserGoogleService_; + ctrl = new Controller( req, res, next ); + + var profile_1 = { + _json: { + id: '121212121212121', + email: 'volodymyrs@clevertech.biz', + verified_email: true, + name: 'Volodymyr Denshchykov', + given_name: 'asasasd', + family_name: 'Denssdfsdfhchykov', + link: 'https://plus.google.com/112064034597570891032', + gender: 'male', + locale: 'ru' + } + } + , profile_2 = { + _json: { + id: '45454454545454', + email: 'volodymyr@clevertech.biz', + verified_email: true, + name: 'Volodymyr Denshchykov', + given_name: 'asasasd', + family_name: 'Denssdfsdfhchykov', + link: 'https://plus.google.com/112064034597570891032', + gender: 'male', + locale: 'ru' + } + }; + + Service + .findOrCreate( profile_1, 'bhbhgvbljbhscvzkchvblzclvnzbkjxcv' ) + .then( function( gUser ) { + + expect( gUser ).to.be.an( 'object' ).and.be.ok; + expect( gUser ).to.have.property( 'id' ).and.be.ok; + + gUsers.push( gUser ) + + Service + .findOrCreate( profile_2, 'kjajkvl zsdvbakhvckhabskhv' ) + .then( function( gUser ) { + + expect( gUser ).to.be.an( 'object' ).and.be.ok; + expect( gUser ).to.have.property( 'id' ).and.be.ok; + + gUsers.push( gUser ) + + done(); + }, done ); + }, done ); + } ); + } ); + + afterEach( function ( done ) { + + ctrl.req = { + params: { action: 'fakeAction'}, + method: 'GET', + query: {}, + body: {} + }; + + ctrl.res = { + json: function () {} + }; + + done(); + }); + + describe( 'loginAction()', function () { + + it( 'should call this.send() with specify parameters', function ( done ) { + + ctrl.send = function ( result, status ) { + + expect( status ).to.equal( 200 ); + + expect( result ).to.be.an( 'object' ).and.be.ok; + expect( result ).to.have.property( 'url' ).and.contain( 'https://accounts.google.com/o/oauth2/auth?' ); + + done(); + }; + + ctrl.loginAction(); + } ); + + } ); + + describe( 'listAction()', function () { + + it( 'should be able to get list of users', function ( done ) { + + ctrl.send = function ( result, status ) { + + expect( status ).to.equal( 200 ); + + expect( result ).to.be.an( 'array' ).and.have.length.above( 1 ); + expect( result[0] ).to.be.an( 'object' ).and.be.ok; + expect( result[0] ).to.have.property( 'id' ).and.be.ok; + expect( result[0] ).to.have.property( 'firstname' ).and.be.ok; + expect( result[0] ).to.have.property( 'lastname' ).and.be.ok; + expect( result[0] ).to.have.property( 'email' ).and.be.ok; + expect( result[0] ).to.have.property( 'googleid' ).and.be.ok; + expect( result[0] ).to.not.have.property( 'token' ); + + done(); + }; + + ctrl.listAction(); + } ); + + } ); + + describe( 'getAction()', function () { + + it( 'should be able to get user by id', function ( done ) { + + ctrl.send = function ( result, status ) { + + expect( status ).to.equal( 200 ); + + expect( result ).to.be.an( 'object' ).and.be.ok; + expect( result ).to.have.property( 'id' ).and.be.ok; + expect( result ).to.have.property( 'firstname' ).and.be.ok; + expect( result ).to.have.property( 'lastname' ).and.be.ok; + expect( result ).to.have.property( 'email' ).and.be.ok; + expect( result ).to.have.property( 'googleid' ).and.be.ok; + expect( result ).to.not.have.property( 'token' ); + + done(); + }; + + ctrl.req.params = { id: gUsers[0].id }; + + ctrl.getAction(); + } ); + + it( 'should be able to get error if user do not exist', function ( done ) { + + ctrl.send = function ( result, status ) { + + expect( status ).to.equal( 403 ); + + expect( result ).to.be.an( 'string' ).and.be.ok; + + done(); + }; + + ctrl.req.params = { id: 15151515151151515 }; + + ctrl.getAction(); + } ); + + } ); + + describe( 'deleteAction()', function () { + + it( 'should be able to delete user by id', function ( done ) { + + ctrl.send = function ( result, status ) { + + expect( status ).to.equal( 200 ); + + expect( result ).to.be.an( 'string' ).and.be.ok; + + done(); + }; + + ctrl.req.params = { id: gUsers[0].id }; + + ctrl.deleteAction(); + } ); + + it( 'should be able to get error if user do not exist', function ( done ) { + + ctrl.send = function ( result, status ) { + + expect( status ).to.equal( 403 ); + + expect( result ).to.be.an( 'string' ).and.be.ok; + + done(); + }; + + ctrl.req.params = { id: 15151515151151515 }; + + ctrl.deleteAction(); + } ); + + } ); + +} ); \ No newline at end of file diff --git a/modules/clever-auth-google/tests/unit/test.service.UserGoogleService.js b/modules/clever-auth-google/tests/unit/test.service.UserGoogleService.js new file mode 100644 index 0000000..266c6b0 --- /dev/null +++ b/modules/clever-auth-google/tests/unit/test.service.UserGoogleService.js @@ -0,0 +1,683 @@ +var expect = require ( 'chai' ).expect + , request = require ( 'supertest' ) + , path = require( 'path' ) + , app = require ( path.resolve( __dirname + '/../../../../' ) + '/index.js' ) + , config = require( 'config' ) + , testEnv = require ( 'utils' ).testEnv() + , sinon = require( 'sinon' ) + , Q = require ( 'q' ); + +var UserService = null; + +var gUserId_1, accessedAtDate, gUser, user_1; + +describe( 'service.UserGoogleService', function () { + var Service, Model; + + before( function ( done ) { + this.timeout( 15000 ); + testEnv( function ( _UserGoogleService_, _ORMUserGoogleModel_ ) { + + Service = _UserGoogleService_; + Model = _ORMUserGoogleModel_; + + done(); + }, done ); + } ); + + describe( '.formatData( profile, accessToken )', function () { + + it( 'should return an object with filtered data', function ( done ) { + var accessToken = 'sdasdasdasdasdasdasdasda' + , profile = { + provider: 'google', + id: '112064034597570891452', + displayName: 'Volodymyr Denshchykov', + name: { familyName: 'Denshchykov', givenName: 'Volodymyr' }, + emails: [ + { value: 'volodymyr@clevertech.biz' } + ], + _raw: '{\n "id": "112064034597570891032",\n "email": "volodymyr@clevertech.biz",\n "verified_email": true,\n "name": "Volodymyr Denshchykov",\n "given_name": "Volodymyr",\n "family_name": "Denshchykov",\n "link": "https://plus.google.com/112064034597570891032",\n "gender": "male",\n "locale": "ru",\n "hd": "clevertech.biz"\n}\n', + _json: { + id: '112064034597570891032', + email: 'volodymyr@clevertech.biz', + verified_email: true, + name: 'Volodymyr Denshchykov', + given_name: 'Volodymyr', + family_name: 'Denshchykov', + link: 'https://plus.google.com/112064034597570891032', + gender: 'male', + locale: 'ru', + hd: 'clevertech.biz' } + }; + + var data = Service.formatData( profile, accessToken ); + + expect( data ).to.be.an( 'object' ).and.be.ok; + + expect( data ).to.have.property( 'token' ).and.equal( accessToken ); + expect( data ).to.have.property( 'firstname' ).and.equal( profile._json.given_name ); + expect( data ).to.have.property( 'lastname' ).and.equal( profile._json.family_name ); + expect( data ).to.have.property( 'email' ).and.equal( profile._json.email ); + expect( data ).to.have.property( 'googleid' ).and.equal( profile._json.id ); + expect( data ).to.have.property( 'picture' ).and.not.be.ok; + expect( data ).to.have.property( 'link' ).and.equal( profile._json.link ); + expect( data ).to.have.property( 'gender' ).and.equal( profile._json.gender ); + expect( data ).to.have.property( 'locale' ).and.equal( profile._json.locale ); + expect( data ).to.have.property( 'verified' ).and.equal( profile._json.verified_email ); + + expect( data ).to.not.have.property( 'hd' ); + expect( data ).to.not.have.property( '_raw' ); + expect( data ).to.not.have.property( 'provider' ); + + done(); + } ); + + } ); + + describe( '.findOrCreate( profile, accessToken )', function () { + + it( 'should not call ORMUserGoogleModel.find() if google account is not verify', function ( done ) { + var accessToken = 'sdasdasdasdasdasdasdasda' + , profile = { + _json: { + id: '112064034597570891032', + email: 'volodymyr@clevertech.biz', + verified_email: false, + name: 'Volodymyr Denshchykov', + given_name: 'Volodymyr', + family_name: 'Denshchykov', + link: 'https://plus.google.com/112064034597570891032', + gender: 'male', + locale: 'ru', + hd: 'clevertech.biz' } + }; + + var spy = sinon.spy( Model, 'find' ); + + Service + .findOrCreate( profile, accessToken ) + .then( function ( result ) { + + expect( result ).to.not.be.ok; + + expect( spy.called ).to.be.false; + + spy.restore(); + + done(); + }, done ) + } ); + + it( 'should call ORMUserGoogleModel.find(), ORMUserGoogleModel.create() and create gUser if google account is verify and do not already exist', function ( done ) { + var accessToken = 'sdasdasdasdasdasdasdasda' + , profile = { + _json: { + id: '112064034597570891032', + email: 'volodymyr@clevertech.biz', + verified_email: true, + name: 'Volodymyr Denshchykov', + given_name: 'Volodymyr', + family_name: 'Denshchykov', + link: 'https://plus.google.com/112064034597570891032', + gender: 'male', + locale: 'ru', + hd: 'clevertech.biz' } + }; + + var spyFind = sinon.spy( Model, 'find' ); + var spyCreate = sinon.spy( Model, 'create' ); + + Service + .findOrCreate( profile, accessToken ) + .delay( 1000 ) + .then( function ( result ) { + + expect( result ).to.be.an( 'object' ).and.be.ok; + expect( result ).to.have.property( 'id' ).and.be.ok; + + expect( spyFind.called ).to.be.true; + expect( spyFind.calledOnce ).to.be.true; + + expect( spyCreate.called ).to.be.true; + expect( spyCreate.calledOnce ).to.be.true; + + spyFind.restore(); + spyCreate.restore(); + + Model + .find( result.id ) + .success( function( user ) { + + expect( user ).to.be.an( 'object' ).and.be.ok; + expect( user ).to.have.property( 'id' ).and.equal( result.id ); + expect( user ).to.have.property( 'token' ).and.equal( accessToken ); + expect( user ).to.have.property( 'firstname' ).and.equal( profile._json.given_name ); + expect( user ).to.have.property( 'lastname' ).and.equal( profile._json.family_name ); + expect( user ).to.have.property( 'email' ).and.equal( profile._json.email ); + expect( user ).to.have.property( 'googleid' ).and.equal( profile._json.id ); + expect( user ).to.have.property( 'accessedAt' ).and.be.ok; + + gUserId_1 = user.id; + accessedAtDate = new Date( user.accessedAt ) + ''; + gUser = user; + + done(); + }) + .error( done ); + }, done ) + } ); + + it( 'should call ORMUserGoogleModel.find(), ORMUserGoogleModel.updateAttributes(), not call ORMUserGoogleModel.create() and update already existing gUser', function ( done ) { + var accessToken = '15151515151515151' + , profile = { + _json: { + id: '112064034597570891032', + email: 'volodymyr@clevertech.biz', + verified_email: true, + name: 'Volodymyr Denshchykov', + given_name: 'Volodymyr', + family_name: 'Denshchykov', + link: 'https://plus.google.com/112064034597570891032', + gender: 'male', + locale: 'ru', + hd: 'clevertech.biz' } + }; + + var spyFind = sinon.spy( Model, 'find' ); + var spyCreate = sinon.spy( Model, 'create' ); + + Service + .findOrCreate( profile, accessToken ) + .then( function ( result ) { + + expect( result ).to.be.an( 'object' ).and.be.ok; + expect( result ).to.have.property( 'id' ).and.equal( gUserId_1 ); + expect( result ).to.have.property( 'token' ).and.equal( accessToken ); + + var newAccessedAtDate = new Date( result.accessedAt ) + ''; + + expect( accessedAtDate ).to.not.equal( newAccessedAtDate ); + + expect( spyFind.called ).to.be.true; + expect( spyFind.calledOnce ).to.be.true; + + expect( spyCreate.called ).to.be.false; + + spyFind.restore(); + spyCreate.restore(); + + done(); + }, done ) + } ); + + } ); + + describe( '.authenticate( gUser, profile )', function () { + + before( function( done ) { + var accessToken = '151216562162351626' + , profile = { + _json: { + id: '121212121212121', + email: 'volodymyrs@clevertech.biz', + verified_email: true, + name: 'Volodymyr Denshchykov', + given_name: 'asasasd', + family_name: 'Denssdfsdfhchykov', + link: 'https://plus.google.com/112064034597570891032', + gender: 'male', + locale: 'ru' + } + }; + + try { + UserService = injector.getInstance( 'UserService' ); + } catch ( err ) { + console.log( err ); + } + + Service + .findOrCreate( profile, accessToken ) + .then( function( user ) { + + expect( user ).to.be.an( 'object' ).and.be.ok; + expect( user ).to.have.property( 'id' ).and.be.ok; + + gUser = user; + + done(); + }, done ); + }); + + it( 'should return gUser if UserService is not defined', function ( done ) { + var profile = { + _json: { + id: '121212121212121', + email: 'volodymyrs@clevertech.biz', + verified_email: true, + name: 'Volodymyr Denshchykov', + given_name: 'asasasd', + family_name: 'Denssdfsdfhchykov', + link: 'https://plus.google.com/112064034597570891032', + gender: 'male', + locale: 'ru' + } + }; + + if ( !UserService ) { + Service + .authenticate( gUser, profile ) + .then( function ( user ) { + + expect( !UserService ).to.be.true; + + expect( user ).to.be.an( 'object' ).and.be.ok; + expect( user ).to.have.property( 'id' ).and.equal( gUser.id ); + expect( user ).to.not.have.property( 'token' ); + expect( user ).to.have.property( 'firstname' ).and.equal( gUser.firstname ); + expect( user ).to.have.property( 'lastname' ).and.equal( gUser.lastname ); + expect( user ).to.have.property( 'email' ).and.equal( gUser.email ); + expect( user ).to.have.property( 'googleid' ).and.equal( gUser.googleid ); + expect( user ).to.have.property( 'accessedAt' ).and.be.ok; + + done(); + } ) + .fail( done ); + } else { + console.log( 'UserService is not defined' ); + done(); + } + } ); + + it( 'should create new user and to associate it with qUser if UserService is defined', function ( done ) { + var profile = { + _json: { + id: '121212121212121', + email: 'volodymyrs@clevertech.biz', + verified_email: true, + name: 'Volodymyr Denshchykov', + given_name: 'asasasd', + family_name: 'Denssdfsdfhchykov', + link: 'https://plus.google.com/112064034597570891032', + gender: 'male', + locale: 'ru' + } + }; + + if ( !!UserService ) { + + var spy = sinon.spy( UserService, 'create' ); + + Service + .authenticate( gUser, profile ) + .then( function ( user ) { + + expect( !!UserService ).to.be.true; + + expect( user ).to.be.an( 'object' ).and.be.ok; + expect( user ).to.have.property( 'id' ).and.not.equal( gUser.id ); + + expect( spy.called ).to.be.true; + expect( spy.calledOnce ).to.be.true; + + spy.restore(); + + UserService + .findById( user.id ) + .then( function( _user ) { + + expect( _user ).to.be.an( 'object' ).and.be.ok; + expect( _user ).to.have.property( 'id' ).and.equal( user.id ); + expect( _user ).to.have.property( 'email' ).and.equal( profile._json.email ); + expect( _user ).to.have.property( 'password' ).and.be.ok; + expect( _user ).to.have.property( 'firstname' ).and.equal( profile._json.given_name ); + expect( _user ).to.have.property( 'lastname' ).and.equal( profile._json.family_name ); + expect( _user ).to.have.property( 'confirmed' ).and.equal( true ); + expect( _user ).to.have.property( 'active' ).and.equal( true ); + + user_1 = _user; + + Service + .find( { where: { id: gUser.id } } ) + .then( function( _gUser ) { + + expect( _gUser ).to.be.an( 'array' ).and.have.length( 1 ); + + _gUser = _gUser[0]; + + expect( _gUser ).to.be.an( 'object' ).and.be.ok; + expect( _gUser ).to.have.property( 'id' ).and.equal( gUser.id ); + expect( _gUser ).to.have.property( 'UserId' ).and.equal( user.id ); + + done(); + }, done ) + }, done ); + } ) + .fail( done ); + } else { + console.log( 'UserService is defined' ); + done(); + } + + } ); + + it( 'should return existing user by gUser.UserId if UserService is defined', function ( done ) { + var profile = { + _json: { + id: '121212121212121', + email: 'volodymyrs@clevertech.biz', + verified_email: true, + name: 'Volodymyr Denshchykov', + given_name: 'asasasd', + family_name: 'Denssdfsdfhchykov', + link: 'https://plus.google.com/112064034597570891032', + gender: 'male', + locale: 'ru' + } + }; + + if ( !!UserService ) { + + var spy = sinon.spy( UserService, 'create' ); + + Service + .authenticate( gUser, profile ) + .then( function ( user ) { + + expect( !!UserService ).to.be.true; + + expect( user ).to.be.an( 'object' ).and.be.ok; + expect( user ).to.have.property( 'id' ).and.equal( user_1.id ); + + expect( spy.called ).to.be.false; + + spy.restore(); + + done(); + } ) + .fail( done ); + } else { + console.log( 'UserService is defined' ); + done(); + } + + } ); + + it( 'should return error if user with such gUser.UserId do not exist and if UserService is defined' , function ( done ) { + var profile = { + _json: { + id: '121212121212121', + email: 'volodymyrs@clevertech.biz', + verified_email: true, + name: 'Volodymyr Denshchykov', + given_name: 'asasasd', + family_name: 'Denssdfsdfhchykov', + link: 'https://plus.google.com/112064034597570891032', + gender: 'male', + locale: 'ru' + } + }; + + if ( !!UserService ) { + + var spy = sinon.spy( UserService, 'create' ); + + gUser + .updateAttributes( { UserId: 1000 } ) + .success( function( result ) { + + expect( result ).to.be.an( 'object' ).and.be.ok; + expect( result ).to.have.property( 'UserId' ).and.equal( 1000 ); + + Service + .authenticate( gUser, profile ) + .then( function ( user ) { + + expect( !!UserService ).to.be.true; + + expect( user ).to.be.an( 'object' ).and.be.ok; + expect( user ).to.have.property( 'statuscode' ).and.equal( 403 ); + expect( user ).to.have.property( 'message' ).and.be.ok; + + expect( spy.called ).to.be.false; + + spy.restore(); + + done(); + } ) + .fail( done ); + }) + .error( done ); + } else { + console.log( 'UserService is defined' ); + done(); + } + + } ); + + it( 'should return existing user by gUser.email if user with such email already exist', function ( done ) { + var profile = { + _json: { + id: '121212121212121', + email: 'volodymyrs@clevertech.biz', + verified_email: true, + name: 'Volodymyr Denshchykov', + given_name: 'asasasd', + family_name: 'Denssdfsdfhchykov', + link: 'https://plus.google.com/112064034597570891032', + gender: 'male', + locale: 'ru' + } + }; + + if ( !!UserService ) { + + var spy = sinon.spy( UserService, 'create' ); + + gUser + .updateAttributes( { UserId: null } ) + .success( function( result ) { + + expect( result ).to.be.an( 'object' ).and.be.ok; + expect( result ).to.have.property( 'UserId' ).and.equal( null ); + + Service + .authenticate( gUser, profile ) + .then( function ( user ) { + + expect( !!UserService ).to.be.true; + + expect( user ).to.be.an( 'object' ).and.be.ok; + expect( user ).to.have.property( 'id' ).and.equal( user_1.id ); + + expect( spy.called ).to.be.false; + + spy.restore(); + + Service + .find( { where: { id: gUser.id } } ) + .then( function( _gUser ) { + + expect( _gUser ).to.be.an( 'array' ).and.have.length( 1 ); + + _gUser = _gUser[0]; + + expect( _gUser ).to.be.an( 'object' ).and.be.ok; + expect( _gUser ).to.have.property( 'id' ).and.equal( gUser.id ); + expect( _gUser ).to.have.property( 'UserId' ).and.equal( user.id ); + + done(); + }, done ) + } ) + .fail( done ); + }) + .error( done ); + } else { + console.log( 'UserService is defined' ); + done(); + } + + } ); + + } ); + + describe( '.updateAccessedDate( user )', function () { + + it( 'should be able to update user', function ( done ) { + + user_1 + .updateAttributes( { accessedAt: null } ) + .success( function( result ) { + + expect( result ).to.be.an( 'object' ).and.be.ok; + expect( result ).to.have.property( 'id' ).and.equal( user_1.id ); + expect( result ).to.have.property( 'accessedAt' ).and.not.be.ok; + + Service + .updateAccessedDate( user_1 ) + .then( function( result ) { + + expect( result ).to.be.an( 'object' ).and.be.ok; + expect( result ).to.have.property( 'id' ).and.equal( user_1.id ); + expect( result ).to.have.property( 'accessedAt' ).and.be.ok; + + done(); + }, done ); + }) + .error( done ); + } ); + + it( 'should be able to update gUser', function ( done ) { + + gUser + .updateAttributes( { accessedAt: null } ) + .success( function( result ) { + + expect( result ).to.be.an( 'object' ).and.be.ok; + expect( result ).to.have.property( 'id' ).and.equal( gUser.id ); + expect( result ).to.have.property( 'accessedAt' ).and.not.be.ok; + + Service + .updateAccessedDate( gUser ) + .then( function( result ) { + + expect( result ).to.be.an( 'object' ).and.be.ok; + expect( result ).to.have.property( 'id' ).and.equal( gUser.id ); + expect( result ).to.have.property( 'accessedAt' ).and.be.ok; + + done(); + }, done ); + }) + .error( done ); + } ); + + it( 'should be able to get object if it is not a user', function ( done ) { + + Service + .updateAccessedDate ( { statuscode: 403, message: 'invalid' } ) + .then ( function ( result ) { + + expect ( result ).to.be.an ( 'object' ).and.be.ok; + expect ( result ).to.have.property ( 'statuscode' ).and.equal ( 403 ); + expect ( result ).to.have.property ( 'message' ).and.be.ok; + + done (); + }, done ); + } ); + + } ); + + describe( '.listUsers()', function () { + + it( 'should be able to get list of all gUsers', function ( done ) { + + Service + .listUsers() + .then( function ( users ) { + + expect( users ).to.be.an( 'array' ).and.have.length.above( 1 ); + expect( users[0] ).to.be.an( 'object' ).and.be.ok; + expect( users[0] ).to.have.property( 'id' ).and.be.ok; + expect( users[0] ).to.have.property( 'googleid' ).and.be.ok; + expect( users[0] ).to.have.property( 'email' ).and.be.ok; + expect( users[0] ).to.have.property( 'gender' ).and.be.ok; + expect( users[0] ).to.not.have.property( 'token' ); + + done(); + } ) + .fail( done ); + } ); + + } ); + + describe( '.findUserById()', function () { + + it( 'should be able to get gUser by Id', function ( done ) { + + Service + .findUserById( gUser.id ) + .then( function ( gUser ) { + + expect( gUser ).to.be.an( 'object' ).and.be.ok; + expect( gUser ).to.have.property( 'id' ).and.equal( gUser.id ); + expect( gUser ).to.have.property( 'googleid' ).and.be.ok; + expect( gUser ).to.have.property( 'email' ).and.be.ok; + expect( gUser ).to.have.property( 'gender' ).and.be.ok; + expect( gUser ).to.not.have.property( 'token' ); + + done(); + } ) + .fail( done ); + } ); + + it( 'should be able to get the error if the gUser does not exist', function ( done ) { + + Service + .findUserById( 151515115151515151 ) + .then( function( result ) { + + expect( result ).to.be.an( 'object' ); + expect( result ).to.have.property( 'statuscode' ).and.equal( 403 ); + expect( result ).to.have.property( 'message' ).and.be.ok; + + done(); + }, done ) + } ); + + } ); + + describe( '.deleteUser( gUserId )', function () { + + it( 'should be able to get the error if the gUser does not exist', function ( done ) { + + Service + .deleteUser( 151515115151515151 ) + .then( function( result ) { + + expect( result ).to.be.an( 'object' ); + expect( result ).to.have.property( 'statuscode' ).and.equal( 403 ); + expect( result ).to.have.property( 'message' ).and.be.ok; + + done(); + }, done ) + } ); + + it( 'should be able to delete gUser', function ( done ) { + + Service + .deleteUser( gUser.id ) + .then( function ( result ) { + + expect( result ).to.be.an( 'object' ); + expect( result ).to.have.property( 'statuscode' ).and.equal( 200 ); + expect( result ).to.have.property( 'message' ).and.be.ok; + + done(); + } ) + .fail( done ); + } ); + + } ); + +} ); \ No newline at end of file From a5f4496aec24ccf8ee94cc8a7c7652854a24b07c Mon Sep 17 00:00:00 2001 From: Volodymyr Denshchykov Date: Tue, 4 Feb 2014 23:42:30 +0200 Subject: [PATCH 20/24] added tests and updated cervise, model and controller --- .../controllers/UserGoogleController.js | 28 +++++-- .../models/orm/UserGoogleModel.js | 5 +- modules/clever-auth-google/package.json | 8 +- .../services/UserGoogleService.js | 83 ++++++++++++++----- package.json | 1 + 5 files changed, 86 insertions(+), 39 deletions(-) diff --git a/modules/clever-auth-google/controllers/UserGoogleController.js b/modules/clever-auth-google/controllers/UserGoogleController.js index 78434d9..7fc220c 100644 --- a/modules/clever-auth-google/controllers/UserGoogleController.js +++ b/modules/clever-auth-google/controllers/UserGoogleController.js @@ -22,17 +22,18 @@ module.exports = function ( UserGoogleService ) { function ( accessToken, refreshToken, profile, done ) { UserGoogleService - .authenticate( profile, accessToken ) + .findOrCreate( profile, accessToken ) .then( function( gUser ) { - return UserGoogleService.associate ( gUser, profile ) + return UserGoogleService.authenticate ( gUser, profile ) }) + .then( UserGoogleService.updateAccessedDate ) .then( done.bind( null, null ) ) .fail( done ); } )); - return (require( 'classes' ).Controller).extend( + return (require( 'classes' ).Controller).extend ( { service: UserGoogleService }, @@ -41,15 +42,24 @@ module.exports = function ( UserGoogleService ) { UserGoogleService.listUsers() .then( this.proxy( 'handleServiceMessage' ) ) .fail( this.proxy( 'handleException' ) ); - }, + }, //tested + + getAction: function () { + var guId = this.req.params.id; + + UserGoogleService + .findUserById( guId ) + .then( this.proxy( 'handleServiceMessage' ) ) + .fail( this.proxy( 'handleException' ) ); + }, //tested deleteAction: function () { - var uId = this.req.params.id; + var guId = this.req.params.id; - UserGoogleService.deleteUser( { id: uId } ) + UserGoogleService.deleteUser( guId ) .then( this.proxy( 'handleServiceMessage' ) ) .fail( this.proxy( 'handleException' ) ); - }, + }, //tested loginAction: function () { var params = { @@ -60,9 +70,9 @@ module.exports = function ( UserGoogleService ) { scope: "https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email" }; - this.send(200, { url: 'https://accounts.google.com/o/oauth2/auth?' + qs.stringify(params) } ); + this.send( { url: 'https://accounts.google.com/o/oauth2/auth?' + qs.stringify( params ) }, 200 ); - }, + }, //tested returnAction: function () { passport.authenticate( 'google', this.proxy( 'handleLocalUser' ) )( this.req, this.res, this.next ); diff --git a/modules/clever-auth-google/models/orm/UserGoogleModel.js b/modules/clever-auth-google/models/orm/UserGoogleModel.js index 60bcd24..41b89c2 100644 --- a/modules/clever-auth-google/models/orm/UserGoogleModel.js +++ b/modules/clever-auth-google/models/orm/UserGoogleModel.js @@ -26,7 +26,7 @@ module.exports = function ( sequelize, DataTypes ) { isEmail: true } }, - googleId: { + googleid: { type: DataTypes.STRING, allowNull: false }, @@ -54,6 +54,9 @@ module.exports = function ( sequelize, DataTypes ) { }, accessedAt: { type: DataTypes.DATE + }, + UserId: { + type: DataTypes.INTEGER } }, { diff --git a/modules/clever-auth-google/package.json b/modules/clever-auth-google/package.json index 01db8b2..ff6bf36 100644 --- a/modules/clever-auth-google/package.json +++ b/modules/clever-auth-google/package.json @@ -5,17 +5,11 @@ "dependencies": { "passport": "~0.1.18", "connect-redis": "~1.4.6", - "connect": "~2.12.0", - "redis": "~0.10.0", "q": "", - "lodash": "2.4.1", "sequelize": "1.7.0-beta8", - "shortid": "~2.0.0", "moment": "", - "async": "", "passport-google-oauth": "~0.1.5", - "qs": "*", - "request": "latest" + "qs": "*" }, "author": { "name": "Clevertech", diff --git a/modules/clever-auth-google/services/UserGoogleService.js b/modules/clever-auth-google/services/UserGoogleService.js index 81cf60b..55b8b52 100644 --- a/modules/clever-auth-google/services/UserGoogleService.js +++ b/modules/clever-auth-google/services/UserGoogleService.js @@ -21,16 +21,16 @@ module.exports = function ( sequelize, email: profile._json.email, firstname: profile._json.given_name, lastname: profile._json.family_name, - picture: profile._json.picture, + picture: profile._json.picture || null, verified: profile._json.verified_email, link: profile._json.link, gender: profile._json.gender, locale: profile._json.locale, - token: accessToken + token: accessToken || null } - }, + }, //tested - authenticate: function ( profile, accessToken ) { + findOrCreate: function ( profile, accessToken ) { var deferred = Q.defer() , data = this.formatData ( profile, accessToken ); @@ -63,9 +63,9 @@ module.exports = function ( sequelize, } return deferred.promise; - }, + }, //tested - associate: function( gUser, profile ) { + authenticate: function( gUser, profile ) { var deferred = Q.defer() , data = this.formatData ( profile ); @@ -77,12 +77,17 @@ module.exports = function ( sequelize, if ( !!gUser && !!UserService ) { - if ( gUser.userId ) { + if ( gUser.UserId ) { UserService - .find( { where: { id: gUser.userId, email: gUser.email } } ) - .then( function( user ) { - deferred.resolve( user.toJSON() ); + .find( { where: { id: gUser.UserId, email: gUser.email } } ) + .then( function( users ) { + + var user = !!users && !!users.length + ? users[0] + : { statuscode: 403, message: 'invalid' }; + + deferred.resolve( user ); }) .fail( deferred.reject ); @@ -97,27 +102,26 @@ module.exports = function ( sequelize, if ( !!user && !!user.id ) { gUser - .updateAttributes( { userId: user.id } ) - .success( function() { - deferred.resolve( user.toJSON() ); - }) + .updateAttributes( { UserId: user.id } ) + .success ( function () { + deferred.resolve ( user ); + } ) .error( deferred.reject ); } else { data.confirmed = true; data.username = data.email; - data.accessedAt = moment.utc().format( 'YYYY-MM-DD HH:ss:mm' ); - data.password = crypto.createHash( 'sha1' ).update( Math.floor(Math.random() * 1e18) ).digest( 'hex' ) + data.password = crypto.createHash( 'sha1' ).update( Math.floor(Math.random() * 1e18) + '' ).digest( 'hex' ) UserService .create( data ) .then( function( user ) { gUser - .updateAttributes ( { userId: user.id } ) + .updateAttributes ( { UserId: user.id } ) .success ( function () { - deferred.resolve ( user.toJSON () ); + deferred.resolve ( user ); } ) .error ( deferred.reject ); }) @@ -128,11 +132,28 @@ module.exports = function ( sequelize, } } else { - deferred.resolve( gUser.toJSON() ); + deferred.resolve( gUser ); } return deferred.promise; - }, + }, //tested + + updateAccessedDate: function( user ){ + var deferred = Q.defer(); + + if ( !!user && !!user.id ) { + user + .updateAttributes( { accessedAt: moment.utc().format( 'YYYY-MM-DD HH:ss:mm' ) } ) + .success( function() { + deferred.resolve( user.toJSON() ); + }) + .error( deferred.reject ); + } else { + deferred.resolve( user ); + } + + return deferred.promise; + }, //tested listUsers: function() { var deferred = Q.defer(); @@ -149,7 +170,25 @@ module.exports = function ( sequelize, .error( deferred.reject ); return deferred.promise; - }, + }, //tested + + findUserById: function( gUserId ) { + var deferred = Q.defer(); + + ORMUserGoogleModel + .find( gUserId ) + .success( function( gUser ) { + + if ( !!gUser && !!gUser.id ){ + deferred.resolve( gUser.toJSON() ); + } else { + deferred.resolve( { statuscode: 403, message: 'user do not exist' } ); + } + }) + .error( deferred.reject ); + + return deferred.promise; + }, //tested deleteUser: function( gUserId ) { var deferred = Q.defer(); @@ -180,7 +219,7 @@ module.exports = function ( sequelize, .error( deferred.reject ); return deferred.promise; - } + } //tested } ); diff --git a/package.json b/package.json index a9efd36..394b493 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "odm", "background-tasks", "clever-auth", + "clever-auth-google", "clever-email" ] } From f3ac5687678cc648e51c2878402d7e5feb3a4b35 Mon Sep 17 00:00:00 2001 From: Volodymyr Denshchykov Date: Fri, 7 Feb 2014 16:17:30 +0200 Subject: [PATCH 21/24] updated handleLoginJson --- modules/clever-auth-google/config/default.json | 4 +++- .../clever-auth-google/controllers/UserGoogleController.js | 6 +++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/modules/clever-auth-google/config/default.json b/modules/clever-auth-google/config/default.json index 295df9c..6c5ce26 100644 --- a/modules/clever-auth-google/config/default.json +++ b/modules/clever-auth-google/config/default.json @@ -17,7 +17,9 @@ "javascriptOrigins": "http://localhost:8080", "authURI": "http://accounts.google.com/o/oauth2/auth", "tokenURI": "http://accounts.google.com/o/oauth2/token" - } + }, + + "frontendURL": "http://localhost:9000/" } } \ No newline at end of file diff --git a/modules/clever-auth-google/controllers/UserGoogleController.js b/modules/clever-auth-google/controllers/UserGoogleController.js index 7fc220c..ec57006 100644 --- a/modules/clever-auth-google/controllers/UserGoogleController.js +++ b/modules/clever-auth-google/controllers/UserGoogleController.js @@ -90,7 +90,11 @@ module.exports = function ( UserGoogleService ) { handleLoginJson: function ( user, err ) { if ( err ) return this.handleException( err ); - this.send( user, 200 ); + + this.res.statusCode = 302; + this.res.setHeader( 'body', user ); + this.res.setHeader( 'Location', config.frontendURL ); + this.res.end(); }, handleServiceMessage: function ( obj ) { From c0a775e11bc882b41755fa4a49d50e5622505ae3 Mon Sep 17 00:00:00 2001 From: Volodymyr Denshchykov Date: Fri, 7 Feb 2014 20:31:18 +0200 Subject: [PATCH 22/24] fix err in tests if UserService is no define --- .../unit/test.service.UserGoogleService.js | 52 +++++++++++-------- 1 file changed, 30 insertions(+), 22 deletions(-) diff --git a/modules/clever-auth-google/tests/unit/test.service.UserGoogleService.js b/modules/clever-auth-google/tests/unit/test.service.UserGoogleService.js index 266c6b0..93489c5 100644 --- a/modules/clever-auth-google/tests/unit/test.service.UserGoogleService.js +++ b/modules/clever-auth-google/tests/unit/test.service.UserGoogleService.js @@ -274,7 +274,7 @@ describe( 'service.UserGoogleService', function () { expect( user ).to.be.an( 'object' ).and.be.ok; expect( user ).to.have.property( 'id' ).and.equal( gUser.id ); - expect( user ).to.not.have.property( 'token' ); + expect( user ).to.have.property( 'token' ).and.be.ok; expect( user ).to.have.property( 'firstname' ).and.equal( gUser.firstname ); expect( user ).to.have.property( 'lastname' ).and.equal( gUser.lastname ); expect( user ).to.have.property( 'email' ).and.equal( gUser.email ); @@ -285,7 +285,7 @@ describe( 'service.UserGoogleService', function () { } ) .fail( done ); } else { - console.log( 'UserService is not defined' ); + console.log( 'UserService is defined' ); done(); } } ); @@ -356,7 +356,7 @@ describe( 'service.UserGoogleService', function () { } ) .fail( done ); } else { - console.log( 'UserService is defined' ); + console.log( 'UserService is not defined' ); done(); } @@ -398,7 +398,7 @@ describe( 'service.UserGoogleService', function () { } ) .fail( done ); } else { - console.log( 'UserService is defined' ); + console.log( 'UserService is not defined' ); done(); } @@ -450,7 +450,7 @@ describe( 'service.UserGoogleService', function () { }) .error( done ); } else { - console.log( 'UserService is defined' ); + console.log( 'UserService is not defined' ); done(); } @@ -514,7 +514,7 @@ describe( 'service.UserGoogleService', function () { }) .error( done ); } else { - console.log( 'UserService is defined' ); + console.log( 'UserService is not defined' ); done(); } @@ -526,26 +526,34 @@ describe( 'service.UserGoogleService', function () { it( 'should be able to update user', function ( done ) { - user_1 - .updateAttributes( { accessedAt: null } ) - .success( function( result ) { + if ( !!UserService ) { + user_1 + .updateAttributes( { accessedAt: null } ) + .success( function( result ) { - expect( result ).to.be.an( 'object' ).and.be.ok; - expect( result ).to.have.property( 'id' ).and.equal( user_1.id ); - expect( result ).to.have.property( 'accessedAt' ).and.not.be.ok; + expect( result ).to.be.an( 'object' ).and.be.ok; + expect( result ).to.have.property( 'id' ).and.equal( user_1.id ); + expect( result ).to.have.property( 'accessedAt' ).and.not.be.ok; - Service - .updateAccessedDate( user_1 ) - .then( function( result ) { + Service + .updateAccessedDate( user_1 ) + .then( function( result ) { + + expect( result ).to.be.an( 'object' ).and.be.ok; + expect( result ).to.have.property( 'id' ).and.equal( user_1.id ); + expect( result ).to.have.property( 'accessedAt' ).and.be.ok; + + done(); + }, done ); + }) + .error( done ); + + } else { + console.log( 'UserService is not defined' ); + done(); + } - expect( result ).to.be.an( 'object' ).and.be.ok; - expect( result ).to.have.property( 'id' ).and.equal( user_1.id ); - expect( result ).to.have.property( 'accessedAt' ).and.be.ok; - done(); - }, done ); - }) - .error( done ); } ); it( 'should be able to update gUser', function ( done ) { From eb901702c92ce25881fe437ca526dd9e621f99b2 Mon Sep 17 00:00:00 2001 From: Volodymyr Denshchykov Date: Mon, 10 Feb 2014 19:40:12 +0200 Subject: [PATCH 23/24] update controller --- .../controllers/UserGoogleController.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/modules/clever-auth-google/controllers/UserGoogleController.js b/modules/clever-auth-google/controllers/UserGoogleController.js index ec57006..af0464d 100644 --- a/modules/clever-auth-google/controllers/UserGoogleController.js +++ b/modules/clever-auth-google/controllers/UserGoogleController.js @@ -80,8 +80,15 @@ module.exports = function ( UserGoogleService ) { handleLocalUser: function ( err, user ) { if ( err ) return this.handleException( err ); - if ( !user ) return this.send( {}, 403 ); - this.loginUserJson( user ); + + if ( !user ) { + this.res.statusCode = 302; + this.res.setHeader( 'body', {} ); + this.res.setHeader( 'Location', config.frontendURL ); + this.res.end(); + } else { + this.loginUserJson( user ); + } }, loginUserJson: function ( user ) { From af1483fb8af5d048f942486f71e45c4fc264396a Mon Sep 17 00:00:00 2001 From: Volodymyr Denshchykov Date: Tue, 11 Feb 2014 19:33:59 +0200 Subject: [PATCH 24/24] feature(module) added clever-auth-dropbox and clever-auth-linkedin modules with tests --- .../clever-auth-dropbox/config/default.json | 21 + .../controllers/UserDropboxController.js | 120 +++ .../models/orm/UserDropboxModel.js | 66 ++ modules/clever-auth-dropbox/module.js | 129 ++++ modules/clever-auth-dropbox/package.json | 32 + modules/clever-auth-dropbox/routes.js | 9 + .../services/UserDropboxService.js | 228 ++++++ .../test.controllers.UserDropboxController.js | 213 ++++++ .../unit/test.service.UserDropboxService.js | 654 +++++++++++++++++ .../clever-auth-linkedin/config/default.json | 21 + .../controllers/UserLinkedinController.js | 125 ++++ .../models/orm/UserLinkedinModel.js | 70 ++ modules/clever-auth-linkedin/module.js | 129 ++++ modules/clever-auth-linkedin/package.json | 32 + modules/clever-auth-linkedin/routes.js | 9 + .../services/UserLinkedinService.js | 230 ++++++ .../test.controllers.UserDropboxController.js | 215 ++++++ .../unit/test.service.UserDropboxService.js | 681 ++++++++++++++++++ modules/orm/config/default.json | 11 - package.json | 5 +- 20 files changed, 2986 insertions(+), 14 deletions(-) create mode 100644 modules/clever-auth-dropbox/config/default.json create mode 100644 modules/clever-auth-dropbox/controllers/UserDropboxController.js create mode 100644 modules/clever-auth-dropbox/models/orm/UserDropboxModel.js create mode 100644 modules/clever-auth-dropbox/module.js create mode 100644 modules/clever-auth-dropbox/package.json create mode 100644 modules/clever-auth-dropbox/routes.js create mode 100644 modules/clever-auth-dropbox/services/UserDropboxService.js create mode 100644 modules/clever-auth-dropbox/tests/unit/test.controllers.UserDropboxController.js create mode 100644 modules/clever-auth-dropbox/tests/unit/test.service.UserDropboxService.js create mode 100644 modules/clever-auth-linkedin/config/default.json create mode 100644 modules/clever-auth-linkedin/controllers/UserLinkedinController.js create mode 100644 modules/clever-auth-linkedin/models/orm/UserLinkedinModel.js create mode 100644 modules/clever-auth-linkedin/module.js create mode 100644 modules/clever-auth-linkedin/package.json create mode 100644 modules/clever-auth-linkedin/routes.js create mode 100644 modules/clever-auth-linkedin/services/UserLinkedinService.js create mode 100644 modules/clever-auth-linkedin/tests/unit/test.controllers.UserDropboxController.js create mode 100644 modules/clever-auth-linkedin/tests/unit/test.service.UserDropboxService.js diff --git a/modules/clever-auth-dropbox/config/default.json b/modules/clever-auth-dropbox/config/default.json new file mode 100644 index 0000000..ae8a352 --- /dev/null +++ b/modules/clever-auth-dropbox/config/default.json @@ -0,0 +1,21 @@ +{ + "clever-auth-dropbox": { + "secretKey": "youMustChangeMe", + + "redis": { + "host": "localhost", + "port": "6379", + "prefix": "", + "key": "" + }, + + "dropbox": { + "AppKey": "8hbtahx62cl0x8c", + "AppSecret": "l1cp2c2u3gsll1h", + "redirectURIs": "http://localhost:8080/auth/dropbox/return" + }, + + "frontendURL": "http://localhost:9000/" + + } +} \ No newline at end of file diff --git a/modules/clever-auth-dropbox/controllers/UserDropboxController.js b/modules/clever-auth-dropbox/controllers/UserDropboxController.js new file mode 100644 index 0000000..3f3cc37 --- /dev/null +++ b/modules/clever-auth-dropbox/controllers/UserDropboxController.js @@ -0,0 +1,120 @@ +var config = require ( 'config' )[ 'clever-auth-dropbox' ] + , passport = require ( 'passport' ) + , qs = require ( 'qs' ) + , DropboxOAuth2Strategy = require( 'passport-dropbox-oauth2' ).Strategy; + +var state = +new Date() + ''; + +module.exports = function ( UserDropboxService ) { + + passport.serializeUser( function ( user, done ) { + done( null, user ); + } ); + + passport.deserializeUser( function ( user, done ) { + done( null, user ) + } ); + + passport.use( new DropboxOAuth2Strategy( + { + clientID: config.dropbox.AppKey, + clientSecret: config.dropbox.AppSecret, + callbackURL: config.dropbox.redirectURIs, + state: state + }, + function ( accessToken, refreshToken, profile, done ) { + + UserDropboxService + .findOrCreate( profile, accessToken ) + .then( function( gUser ) { + return UserDropboxService.authenticate ( gUser, profile ) + }) + .then( UserDropboxService.updateAccessedDate ) + .then( done.bind( null, null ) ) + .fail( done ); + } + )); + + + return (require( 'classes' ).Controller).extend ( + { + service: UserDropboxService + }, + { + listAction: function () { + UserDropboxService.listUsers() + .then( this.proxy( 'handleServiceMessage' ) ) + .fail( this.proxy( 'handleException' ) ); + }, + + getAction: function () { + var guId = this.req.params.id; + + UserDropboxService + .findUserById( guId ) + .then( this.proxy( 'handleServiceMessage' ) ) + .fail( this.proxy( 'handleException' ) ); + }, + + deleteAction: function () { + var guId = this.req.params.id; + + UserDropboxService.deleteUser( guId ) + .then( this.proxy( 'handleServiceMessage' ) ) + .fail( this.proxy( 'handleException' ) ); + }, + + loginAction: function () { + var params = { + response_type: "code", + client_id: config.dropbox.AppKey, + redirect_uri: config.dropbox.redirectURIs, + state: state + }; + + this.send( { url: 'https://www.dropbox.com/1/oauth2/authorize?' + qs.stringify( params ) }, 200 ); + + }, + + returnAction: function () { + passport.authenticate( 'dropbox-oauth2', this.proxy( 'handleLocalUser' ) )( this.req, this.res, this.next ); + }, + + handleLocalUser: function ( err, user ) { + if ( err ) return this.handleException( err ); + + if ( !user ) { + this.res.statusCode = 302; + this.res.setHeader( 'body', {} ); + this.res.setHeader( 'Location', config.frontendURL ); + this.res.end(); + } else { + this.loginUserJson( user ); + } + }, + + loginUserJson: function ( user ) { + this.req.login( user, this.proxy( 'handleLoginJson', user ) ); + }, + + handleLoginJson: function ( user, err ) { + if ( err ) return this.handleException( err ); + + this.res.statusCode = 302; + this.res.setHeader( 'body', user ); + this.res.setHeader( 'Location', config.frontendURL ); + this.res.end(); + }, + + handleServiceMessage: function ( obj ) { + + if ( obj.statuscode ) { + this.send( obj.message, obj.statuscode ); + return; + } + + this.send( obj, 200 ); + } + + } ); +}; \ No newline at end of file diff --git a/modules/clever-auth-dropbox/models/orm/UserDropboxModel.js b/modules/clever-auth-dropbox/models/orm/UserDropboxModel.js new file mode 100644 index 0000000..776cecf --- /dev/null +++ b/modules/clever-auth-dropbox/models/orm/UserDropboxModel.js @@ -0,0 +1,66 @@ +module.exports = function ( sequelize, DataTypes ) { + return sequelize.define( "UserDropbox", + { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true + }, + email: { + type: DataTypes.STRING, + allowNull: false, + unique: true, + validate: { + isEmail: true + } + }, + firstname: { + type: DataTypes.STRING, + allowNull: true + }, + lastname: { + type: DataTypes.STRING, + allowNull: true + }, + dropboxid: { + type: DataTypes.INTEGER, + allowNull: false + }, + link: { + type: DataTypes.STRING, + allowNull: true + }, + locale: { + type: DataTypes.STRING, + allowNull: true + }, + token: { + type: DataTypes.STRING, + allowNull: false + }, + accessedAt: { + type: DataTypes.DATE + }, + UserId: { + type: DataTypes.INTEGER + } + }, + { + paranoid: true, + + getterMethods: { + fullName: function () { + return [ this.getDataValue( 'firstname' ), this.getDataValue( 'lastname' )].join( ' ' ); + } + }, + + instanceMethods: { + toJSON: function () { + var values = this.values; + values.fullName = this.fullName; + delete values.token; + return values; + } + } + } ); +}; \ No newline at end of file diff --git a/modules/clever-auth-dropbox/module.js b/modules/clever-auth-dropbox/module.js new file mode 100644 index 0000000..c86e331 --- /dev/null +++ b/modules/clever-auth-dropbox/module.js @@ -0,0 +1,129 @@ +var passport = require( 'passport' ) + , debug = require( 'debug' )( 'CleverAuthDropboxModule' ) + , fs = require( 'fs' ) + , path = require( 'path' ) + , ModuleClass = require( 'classes' ).ModuleClass + , RedisStore = require( 'connect-redis' )( injector.getInstance( 'express' ) ) + , Module; + +Module = ModuleClass.extend( { + models: null, + store: null, + sequelize: null, + + preSetup: function () { + var self = this; + this.models = {}; + this.sequelize = injector.getInstance ( 'sequelize' ); + var dir = fs.readdirSync ( path.join ( __dirname, 'models', 'orm' ) ); + dir.forEach ( function ( model ) { + self.getModel ( path.join ( __dirname, 'models', 'orm', model ) ); + } ); + }, + + preResources: function () { + injector.instance ( 'passport', passport ); + }, + + modulesLoaded: function () { + this.defineModelsAssociations (); + }, + + defineModelsAssociations: function () { + if ( !this.config.hasOwnProperty ( 'modelAssociations' ) ) { + return true; + } + + debug ( 'Defining model assocations' ); + + Object.keys ( this.config.modelAssociations ).forEach ( this.proxy ( function ( modelName ) { + Object.keys ( this.config.modelAssociations[ modelName ] ).forEach ( this.proxy ( 'defineModelAssociations', modelName ) ); + } ) ); + }, + + defineModelAssociations: function ( modelName, assocType ) { + var associatedWith = this.config.modelAssociations[ modelName ][ assocType ]; + if ( !associatedWith instanceof Array ) { + associatedWith = [ associatedWith ]; + } + + associatedWith.forEach ( this.proxy ( 'associateModels', modelName, assocType ) ); + }, + + associateModels: function ( modelName, assocType, assocTo ) { + // Support second argument + if ( assocTo instanceof Array ) { + debug ( '%s %s %s with second argument of ', modelName, assocType, assocTo[0], assocTo[1] ); + this.models[ modelName ][ assocType ] ( this.models[ assocTo[0] ], assocTo[1] ); + } else { + debug ( '%s %s %s', modelName, assocType, assocTo ); + this.models[ modelName ][ assocType ] ( this.models[assocTo] ); + } + }, + + getModel: function ( modelPath ) { + var modelName = modelPath.split ( '/' ).pop ().split ( '.' ).shift (); + + if ( typeof this.models[ modelName ] === 'undefined' ) { + debug ( [ 'Loading model', modelName, 'from', modelPath ].join ( ' ' ) ); + + // Call on sequelizejs to load the model + this.models[ modelName ] = this.sequelize.import ( modelPath ); + + // Set a flat for tracking + this.models[ modelName ].ORM = true; + + // Add the model to the injector + injector.instance ( 'ORM' + modelName, this.models[ modelName ] ); + } + + return this.models[ modelName ]; + }, + + configureApp: function ( app, express ) { +// Setup the redis store + this.store = new RedisStore ( { + host: this.config.redis.host, + port: this.config.redis.port, + prefix: this.config.redis.prefix + process.env.NODE_ENV + "_", + password: this.config.redis.key + } ); + +// Session management + app.use ( express.cookieParser () ); + app.use ( express.session ( { + secret: this.config.secretKey, + cookie: { secure: false, maxAge: 86400000 }, + store: this.store + } ) ); + + // Enable CORS + app.use ( this.proxy ( 'enableCors' ) ); + + // Initialize passport + app.use ( passport.initialize () ); + app.use ( passport.session () ); + }, + + enableCors: function ( req, res, next ) { + + res.header ( 'Access-Control-Allow-Origin', req.headers.origin ); + res.header ( 'Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE' ); + res.header ( 'Access-Control-Allow-Headers', 'Content-Type, Authorization, x-requested-with' ); + res.header( "Access-Control-Allow-Credentials", "true" ); + res.header( "Access-Control-Max-Age", "1000000000" ); + // intercept OPTIONS method + if ( 'OPTIONS' == req.method ) { + res.send ( 200 ); + } + else { + next (); + } + }, + + preShutdown: function () { + this.store.client.quit (); + } +} ); + +module.exports = new Module( 'clever-auth-dropbox', injector ); \ No newline at end of file diff --git a/modules/clever-auth-dropbox/package.json b/modules/clever-auth-dropbox/package.json new file mode 100644 index 0000000..0f9b6ad --- /dev/null +++ b/modules/clever-auth-dropbox/package.json @@ -0,0 +1,32 @@ +{ + "name": "clever-auth-dropbox", + "version": "0.0.1", + "private": true, + "dependencies": { + "passport": "~0.1.18", + "connect-redis": "~1.4.6", + "q": "", + "sequelize": "1.7.0-beta8", + "moment": "", + "passport-dropbox-oauth2": "", + "qs": "*" + }, + "author": { + "name": "Clevertech", + "email": "info@clevertech.biz", + "web": "http://www.clevertech.biz" + }, + "collaborators": [ + "" + ], + "description": "", + "keywords": [ + "" + ], + "main": "module.js", + "devDependencies": { + "chai": "*", + "mocha": "*", + "sinon": "" + } +} \ No newline at end of file diff --git a/modules/clever-auth-dropbox/routes.js b/modules/clever-auth-dropbox/routes.js new file mode 100644 index 0000000..4b7b231 --- /dev/null +++ b/modules/clever-auth-dropbox/routes.js @@ -0,0 +1,9 @@ +module.exports = function ( + app, + UserDropboxController ) +{ + + app.all('/auth/dropbox/?:action?', UserDropboxController.attach() ); + +}; + diff --git a/modules/clever-auth-dropbox/services/UserDropboxService.js b/modules/clever-auth-dropbox/services/UserDropboxService.js new file mode 100644 index 0000000..47e2d59 --- /dev/null +++ b/modules/clever-auth-dropbox/services/UserDropboxService.js @@ -0,0 +1,228 @@ +var Q = require( 'q' ) + , crypto = require( 'crypto' ) + , moment = require( 'moment' ) + , Sequelize = require( 'sequelize' ) + , UserDropboxService = null; + +module.exports = function ( sequelize, + ORMUserDropboxModel ) { + + if ( UserDropboxService && UserDropboxService.instance ) { + return UserDropboxService.instance; + } + + var UserService = null; + + UserDropboxService = require( 'services' ).BaseService.extend( { + + formatData: function ( profile, accessToken ) { + var name = !!profile._json.display_name ? profile._json.display_name.split( ' ' ) : []; + return { + dropboxid: profile._json.uid, + email: profile._json.email, + firstname: !!name.length ? name[ 0 ] : null, + lastname: !!name.length && name.length > 1 ? name[ name.length - 1 ] : null, + link: profile._json.referral_link, + locale: profile._json.country || null, + token: accessToken || null + } + }, //tested + + findOrCreate: function ( profile, accessToken ) { + var deferred = Q.defer() + , data = this.formatData ( profile, accessToken ); + + if ( !!data.email ) { + + ORMUserDropboxModel + .find( { where: { email: data.email } } ) + .success( function ( dbUser ) { + + if ( !dbUser ) { + + data.accessedAt = moment.utc().format( 'YYYY-MM-DD HH:ss:mm' ); + + ORMUserDropboxModel + .create( data ) + .success( deferred.resolve ) + .error( deferred.reject ); + + } else { + + dbUser + .updateAttributes( { accessedAt: moment.utc().format( 'YYYY-MM-DD HH:ss:mm' ), token: data.token } ) + .success( deferred.resolve ) + .error( deferred.reject ); + } + } ) + .error( deferred.reject ); + } else { + deferred.resolve(); + } + + return deferred.promise; + }, //tested + + authenticate: function( dbUser, profile ) { + var deferred = Q.defer() + , data = this.formatData ( profile ); + + try { + UserService = injector.getInstance( 'UserService' ); + } catch ( err ) { + console.log( err ); + } + + if ( !!dbUser && !!UserService ) { + + if ( dbUser.UserId ) { + + UserService + .find( { where: { id: dbUser.UserId, email: dbUser.email } } ) + .then( function( users ) { + + var user = !!users && !!users.length + ? users[0] + : { statuscode: 403, message: 'invalid' }; + + deferred.resolve( user ); + }) + .fail( deferred.reject ); + + } else { + + UserService + .find( { where: { email: dbUser.email } } ) + .then( function( user ) { + + user = user[0]; + + if ( !!user && !!user.id ) { + + dbUser + .updateAttributes( { UserId: user.id } ) + .success ( function () { + deferred.resolve ( user ); + } ) + .error( deferred.reject ); + + } else { + + data.confirmed = true; + data.username = data.email; + data.password = crypto.createHash( 'sha1' ).update( Math.floor(Math.random() * 1e18) + '' ).digest( 'hex' ) + + UserService + .create( data ) + .then( function( user ) { + + dbUser + .updateAttributes ( { UserId: user.id } ) + .success ( function () { + deferred.resolve ( user ); + } ) + .error ( deferred.reject ); + }) + .fail( deferred.reject ); + } + }) + .fail( deferred.reject ); + } + + } else { + deferred.resolve( dbUser ); + } + + return deferred.promise; + }, //tested + + updateAccessedDate: function( user ){ + var deferred = Q.defer(); + + if ( !!user && !!user.id ) { + user + .updateAttributes( { accessedAt: moment.utc().format( 'YYYY-MM-DD HH:ss:mm' ) } ) + .success( function() { + deferred.resolve( user.toJSON() ); + }) + .error( deferred.reject ); + } else { + deferred.resolve( user ); + } + + return deferred.promise; + }, //tested + + listUsers: function() { + var deferred = Q.defer(); + + ORMUserDropboxModel + .findAll( { where: { deletedAt: null } } ) + .success( function( dbUsers ) { + if ( !!dbUsers && !!dbUsers.length ) { + deferred.resolve( dbUsers.map( function( u ) { return u.toJSON(); } ) ); + } else { + deferred.resolve( {} ); + } + }) + .error( deferred.reject ); + + return deferred.promise; + }, //tested + + findUserById: function( dbUserId ) { + var deferred = Q.defer(); + + ORMUserDropboxModel + .find( dbUserId ) + .success( function( dbUser ) { + + if ( !!dbUser && !!dbUser.id ){ + deferred.resolve( dbUser.toJSON() ); + } else { + deferred.resolve( { statuscode: 403, message: 'user do not exist' } ); + } + }) + .error( deferred.reject ); + + return deferred.promise; + }, //tested + + deleteUser: function( dbUserId ) { + var deferred = Q.defer(); + + ORMUserDropboxModel + .find( dbUserId ) + .success( function( dbUser ) { + + if ( !!dbUser && !!dbUser.id ) { + + dbUser + .destroy() + .success( function( result ) { + + if ( !result.deletedAt ) { + deferred.resolve( { statuscode: 500, message: 'error' } ); + } else { + deferred.resolve( { statuscode: 200, message: 'user is deleted' } ); + } + + }) + .error( deferred.reject ); + + } else { + deferred.resolve( { statuscode: 403, message: 'user do not exist' } ) + } + }) + .error( deferred.reject ); + + return deferred.promise; + } //tested + + } ); + + UserDropboxService.instance = new UserDropboxService( sequelize ); + UserDropboxService.Model = ORMUserDropboxModel; + + return UserDropboxService.instance; +}; \ No newline at end of file diff --git a/modules/clever-auth-dropbox/tests/unit/test.controllers.UserDropboxController.js b/modules/clever-auth-dropbox/tests/unit/test.controllers.UserDropboxController.js new file mode 100644 index 0000000..e6c24c4 --- /dev/null +++ b/modules/clever-auth-dropbox/tests/unit/test.controllers.UserDropboxController.js @@ -0,0 +1,213 @@ +// Bootstrap the testing environmen +var testEnv = require( 'utils' ).testEnv(); + +var expect = require( 'chai' ).expect + , Q = require ( 'q' ) + , sinon = require( 'sinon' ) + , Service; + +describe( 'controllers.UserDropboxController', function () { + var Service, UserDropboxController, ctrl, dbUsers = []; + + before( function ( done ) { + testEnv( function ( _UserDropboxService_, _UserDropboxController_ ) { + var req = { + params: { action: 'fakeAction'}, + method: 'GET', + query: {} + }; + + var res = { + json: function () {} + }; + + var next = function () {}; + + Controller = _UserDropboxController_; + Service = _UserDropboxService_; + ctrl = new Controller( req, res, next ); + + var profile_1 = { + _json: { + referral_link: 'https://db.tt/FJ8lM2uy', + display_name: 'Volodymyr Denshchykov', + uid: 266578368, + country: 'UA', + quota_info: { datastores: 0, shared: 0, quota: 2147483648, normal: 107730 }, + email: 'denshikov_vovan_2@mail.ru' + } + } + , profile_2 = { + _json: { + referral_link: 'https://db.tt/FJ8lM2uy', + display_name: 'Volodymyr Denshchykov', + uid: 266578368, + country: 'UA', + quota_info: { datastores: 0, shared: 0, quota: 2147483648, normal: 107730 }, + email: 'denshikov_vovan_3@mail.ru' + } + }; + + Service + .findOrCreate( profile_1, 'bhbhgvbljbhscvzkchvblzclvnzbkjxcv' ) + .then( function( dbUser ) { + + expect( dbUser ).to.be.an( 'object' ).and.be.ok; + expect( dbUser ).to.have.property( 'id' ).and.be.ok; + + dbUsers.push( dbUser ) + + Service + .findOrCreate( profile_2, 'kjajkvl zsdvbakhvckhabskhv' ) + .then( function( dbUser ) { + + expect( dbUser ).to.be.an( 'object' ).and.be.ok; + expect( dbUser ).to.have.property( 'id' ).and.be.ok; + + dbUsers.push( dbUser ) + + done(); + }, done ); + }, done ); + } ); + } ); + + afterEach( function ( done ) { + + ctrl.req = { + params: { action: 'fakeAction'}, + method: 'GET', + query: {}, + body: {} + }; + + ctrl.res = { + json: function () {} + }; + + done(); + }); + + describe( 'loginAction()', function () { + + it( 'should call this.send() with specify parameters', function ( done ) { + + ctrl.send = function ( result, status ) { + + expect( status ).to.equal( 200 ); + + expect( result ).to.be.an( 'object' ).and.be.ok; + expect( result ).to.have.property( 'url' ).and.contain( 'https://www.dropbox.com/1/oauth2/authorize?' ); + + done(); + }; + + ctrl.loginAction(); + } ); + + } ); + + describe( 'listAction()', function () { + + it( 'should be able to get list of users', function ( done ) { + + ctrl.send = function ( result, status ) { + + expect( status ).to.equal( 200 ); + + expect( result ).to.be.an( 'array' ).and.have.length.above( 1 ); + expect( result[0] ).to.be.an( 'object' ).and.be.ok; + expect( result[0] ).to.have.property( 'id' ).and.be.ok; + expect( result[0] ).to.have.property( 'firstname' ).and.be.ok; + expect( result[0] ).to.have.property( 'lastname' ).and.be.ok; + expect( result[0] ).to.have.property( 'email' ).and.be.ok; + expect( result[0] ).to.have.property( 'dropboxid' ).and.be.ok; + expect( result[0] ).to.not.have.property( 'token' ); + + done(); + }; + + ctrl.listAction(); + } ); + + } ); + + describe( 'getAction()', function () { + + it( 'should be able to get user by id', function ( done ) { + + ctrl.send = function ( result, status ) { + + expect( status ).to.equal( 200 ); + + expect( result ).to.be.an( 'object' ).and.be.ok; + expect( result ).to.have.property( 'id' ).and.be.ok; + expect( result ).to.have.property( 'firstname' ).and.be.ok; + expect( result ).to.have.property( 'lastname' ).and.be.ok; + expect( result ).to.have.property( 'email' ).and.be.ok; + expect( result ).to.have.property( 'dropboxid' ).and.be.ok; + expect( result ).to.not.have.property( 'token' ); + + done(); + }; + + ctrl.req.params = { id: dbUsers[0].id }; + + ctrl.getAction(); + } ); + + it( 'should be able to get error if user do not exist', function ( done ) { + + ctrl.send = function ( result, status ) { + + expect( status ).to.equal( 403 ); + + expect( result ).to.be.an( 'string' ).and.be.ok; + + done(); + }; + + ctrl.req.params = { id: 15151515151151515 }; + + ctrl.getAction(); + } ); + + } ); + + describe( 'deleteAction()', function () { + + it( 'should be able to delete user by id', function ( done ) { + + ctrl.send = function ( result, status ) { + + expect( status ).to.equal( 200 ); + + expect( result ).to.be.an( 'string' ).and.be.ok; + + done(); + }; + + ctrl.req.params = { id: dbUsers[0].id }; + + ctrl.deleteAction(); + } ); + + it( 'should be able to get error if user do not exist', function ( done ) { + + ctrl.send = function ( result, status ) { + + expect( status ).to.equal( 403 ); + + expect( result ).to.be.an( 'string' ).and.be.ok; + + done(); + }; + + ctrl.req.params = { id: 15151515151151515 }; + + ctrl.deleteAction(); + } ); + + } ); + +} ); \ No newline at end of file diff --git a/modules/clever-auth-dropbox/tests/unit/test.service.UserDropboxService.js b/modules/clever-auth-dropbox/tests/unit/test.service.UserDropboxService.js new file mode 100644 index 0000000..b1046a2 --- /dev/null +++ b/modules/clever-auth-dropbox/tests/unit/test.service.UserDropboxService.js @@ -0,0 +1,654 @@ +var expect = require ( 'chai' ).expect + , request = require ( 'supertest' ) + , path = require( 'path' ) + , app = require ( path.resolve( __dirname + '/../../../../' ) + '/index.js' ) + , config = require( 'config' ) + , testEnv = require ( 'utils' ).testEnv() + , sinon = require( 'sinon' ) + , Q = require ( 'q' ); + +var UserService = null; + +var dbUserId_1, accessedAtDate, dbUser, user_1; + +describe( 'service.UserDropboxService', function () { + var Service, Model; + + before( function ( done ) { + this.timeout( 15000 ); + testEnv( function ( _UserDropboxService_, _ORMUserDropboxModel_ ) { + + Service = _UserDropboxService_; + Model = _ORMUserDropboxModel_; + + done(); + }, done ); + } ); + + describe( '.formatData( profile, accessToken )', function () { + + it( 'should return an object with filtered data', function ( done ) { + var accessToken = 'sdasdasdasdasdasdasdasda' + , profile = { + provider: 'github', + id: 1515151515, + displayName: 'Volodymyr Denshchykov', + emails: [ { value: 'denshikov_vovan@mail.ru' } ], + _raw: '{"referral_link": "https://db.tt/FJ8lM2uy", "display_name": "\\u0412\\u043b\\u0430\\u0434\\u0438\\u043c\\u0438\\u0440 \\u0414\\u0435\\u043d\\u0449\\u0438\\u043a\\u043e\\u0432", "uid": 266578368, "country": "UA", "quota_info": {"datastores": 0, "shared": 0, "quota": 2147483648, "normal": 107730}, "email": "denshikov_vovan@mail.ru"}', + _json: { + referral_link: 'https://db.tt/FJ8lM2uy', + display_name: 'Volodymyr Denshchykov', + uid: 266578368, + country: 'UA', + quota_info: { datastores: 0, shared: 0, quota: 2147483648, normal: 107730 }, + email: 'denshikov_vovan@mail.ru' + } + }; + + var data = Service.formatData( profile, accessToken ); + + expect( data ).to.be.an( 'object' ).and.be.ok; + + expect( data ).to.have.property( 'token' ).and.equal( accessToken ); + expect( data ).to.have.property( 'email' ).and.equal( profile._json.email ); + expect( data ).to.have.property( 'firstname' ).and.equal( 'Volodymyr' ); + expect( data ).to.have.property( 'lastname' ).and.equal( 'Denshchykov' ); + expect( data ).to.have.property( 'dropboxid' ).and.equal( profile._json.uid ); + expect( data ).to.have.property( 'link' ).and.equal( profile._json.referral_link ); + expect( data ).to.have.property( 'locale' ).and.equal( profile._json.country ); + + expect( data ).to.not.have.property( 'quota_info' ); + expect( data ).to.not.have.property( 'emails' ); + expect( data ).to.not.have.property( 'provider' ); + + done(); + } ); + + } ); + + describe( '.findOrCreate( profile, accessToken )', function () { + + it( 'should not call ORMUserDropboxModel.find() if dropbox account do not have email field', function ( done ) { + var accessToken = 'sdasdasdasdasdasdasdasda' + , profile = { + _json: { + referral_link: 'https://db.tt/FJ8lM2uy', + display_name: 'Volodymyr Denshchykov', + uid: 266578368, + country: 'UA', + quota_info: { datastores: 0, shared: 0, quota: 2147483648, normal: 107730 } + } + }; + + var spy = sinon.spy( Model, 'find' ); + + Service + .findOrCreate( profile, accessToken ) + .then( function ( result ) { + + expect( result ).to.not.be.ok; + + expect( spy.called ).to.be.false; + + spy.restore(); + + done(); + }, done ) + } ); + + it( 'should call ORMUserDropboxModel.find(), ORMUserDropboxModel.create() and create dbUser if dropbox account is verify and do not already exist', function ( done ) { + var accessToken = 'sdasdasdasdasdasdasdasda' + , profile = { + _json: { + referral_link: 'https://db.tt/FJ8lM2uy', + display_name: 'Volodymyr Denshchykov', + uid: 266578368, + country: 'UA', + quota_info: { datastores: 0, shared: 0, quota: 2147483648, normal: 107730 }, + email: 'denshikov_vovan@mail.ru' + } + }; + + var spyFind = sinon.spy( Model, 'find' ); + var spyCreate = sinon.spy( Model, 'create' ); + + Service + .findOrCreate( profile, accessToken ) + .delay( 1000 ) + .then( function ( result ) { + + expect( result ).to.be.an( 'object' ).and.be.ok; + expect( result ).to.have.property( 'id' ).and.be.ok; + + expect( spyFind.called ).to.be.true; + expect( spyFind.calledOnce ).to.be.true; + + expect( spyCreate.called ).to.be.true; + expect( spyCreate.calledOnce ).to.be.true; + + spyFind.restore(); + spyCreate.restore(); + + Model + .find( result.id ) + .success( function( user ) { + + expect( user ).to.be.an( 'object' ).and.be.ok; + expect( user ).to.have.property( 'id' ).and.equal( result.id ); + expect( user ).to.have.property( 'token' ).and.equal( accessToken ); + expect( user ).to.have.property( 'firstname' ).and.equal( 'Volodymyr' ); + expect( user ).to.have.property( 'lastname' ).and.equal( 'Denshchykov' ); + expect( user ).to.have.property( 'email' ).and.equal( profile._json.email ); + expect( user ).to.have.property( 'dropboxid' ).and.equal( profile._json.uid ); + expect( user ).to.have.property( 'locale' ).and.equal( profile._json.country ); + expect( user ).to.have.property( 'link' ).and.equal( profile._json.referral_link ); + expect( user ).to.have.property( 'accessedAt' ).and.be.ok; + + dbUserId_1 = user.id; + accessedAtDate = new Date( user.accessedAt ) + ''; + dbUser = user; + + done(); + }) + .error( done ); + }, done ) + } ); + + it( 'should call ORMUserDropboxModel.find(), ORMUserDropboxModel.updateAttributes(), not call ORMUserDropboxModel.create() and update already existing dbUser', function ( done ) { + var accessToken = '15151515151515151' + , profile = { + _json: { + referral_link: 'https://db.tt/FJ8lM2uy', + display_name: 'Volodymyr Denshchykov', + uid: 266578368, + country: 'UA', + quota_info: { datastores: 0, shared: 0, quota: 2147483648, normal: 107730 }, + email: 'denshikov_vovan@mail.ru' + } + }; + + var spyFind = sinon.spy( Model, 'find' ); + var spyCreate = sinon.spy( Model, 'create' ); + + Service + .findOrCreate( profile, accessToken ) + .then( function ( result ) { + + expect( result ).to.be.an( 'object' ).and.be.ok; + expect( result ).to.have.property( 'id' ).and.equal( dbUserId_1 ); + expect( result ).to.have.property( 'token' ).and.equal( accessToken ); + + var newAccessedAtDate = new Date( result.accessedAt ) + ''; + + expect( accessedAtDate ).to.not.equal( newAccessedAtDate ); + + expect( spyFind.called ).to.be.true; + expect( spyFind.calledOnce ).to.be.true; + + expect( spyCreate.called ).to.be.false; + + spyFind.restore(); + spyCreate.restore(); + + done(); + }, done ) + } ); + + } ); + + describe( '.authenticate( dbUser, profile )', function () { + + before( function( done ) { + var accessToken = '151216562162351626' + , profile = { + _json: { + referral_link: 'https://db.tt/FJ8lM2uy', + display_name: 'Volodymyr Denshchykov', + uid: 266578368, + country: 'UA', + quota_info: { datastores: 0, shared: 0, quota: 2147483648, normal: 107730 }, + email: 'denshikov_vovan_1@mail.ru' + } + }; + + try { + UserService = injector.getInstance( 'UserService' ); + } catch ( err ) { + console.log( err ); + } + + Service + .findOrCreate( profile, accessToken ) + .then( function( user ) { + + expect( user ).to.be.an( 'object' ).and.be.ok; + expect( user ).to.have.property( 'id' ).and.be.ok; + + dbUser = user; + + done(); + }, done ); + }); + + it( 'should return dbUser if UserService is not defined', function ( done ) { + var profile = { + _json: { + referral_link: 'https://db.tt/FJ8lM2uy', + display_name: 'Volodymyr Denshchykov', + uid: 266578368, + country: 'UA', + quota_info: { datastores: 0, shared: 0, quota: 2147483648, normal: 107730 }, + email: 'denshikov_vovan_1@mail.ru' + } + }; + + if ( !UserService ) { + Service + .authenticate( dbUser, profile ) + .then( function ( user ) { + + expect( !UserService ).to.be.true; + + expect( user ).to.be.an( 'object' ).and.be.ok; + expect( user ).to.have.property( 'id' ).and.equal( dbUser.id ); + expect( user ).to.have.property( 'token' ).and.be.ok; + expect( user ).to.have.property( 'firstname' ).and.equal( dbUser.firstname ); + expect( user ).to.have.property( 'lastname' ).and.equal( dbUser.lastname ); + expect( user ).to.have.property( 'email' ).and.equal( dbUser.email ); + expect( user ).to.have.property( 'dropboxid' ).and.equal( dbUser.dropboxid ); + expect( user ).to.have.property( 'accessedAt' ).and.be.ok; + + done(); + } ) + .fail( done ); + } else { + console.log( 'UserService is defined' ); + done(); + } + } ); + + it( 'should create new user and to associate it with qUser if UserService is defined', function ( done ) { + var profile = { + _json: { + referral_link: 'https://db.tt/FJ8lM2uy', + display_name: 'Volodymyr Denshchykov', + uid: 266578368, + country: 'UA', + quota_info: { datastores: 0, shared: 0, quota: 2147483648, normal: 107730 }, + email: 'denshikov_vovan_1@mail.ru' + } + }; + + if ( !!UserService ) { + + var spy = sinon.spy( UserService, 'create' ); + + Service + .authenticate( dbUser, profile ) + .then( function ( user ) { + + expect( !!UserService ).to.be.true; + + expect( user ).to.be.an( 'object' ).and.be.ok; + expect( user ).to.have.property( 'id' ).and.not.equal( dbUser.id ); + + expect( spy.called ).to.be.true; + expect( spy.calledOnce ).to.be.true; + + spy.restore(); + + UserService + .findById( user.id ) + .then( function( _user ) { + + expect( _user ).to.be.an( 'object' ).and.be.ok; + expect( _user ).to.have.property( 'id' ).and.equal( user.id ); + expect( _user ).to.have.property( 'email' ).and.equal( profile._json.email ); + expect( _user ).to.have.property( 'password' ).and.be.ok; + expect( _user ).to.have.property( 'firstname' ).and.equal( 'Volodymyr' ); + expect( _user ).to.have.property( 'lastname' ).and.equal( 'Denshchykov' ); + expect( _user ).to.have.property( 'confirmed' ).and.equal( true ); + expect( _user ).to.have.property( 'active' ).and.equal( true ); + + user_1 = _user; + + Service + .find( { where: { id: dbUser.id } } ) + .then( function( _dbUser ) { + + expect( _dbUser ).to.be.an( 'array' ).and.have.length( 1 ); + + _dbUser = _dbUser[0]; + + expect( _dbUser ).to.be.an( 'object' ).and.be.ok; + expect( _dbUser ).to.have.property( 'id' ).and.equal( dbUser.id ); + expect( _dbUser ).to.have.property( 'UserId' ).and.equal( user.id ); + + done(); + }, done ) + }, done ); + } ) + .fail( done ); + } else { + console.log( 'UserService is not defined' ); + done(); + } + + } ); + + it( 'should return existing user by dbUser.UserId if UserService is defined', function ( done ) { + var profile = { + _json: { + referral_link: 'https://db.tt/FJ8lM2uy', + display_name: 'Volodymyr Denshchykov', + uid: 266578368, + country: 'UA', + quota_info: { datastores: 0, shared: 0, quota: 2147483648, normal: 107730 }, + email: 'denshikov_vovan_1@mail.ru' + } + }; + + if ( !!UserService ) { + + var spy = sinon.spy( UserService, 'create' ); + + Service + .authenticate( dbUser, profile ) + .then( function ( user ) { + + expect( !!UserService ).to.be.true; + + expect( user ).to.be.an( 'object' ).and.be.ok; + expect( user ).to.have.property( 'id' ).and.equal( user_1.id ); + + expect( spy.called ).to.be.false; + + spy.restore(); + + done(); + } ) + .fail( done ); + } else { + console.log( 'UserService is not defined' ); + done(); + } + + } ); + + it( 'should return error if user with such dbUser.UserId do not exist and if UserService is defined' , function ( done ) { + var profile = { + _json: { + referral_link: 'https://db.tt/FJ8lM2uy', + display_name: 'Volodymyr Denshchykov', + uid: 266578368, + country: 'UA', + quota_info: { datastores: 0, shared: 0, quota: 2147483648, normal: 107730 }, + email: 'denshikov_vovan_1@mail.ru' + } + }; + + if ( !!UserService ) { + + var spy = sinon.spy( UserService, 'create' ); + + dbUser + .updateAttributes( { UserId: 1000 } ) + .success( function( result ) { + + expect( result ).to.be.an( 'object' ).and.be.ok; + expect( result ).to.have.property( 'UserId' ).and.equal( 1000 ); + + Service + .authenticate( dbUser, profile ) + .then( function ( user ) { + + expect( !!UserService ).to.be.true; + + expect( user ).to.be.an( 'object' ).and.be.ok; + expect( user ).to.have.property( 'statuscode' ).and.equal( 403 ); + expect( user ).to.have.property( 'message' ).and.be.ok; + + expect( spy.called ).to.be.false; + + spy.restore(); + + done(); + } ) + .fail( done ); + }) + .error( done ); + } else { + console.log( 'UserService is not defined' ); + done(); + } + + } ); + + it( 'should return existing user by dbUser.email if user with such email already exist', function ( done ) { + var profile = { + _json: { + referral_link: 'https://db.tt/FJ8lM2uy', + display_name: 'Volodymyr Denshchykov', + uid: 266578368, + country: 'UA', + quota_info: { datastores: 0, shared: 0, quota: 2147483648, normal: 107730 }, + email: 'denshikov_vovan_1@mail.ru' + } + }; + + if ( !!UserService ) { + + var spy = sinon.spy( UserService, 'create' ); + + dbUser + .updateAttributes( { UserId: null } ) + .success( function( result ) { + + expect( result ).to.be.an( 'object' ).and.be.ok; + expect( result ).to.have.property( 'UserId' ).and.equal( null ); + + Service + .authenticate( dbUser, profile ) + .then( function ( user ) { + + expect( !!UserService ).to.be.true; + + expect( user ).to.be.an( 'object' ).and.be.ok; + expect( user ).to.have.property( 'id' ).and.equal( user_1.id ); + + expect( spy.called ).to.be.false; + + spy.restore(); + + Service + .find( { where: { id: dbUser.id } } ) + .then( function( _dbUser ) { + + expect( _dbUser ).to.be.an( 'array' ).and.have.length( 1 ); + + _dbUser = _dbUser[0]; + + expect( _dbUser ).to.be.an( 'object' ).and.be.ok; + expect( _dbUser ).to.have.property( 'id' ).and.equal( dbUser.id ); + expect( _dbUser ).to.have.property( 'UserId' ).and.equal( user.id ); + + done(); + }, done ) + } ) + .fail( done ); + }) + .error( done ); + } else { + console.log( 'UserService is not defined' ); + done(); + } + + } ); + + } ); + + describe( '.updateAccessedDate( user )', function () { + + it( 'should be able to update user', function ( done ) { + + if ( !!UserService ) { + user_1 + .updateAttributes( { accessedAt: null } ) + .success( function( result ) { + + expect( result ).to.be.an( 'object' ).and.be.ok; + expect( result ).to.have.property( 'id' ).and.equal( user_1.id ); + expect( result ).to.have.property( 'accessedAt' ).and.not.be.ok; + + Service + .updateAccessedDate( user_1 ) + .then( function( result ) { + + expect( result ).to.be.an( 'object' ).and.be.ok; + expect( result ).to.have.property( 'id' ).and.equal( user_1.id ); + expect( result ).to.have.property( 'accessedAt' ).and.be.ok; + + done(); + }, done ); + }) + .error( done ); + + } else { + console.log( 'UserService is not defined' ); + done(); + } + + } ); + + it( 'should be able to update dbUser', function ( done ) { + + dbUser + .updateAttributes( { accessedAt: null } ) + .success( function( result ) { + + expect( result ).to.be.an( 'object' ).and.be.ok; + expect( result ).to.have.property( 'id' ).and.equal( dbUser.id ); + expect( result ).to.have.property( 'accessedAt' ).and.not.be.ok; + + Service + .updateAccessedDate( dbUser ) + .then( function( result ) { + + expect( result ).to.be.an( 'object' ).and.be.ok; + expect( result ).to.have.property( 'id' ).and.equal( dbUser.id ); + expect( result ).to.have.property( 'accessedAt' ).and.be.ok; + + done(); + }, done ); + }) + .error( done ); + } ); + + it( 'should be able to get object if it is not a user', function ( done ) { + + Service + .updateAccessedDate ( { statuscode: 403, message: 'invalid' } ) + .then ( function ( result ) { + + expect ( result ).to.be.an ( 'object' ).and.be.ok; + expect ( result ).to.have.property ( 'statuscode' ).and.equal ( 403 ); + expect ( result ).to.have.property ( 'message' ).and.be.ok; + + done (); + }, done ); + } ); + + } ); + + describe( '.listUsers()', function () { + + it( 'should be able to get list of all dbUsers', function ( done ) { + + Service + .listUsers() + .then( function ( users ) { + + expect( users ).to.be.an( 'array' ).and.have.length.above( 1 ); + expect( users[0] ).to.be.an( 'object' ).and.be.ok; + expect( users[0] ).to.have.property( 'id' ).and.be.ok; + expect( users[0] ).to.have.property( 'dropboxid' ).and.be.ok; + expect( users[0] ).to.have.property( 'email' ).and.be.ok; + expect( users[0] ).to.not.have.property( 'token' ); + + done(); + } ) + .fail( done ); + } ); + + } ); + + describe( '.findUserById()', function () { + + it( 'should be able to get dbUser by Id', function ( done ) { + + Service + .findUserById( dbUser.id ) + .then( function ( dbUser ) { + + expect( dbUser ).to.be.an( 'object' ).and.be.ok; + expect( dbUser ).to.have.property( 'id' ).and.equal( dbUser.id ); + expect( dbUser ).to.have.property( 'dropboxid' ).and.be.ok; + expect( dbUser ).to.have.property( 'email' ).and.be.ok; + expect( dbUser ).to.have.property( 'link' ).and.be.ok; + expect( dbUser ).to.not.have.property( 'token' ); + + done(); + } ) + .fail( done ); + } ); + + it( 'should be able to get the error if the dbUser does not exist', function ( done ) { + + Service + .findUserById( 151515115151515151 ) + .then( function( result ) { + + expect( result ).to.be.an( 'object' ); + expect( result ).to.have.property( 'statuscode' ).and.equal( 403 ); + expect( result ).to.have.property( 'message' ).and.be.ok; + + done(); + }, done ) + } ); + + } ); + + describe( '.deleteUser( dbUserId )', function () { + + it( 'should be able to get the error if the dbUser does not exist', function ( done ) { + + Service + .deleteUser( 151515115151515151 ) + .then( function( result ) { + + expect( result ).to.be.an( 'object' ); + expect( result ).to.have.property( 'statuscode' ).and.equal( 403 ); + expect( result ).to.have.property( 'message' ).and.be.ok; + + done(); + }, done ) + } ); + + it( 'should be able to delete dbUser', function ( done ) { + + Service + .deleteUser( dbUser.id ) + .then( function ( result ) { + + expect( result ).to.be.an( 'object' ); + expect( result ).to.have.property( 'statuscode' ).and.equal( 200 ); + expect( result ).to.have.property( 'message' ).and.be.ok; + + done(); + } ) + .fail( done ); + } ); + + } ); + +} ); \ No newline at end of file diff --git a/modules/clever-auth-linkedin/config/default.json b/modules/clever-auth-linkedin/config/default.json new file mode 100644 index 0000000..71d306b --- /dev/null +++ b/modules/clever-auth-linkedin/config/default.json @@ -0,0 +1,21 @@ +{ + "clever-auth-linkedin": { + "secretKey": "youMustChangeMe", + + "redis": { + "host": "localhost", + "port": "6379", + "prefix": "", + "key": "" + }, + + "linkedin": { + "AppKey": "77dnae99wih7t1", + "AppSecret": "Llr2J8zlPnrYWNSn", + "redirectURIs": "http://localhost:8080/auth/linkedin/return" + }, + + "frontendURL": "http://localhost:9000/" + + } +} \ No newline at end of file diff --git a/modules/clever-auth-linkedin/controllers/UserLinkedinController.js b/modules/clever-auth-linkedin/controllers/UserLinkedinController.js new file mode 100644 index 0000000..1fbc380 --- /dev/null +++ b/modules/clever-auth-linkedin/controllers/UserLinkedinController.js @@ -0,0 +1,125 @@ +var config = require ( 'config' )[ 'clever-auth-linkedin' ] + , passport = require ( 'passport' ) + , qs = require ( 'qs' ) + , LinkedInStrategy = require('passport-linkedin-oauth2').Strategy; + +var state = +new Date() + '' + , scope = [ 'r_emailaddress', 'r_basicprofile' ]; + +module.exports = function ( UserLinkedinService ) { + + passport.serializeUser( function ( user, done ) { + done( null, user ); + } ); + + passport.deserializeUser( function ( user, done ) { + done( null, user ) + } ); + + passport.use( new LinkedInStrategy( + { + clientID: config.linkedin.AppKey, + clientSecret: config.linkedin.AppSecret, + callbackURL: config.linkedin.redirectURIs, + state: state, + scope: scope + }, + function ( accessToken, refreshToken, profile, done ) { + + UserLinkedinService + .findOrCreate( profile, accessToken ) + .then( function( gUser ) { + return UserLinkedinService.authenticate ( gUser, profile ) + }) + .then( UserLinkedinService.updateAccessedDate ) + .then( done.bind( null, null ) ) + .fail( done ); + } + )); + + + return (require( 'classes' ).Controller).extend ( + { + service: UserLinkedinService + }, + { + listAction: function () { + UserLinkedinService.listUsers() + .then( this.proxy( 'handleServiceMessage' ) ) + .fail( this.proxy( 'handleException' ) ); + }, + + getAction: function () { + var guId = this.req.params.id; + + UserLinkedinService + .findUserById( guId ) + .then( this.proxy( 'handleServiceMessage' ) ) + .fail( this.proxy( 'handleException' ) ); + }, + + deleteAction: function () { + var guId = this.req.params.id; + + UserLinkedinService.deleteUser( guId ) + .then( this.proxy( 'handleServiceMessage' ) ) + .fail( this.proxy( 'handleException' ) ); + }, + + loginAction: function () { + var params = { + response_type: "code", + client_id: config.linkedin.AppKey, + redirect_uri: config.linkedin.redirectURIs, + state: state, + scope: scope + }; + + this.send( { url: 'https://www.linkedin.com/uas/oauth2/authorization?' + qs.stringify( params ) }, 200 ); + + }, + + returnAction: function () { + passport.authenticate( 'linkedin', this.proxy( 'handleLocalUser' ) )( this.req, this.res, this.next ); + }, + + handleLocalUser: function ( err, user ) { + + if ( err ) return this.handleException( err ); + + if ( !user ) { + this.res.statusCode = 302; + this.res.setHeader( 'body', {} ); + this.res.setHeader( 'Location', config.frontendURL ); + this.res.end(); + } else { + this.loginUserJson( user ); + } + }, + + loginUserJson: function ( user ) { + this.req.login( user, this.proxy( 'handleLoginJson', user ) ); + }, + + handleLoginJson: function ( user, err ) { + console.log(user) + if ( err ) return this.handleException( err ); + + this.res.statusCode = 302; + this.res.setHeader( 'body', user ); + this.res.setHeader( 'Location', config.frontendURL ); + this.res.end(); + }, + + handleServiceMessage: function ( obj ) { + + if ( obj.statuscode ) { + this.send( obj.message, obj.statuscode ); + return; + } + + this.send( obj, 200 ); + } + + } ); +}; \ No newline at end of file diff --git a/modules/clever-auth-linkedin/models/orm/UserLinkedinModel.js b/modules/clever-auth-linkedin/models/orm/UserLinkedinModel.js new file mode 100644 index 0000000..b5a7ee1 --- /dev/null +++ b/modules/clever-auth-linkedin/models/orm/UserLinkedinModel.js @@ -0,0 +1,70 @@ +module.exports = function ( sequelize, DataTypes ) { + return sequelize.define( "UserLinkedin", + { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true + }, + email: { + type: DataTypes.STRING, + allowNull: false, + unique: true, + validate: { + isEmail: true + } + }, + firstname: { + type: DataTypes.STRING, + allowNull: true + }, + lastname: { + type: DataTypes.STRING, + allowNull: true + }, + linkedinid: { + type: DataTypes.STRING, + allowNull: false + }, + picture: { + type: DataTypes.STRING, + allowNull: true + }, + link: { + type: DataTypes.STRING, + allowNull: true + }, + locale: { + type: DataTypes.STRING, + allowNull: true + }, + token: { + type: DataTypes.STRING, + allowNull: false + }, + accessedAt: { + type: DataTypes.DATE + }, + UserId: { + type: DataTypes.INTEGER + } + }, + { + paranoid: true, + + getterMethods: { + fullName: function () { + return [ this.getDataValue( 'firstname' ), this.getDataValue( 'lastname' )].join( ' ' ); + } + }, + + instanceMethods: { + toJSON: function () { + var values = this.values; + values.fullName = this.fullName; + delete values.token; + return values; + } + } + } ); +}; \ No newline at end of file diff --git a/modules/clever-auth-linkedin/module.js b/modules/clever-auth-linkedin/module.js new file mode 100644 index 0000000..ef72603 --- /dev/null +++ b/modules/clever-auth-linkedin/module.js @@ -0,0 +1,129 @@ +var passport = require( 'passport' ) + , debug = require( 'debug' )( 'CleverAuthLinkedinModule' ) + , fs = require( 'fs' ) + , path = require( 'path' ) + , ModuleClass = require( 'classes' ).ModuleClass + , RedisStore = require( 'connect-redis' )( injector.getInstance( 'express' ) ) + , Module; + +Module = ModuleClass.extend( { + models: null, + store: null, + sequelize: null, + + preSetup: function () { + var self = this; + this.models = {}; + this.sequelize = injector.getInstance ( 'sequelize' ); + var dir = fs.readdirSync ( path.join ( __dirname, 'models', 'orm' ) ); + dir.forEach ( function ( model ) { + self.getModel ( path.join ( __dirname, 'models', 'orm', model ) ); + } ); + }, + + preResources: function () { + injector.instance ( 'passport', passport ); + }, + + modulesLoaded: function () { + this.defineModelsAssociations (); + }, + + defineModelsAssociations: function () { + if ( !this.config.hasOwnProperty ( 'modelAssociations' ) ) { + return true; + } + + debug ( 'Defining model assocations' ); + + Object.keys ( this.config.modelAssociations ).forEach ( this.proxy ( function ( modelName ) { + Object.keys ( this.config.modelAssociations[ modelName ] ).forEach ( this.proxy ( 'defineModelAssociations', modelName ) ); + } ) ); + }, + + defineModelAssociations: function ( modelName, assocType ) { + var associatedWith = this.config.modelAssociations[ modelName ][ assocType ]; + if ( !associatedWith instanceof Array ) { + associatedWith = [ associatedWith ]; + } + + associatedWith.forEach ( this.proxy ( 'associateModels', modelName, assocType ) ); + }, + + associateModels: function ( modelName, assocType, assocTo ) { + // Support second argument + if ( assocTo instanceof Array ) { + debug ( '%s %s %s with second argument of ', modelName, assocType, assocTo[0], assocTo[1] ); + this.models[ modelName ][ assocType ] ( this.models[ assocTo[0] ], assocTo[1] ); + } else { + debug ( '%s %s %s', modelName, assocType, assocTo ); + this.models[ modelName ][ assocType ] ( this.models[assocTo] ); + } + }, + + getModel: function ( modelPath ) { + var modelName = modelPath.split ( '/' ).pop ().split ( '.' ).shift (); + + if ( typeof this.models[ modelName ] === 'undefined' ) { + debug ( [ 'Loading model', modelName, 'from', modelPath ].join ( ' ' ) ); + + // Call on sequelizejs to load the model + this.models[ modelName ] = this.sequelize.import ( modelPath ); + + // Set a flat for tracking + this.models[ modelName ].ORM = true; + + // Add the model to the injector + injector.instance ( 'ORM' + modelName, this.models[ modelName ] ); + } + + return this.models[ modelName ]; + }, + + configureApp: function ( app, express ) { +// Setup the redis store + this.store = new RedisStore ( { + host: this.config.redis.host, + port: this.config.redis.port, + prefix: this.config.redis.prefix + process.env.NODE_ENV + "_", + password: this.config.redis.key + } ); + +// Session management + app.use ( express.cookieParser () ); + app.use ( express.session ( { + secret: this.config.secretKey, + cookie: { secure: false, maxAge: 86400000 }, + store: this.store + } ) ); + + // Enable CORS + app.use ( this.proxy ( 'enableCors' ) ); + + // Initialize passport + app.use ( passport.initialize () ); + app.use ( passport.session () ); + }, + + enableCors: function ( req, res, next ) { + + res.header ( 'Access-Control-Allow-Origin', req.headers.origin ); + res.header ( 'Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE' ); + res.header ( 'Access-Control-Allow-Headers', 'Content-Type, Authorization, x-requested-with' ); + res.header( "Access-Control-Allow-Credentials", "true" ); + res.header( "Access-Control-Max-Age", "1000000000" ); + // intercept OPTIONS method + if ( 'OPTIONS' == req.method ) { + res.send ( 200 ); + } + else { + next (); + } + }, + + preShutdown: function () { + this.store.client.quit (); + } +} ); + +module.exports = new Module( 'clever-auth-linkedin', injector ); \ No newline at end of file diff --git a/modules/clever-auth-linkedin/package.json b/modules/clever-auth-linkedin/package.json new file mode 100644 index 0000000..432967e --- /dev/null +++ b/modules/clever-auth-linkedin/package.json @@ -0,0 +1,32 @@ +{ + "name": "clever-auth-linkedin", + "version": "0.0.1", + "private": true, + "dependencies": { + "passport": "~0.1.18", + "connect-redis": "~1.4.6", + "q": "", + "sequelize": "1.7.0-beta8", + "moment": "", + "passport-linkedin-oauth2": "", + "qs": "*" + }, + "author": { + "name": "Clevertech", + "email": "info@clevertech.biz", + "web": "http://www.clevertech.biz" + }, + "collaborators": [ + "" + ], + "description": "", + "keywords": [ + "" + ], + "main": "module.js", + "devDependencies": { + "chai": "*", + "mocha": "*", + "sinon": "" + } +} \ No newline at end of file diff --git a/modules/clever-auth-linkedin/routes.js b/modules/clever-auth-linkedin/routes.js new file mode 100644 index 0000000..61079ab --- /dev/null +++ b/modules/clever-auth-linkedin/routes.js @@ -0,0 +1,9 @@ +module.exports = function ( + app, + UserLinkedinController ) +{ + + app.all('/auth/linkedin/?:action?', UserLinkedinController.attach() ); + +}; + diff --git a/modules/clever-auth-linkedin/services/UserLinkedinService.js b/modules/clever-auth-linkedin/services/UserLinkedinService.js new file mode 100644 index 0000000..c7eb1f6 --- /dev/null +++ b/modules/clever-auth-linkedin/services/UserLinkedinService.js @@ -0,0 +1,230 @@ +var Q = require( 'q' ) + , crypto = require( 'crypto' ) + , moment = require( 'moment' ) + , Sequelize = require( 'sequelize' ) + , UserLinkedinService = null; + +module.exports = function ( sequelize, + ORMUserLinkedinModel ) { + + if ( UserLinkedinService && UserLinkedinService.instance ) { + return UserLinkedinService.instance; + } + + var UserService = null; + + UserLinkedinService = require( 'services' ).BaseService.extend( { + + formatData: function ( profile, accessToken ) { + return { + linkedinid: profile._json.id, + email: profile._json.emailAddress, + firstname: profile._json.firstName, + lastname: profile._json.lastName, + link: profile._json.publicProfileUrl, + picture: profile._json.pictureUrl || null, + locale: !!profile._json.location && typeof profile._json.location == 'object' + ? profile._json.location.name + : null, + token: accessToken || null + } + }, + + findOrCreate: function ( profile, accessToken ) { + var deferred = Q.defer() + , data = this.formatData ( profile, accessToken ); + + if ( !!data.email ) { + + ORMUserLinkedinModel + .find( { where: { email: data.email } } ) + .success( function ( dbUser ) { + + if ( !dbUser ) { + + data.accessedAt = moment.utc().format( 'YYYY-MM-DD HH:ss:mm' ); + + ORMUserLinkedinModel + .create( data ) + .success( deferred.resolve ) + .error( deferred.reject ); + + } else { + + dbUser + .updateAttributes( { accessedAt: moment.utc().format( 'YYYY-MM-DD HH:ss:mm' ), token: data.token } ) + .success( deferred.resolve ) + .error( deferred.reject ); + } + } ) + .error( deferred.reject ); + } else { + deferred.resolve(); + } + + return deferred.promise; + }, + + authenticate: function( dbUser, profile ) { + var deferred = Q.defer() + , data = this.formatData ( profile ); + + try { + UserService = injector.getInstance( 'UserService' ); + } catch ( err ) { + console.log( err ); + } + + if ( !!dbUser && !!UserService ) { + + if ( dbUser.UserId ) { + + UserService + .find( { where: { id: dbUser.UserId, email: dbUser.email } } ) + .then( function( users ) { + + var user = !!users && !!users.length + ? users[0] + : { statuscode: 403, message: 'invalid' }; + + deferred.resolve( user ); + }) + .fail( deferred.reject ); + + } else { + + UserService + .find( { where: { email: dbUser.email } } ) + .then( function( user ) { + + user = user[0]; + + if ( !!user && !!user.id ) { + + dbUser + .updateAttributes( { UserId: user.id } ) + .success ( function () { + deferred.resolve ( user ); + } ) + .error( deferred.reject ); + + } else { + + data.confirmed = true; + data.username = data.email; + data.password = crypto.createHash( 'sha1' ).update( Math.floor(Math.random() * 1e18) + '' ).digest( 'hex' ) + + UserService + .create( data ) + .then( function( user ) { + + dbUser + .updateAttributes ( { UserId: user.id } ) + .success ( function () { + deferred.resolve ( user ); + } ) + .error ( deferred.reject ); + }) + .fail( deferred.reject ); + } + }) + .fail( deferred.reject ); + } + + } else { + deferred.resolve( dbUser ); + } + + return deferred.promise; + }, + + updateAccessedDate: function( user ){ + var deferred = Q.defer(); + + if ( !!user && !!user.id ) { + user + .updateAttributes( { accessedAt: moment.utc().format( 'YYYY-MM-DD HH:ss:mm' ) } ) + .success( function() { + deferred.resolve( user.toJSON() ); + }) + .error( deferred.reject ); + } else { + deferred.resolve( user ); + } + + return deferred.promise; + }, + + listUsers: function() { + var deferred = Q.defer(); + + ORMUserLinkedinModel + .findAll( { where: { deletedAt: null } } ) + .success( function( dbUsers ) { + if ( !!dbUsers && !!dbUsers.length ) { + deferred.resolve( dbUsers.map( function( u ) { return u.toJSON(); } ) ); + } else { + deferred.resolve( {} ); + } + }) + .error( deferred.reject ); + + return deferred.promise; + }, + + findUserById: function( dbUserId ) { + var deferred = Q.defer(); + + ORMUserLinkedinModel + .find( dbUserId ) + .success( function( dbUser ) { + + if ( !!dbUser && !!dbUser.id ){ + deferred.resolve( dbUser.toJSON() ); + } else { + deferred.resolve( { statuscode: 403, message: 'user do not exist' } ); + } + }) + .error( deferred.reject ); + + return deferred.promise; + }, + + deleteUser: function( dbUserId ) { + var deferred = Q.defer(); + + ORMUserLinkedinModel + .find( dbUserId ) + .success( function( dbUser ) { + + if ( !!dbUser && !!dbUser.id ) { + + dbUser + .destroy() + .success( function( result ) { + + if ( !result.deletedAt ) { + deferred.resolve( { statuscode: 500, message: 'error' } ); + } else { + deferred.resolve( { statuscode: 200, message: 'user is deleted' } ); + } + + }) + .error( deferred.reject ); + + } else { + deferred.resolve( { statuscode: 403, message: 'user do not exist' } ) + } + }) + .error( deferred.reject ); + + return deferred.promise; + } + + } ); + + UserLinkedinService.instance = new UserLinkedinService( sequelize ); + UserLinkedinService.Model = ORMUserLinkedinModel; + + return UserLinkedinService.instance; +}; \ No newline at end of file diff --git a/modules/clever-auth-linkedin/tests/unit/test.controllers.UserDropboxController.js b/modules/clever-auth-linkedin/tests/unit/test.controllers.UserDropboxController.js new file mode 100644 index 0000000..18c325b --- /dev/null +++ b/modules/clever-auth-linkedin/tests/unit/test.controllers.UserDropboxController.js @@ -0,0 +1,215 @@ +// Bootstrap the testing environmen +var testEnv = require( 'utils' ).testEnv(); + +var expect = require( 'chai' ).expect + , Q = require ( 'q' ) + , sinon = require( 'sinon' ) + , Service; + +describe( 'controllers.UserLinkedinController', function () { + var Service, UserLinkedinController, ctrl, lnUsers = []; + + before( function ( done ) { + testEnv( function ( _UserLinkedinService_, _UserLinkedinController_ ) { + var req = { + params: { action: 'fakeAction'}, + method: 'GET', + query: {} + }; + + var res = { + json: function () {} + }; + + var next = function () {}; + + Controller = _UserLinkedinController_; + Service = _UserLinkedinService_; + ctrl = new Controller( req, res, next ); + + var profile_1 = { + _json: { + emailAddress: 'denshikov_vovan_link_2@mail.ru', + firstName: 'Volodymyr', + id: 'sdjsadj23', + lastName: 'Denshchykov', + location: { country: { code: "ua"}, name: 'Ukraine' }, + pictureUrl: 'http://m.c.lnkd.licdn.com/mpr/mprx/0_5R4EHm1kXoIbuS7QI0IzHDhF6uZnaSOQFV2zHDrnJWubAdd6djHQQSkZHG4eSE0Ek4VNFEmO_o2w', + publicProfileUrl: 'http://www.linkedin.com/pub/%D0%B2%D0%BB%D0%B0%D0%B4%D0%B8%D0%BC%D0%B8%D1%80-%D0%B4%D0%B5%D0%BD%D1%89%D0%B8%D0%BA%D0%BE%D0%B2/8b/725/619' + } + } + , profile_2 = { + _json: { + emailAddress: 'denshikov_vovan_link_3@mail.ru', + firstName: 'Volodymyr', + id: 'sdjsadj23', + lastName: 'Denshchykov', + location: { country: { code: "ua"}, name: 'Ukraine' }, + pictureUrl: 'http://m.c.lnkd.licdn.com/mpr/mprx/0_5R4EHm1kXoIbuS7QI0IzHDhF6uZnaSOQFV2zHDrnJWubAdd6djHQQSkZHG4eSE0Ek4VNFEmO_o2w', + publicProfileUrl: 'http://www.linkedin.com/pub/%D0%B2%D0%BB%D0%B0%D0%B4%D0%B8%D0%BC%D0%B8%D1%80-%D0%B4%D0%B5%D0%BD%D1%89%D0%B8%D0%BA%D0%BE%D0%B2/8b/725/619' + } + }; + + Service + .findOrCreate( profile_1, 'bhbhgvbljbhscvzkchvblzclvnzbkjxcv' ) + .then( function( lnUser ) { + + expect( lnUser ).to.be.an( 'object' ).and.be.ok; + expect( lnUser ).to.have.property( 'id' ).and.be.ok; + + lnUsers.push( lnUser ) + + Service + .findOrCreate( profile_2, 'kjajkvl zsdvbakhvckhabskhv' ) + .then( function( lnUser ) { + + expect( lnUser ).to.be.an( 'object' ).and.be.ok; + expect( lnUser ).to.have.property( 'id' ).and.be.ok; + + lnUsers.push( lnUser ) + + done(); + }, done ); + }, done ); + } ); + } ); + + afterEach( function ( done ) { + + ctrl.req = { + params: { action: 'fakeAction'}, + method: 'GET', + query: {}, + body: {} + }; + + ctrl.res = { + json: function () {} + }; + + done(); + }); + + describe( 'loginAction()', function () { + + it( 'should call this.send() with specify parameters', function ( done ) { + + ctrl.send = function ( result, status ) { + + expect( status ).to.equal( 200 ); + + expect( result ).to.be.an( 'object' ).and.be.ok; + expect( result ).to.have.property( 'url' ).and.contain( 'https://www.linkedin.com/uas/oauth2/authorization?' ); + + done(); + }; + + ctrl.loginAction(); + } ); + + } ); + + describe( 'listAction()', function () { + + it( 'should be able to get list of users', function ( done ) { + + ctrl.send = function ( result, status ) { + + expect( status ).to.equal( 200 ); + + expect( result ).to.be.an( 'array' ).and.have.length.above( 1 ); + expect( result[0] ).to.be.an( 'object' ).and.be.ok; + expect( result[0] ).to.have.property( 'id' ).and.be.ok; + expect( result[0] ).to.have.property( 'firstname' ).and.be.ok; + expect( result[0] ).to.have.property( 'lastname' ).and.be.ok; + expect( result[0] ).to.have.property( 'email' ).and.be.ok; + expect( result[0] ).to.have.property( 'linkedinid' ).and.be.ok; + expect( result[0] ).to.not.have.property( 'token' ); + + done(); + }; + + ctrl.listAction(); + } ); + + } ); + + describe( 'getAction()', function () { + + it( 'should be able to get user by id', function ( done ) { + + ctrl.send = function ( result, status ) { + + expect( status ).to.equal( 200 ); + + expect( result ).to.be.an( 'object' ).and.be.ok; + expect( result ).to.have.property( 'id' ).and.be.ok; + expect( result ).to.have.property( 'firstname' ).and.be.ok; + expect( result ).to.have.property( 'lastname' ).and.be.ok; + expect( result ).to.have.property( 'email' ).and.be.ok; + expect( result ).to.have.property( 'linkedinid' ).and.be.ok; + expect( result ).to.not.have.property( 'token' ); + + done(); + }; + + ctrl.req.params = { id: lnUsers[0].id }; + + ctrl.getAction(); + } ); + + it( 'should be able to get error if user do not exist', function ( done ) { + + ctrl.send = function ( result, status ) { + + expect( status ).to.equal( 403 ); + + expect( result ).to.be.an( 'string' ).and.be.ok; + + done(); + }; + + ctrl.req.params = { id: 15151515151151515 }; + + ctrl.getAction(); + } ); + + } ); + + describe( 'deleteAction()', function () { + + it( 'should be able to delete user by id', function ( done ) { + + ctrl.send = function ( result, status ) { + + expect( status ).to.equal( 200 ); + + expect( result ).to.be.an( 'string' ).and.be.ok; + + done(); + }; + + ctrl.req.params = { id: lnUsers[0].id }; + + ctrl.deleteAction(); + } ); + + it( 'should be able to get error if user do not exist', function ( done ) { + + ctrl.send = function ( result, status ) { + + expect( status ).to.equal( 403 ); + + expect( result ).to.be.an( 'string' ).and.be.ok; + + done(); + }; + + ctrl.req.params = { id: 15151515151151515 }; + + ctrl.deleteAction(); + } ); + + } ); + +} ); \ No newline at end of file diff --git a/modules/clever-auth-linkedin/tests/unit/test.service.UserDropboxService.js b/modules/clever-auth-linkedin/tests/unit/test.service.UserDropboxService.js new file mode 100644 index 0000000..b65ebc8 --- /dev/null +++ b/modules/clever-auth-linkedin/tests/unit/test.service.UserDropboxService.js @@ -0,0 +1,681 @@ +var expect = require ( 'chai' ).expect + , request = require ( 'supertest' ) + , path = require( 'path' ) + , app = require ( path.resolve( __dirname + '/../../../../' ) + '/index.js' ) + , config = require( 'config' ) + , testEnv = require ( 'utils' ).testEnv() + , sinon = require( 'sinon' ) + , Q = require ( 'q' ); + +var UserService = null; + +var lnUserId_1, accessedAtDate, lnUser, user_1; + +describe( 'service.UserLinkedinService', function () { + var Service, Model; + + before( function ( done ) { + this.timeout( 15000 ); + testEnv( function ( _UserLinkedinService_, _ORMUserLinkedinModel_ ) { + + Service = _UserLinkedinService_; + Model = _ORMUserLinkedinModel_; + + done(); + }, done ); + } ); + + describe( '.formatData( profile, accessToken )', function () { + + it( 'should return an object with filtered data', function ( done ) { + var accessToken = 'sdasdasdasdasdasdasdasda' + , profile = { provider: 'linkedin', + id: 'sdjsadj23', + displayName: 'Volodymyr Denshchykov', + name: { familyName: 'Denshchykov', givenName: 'Volodymyr' }, + emails: [ { value: 'denshikov_vovan_link@mail.ru' } ], + _raw: '{"publicProfileUrl": "http://www.linkedin.com/pub/%D0%B2%D0%BB%D0%B0%D0%B4%D0%B8%D0%BC%D0%B8%D1%80-%D0%B4%D0%B5%D0%BD%D1%89%D0%B8%D0%BA%D0%BE%D0%B2/8b/725/619",\n "relationToViewer": {"distance": 0}', + _json: { + apiStandardProfileRequest: { + url: 'http://api.linkedin.com/v1/people/j45jKBkW74' + }, + distance: 0, + emailAddress: 'denshikov_vovan_link@mail.ru', + firstName: 'Volodymyr', + formattedName: 'Volodymyr Denshchykov', + headline: 'developer (CleverTech)', + id: 'sdjsadj23', + industry: 'Information Technology and Services', + lastName: 'Denshchykov', + location: { country: { code: "ua"}, name: 'Ukraine' }, + numConnections: 0, + numConnectionsCapped: false, + pictureUrl: 'http://m.c.lnkd.licdn.com/mpr/mprx/0_5R4EHm1kXoIbuS7QI0IzHDhF6uZnaSOQFV2zHDrnJWubAdd6djHQQSkZHG4eSE0Ek4VNFEmO_o2w', + positions: { _total: 1, values: [Object] }, + publicProfileUrl: 'http://www.linkedin.com/pub/%D0%B2%D0%BB%D0%B0%D0%B4%D0%B8%D0%BC%D0%B8%D1%80-%D0%B4%D0%B5%D0%BD%D1%89%D0%B8%D0%BA%D0%BE%D0%B2/8b/725/619', + relationToViewer: { distance: 0 }, + siteStandardProfileRequest: { + url: 'http://www.linkedin.com/profile/view?id=321293109&authType=name&authToken=W58U&trk=api*a3174153*s3248303*' + } + } + }; + + var data = Service.formatData( profile, accessToken ); + + expect( data ).to.be.an( 'object' ).and.be.ok; + + expect( data ).to.have.property( 'token' ).and.equal( accessToken ); + expect( data ).to.have.property( 'email' ).and.equal( profile._json.emailAddress ); + expect( data ).to.have.property( 'firstname' ).and.equal( profile._json.firstName ); + expect( data ).to.have.property( 'lastname' ).and.equal( profile._json.lastName ); + expect( data ).to.have.property( 'linkedinid' ).and.equal( profile._json.id ); + expect( data ).to.have.property( 'picture' ).and.equal( profile._json.pictureUrl ); + expect( data ).to.have.property( 'link' ).and.equal( profile._json.publicProfileUrl ); + expect( data ).to.have.property( 'locale' ).and.equal( profile._json.location.name ); + + expect( data ).to.not.have.property( 'relationToViewer' ); + expect( data ).to.not.have.property( 'positions' ); + expect( data ).to.not.have.property( 'provider' ); + + done(); + } ); + + } ); + + describe( '.findOrCreate( profile, accessToken )', function () { + + it( 'should not call ORMUserLinkedinModel.find() if linkedin account do not have email field', function ( done ) { + var accessToken = 'sdasdasdasdasdasdasdasda' + , profile = { + _json: { + firstName: 'Volodymyr', + id: 'sdjsadj23', + lastName: 'Denshchykov', + location: { country: { code: "ua"}, name: 'Ukraine' }, + pictureUrl: 'http://m.c.lnkd.licdn.com/mpr/mprx/0_5R4EHm1kXoIbuS7QI0IzHDhF6uZnaSOQFV2zHDrnJWubAdd6djHQQSkZHG4eSE0Ek4VNFEmO_o2w', + publicProfileUrl: 'http://www.linkedin.com/pub/%D0%B2%D0%BB%D0%B0%D0%B4%D0%B8%D0%BC%D0%B8%D1%80-%D0%B4%D0%B5%D0%BD%D1%89%D0%B8%D0%BA%D0%BE%D0%B2/8b/725/619' + } + }; + + var spy = sinon.spy( Model, 'find' ); + + Service + .findOrCreate( profile, accessToken ) + .then( function ( result ) { + + expect( result ).to.not.be.ok; + + expect( spy.called ).to.be.false; + + spy.restore(); + + done(); + }, done ) + } ); + + it( 'should call ORMUserLinkedinModel.find(), ORMUserLinkedinModel.create() and create lnUser if linkedin account is verify and do not already exist', function ( done ) { + var accessToken = 'sdasdasdasdasdasdasdasda' + , profile = { + _json: { + emailAddress: 'denshikov_vovan_link@mail.ru', + firstName: 'Volodymyr', + id: 'sdjsadj23', + lastName: 'Denshchykov', + location: { country: { code: "ua"}, name: 'Ukraine' }, + pictureUrl: 'http://m.c.lnkd.licdn.com/mpr/mprx/0_5R4EHm1kXoIbuS7QI0IzHDhF6uZnaSOQFV2zHDrnJWubAdd6djHQQSkZHG4eSE0Ek4VNFEmO_o2w', + publicProfileUrl: 'http://www.linkedin.com/pub/%D0%B2%D0%BB%D0%B0%D0%B4%D0%B8%D0%BC%D0%B8%D1%80-%D0%B4%D0%B5%D0%BD%D1%89%D0%B8%D0%BA%D0%BE%D0%B2/8b/725/619' + } + }; + + var spyFind = sinon.spy( Model, 'find' ); + var spyCreate = sinon.spy( Model, 'create' ); + + Service + .findOrCreate( profile, accessToken ) + .delay( 1000 ) + .then( function ( result ) { + + expect( result ).to.be.an( 'object' ).and.be.ok; + expect( result ).to.have.property( 'id' ).and.be.ok; + + expect( spyFind.called ).to.be.true; + expect( spyFind.calledOnce ).to.be.true; + + expect( spyCreate.called ).to.be.true; + expect( spyCreate.calledOnce ).to.be.true; + + spyFind.restore(); + spyCreate.restore(); + + Model + .find( result.id ) + .success( function( user ) { + + expect( user ).to.be.an( 'object' ).and.be.ok; + expect( user ).to.have.property( 'id' ).and.equal( result.id ); + expect( user ).to.have.property( 'token' ).and.equal( accessToken ); + expect( user ).to.have.property( 'firstname' ).and.equal( profile._json.firstName ); + expect( user ).to.have.property( 'lastname' ).and.equal( profile._json.lastName ); + expect( user ).to.have.property( 'email' ).and.equal( profile._json.emailAddress ); + expect( user ).to.have.property( 'linkedinid' ).and.equal( profile._json.id ); + expect( user ).to.have.property( 'locale' ).and.equal( profile._json.location.name ); + expect( user ).to.have.property( 'picture' ).and.equal( profile._json.pictureUrl ); + expect( user ).to.have.property( 'link' ).and.equal( profile._json.publicProfileUrl ); + expect( user ).to.have.property( 'accessedAt' ).and.be.ok; + + lnUserId_1 = user.id; + accessedAtDate = new Date( user.accessedAt ) + ''; + lnUser = user; + + done(); + }) + .error( done ); + }, done ) + } ); + + it( 'should call ORMUserLinkedinModel.find(), ORMUserLinkedinModel.updateAttributes(), not call ORMUserLinkedinModel.create() and update already existing lnUser', function ( done ) { + var accessToken = '15151515151515151' + , profile = { + _json: { + emailAddress: 'denshikov_vovan_link@mail.ru', + firstName: 'Volodymyr', + id: 'sdjsadj23', + lastName: 'Denshchykov', + location: { country: { code: "ua"}, name: 'Ukraine' }, + pictureUrl: 'http://m.c.lnkd.licdn.com/mpr/mprx/0_5R4EHm1kXoIbuS7QI0IzHDhF6uZnaSOQFV2zHDrnJWubAdd6djHQQSkZHG4eSE0Ek4VNFEmO_o2w', + publicProfileUrl: 'http://www.linkedin.com/pub/%D0%B2%D0%BB%D0%B0%D0%B4%D0%B8%D0%BC%D0%B8%D1%80-%D0%B4%D0%B5%D0%BD%D1%89%D0%B8%D0%BA%D0%BE%D0%B2/8b/725/619' + } + }; + + var spyFind = sinon.spy( Model, 'find' ); + var spyCreate = sinon.spy( Model, 'create' ); + + Service + .findOrCreate( profile, accessToken ) + .then( function ( result ) { + + expect( result ).to.be.an( 'object' ).and.be.ok; + expect( result ).to.have.property( 'id' ).and.equal( lnUserId_1 ); + expect( result ).to.have.property( 'token' ).and.equal( accessToken ); + + var newAccessedAtDate = new Date( result.accessedAt ) + ''; + + expect( accessedAtDate ).to.not.equal( newAccessedAtDate ); + + expect( spyFind.called ).to.be.true; + expect( spyFind.calledOnce ).to.be.true; + + expect( spyCreate.called ).to.be.false; + + spyFind.restore(); + spyCreate.restore(); + + done(); + }, done ) + } ); + + } ); + + describe( '.authenticate( lnUser, profile )', function () { + + before( function( done ) { + var accessToken = '151216562162351626' + , profile = { + _json: { + emailAddress: 'denshikov_vovan_link_1@mail.ru', + firstName: 'Volodymyr', + id: 'sdjsadj23', + lastName: 'Denshchykov', + location: { country: { code: "ua"}, name: 'Ukraine' }, + pictureUrl: 'http://m.c.lnkd.licdn.com/mpr/mprx/0_5R4EHm1kXoIbuS7QI0IzHDhF6uZnaSOQFV2zHDrnJWubAdd6djHQQSkZHG4eSE0Ek4VNFEmO_o2w', + publicProfileUrl: 'http://www.linkedin.com/pub/%D0%B2%D0%BB%D0%B0%D0%B4%D0%B8%D0%BC%D0%B8%D1%80-%D0%B4%D0%B5%D0%BD%D1%89%D0%B8%D0%BA%D0%BE%D0%B2/8b/725/619' + } + }; + + try { + UserService = injector.getInstance( 'UserService' ); + } catch ( err ) { + console.log( err ); + } + + Service + .findOrCreate( profile, accessToken ) + .then( function( user ) { + + expect( user ).to.be.an( 'object' ).and.be.ok; + expect( user ).to.have.property( 'id' ).and.be.ok; + + lnUser = user; + + done(); + }, done ); + }); + + it( 'should return lnUser if UserService is not defined', function ( done ) { + var profile = { + _json: { + emailAddress: 'denshikov_vovan_link_1@mail.ru', + firstName: 'Volodymyr', + id: 'sdjsadj23', + lastName: 'Denshchykov', + location: { country: { code: "ua"}, name: 'Ukraine' }, + pictureUrl: 'http://m.c.lnkd.licdn.com/mpr/mprx/0_5R4EHm1kXoIbuS7QI0IzHDhF6uZnaSOQFV2zHDrnJWubAdd6djHQQSkZHG4eSE0Ek4VNFEmO_o2w', + publicProfileUrl: 'http://www.linkedin.com/pub/%D0%B2%D0%BB%D0%B0%D0%B4%D0%B8%D0%BC%D0%B8%D1%80-%D0%B4%D0%B5%D0%BD%D1%89%D0%B8%D0%BA%D0%BE%D0%B2/8b/725/619' + } + }; + + if ( !UserService ) { + Service + .authenticate( lnUser, profile ) + .then( function ( user ) { + + expect( !UserService ).to.be.true; + + expect( user ).to.be.an( 'object' ).and.be.ok; + expect( user ).to.have.property( 'id' ).and.equal( lnUser.id ); + expect( user ).to.have.property( 'token' ).and.be.ok; + expect( user ).to.have.property( 'firstname' ).and.equal( lnUser.firstname ); + expect( user ).to.have.property( 'lastname' ).and.equal( lnUser.lastname ); + expect( user ).to.have.property( 'email' ).and.equal( lnUser.email ); + expect( user ).to.have.property( 'linkedinid' ).and.equal( lnUser.linkedinid ); + expect( user ).to.have.property( 'accessedAt' ).and.be.ok; + + done(); + } ) + .fail( done ); + } else { + console.log( 'UserService is defined' ); + done(); + } + } ); + + it( 'should create new user and to associate it with qUser if UserService is defined', function ( done ) { + var profile = { + _json: { + emailAddress: 'denshikov_vovan_link_1@mail.ru', + firstName: 'Volodymyr', + id: 'sdjsadj23', + lastName: 'Denshchykov', + location: { country: { code: "ua"}, name: 'Ukraine' }, + pictureUrl: 'http://m.c.lnkd.licdn.com/mpr/mprx/0_5R4EHm1kXoIbuS7QI0IzHDhF6uZnaSOQFV2zHDrnJWubAdd6djHQQSkZHG4eSE0Ek4VNFEmO_o2w', + publicProfileUrl: 'http://www.linkedin.com/pub/%D0%B2%D0%BB%D0%B0%D0%B4%D0%B8%D0%BC%D0%B8%D1%80-%D0%B4%D0%B5%D0%BD%D1%89%D0%B8%D0%BA%D0%BE%D0%B2/8b/725/619' + } + }; + + if ( !!UserService ) { + + var spy = sinon.spy( UserService, 'create' ); + + Service + .authenticate( lnUser, profile ) + .then( function ( user ) { + + expect( !!UserService ).to.be.true; + + expect( user ).to.be.an( 'object' ).and.be.ok; + expect( user ).to.have.property( 'id' ).and.not.equal( lnUser.id ); + + expect( spy.called ).to.be.true; + expect( spy.calledOnce ).to.be.true; + + spy.restore(); + + UserService + .findById( user.id ) + .then( function( _user ) { + + expect( _user ).to.be.an( 'object' ).and.be.ok; + expect( _user ).to.have.property( 'id' ).and.equal( user.id ); + expect( _user ).to.have.property( 'email' ).and.equal( profile._json.emailAddress ); + expect( _user ).to.have.property( 'password' ).and.be.ok; + expect( _user ).to.have.property( 'firstname' ).and.equal( profile._json.firstName ); + expect( _user ).to.have.property( 'lastname' ).and.equal( profile._json.lastName ); + expect( _user ).to.have.property( 'confirmed' ).and.equal( true ); + expect( _user ).to.have.property( 'active' ).and.equal( true ); + + user_1 = _user; + + Service + .find( { where: { id: lnUser.id } } ) + .then( function( _lnUser ) { + + expect( _lnUser ).to.be.an( 'array' ).and.have.length( 1 ); + + _lnUser = _lnUser[0]; + + expect( _lnUser ).to.be.an( 'object' ).and.be.ok; + expect( _lnUser ).to.have.property( 'id' ).and.equal( lnUser.id ); + expect( _lnUser ).to.have.property( 'UserId' ).and.equal( user.id ); + + done(); + }, done ) + }, done ); + } ) + .fail( done ); + } else { + console.log( 'UserService is not defined' ); + done(); + } + + } ); + + it( 'should return existing user by lnUser.UserId if UserService is defined', function ( done ) { + var profile = { + _json: { + emailAddress: 'denshikov_vovan_link_1@mail.ru', + firstName: 'Volodymyr', + id: 'sdjsadj23', + lastName: 'Denshchykov', + location: { country: { code: "ua"}, name: 'Ukraine' }, + pictureUrl: 'http://m.c.lnkd.licdn.com/mpr/mprx/0_5R4EHm1kXoIbuS7QI0IzHDhF6uZnaSOQFV2zHDrnJWubAdd6djHQQSkZHG4eSE0Ek4VNFEmO_o2w', + publicProfileUrl: 'http://www.linkedin.com/pub/%D0%B2%D0%BB%D0%B0%D0%B4%D0%B8%D0%BC%D0%B8%D1%80-%D0%B4%D0%B5%D0%BD%D1%89%D0%B8%D0%BA%D0%BE%D0%B2/8b/725/619' + } + }; + + if ( !!UserService ) { + + var spy = sinon.spy( UserService, 'create' ); + + Service + .authenticate( lnUser, profile ) + .then( function ( user ) { + + expect( !!UserService ).to.be.true; + + expect( user ).to.be.an( 'object' ).and.be.ok; + expect( user ).to.have.property( 'id' ).and.equal( user_1.id ); + + expect( spy.called ).to.be.false; + + spy.restore(); + + done(); + } ) + .fail( done ); + } else { + console.log( 'UserService is not defined' ); + done(); + } + + } ); + + it( 'should return error if user with such lnUser.UserId do not exist and if UserService is defined' , function ( done ) { + var profile = { + _json: { + emailAddress: 'denshikov_vovan_link_1@mail.ru', + firstName: 'Volodymyr', + id: 'sdjsadj23', + lastName: 'Denshchykov', + location: { country: { code: "ua"}, name: 'Ukraine' }, + pictureUrl: 'http://m.c.lnkd.licdn.com/mpr/mprx/0_5R4EHm1kXoIbuS7QI0IzHDhF6uZnaSOQFV2zHDrnJWubAdd6djHQQSkZHG4eSE0Ek4VNFEmO_o2w', + publicProfileUrl: 'http://www.linkedin.com/pub/%D0%B2%D0%BB%D0%B0%D0%B4%D0%B8%D0%BC%D0%B8%D1%80-%D0%B4%D0%B5%D0%BD%D1%89%D0%B8%D0%BA%D0%BE%D0%B2/8b/725/619' + } + }; + + if ( !!UserService ) { + + var spy = sinon.spy( UserService, 'create' ); + + lnUser + .updateAttributes( { UserId: 1000 } ) + .success( function( result ) { + + expect( result ).to.be.an( 'object' ).and.be.ok; + expect( result ).to.have.property( 'UserId' ).and.equal( 1000 ); + + Service + .authenticate( lnUser, profile ) + .then( function ( user ) { + + expect( !!UserService ).to.be.true; + + expect( user ).to.be.an( 'object' ).and.be.ok; + expect( user ).to.have.property( 'statuscode' ).and.equal( 403 ); + expect( user ).to.have.property( 'message' ).and.be.ok; + + expect( spy.called ).to.be.false; + + spy.restore(); + + done(); + } ) + .fail( done ); + }) + .error( done ); + } else { + console.log( 'UserService is not defined' ); + done(); + } + + } ); + + it( 'should return existing user by lnUser.email if user with such email already exist', function ( done ) { + var profile = { + _json: { + emailAddress: 'denshikov_vovan_link_1@mail.ru', + firstName: 'Volodymyr', + id: 'sdjsadj23', + lastName: 'Denshchykov', + location: { country: { code: "ua"}, name: 'Ukraine' }, + pictureUrl: 'http://m.c.lnkd.licdn.com/mpr/mprx/0_5R4EHm1kXoIbuS7QI0IzHDhF6uZnaSOQFV2zHDrnJWubAdd6djHQQSkZHG4eSE0Ek4VNFEmO_o2w', + publicProfileUrl: 'http://www.linkedin.com/pub/%D0%B2%D0%BB%D0%B0%D0%B4%D0%B8%D0%BC%D0%B8%D1%80-%D0%B4%D0%B5%D0%BD%D1%89%D0%B8%D0%BA%D0%BE%D0%B2/8b/725/619' + } + }; + + if ( !!UserService ) { + + var spy = sinon.spy( UserService, 'create' ); + + lnUser + .updateAttributes( { UserId: null } ) + .success( function( result ) { + + expect( result ).to.be.an( 'object' ).and.be.ok; + expect( result ).to.have.property( 'UserId' ).and.equal( null ); + + Service + .authenticate( lnUser, profile ) + .then( function ( user ) { + + expect( !!UserService ).to.be.true; + + expect( user ).to.be.an( 'object' ).and.be.ok; + expect( user ).to.have.property( 'id' ).and.equal( user_1.id ); + + expect( spy.called ).to.be.false; + + spy.restore(); + + Service + .find( { where: { id: lnUser.id } } ) + .then( function( _lnUser ) { + + expect( _lnUser ).to.be.an( 'array' ).and.have.length( 1 ); + + _lnUser = _lnUser[0]; + + expect( _lnUser ).to.be.an( 'object' ).and.be.ok; + expect( _lnUser ).to.have.property( 'id' ).and.equal( lnUser.id ); + expect( _lnUser ).to.have.property( 'UserId' ).and.equal( user.id ); + + done(); + }, done ) + } ) + .fail( done ); + }) + .error( done ); + } else { + console.log( 'UserService is not defined' ); + done(); + } + + } ); + + } ); + + describe( '.updateAccessedDate( user )', function () { + + it( 'should be able to update user', function ( done ) { + + if ( !!UserService ) { + user_1 + .updateAttributes( { accessedAt: null } ) + .success( function( result ) { + + expect( result ).to.be.an( 'object' ).and.be.ok; + expect( result ).to.have.property( 'id' ).and.equal( user_1.id ); + expect( result ).to.have.property( 'accessedAt' ).and.not.be.ok; + + Service + .updateAccessedDate( user_1 ) + .then( function( result ) { + + expect( result ).to.be.an( 'object' ).and.be.ok; + expect( result ).to.have.property( 'id' ).and.equal( user_1.id ); + expect( result ).to.have.property( 'accessedAt' ).and.be.ok; + + done(); + }, done ); + }) + .error( done ); + + } else { + console.log( 'UserService is not defined' ); + done(); + } + + } ); + + it( 'should be able to update lnUser', function ( done ) { + + lnUser + .updateAttributes( { accessedAt: null } ) + .success( function( result ) { + + expect( result ).to.be.an( 'object' ).and.be.ok; + expect( result ).to.have.property( 'id' ).and.equal( lnUser.id ); + expect( result ).to.have.property( 'accessedAt' ).and.not.be.ok; + + Service + .updateAccessedDate( lnUser ) + .then( function( result ) { + + expect( result ).to.be.an( 'object' ).and.be.ok; + expect( result ).to.have.property( 'id' ).and.equal( lnUser.id ); + expect( result ).to.have.property( 'accessedAt' ).and.be.ok; + + done(); + }, done ); + }) + .error( done ); + } ); + + it( 'should be able to get object if it is not a user', function ( done ) { + + Service + .updateAccessedDate ( { statuscode: 403, message: 'invalid' } ) + .then ( function ( result ) { + + expect ( result ).to.be.an ( 'object' ).and.be.ok; + expect ( result ).to.have.property ( 'statuscode' ).and.equal ( 403 ); + expect ( result ).to.have.property ( 'message' ).and.be.ok; + + done (); + }, done ); + } ); + + } ); + + describe( '.listUsers()', function () { + + it( 'should be able to get list of all lnUsers', function ( done ) { + + Service + .listUsers() + .then( function ( users ) { + + expect( users ).to.be.an( 'array' ).and.have.length.above( 1 ); + expect( users[0] ).to.be.an( 'object' ).and.be.ok; + expect( users[0] ).to.have.property( 'id' ).and.be.ok; + expect( users[0] ).to.have.property( 'linkedinid' ).and.be.ok; + expect( users[0] ).to.have.property( 'email' ).and.be.ok; + expect( users[0] ).to.have.property( 'picture' ).and.be.ok; + expect( users[0] ).to.not.have.property( 'token' ); + + done(); + } ) + .fail( done ); + } ); + + } ); + + describe( '.findUserById()', function () { + + it( 'should be able to get lnUser by Id', function ( done ) { + + Service + .findUserById( lnUser.id ) + .then( function ( lnUser ) { + + expect( lnUser ).to.be.an( 'object' ).and.be.ok; + expect( lnUser ).to.have.property( 'id' ).and.equal( lnUser.id ); + expect( lnUser ).to.have.property( 'linkedinid' ).and.be.ok; + expect( lnUser ).to.have.property( 'email' ).and.be.ok; + expect( lnUser ).to.have.property( 'link' ).and.be.ok; + expect( lnUser ).to.not.have.property( 'token' ); + + done(); + } ) + .fail( done ); + } ); + + it( 'should be able to get the error if the lnUser does not exist', function ( done ) { + + Service + .findUserById( 151515115151515151 ) + .then( function( result ) { + + expect( result ).to.be.an( 'object' ); + expect( result ).to.have.property( 'statuscode' ).and.equal( 403 ); + expect( result ).to.have.property( 'message' ).and.be.ok; + + done(); + }, done ) + } ); + + } ); + + describe( '.deleteUser( lnUserId )', function () { + + it( 'should be able to get the error if the lnUser does not exist', function ( done ) { + + Service + .deleteUser( 151515115151515151 ) + .then( function( result ) { + + expect( result ).to.be.an( 'object' ); + expect( result ).to.have.property( 'statuscode' ).and.equal( 403 ); + expect( result ).to.have.property( 'message' ).and.be.ok; + + done(); + }, done ) + } ); + + it( 'should be able to delete lnUser', function ( done ) { + + Service + .deleteUser( lnUser.id ) + .then( function ( result ) { + + expect( result ).to.be.an( 'object' ); + expect( result ).to.have.property( 'statuscode' ).and.equal( 200 ); + expect( result ).to.have.property( 'message' ).and.be.ok; + + done(); + } ) + .fail( done ); + } ); + + } ); + +} ); \ No newline at end of file diff --git a/modules/orm/config/default.json b/modules/orm/config/default.json index ec9fbcf..69dbf11 100644 --- a/modules/orm/config/default.json +++ b/modules/orm/config/default.json @@ -11,17 +11,6 @@ } }, "modelAssociations": { - "UserModel": {}, - "EmailModel": { - "hasMany": [ "EmailAttachmentModel" ], - "belongsTo": [ "UserModel" ] - }, - "EmailUserModel":{ - "belongsTo" : [ "EmailModel", "UserModel" ] - }, - "EmailAttachmentModel": { - "belongsTo": [ "EmailModel" ] - } } } diff --git a/package.json b/package.json index 394b493..48f70bd 100644 --- a/package.json +++ b/package.json @@ -54,9 +54,8 @@ "bundledDependencies": [ "orm", "odm", - "background-tasks", "clever-auth", - "clever-auth-google", - "clever-email" + "clever-auth-linkedin", + "clever-auth-dropbox" ] }