diff --git a/config/env/common.js b/config/env/common.js
index 28a73a0..3ffae82 100644
--- a/config/env/common.js
+++ b/config/env/common.js
@@ -5,5 +5,8 @@
module.exports = {
port: process.env.PORT || 5001,
- logPass: 'test'
+ logPass: 'test',
+ emailServer: process.env.EMAILSERVER ? process.env.EMAILSERVER : 'mail.someserver.com',
+ emailUser: process.env.EMAILUSER ? process.env.EMAILUSER : 'noreply@someserver.com',
+ emailPassword: process.env.EMAILPASS ? process.env.EMAILPASS : 'emailpassword'
}
diff --git a/package-lock.json b/package-lock.json
index c763e09..d1a8144 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -5864,6 +5864,11 @@
"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": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.4.tgz",
diff --git a/package.json b/package.json
index 042c652..779a4f6 100644
--- a/package.json
+++ b/package.json
@@ -48,6 +48,7 @@
"koa-static": "^5.0.0",
"line-reader": "^0.4.0",
"mongoose": "^5.5.12",
+ "nodemailer": "^6.4.10",
"passport-local": "^1.0.0",
"winston": "^3.2.1",
"winston-daily-rotate-file": "^4.0.0"
diff --git a/src/lib/nodemailer.js b/src/lib/nodemailer.js
new file mode 100644
index 0000000..f839682
--- /dev/null
+++ b/src/lib/nodemailer.js
@@ -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, '
')
+
+ 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]}
`
+ })
+ // This paragraph just be added to the message for
+ // emails that are not password reset ones
+ const paragraphTag = `
${emailUser} would like to share the document ${payload} with you through
+ LaunchpadIP.net.
+
`
+
+ const htmlMsg = `${subject}:
+ ${payload === 'Email reset' ? '' : paragraphTag}
+
+ time: ${now.toLocaleString()}
+ ${htmlData}
+
`
+ // 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: 'This is a test email' // 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
diff --git a/src/modules/contact/controller.js b/src/modules/contact/controller.js
new file mode 100644
index 0000000..504762b
--- /dev/null
+++ b/src/modules/contact/controller.js
@@ -0,0 +1,76 @@
+/* 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
+ }
+ }
+}
+module.exports = Contact
diff --git a/src/modules/contact/router.js b/src/modules/contact/router.js
new file mode 100644
index 0000000..0f0bf3c
--- /dev/null
+++ b/src/modules/contact/router.js
@@ -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
+ ]
+ }
+]
diff --git a/test/a03-nodemailer.spec.js b/test/a03-nodemailer.spec.js
new file mode 100644
index 0000000..e048ccf
--- /dev/null
+++ b/test/a03-nodemailer.spec.js
@@ -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 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 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')
+ }
+ })
+ })
+})
diff --git a/test/a04-contact.spec.js b/test/a04-contact.spec.js
new file mode 100644
index 0000000..cc07a16
--- /dev/null
+++ b/test/a04-contact.spec.js
@@ -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')
+ }
+ }) */
+ })
+})