diff --git a/index.js b/index.js index 5f5fc22..e26f8eb 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,6 @@ 'use strict'; +const cors = require('cors'); const nodemailer = require('nodemailer'); const bunyan = require('bunyan'); @@ -7,7 +8,16 @@ const config = require('./config.json'); /** + * Check Google Cloud Functions status * + * Trigger this function by making a GET request to: + * https://[YOUR_REGION].[YOUR_PROJECT_ID].cloudfunctions.net/ping + * + * @example + * curl -X GET "https://us-central1.your-project-id.cloudfunctions.net/ping" + * + * @param {object} request Cloud Function request context. + * @param {object} response Cloud Function response context. */ exports.ping = (request, response) => { // Everything is ok @@ -15,7 +25,9 @@ exports.ping = (request, response) => { }; /** + * Returns a nodemailer transporter. * + * @returns {object} Transporter object. */ function getTransporter() { const logger = bunyan.createLogger({ name: 'nodemailer' }); @@ -30,17 +42,141 @@ function getTransporter() { } /** + * Check your email provider status + * + * Trigger this function by making a GET request to: + * https://[YOUR_REGION].[YOUR_PROJECT_ID].cloudfunctions.net/check + * + * @example + * curl -X GET "https://us-central1.your-project-id.cloudfunctions.net/check" * + * @param {object} request Cloud Function request context. + * @param {object} response Cloud Function response context. */ exports.check = (request, response) => { - const transporter = getTransporter(); - - transporter.verify((error, success) => { - if (error) { - console.log(`Error: ${error}`); - response.status(400).send('Error: Server is not ready to accept messages'); - } else { + return Promise.resolve() + .then(_ => { + const transporter = getTransporter(); + return transporter.verify(); + }) + .then(_ => { response.status(200).send('Success: Server ready to take our messages'); - } - }); + }) + .catch(err => { + console.log(`[Error] { code: ${err.code}, message: ${err.message} }`); + response.status(400).send('Error: Server is not ready to accept messages'); + }); } + +/** + * Gets the email body data from the HTTP request body. + * + * @param {object} requestBody The request payload. + * @param {string} requestBody.botTrap Field to prevent spam. + * @param {string} requestBody.name Name of the sender. + * @param {string} requestBody.email Email address of the sender. + * @param {string} requestBody.message Body of the email message. + * @returns {object} Payload object. + */ +function getPayload(requestBody) { + if (requestBody.botTrap) { + const error = new Error('Spam not allowed!'); + error.code = 400; + throw error; + } else if (!requestBody.name) { + const error = new Error('Name not provided. Make sure you have a "name" property in your request'); + error.code = 400; + throw error; + } else if (!requestBody.email) { + const error = new Error('Email address not provided. Make sure you have an "email" property in your request'); + error.code = 400; + throw error; + } else if (!requestBody.message) { + const error = new Error('Email content not provided. Make sure you have a "message" property in your request'); + error.code = 400; + throw error; + } + + return { + name: requestBody.name, + email: requestBody.email, + message: requestBody.message + }; +} + +/** + * Ask nodemailer to send an email using your email provider. + * + * @param {object} req Cloud Function request context. + * @param {object} req.body The request payload. + * @param {object} res Cloud Function response context. + */ +function fcontact(req, res) { + return Promise.resolve() + .then(_ => { + const payload = getPayload(req.body); + const message = { + // email address of the sender + from: config.envelope.sender, + // comma separated list or an array of recipients email addresses + to: config.envelope.recipient, + // subject of the email + subject: config.envelope.subject, + // email address that will appear on the Reply-To: field + replyTo: payload.email, + // plaintext body + text: ` + Formulario de contacto: + + Nombre: ${payload.name} + Email: ${payload.email} + + ${payload.message} + `, + // HTML body + html: ` +
+ Nombre: ${payload.name}
+ Email: ${payload.email}
+
${payload.message}
+ `, + }; + return message; + }) + .then(message => { + const transporter = getTransporter(); + return transporter.sendMail(message); + }) + .then(_ => { + // console.log(req.body); + // Send number of emails sent successfully + res.status(200).send("1"); + }) + .catch(err => { + console.log(`[Error] { code: ${err.code}, message: ${err.message} }`); + res.status(400).send(`Error: ${err.message}`); + }); +}; + +/** + * Send an email using your email provider. + * + * Trigger this function by making a POST request with a payload to: + * https://[YOUR_REGION].[YOUR_PROJECT_ID].cloudfunctions.net/contact + * + * @example + * curl -X POST "https://us-central1.your-project-id.cloudfunctions.net/contact" --data '{"name":"Jane Doe","email":"jane.doe@email.com","message":"Hello World!"}' + * + * @param {object} request Cloud Function request context. + * @param {object} response Cloud Function response context. + */ +exports.contact = (request, response) => { + const fcors = cors({ + origin: true, + methods: ['POST', 'OPTIONS'] + }); + + fcors(request, response, () => fcontact(request, response)); +}; diff --git a/package-lock.json b/package-lock.json index 064eeaf..81a2fbd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -158,6 +158,15 @@ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, + "cors": { + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.4.tgz", + "integrity": "sha1-K9OB8usgECAQXNUOpZ2mMJBpRoY=", + "requires": { + "object-assign": "4.1.1", + "vary": "1.1.2" + } + }, "cryptiles": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz", @@ -573,6 +582,11 @@ "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -742,6 +756,11 @@ "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==" }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + }, "verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", diff --git a/package.json b/package.json index 8a70144..34eee90 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ }, "dependencies": { "bunyan": "^1.8.12", + "cors": "^2.8.4", "nodemailer": "^4.4.2", "serverless-google-cloudfunctions": "^1.1.0" } diff --git a/serverless.yml b/serverless.yml index e9ad592..7b2c082 100644 --- a/serverless.yml +++ b/serverless.yml @@ -6,14 +6,11 @@ provider: memorySize: 256 timeout: 60s project: ${file(./config.json):project_id} - # the path to the credentials file needs to be absolute credentials: ${file(./config.json):credentials_file} plugins: - serverless-google-cloudfunctions -# needs more granular excluding in production as only the serverless provider npm -# package should be excluded (and not the whole node_modules directory) package: exclude: - node_modules/** @@ -33,4 +30,9 @@ functions: events: - http: path + contact: + handler: contact + events: + - http: path + frameworkVersion: "=1.25.0"