Simon MacDonald
@macdonst
🇨🇦
✉️ Create a reminder email with game time and location (10 minutes)
📥 Collect responses from those who can't play (20 minutes)
🔍 Find spares (20 minutes)
📋 Create a roster for the game (20 minutes)
✉️ Automate reminder email with game time and location (-10 minutes)
🔍 Automate email to find spares (-10 minutes)
📋 Partially automate roster creation (-10 minutes)
🏒 Be able to send emails
🏒 Perform CRUD operations on a JSON Database
🏒 Create a spreadsheet
const nodemailer = require('nodemailer');
const { google } = require('googleapis');
const OAuth2 = google.auth.OAuth2;
exports.handler = async function(event, context) {
const clientId = process.env.CLIENT_ID;
const clientSecret = process.env.CLIENT_SECRET;
const refresh_token = process.env.REFRESH_TOKEN;
const mailUser = process.env.MAIL_USER;
const oauth2Client = new OAuth2(
clientId,
clientSecret,
'https://developers.google.com/oauthplayground'
);
oauth2Client.setCredentials({ refresh_token });
const tokens = await oauth2Client.refreshAccessToken();
const accessToken = tokens.credentials.access_token;
const smtpTransport = nodemailer.createTransport({
service: 'gmail',
auth: {
type: 'OAuth2',
user: mailUser,
clientId,
clientSecret,
refreshToken: refresh_token,
accessToken: accessToken
}
});
return smtpTransport
.sendMail(event.template)
.then(response => {
smtpTransport.close();
return response;
})
.catch(error => {
smtpTransport.close();
return error;
});
};
Because of the spam abuse that has historically been sent from people using EC2 instances, virtually ALL popular mail providers block the receipt of email from EC2 instances. The world of email and anti-spam measures is part-technical, part-political.
const nodemailer = require('nodemailer');
const { google } = require('googleapis');
const OAuth2 = google.auth.OAuth2;
module.exports = async function(context, template) {
const clientId = process.env.CLIENT_ID;
const clientSecret = process.env.CLIENT_SECRET;
const refresh_token = process.env.REFRESH_TOKEN;
const mailUser = process.env.MAIL_USER;
context.log(
'JavaScript HTTP trigger function processed a request using environment vars.'
);
const oauth2Client = new OAuth2(
clientId,
clientSecret,
'https://developers.google.com/oauthplayground'
);
oauth2Client.setCredentials({ refresh_token });
const tokens = await oauth2Client.refreshAccessToken();
const accessToken = tokens.credentials.access_token;
const smtpTransport = nodemailer.createTransport({
service: 'gmail',
auth: {
type: 'OAuth2',
user: mailUser,
clientId,
clientSecret,
refreshToken: refresh_token,
accessToken: accessToken
}
});
return smtpTransport
.sendMail(template)
.then(response => {
smtpTransport.close();
context.done(null, response);
})
.catch(error => {
smtpTransport.close();
context.done(null, error);
});
};
[
{
"facility": "Sensplex - Canadian Tire",
"date": "2019-05-03",
"time": "10:15 PM"
},
{
"facility": "Sensplex - Bradley's Insurance Arena",
"date": "2019-05-10",
"time": "9:30 PM"
},
{
"facility": "Sensplex - Bradley's Insurance Arena",
"date": "2019-05-17",
"time": "10:00 PM",
"spares": [
{ "name": "Byron Carrillo", "email": "bcd@nope.ca" },
{ "name": "Matthew Pham", "email": "matthewpham@mattpham.com" }
]
}
]
{
"bindings": [
{
"name": "template",
"type": "activityTrigger",
"direction": "in"
},
{
"name": "gamesDocument",
"type": "cosmosDB",
"databaseName": "Games",
"collectionName": "Items",
"createIfNotExists": true,
"connectionStringSetting": "CosmosDB",
"direction": "in"
},
…
],
"disabled": false
}
const dayjs = require('dayjs');
const Database = require('../utils/db');
module.exports = async function(context, req) {
const client = new Database();
const players = context.bindings.playersDocument;
const games = context.bindings.gamesDocument;
const {
facility: facility,
date: gameDate,
time: gameTime
} = await client.getNextGame(games);
// If there is no game this Friday skip sending a reminder
if (gameDate === null) {
return {
body: 'No Game'
};
}
const allSpares = context.bindings.sparesDocument;
const spares = allSpares.filter(spare => spare.playing.includes(gameDate));
const email = {
from: 'me@gmail.com',
to: generateEmailList(players),
cc: generateEmailList(spares),
subject: `Hockey: Friday ${dayjs(gameDate).format(
'MMMM D'
)} ${gameTime} ${facility}`,
html: generateBody(players, facility, gameDate, gameTime, spares),
generateTextFromHTML: true
};
context.log(email);
context.done(null, email);
};
const df = require('durable-functions');
module.exports = df.orchestrator(function*(context) {
const template = yield context.df.callActivity('template', 'test');
const email = yield context.df.callActivity('email', template);
return email;
});
⏰
Cron
Jobs
{
"bindings": [
{
"name": "sheetTimer",
"type": "timerTrigger",
"direction": "in",
"schedule": "0 0 6 * * 5"
},
{
"name": "playersDocument",
"type": "cosmosDB",
"databaseName": "Players",
"collectionName": "Items",
"createIfNotExists": true,
"connectionStringSetting": "CosmosDB",
"direction": "in"
},
…
]
}
const { google } = require('googleapis');
const OAuth2 = google.auth.OAuth2;
const dayjs = require('dayjs');
// read what database class we want from runtime vars
const Database = require('../utils/db');
module.exports = async function(context, req) {
const clientId = process.env.SHEETS_CLIENT_ID;
const clientSecret = process.env.SHEETS_CLIENT_SECRET;
const refresh_token = process.env.SHEETS_REFRESH_TOKEN;
const oauth2Client = new OAuth2(
clientId,
clientSecret,
'https://developers.google.com/oauthplayground'
);
oauth2Client.setCredentials({ refresh_token });
const tokens = await oauth2Client.refreshAccessToken();
const accessToken = tokens.credentials.access_token;
oauth2Client.credentials = {
access_token: accessToken
};
const client = new Database();
const players = context.bindings.playersDocument;
const games = context.bindings.gamesDocument;
const { date: gameDate } = await client.getNextGame(games);
const allSpares = context.bindings.sparesDocument;
const spares = allSpares.filter(spare => spare.playing.includes(gameDate));
const playing = generateAvailable(players, spares, gameDate);
const resource = {
properties: {
title: `Roster for ${dayjs(gameDate).format('MMMM D')}`
}
};
const sheets = google.sheets({ version: 'v4', auth: oauth2Client });
const spreadsheetId = await createSpreadsheet(resource, sheets);
const appendResult = await appendValues(spreadsheetId, playing, sheets);
const formattingResult = await conditionalFormatting(spreadsheetId, sheets);
console.log('we have success');
return {
body: formattingResult
};
};
Continue the conversation with me on:
Slides available at:
Code at: