feat(contact): Ported /contact endpoint

This commit is contained in:
Daniel Gonzalez
2020-07-14 22:11:11 -04:00
parent e92a5ab631
commit d54feaa308
8 changed files with 643 additions and 1 deletions
+4 -1
View File
@@ -5,5 +5,8 @@
module.exports = { module.exports = {
port: process.env.PORT || 5001, port: process.env.PORT || 5001,
logPass: 'test' logPass: 'test',
emailServer: process.env.EMAILSERVER ? process.env.EMAILSERVER : 'mail.launchpadip.net',
emailUser: process.env.EMAILUSER ? process.env.EMAILUSER : 'noreply@launchpadip.net',
emailPassword: process.env.EMAILPASS ? process.env.EMAILPASS : 'testtest'
} }
+5
View File
@@ -5864,6 +5864,11 @@
"process-on-spawn": "^1.0.0" "process-on-spawn": "^1.0.0"
} }
}, },
"nodemailer": {
"version": "6.4.10",
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.4.10.tgz",
"integrity": "sha512-j+pS9CURhPgk6r0ENr7dji+As2xZiHSvZeVnzKniLOw1eRAyM/7flP0u65tCnsapV8JFu+t0l/5VeHsCZEeh9g=="
},
"nodemon": { "nodemon": {
"version": "2.0.4", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.4.tgz", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.4.tgz",
+1
View File
@@ -48,6 +48,7 @@
"koa-static": "^5.0.0", "koa-static": "^5.0.0",
"line-reader": "^0.4.0", "line-reader": "^0.4.0",
"mongoose": "^5.5.12", "mongoose": "^5.5.12",
"nodemailer": "^6.4.10",
"passport-local": "^1.0.0", "passport-local": "^1.0.0",
"winston": "^3.2.1", "winston": "^3.2.1",
"winston-daily-rotate-file": "^4.0.0" "winston-daily-rotate-file": "^4.0.0"
+170
View File
@@ -0,0 +1,170 @@
/*
A library for controlling the sending email.
*/
'use strict'
const nodemailer = require('nodemailer')
const config = require('../../config')
const wlogger = require('./wlogger')
let _this
class NodeMailer {
constructor () {
this.nodemailer = nodemailer
this.config = config
_this = this
}
// Validate email
async validateEmail (email) {
// eslint-disable-next-line no-useless-escape
if (/^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/.test(email)) {
return true
}
return false
}
// Handles the sending of data via email.
async sendEmail (data) {
try {
// Validate input
if (!data.email || typeof data.email !== 'string') {
throw new Error("Property 'email' must be a string!")
}
const isEmail = await _this.validateEmail(data.email)
if (!isEmail) {
throw new Error("Property 'email' must be email format!")
}
if (!data.to || !Array.isArray(data.to)) {
throw new Error("Property 'to' must be a array!")
}
const isEmailList = await _this.validateEmailArray(data.to)
if (!isEmailList) {
throw new Error("Property 'to' must be array of email format!")
}
if (!data.formMessage || typeof data.formMessage !== 'string') {
throw new Error("Property 'message' must be a string!")
}
if (!data.subject || typeof data.subject !== 'string') {
throw new Error("Property 'subject' must be a string!")
}
if (!data.payloadTitle || typeof data.payloadTitle !== 'string') {
throw new Error("Property 'payloadTitle' must be a string!")
}
// create reusable transporter object using the default SMTP transport
const transporter = await _this.nodemailer.createTransport({
host: _this.config.emailServer,
port: 587,
secure: false, // true for 465, false for other ports
auth: {
user: _this.config.emailUser, // generated ethereal user
pass: _this.config.emailPassword // generated ethereal password
}
})
// console.log(`transporter: ${JSON.stringify(transporter)}`)
const msg = data.formMessage.replace(/(\r\n|\n|\r)/g, '<br />')
const now = new Date()
const subject = data.subject
const to = data.to
const emailUser = data.email // email from the user who initiated the sharing email
const payload = data.payloadTitle
const bodyJson = data
delete bodyJson.to
delete bodyJson.subject
delete bodyJson.formMessage
// Prototyping email output.
bodyJson.email = 'noreply@launchpadip.net'
delete bodyJson.email
delete bodyJson.emailList
delete bodyJson.payloadTitle
bodyJson.message = msg
// Html body
let htmlData = ''
// maps the object and converts it into html format
Object.keys(bodyJson).forEach(function (key) {
htmlData += `${key}: ${bodyJson[key]}<br/>`
})
// This paragraph just be added to the message for
// emails that are not password reset ones
const paragraphTag = `<p>${emailUser} would like to share the document <b>${payload}</b> with you through
<a href="https://launchpadip.net">LaunchpadIP.net</a>.
</p>`
const htmlMsg = `<h3>${subject}:</h3>
${payload === 'Email reset' ? '' : paragraphTag}
<p>
time: ${now.toLocaleString()}<br/>
${htmlData}
</p>`
// send mail with defined transport object
const info = await transporter.sendMail({
// from: `${data.email}`, // sender address
from: 'noreply@launchpadip.net',
to: `${to}`, // list of receivers
// subject: `Pearson ${subject}`, // Subject line
subject: subject,
// html: '<b>This is a test email</b>' // html body
html: htmlMsg
})
console.log('Message sent: %s', info.messageId)
} catch (err) {
console.log('Error in sendEmail()')
throw err
}
}
async validateEmailArray (emailList) {
try {
if (!emailList || !Array.isArray(emailList)) {
throw new Error("Property 'emailList' must be a array!")
}
// Email list can't be empty
if (!emailList.length > 0) {
throw new Error("Property 'emailList' cant be empty!")
}
// Iterates the array and validates each email format
const isValid = await new Promise(resolve => {
emailList.map(async (value, i) => {
const isEmail = await _this.validateEmail(value)
if (!isEmail) {
resolve(false)
}
if (i >= emailList.length - 1) {
resolve(true)
}
})
})
if (!isValid) {
throw new Error('Array must contain emails format!')
}
return true
} catch (err) {
wlogger.error('Error in lib/nodemailer.js/validateEmailArray()')
throw err
}
}
}
module.exports = NodeMailer
+132
View File
@@ -0,0 +1,132 @@
/* eslint-disable no-useless-escape */
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
const config = require('../../../config')
const NodeMailer = require('../../lib/nodemailer')
const nodemailer = new NodeMailer()
let _this
class Contact {
constructor () {
_this = this
_this.config = config
_this.nodemailer = nodemailer
}
async email (ctx) {
try {
const data = ctx.request.body
const emailObj = data.obj
// Validate input
if (!emailObj.email || typeof emailObj.email !== 'string') {
throw new Error("Property 'email' must be a string!")
}
const isEmail = await _this.nodemailer.validateEmail(emailObj.email)
if (!isEmail) {
throw new Error("Property 'email' must be email format!")
}
if (!emailObj.formMessage || typeof emailObj.formMessage !== 'string') {
throw new Error("Property 'message' must be a string!")
}
if (!emailObj.payloadTitle || typeof emailObj.payloadTitle !== 'string') {
throw new Error("Property 'payloadTitle' must be a string!")
}
// If an email list exists, the email will be sended to that list
// otherwhise will be sended by default to the variable "_this.config.emailUser"
let _to = [_this.config.emailUser]
// Email list is optional
if (emailObj.emailList) {
if (!Array.isArray(emailObj.emailList) || !emailObj.emailList.length > 0) {
throw new Error("Property 'emailList' must be a array of emails!")
} else {
_to = emailObj.emailList
}
}
console.log(`Trying send message to : ${_to}`)
emailObj.subject = 'Someone wants to share a document with you.'
emailObj.to = _to
await _this.nodemailer.sendEmail(emailObj)
ctx.body = {
success: true
}
} catch (err) {
ctx.body = {
success: false
}
// console.error(`Error: `, err)
throw err
}
}
/* // Handles the sending of data via email.
async sendEmail (data) {
try {
// Validate input
if (!data.email || typeof data.email !== 'string') {
throw new Error('Property \'email\' must be a string!')
}
const isEmail = await _this.validateEmail(data.email)
if (!isEmail) {
throw new Error('Property \'email\' must be email format!')
}
if (!data.formMessage || typeof data.formMessage !== 'string') {
throw new Error('Property \'message\' must be a string!')
}
// create reusable transporter object using the default SMTP transport
const transporter = await _this.nodemailer.createTransport({
host: 'box.bchtest.net',
port: 587,
secure: false, // true for 465, false for other ports
auth: {
user: _this.config.emailUser, // generated ethereal user
pass: _this.config.emailPassword // generated ethereal password
}
})
// console.log(`transporter: ${JSON.stringify(transporter.sendMail)}`)
const msg = data.formMessage.replace(/(\r\n|\n|\r)/g, '<br />')
const now = new Date()
const htmlMsg = `
<h3>New Contact Form</h3>
<p>
time: ${now.toLocaleString()}<br />
name: ${data.name}<br />
email: ${data.email}<br />
message: ${msg}<br />
</p>
`
// send mail with defined transport object
const info = await transporter.sendMail({
from: `${data.email}`, // sender address
to: _this.config.emailUser, // list of receivers
subject: 'Project Pearson', // Subject line
// html: '<b>This is a test email</b>' // html body
html: htmlMsg
})
console.log('Message sent: %s', info.messageId)
} catch (err) {
console.log('Error in sendEmail()')
throw err
}
}
// Validate email
async validateEmail (email) {
// eslint-disable-next-line no-useless-escape
if (/^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/.test(email)) {
return (true)
}
return (false)
} */
}
module.exports = Contact
+17
View File
@@ -0,0 +1,17 @@
// const ensureUser = require('../../midleware/validators')
const ContactController = require('./controller')
const contactController = new ContactController()
// export const baseUrl = '/users'
module.exports.baseUrl = '/contact'
module.exports.routes = [
{
method: 'POST',
route: '/email',
handlers: [
contactController.email
]
}
]
+171
View File
@@ -0,0 +1,171 @@
const assert = require('chai').assert
const NodeMailer = require('../src/lib/nodemailer')
const nodemailer = new NodeMailer()
describe('NodeMailer', () => {
describe('sendEmail()', () => {
it('should throw error if email property is not provided', async () => {
try {
const data = {
formMessage: 'test msg',
name: 'test name',
subject: 'test subject',
to: ['test2@email.com']
}
await nodemailer.sendEmail(data)
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, 'Property \'email\' must be a string!')
}
})
it('should throw error if email property is wrong format', async () => {
try {
const data = {
email: 'test',
formMessage: 'test msg',
name: 'test name',
subject: 'test subject',
to: ['test2@email.com']
}
await nodemailer.sendEmail(data)
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, 'Property \'email\' must be email format!')
}
})
it('should throw error if formMessage property is not provided', async () => {
try {
const data = {
email: 'test@email.com',
name: 'test name',
subject: 'test subject',
to: ['test2@email.com']
}
await nodemailer.sendEmail(data)
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, 'Property \'message\' must be a string!')
}
})
it('should throw error if <to> property is not provided', async () => {
try {
const data = {
email: 'test@email.com',
name: 'test name',
subject: 'test subject'
}
await nodemailer.sendEmail(data)
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, 'Property \'to\' must be a array!')
}
})
it('should throw error if <to> is wrong format', async () => {
try {
const data = {
email: 'test@email.com',
formMessage: 'test msg',
name: 'test name',
subject: 'test subject',
to: ['test']
}
await nodemailer.sendEmail(data)
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, 'Array must contain emails format!')
}
})
it('should throw error if subject Property is not provided', async () => {
try {
const data = {
email: 'test@email.com',
formMessage: 'test msg',
name: 'test name',
to: ['test2@email.com']
}
await nodemailer.sendEmail(data)
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, 'Property \'subject\' must be a string!')
}
})
it('should throw error if payloadTitle property is not provided', async () => {
try {
const data = {
email: 'test@email.com',
formMessage: 'test msg',
name: 'test name',
subject: 'test subject',
to: ['test2@email.com']
}
await nodemailer.sendEmail(data)
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, 'Property \'payloadTitle\' must be a string!')
}
})
/* it('should send email', async () => {
try {
const data = {
email: 'test@email.com',
formMessage: 'test msg',
name: 'test name',
to: 'test2@email.com',
subject: 'test subject',
payloadTitle: 'test title'
}
await nodemailer.sendEmail(data)
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, 'Property \'subject\' must be a string!')
}
}) */
})
describe('validateEmailArray()', () => {
it('should throw error if email list is not provided ', async () => {
try {
await nodemailer.validateEmailArray()
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, 'Property \'emailList\' must be a array!')
}
})
it('should throw error if email list is empty', async () => {
try {
const emailList = []
await nodemailer.validateEmailArray(emailList)
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, 'Property \'emailList\' cant be empty!')
}
})
it('should throw error if email list contain wrong format', async () => {
try {
const emailList = [
'wrongEmail',
'bad format'
]
await nodemailer.validateEmailArray(emailList)
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, 'Array must contain emails format!')
}
})
it('should return true if email list contain email format', async () => {
try {
const emailList = [
'test@email.com',
'simple@email.com'
]
const result = await nodemailer.validateEmailArray(emailList)
assert.isTrue(result)
} catch (err) {
assert(false, 'Unexpected result')
}
})
})
})
+143
View File
@@ -0,0 +1,143 @@
const config = require('../config')
const axios = require('axios').default
const assert = require('chai').assert
// Mock data
// const mockData = require('./mocks/contact-mocks')
const LOCALHOST = `http://localhost:${config.port}`
describe('Contact', () => {
describe('POST /contact/email', () => {
it('should throw error if email property is not provided', async () => {
try {
const options = {
method: 'POST',
url: `${LOCALHOST}/contact/email`,
data: {
obj: {
formMessage: 'message'
}
}
}
await axios(options)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// console.log(`result stringified: ${JSON.stringify(result, null, 2)}`)
assert(false, 'Unexpected result')
} catch (err) {
assert.equal(err.response.status, 500)
assert.include(err.response.data, "Property 'email' must be a string!")
}
})
it('should throw error if email property is wrong format', async () => {
try {
const options = {
method: 'POST',
url: `${LOCALHOST}/contact/email`,
data: {
obj: {
email: 'email',
formMessage: 'test message'
}
}
}
await axios(options)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// console.log(`result stringified: ${JSON.stringify(result, null, 2)}`)
assert(false, 'Unexpected result')
} catch (err) {
assert.equal(err.response.status, 500)
assert.include(
err.response.data,
"Property 'email' must be email format!"
)
}
})
it('should throw error if formMessage property is not provided', async () => {
try {
const options = {
method: 'POST',
url: `${LOCALHOST}/contact/email`,
data: {
obj: {
email: 'email@email.com'
}
}
}
await axios(options)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// console.log(`result stringified: ${JSON.stringify(result, null, 2)}`)
assert(false, 'Unexpected result')
} catch (err) {
assert.equal(err.response.status, 500)
assert.include(
err.response.data,
"Property 'message' must be a string!"
)
}
})
it('should throw error if payloadTitle property is not provided', async () => {
try {
const options = {
method: 'POST',
url: `${LOCALHOST}/contact/email`,
data: {
obj: {
email: 'email@email.com',
formMessage: 'test message'
}
}
}
await axios(options)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// console.log(`result stringified: ${JSON.stringify(result, null, 2)}`)
assert(false, 'Unexpected result')
} catch (err) {
assert.equal(err.response.status, 500)
assert.include(
err.response.data,
"Property 'payloadTitle' must be a string!"
)
}
})
/* I presented problems here trying to use sandbox in this test */
/* it('should send email with all input', async () => {
// Mock live network calls.
sandbox.stub(
contactController.transporter, 'sendMail')
.resolves(mockData)
try {
const options = {
method: 'POST',
url: `${LOCALHOST}/contact/email`,
data: {
obj: {
email: 'email@email.com',
formMessage: 'test email',
name: 'test name',
payloadtitle: 'test title'
}
}
}
const result = await axios(options)
console.log('RESULT', result.data)
} catch (err) {
// console.log(err)
assert(false, 'Unexpected result')
}
}) */
})
})