Skip to content

Instantly share code, notes, and snippets.

@imhogan
Last active December 5, 2017 06:33
Show Gist options
  • Save imhogan/efb3c0a6277fada06b2f992a0f299de0 to your computer and use it in GitHub Desktop.
Save imhogan/efb3c0a6277fada06b2f992a0f299de0 to your computer and use it in GitHub Desktop.
Secret Santa / Kris Kringle Generator
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);
}
};
{
"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
}
@imhogan
Copy link
Author

imhogan commented Dec 3, 2017

This is a simple Kris Kringle generator Javascript AWS Lamba function.

See the comment block in index.js for usage instructions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment