Last active
December 5, 2017 06:33
-
-
Save imhogan/efb3c0a6277fada06b2f992a0f299de0 to your computer and use it in GitHub Desktop.
Secret Santa / Kris Kringle Generator
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
exports.handler = (event, context, callback) => { | |
// This code implements an AWS Lambda Secret Santa / Kris Kringle Generator which emails allocations | |
// using AWS SES. You need to set up a verified email sender first and make sure your account is out | |
// of SES Sandbox mode. See http://docs.aws.amazon.com/ses/latest/DeveloperGuide/request-production-access.html | |
// for details. | |
// | |
// Features: | |
// 1. All configuration data is provided by the invokation event. This includes the set of participants. | |
// | |
// 2. Logging is provided for debug purposes, but set logLevel in the invokation event to 1 for your | |
// production run. | |
// | |
// 3. The mode property of the test event can be: | |
// "test" - to test allocation - set logLevel to 4 to see the allocations made. | |
// "do-it" - to send the emails - set logLevel to 1 so no-one but each recipient will know their | |
// allocation. | |
// "dryrun" - or any value besides "test" or "do-it" to send each email to the email sender address. | |
// | |
// 4. The participants is an array of either indvidual particpants, or arrays of participant groups. | |
// No particpant in a group will be allocated as Kris Kringle for another member of that group. | |
// Typically groups could be couples. | |
// | |
// 5. Each particpant is represented by an object having at least a "name" and "email" property. | |
// | |
// 5. The process is to create an array of recipients, where initially each participant is their own | |
// Kris Kringle. Then the array of recipients is shuffled randomly until each participant is allocated | |
// a recipient who is neither themselves nor a member of the same group. If 100 shuffles does not | |
// achieve this situation then an error is thrown. | |
// | |
// 6. The "SES_Config" property of the invokation event provides the verified email sender details | |
// including the region the sender is verified in. | |
// | |
// 7. The "message" property contains the subject line and text body for the email to be sent. Note | |
// the body is an array of lines which will be joined with a new-line character to form the body. | |
// The placeholders $name and $recipient will be replaced with the participant's name and the | |
// name of their allocated gift recipient. | |
// ================================================================================================ | |
console.log('Version 0.0.2'); | |
// Simple logger function which takes account of the logLevel in the invokation event. | |
function myLog(level, message) { | |
if (event.logLevel === null || level < event.logLevel) { | |
console.log(message); | |
} | |
} | |
// Simple function to check if an opject has a required set of properties | |
function checkProperties(name, obj, props) { | |
myLog(3, 'Checking properties for '+name); | |
for(var p = 0; p < props.length; p++) { | |
if (obj[props[p]] == null) { | |
throw { | |
name:'Missing Property' | |
,description:'Object '+name+' is missing required property "'+props[p]+'"' | |
}; | |
} | |
} | |
myLog(3, 'Checked properties for '+name); | |
return true; | |
} | |
// Function to replace placeholders of the form $variable with values from an array of objects | |
function applyContext(textBlock, givenContext) { | |
var result = textBlock+""; | |
if (givenContext && givenContext.length) { | |
for (var c=0; c<givenContext.length; c++) { | |
for (var v in givenContext[c]) { | |
var pattern = '$'+v; | |
while (result.indexOf(pattern) >= 0) { | |
result = result.replace(pattern, givenContext[c][v]); | |
} | |
} | |
} | |
return result.replace(/^\s+|\s+$/g, ''); | |
} | |
return result; | |
} | |
// Define function to create the Kris Kringle object | |
function krisKringle() { | |
// Private properties | |
var gifters = []; | |
var recipients = []; | |
var group = 0; | |
// Function to add a participant or an array of participants in a group. | |
function _add(participant) { | |
if (typeof participant.length === 'number') { | |
myLog(3 , 'Adding group '+group+' of '+participant.length+' members'); | |
for (var p=0; p<participant.length; p++) { | |
_add(participant[p]); | |
} | |
group++; | |
} | |
else { | |
if (checkProperties("participant", participant, ["name", "email"])) { | |
myLog(3 , 'Adding participant '+participant.name) | |
participant.id = gifters.length; | |
participant.group = group; | |
gifters[participant.id] = participant; | |
recipients[participant.id] = participant.id; | |
myLog(3 , 'participant['+participant.id+'] is '+gifters[participant.id].name+' in group '+gifters[participant.id].group); | |
} | |
} | |
} | |
// Function to add all single participants & groups of participants | |
function _addParticipants(participants) { | |
for (var p = 0; p < participants.length; p++) { | |
_add(participants[p]); | |
group++; | |
} | |
myLog(3 , 'Added '+gifters.length+' participants'); | |
return _self; | |
} | |
// Randomly shuffles array - using Fisher-Yates Shuffle | |
function _shuffle(array) { | |
var m = array.length, t, i; | |
while (m > 0) | |
{ | |
i = Math.floor(Math.random() * m--); | |
t = array[m]; | |
array[m] = array[i]; | |
array[i] = t; | |
} | |
return array; | |
} | |
// Function to allocate Kris Kringles | |
function _allocate() { | |
// Check that each gifter's recipient is not themselves or a member of the same group | |
function checkRecipients() { | |
for(var g = 0; g < gifters.length; g++) { | |
if(recipients[g] === g || gifters[recipients[g]].group === gifters[g].group) | |
return false; | |
gifters[g].recipient = gifters[recipients[g]].name; | |
} | |
return true; | |
} | |
// Shuffle recipients until all okay - or limit reached. | |
var limit = 100; | |
do { | |
recipients = _shuffle(recipients); | |
} while(!checkRecipients() || limit-- === 0); | |
if (limit === 0) { | |
throw { | |
name:'No shuffle worked!' | |
,description:'Unable to shuffle recipients to meet check criteria!' | |
}; | |
} | |
myLog(0, 'Allocated Kris Kringles'); | |
myLog(3, 'Results:\n'); | |
for(var g = 0; g < gifters.length; g++) { | |
myLog(3, gifters[g].name + '<'+ gifters[g].email + '> is Kris Kringle for '+ gifters[g].recipient + '\n'); | |
} | |
return _self; | |
} | |
// Function to process each participant - eg to send emails. | |
function _forEachParticipant(fn) { | |
for(var g = 0; g < gifters.length; g++) { | |
fn(gifters[g]); | |
} | |
} | |
// Self object used to hide private variables. | |
var _self = { | |
addParticipants: _addParticipants, | |
allocate: _allocate, | |
forEachParticipant: _forEachParticipant | |
}; | |
return _self; | |
} | |
// Function to create an email parameters object for a gifter. | |
function makeKrisKringleEmail(gifter) { | |
return { | |
Destination: { | |
ToAddresses: [ | |
(event.mode === 'do-it' ? gifter.email : event.SES_Config.from.email) | |
] | |
}, | |
Message: { | |
Subject: { | |
Data: event.message.subject, | |
Charset: 'UTF-8' | |
}, | |
Body: { | |
Text: { | |
Data: applyContext(event.message.body.join("\n"),[gifter]), | |
Charset: 'UTF-8' | |
} | |
} | |
}, | |
Source: event.SES_Config.from.email, | |
ReplyToAddresses: [ | |
event.SES_Config.from.name + '<' + event.SES_Config.from.email + '>' | |
] | |
}; | |
} | |
try { | |
// Check the event has the required properties. | |
checkProperties("event", event, ["SES_Config", "message", "participants"]); | |
checkProperties("SES_Config", event.SES_Config, ["from", "region"]); | |
checkProperties("SES_Config.from", event.SES_Config.from, ["email", "name"]); | |
checkProperties("message", event.message, ["subject", "body"]); | |
// Create and initialise the krisKringle object, and allocate recipients. | |
var myKrisKringle = krisKringle() | |
.addParticipants(event.participants) | |
.allocate(); | |
if (event.mode === 'test') { | |
callback(null, "Test run complete"); | |
} | |
else { | |
// Initialise AWS services required. | |
var aws = require('aws-sdk'); | |
aws.config.update({region: event.SES_Config.region}); | |
var ses = new aws.SES(); | |
// Send the emails | |
myKrisKringle.forEachParticipant(function(gifter) { | |
ses.sendEmail(makeKrisKringleEmail(gifter), function (err, data) { | |
if (err) { | |
myLog(0, err, err.stack); | |
callback(err, 'Internal Error: The email could not be sent.'); | |
} else { | |
myLog(2, data); // successful response | |
} | |
} | |
); | |
} | |
); | |
callback(null, 'All emails were successfully sent.'); | |
} | |
} | |
catch (err) { | |
myLog(0, err, err.stack); | |
callback(err, err.description); | |
} | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"SES_Config": { | |
"from": { | |
"email": "me@some.ses.domain", | |
"name": "J Blogs" | |
}, | |
"region": "us-east-1" | |
}, | |
"message": { | |
"subject": "Your Kris Kringe Allocation", | |
"body": [ | |
"Dear $name,", | |
"You are to be Kris Kringle for $recipient.", | |
"Please click on this link (...) to confirm you have received this message." | |
] | |
}, | |
"participants": [ | |
[ | |
{ | |
"name": "name1", | |
"email": "email1" | |
}, | |
{ | |
"name": "spouse1", | |
"email": "email2" | |
} | |
], | |
[ | |
{ | |
"name": "name3", | |
"email": "email3" | |
}, | |
{ | |
"name": "spouse2", | |
"email": "email4" | |
} | |
], | |
[ | |
{ | |
"name": "name5", | |
"email": "email5" | |
}, | |
{ | |
"name": "spouse3", | |
"email": "email6" | |
} | |
] | |
], | |
"mode": "test", | |
"logLevel": 4 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is a simple Kris Kringle generator Javascript AWS Lamba function.
See the comment block in index.js for usage instructions.