Skip to content

Instantly share code, notes, and snippets.

@nosvalds
Last active September 10, 2020 08:59
Show Gist options
  • Save nosvalds/c5d7ab17bcf095ed797200399aab538d to your computer and use it in GitHub Desktop.
Save nosvalds/c5d7ab17bcf095ed797200399aab538d to your computer and use it in GitHub Desktop.
DigitalHumani Tree Report Lambda Function
const Responses = {
_200(data = {}){
return {
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Methods': '*',
'Access-Control-Allow-Origin': '*',
},
statusCode: 200,
body: JSON.stringify(data)
}
},
_400(data = {}){
return {
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Methods': '*',
'Access-Control-Allow-Origin': '*',
},
statusCode: 400,
body: JSON.stringify(data)
}
}
}
module.exports = Responses;
const AWS = require('aws-sdk');
// offline setup
let options = {};
if (process.env.IS_OFFLINE) {
options = {
region: "localhost",
endpoint: "http://localhost:8008"
}
}
const documentClient = new AWS.DynamoDB.DocumentClient(options);
const Dynamo = {
async get (params) {
const data = await documentClient
.get(params)
.promise()
if (!data || !data.Item){
throw Error(`There was an error fetching the data from DynamoDB for parameters ${params}`)
}
console.log(data);
return data.Item;
},
async query (params) {
const data = await documentClient
.query(params)
.promise()
if (!data || !data.Items){
throw Error(`There was an error fetching the data from DynamoDB for parameters ${params}`)
}
console.log(data);
return data.Items;
},
};
module.exports = Dynamo;
'use strict'
// Built-in
const { log } = console
// common
const Responses = require('./API_Responses');
const Dynamo = require('./Dynamo');
// AWS resources
const aws = require('aws-sdk')
const ses = new aws.SES({region: 'us-east-1'});
// libraries
const fs = require('fs');
const { promisify } = require('util')
const readFile = promisify(fs.readFile)
const Handlebars = require('handlebars');
// environment variables
const { ENTERPRISE_TABLE, PROJECT_TABLE, TREE_TABLE } = process.env
exports.handler = async event => {
// Input data for organisation and which month
let body = JSON.parse(event.body); // parse JSON object
// need to update this to loop on active enterprises
const enterpriseId = body.enterpriseId // get from the event input JSON
let projectId = "";
let monthDate = "";
// if monthDate (YYYY-MM) is passed in, use that value
if (body.monthDate !== "") {
monthDate = body.monthDate;
} else { // report to be run 1st of following month, so we want last month
let today = new Date();
today.setMonth(today.getMonth() - 1);
let month = today.getMonth() + 1; // +1 for human readable month
month = month < 10 ? `0${month}` : `${month}`; // deal with padding 0
monthDate = `${today.getFullYear()}-${month}`; // format YYYY-MM
}
// do some validation of enterprise ID and monthDate
if (!enterpriseId.match(/^[0-9a-f]{8}$/)) {
let error = `The "enterpriseId" must be 8 hex digit string. Got "${enterpriseId}" instead.`
log(error);
return Responses._400({message: error});
} else if (!monthDate.match(/^\d{4}-\d{2}$/)) {
let error = `Required "month date" format is YYYY-MM. Got "${monthDate}" instead.`
log(error);
return Responses._400({message: error});
}
// Start getting data for the email
const TableName = TREE_TABLE
const IndexName = 'myGSI'
const query = {
enterpriseId,
}
const KeyConditionExpression =
Object.keys(query).map(x => `#${x[0]} = :${x[0]}`).join()
+ ' and begins_with(created, :dt)'
// { "#n": "name" }
const ExpressionAttributeNames = Object
.keys(query)
.map(x => ({ [`#${x[0]}`] : x }))
.reduce((x, acc) => Object.assign(acc, x), {})
// { ":n": "John" }
const ExpressionAttributeValues = Object
.keys(query)
.map(x => ({ [`:${x[0]}`] : query[x] }))
.concat({':dt': monthDate}) // Ex. '2018-11'
.reduce((x, acc) => Object.assign(acc, x), {})
let treeResult = await Dynamo.query({
TableName,
IndexName,
KeyConditionExpression,
ExpressionAttributeNames,
ExpressionAttributeValues,
}).catch(err => {
log('Error in Dynamo Query', err)
return null
})
if (!treeResult){
return Responses._400({message: `There was an error fetching the data for enterprise of ${enterpriseId} and monthDate of ${monthDate} from ${TableName}`})
}
// calculate tree count per project from result
let report = {};
let treeCount = "";
// sum the tree count for each projectId
treeResult
.forEach(val => {
report[val.projectId] = (report[val.projectId] > 0 ? report[val.projectId] : 0 ) + val.treeCount;
})
// get the just project IDs in an array
let projectIds = Object.keys(report);
if (projectIds.length === 1) { // only purchased with 1 project (this is the norm)
projectId = projectIds.join(); // get the single projectId
treeCount = report[projectId]; // get the treeCount for that project
} else {
let error = `Trees planted with multiple projects for Enterprise id ${enterpriseId} and monthDate ${monthDate}, email must be generated manually at this time`;
log(error);
return Responses._400({message: error});
}
// get project details
let projectDetails = await Dynamo.get(
{
TableName: PROJECT_TABLE,
Key: { id: projectId }
}
).catch(err => {
log('Error in Dynamo Get', err)
return null
})
if (!projectDetails) {
error = "Could not retrieve project data for project: " + projectId
log(error)
return Responses._400({ error })
}
// get enterprise details
let enterpriseDetails = await Dynamo.get(
{
TableName: ENTERPRISE_TABLE,
Key: { id: enterpriseId }
}
).catch(err => {
log('Error in Dynamo Get', err)
return null
})
if (!enterpriseDetails) {
let error = "Count not retrieve enterprise data for enterprise: " + enterpriseId
log(error)
return Responses._400({ error })
}
// prepare the email
let emailHtmlTemplate = await readFile(__dirname + "/emailTemplates/monthlyTreeReportEmail.html").then((result) => {
if (!result) {
log('Unable to load HTML template');
throw 'Unable to load HTML template'
}
return result
}).catch(err => {
log('Unable to load HTML template', err)
})
// Prepare data for template placeholders
let emailData = {
"enterpriseName": enterpriseDetails.name,
"monthDate": monthDate,
"treeCount": treeCount,
"projectName": projectDetails.reforestationProjectCountry_en,
"organisationName": projectDetails.reforestationCompanyName_en,
"contactName": enterpriseDetails.contact.name,
"contactEmail": enterpriseDetails.contact["email address"],
"year": monthDate.slice(0,4),
};
// Inject data into the template
let templateHtml = Handlebars.compile(emailHtmlTemplate.toString());
let bodyHtml = templateHtml(emailData);
// Prepare SES Parameters
let params = {
Destination: {
ToAddresses: [emailData.contactEmail]
},
Message: {
Body: {
Text: { Data: `Enterprise Name: ${emailData.enterpriseName}, Trees: ${treeCount}`
},
Html: {
Data: bodyHtml
}
},
Subject: {
Data: `Monthly Report Email for ${emailData.enterpriseName} Trees: ${treeCount}`
}
},
Source: "info@DigitalHumani.com"
};
try {
await ses.sendEmail(params).promise();
return Responses._200({message: 'email sent'})
} catch (error) {
console.log('error', error);
return Responses._400({message: 'failed to send email'})
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment