Skip to content

Instantly share code, notes, and snippets.

@ccjmne
Last active May 17, 2017 17:25
Show Gist options
  • Save ccjmne/096a1e2e8198d0efc2a6d93178b5261e to your computer and use it in GitHub Desktop.
Save ccjmne/096a1e2e8198d0efc2a6d93178b5261e to your computer and use it in GitHub Desktop.
AWS Lambda function for NCLSDevelopment's Orca solution - uses SES to send a confirmation email to a given address
const AWS = require('aws-sdk');
ses = new AWS.SES();
const debug = false;
// Base64 'Orca — Comptabilité' is 'T3JjYSDigJQgQ29tcHRhYmlsaXTDqQ=='
const Source_64 = 'T3JjYSDigJQgQ29tcHRhYmlsaXTDqQ==';
function _proxyLambdaCallback(context, status, body) {
// TODO: what context method to use on failure?
(status >= 200 && status < 300 ? context.succeed : context.succeed)({
statusCode: status,
headers: {
'Access-Control-Allow-Methods': 'GET, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token',
'Access-Control-Allow-Origin': '*'
},
body: JSON.stringify(body)
});
}
function _error(context, callback, reason) {
_proxyLambdaCallback(context, 400, {
outcome: 'failure',
reason: reason
});
callback(reason);
}
function _success(context, callback, recipient) {
_proxyLambdaCallback(context, 200, {
outcome: 'success',
recipient: recipient
});
callback(null, 'mailto completed successfully -- email sent to ' + recipient);
}
function send(recipient, messageTitle, messageBody, callback) {
ses.sendEmail({
Source: '"=?utf-8?B?' + Source_64 + '?=" <accounting@orca-solution.com>',
Destination: {
ToAddresses: [recipient]
},
ReturnPath: 'nclsdevelopment@gmail.com',
Message: {
Subject: {
Charset: 'UTF-8',
Data: messageTitle
},
Body: {
Html: {
Charset: 'UTF-8',
Data: messageBody
}
}
}
}, callback);
}
function notifyMe(recipient, invoice, callback) {
ses.sendEmail({
Source: '"=?utf-8?B?' + Source_64 + '?=" <accounting@orca-solution.com>',
Destination: {
ToAddresses: ['nclsdevelopment@gmail.com']
},
Message: {
Subject: {
Charset: 'UTF-8',
Data: 'Nouvelle prise de contact -- ' + recipient.organization
},
Body: {
Html: {
Charset: 'UTF-8',
Data: '\
<table> \
<tbody> \
<tr><td style="padding: 3px 5px">Nom</td><td style="padding: 3px 5px"><code style="font-size: 1.2em;">' + recipient.name + '</code></td></tr> \
<tr><td style="padding: 3px 5px">Organisation</td><td style="padding: 3px 5px"><code style="font-size: 1.2em;">' + recipient.organization + '</code></td></tr> \
<tr><td style="padding: 3px 5px">Courriel</td><td style="padding: 3px 5px"><code style="font-size: 1.2em;">' + recipient.address + '</code></td></tr> \
</tbody> \
</table> \
' + invoice
}
}
}
}, callback);
}
function _compute(value, yearly, discount) {
return yearly ? value * (discount ? 11 : 12) : value;
}
function _center(content) {
return ' \
<table width="100%" border="0" cellspacing="0" cellpadding="0"> \
<tr> \
<td align="center"> \
' + content + ' \
</td> \
</tr> \
</table>';
}
exports.handler = (event, context, callback) => {
const error = _error.bind(_error, context, callback),
success = _success.bind(_success, context, callback);
function mock(body) {
context.succeed({
statusCode: 200,
headers: {
'Access-Control-Allow-Methods': 'GET, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token',
'Access-Control-Allow-Origin': '*'
},
body: body
});
callback(null, '`mailme` mocked successfully');
}
var body;
try {
body = JSON.parse(event.body);
} catch (e) {
error('Invalid payload -- expected JSON-formatted data');
return;
}
const plan = body.plan,
recipient = body.recipient;
const hasOwnProperties = (object, properties) => properties.reduce((agg, cur) => agg && object.hasOwnProperty(cur), true);
if (!recipient || !plan || !hasOwnProperties(recipient, ['name', 'organization', 'address']) || !hasOwnProperties(plan, ['name', 'display', 'base', 'yearly']) || (plan.name ===
'business' && !hasOwnProperties(plan, ['upper', 'extra']))) {
error('Bad argument -- not enough information provided');
return;
}
const messageTitle = 'Acquisition de la solution Orca';
var messageBody = _center(
'\
<img src="https://s3-eu-west-1.amazonaws.com/orca-resources/logo_complete.png" style="max-height: 60px" /> \
<h1 style="font-weight: lighter;">Orca &mdash; Gestion des Formations S&eacute;curit&eacute;</h1>'
);
messageBody += '\
<hr /> \
<p style="text-align: justify;"> \
&Agrave; l\'attention de <em style="background-color: yellow;">' + recipient.name + '</em>. \
<br />Vous recevez ce message car vous avez exprim&eacute; de l\'int&eacute;r&ecirc;t pour la solution Orca et envisagez de l\'acqu&eacute;rir pour votre organisation&nbsp;: <em style="background-color: yellow;">' + recipient.organization + '</em>. \
<br />Voici le d&eacute;tail du mod&egrave;le sur lequel votre attention s\'est port&eacute;e&nbsp;: \
</p> \
<h2 style="border-bottom: 1px dashed grey;">Solution Orca &mdash; mod&egrave;le <em>' + plan.display + '</em></h2>';
messageBody += {
micro: '\
<p style="text-align: justify;"> \
Le mod&egrave;le <em>Micro</em> est le plan le moins on&eacute;reux pour la solution Orca. \
<br /> Il permet aux petites entit&eacute;s d\'acc&eacute;der &agrave; la solution &agrave; un moindre prix. Sa particularit&eacute; essentielle concerne la limite \
du nombre d\'agents g&eacute;r&eacute;s&nbsp;: pas plus de 500. Comme les deux autres plans, il offre un acc&egrave;s &agrave; un nombre illimit&eacute; d\'utilisateurs \
&agrave; tout moment. Sa configuration mat&eacute;rielle est amplement suffisante pour convenir m&ecirc;me &agrave; un usage intensif et ce mod&egrave;le \
inclut &eacute;galement l\'acc&egrave;s &agrave; toutes les mises &agrave; jour de la solution, majeures comme mineures. \
</p>',
business: '\
<p style="text-align: justify;"> \
Le plan <em>Business</em> est le mod&egrave;le standard pour la solution Orca. \
<br />Il offre une configuration mat&eacute;rielle amplement suffisante pour convenir m&ecirc;me &agrave; un usage intensif. Les donn&eacute;es sont r&eacute;pliqu&eacute;es \
et sauvegard&eacute;es quotidiennement &mdash; avec une semaine de disponibilit&eacute;. Ce mod&egrave;le inclue &eacute;galement l\'acc&egrave;s &agrave; toutes \
les mises &agrave; jour de la solution, majeures comme mineures. \
</p>',
partner: '\
<p style="text-align: justify;"> \
Le plan <em>Partner</em> est le mod&egrave;le haut-de-gamme pour la solution Orca. \
<br />Il offre, en plus des fonctionnalit&eacute;s du plan <em>Business</em>&nbsp;: \
</p> \
<ol type="I"> \
<li>une configuration mat&eacute;rielle d&eacute;di&eacute;e et de haute performance</li> \
<li>un monitoring des tentatives d\'intrusion</li> \
<li>un nombre d\'agents actifs et comptes utilisateurs illimit&eacute;s</li> \
<li>un acc&egrave;s participatif au tableau de bord de d&eacute;veloppement de la solution</li> \
</ol> \
<p style="text-align: justify;"> \
Avec ce plan, soyez impliqu&eacute;s dans l\'&eacute;volution d\'Orca en b&eacute;n&eacute;ficiant d\'un contact direct avec l\'&eacute;quipe de d&eacute;veloppement. Discutez des am&eacute;liorations \
que vous voudriez voir appara&icirc;tre et suivez en d&eacute;tail l\'avancement de la r&eacute;solution de bugs ou de l\'int&eacute;gration de nouvelles fonctionnalit&eacute;s. \
</p>'
}[plan.name];
const invoice = '\
<h2 style="border-bottom: 1px dashed grey;">Estimation de votre devis</h2>'
+ _center('\<table style="max-width: 600px; border: 1px solid lightgrey; border-radius: 2px; border-collapse: collapse; box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2)"> \
<thead style="background-color: #eee; border-bottom: 1px solid lightgrey;"> \
<tr> \
<th style="padding: 5px 16px;">Description</th> \
<th style="padding: 5px 16px;" width="1%">Montant&nbsp;' + (plan.yearly ? 'annuel' : 'mensuel') + '</th> \
</tr> \
</thead> \
<tbody> \
<tr> \
<td style="padding: 5px 16px;">Mod&egrave;le <em>' + plan.display + '</em></td> \
<td style="padding: 5px 16px; text-align: right;">+ ' + _compute(plan.base, plan.yearly) + '&euro;</td> \
</tr> \
<tr style="display: ' + (plan.name === 'business' ? 'visible' : 'none') + ';"> \
<td style="padding: 5px 16px;">Jusqu\'&agrave; <em>' + plan.upper + '</em> agents actifs</td> \
<td style="padding: 5px 16px; text-align: right;">+ ' + _compute(plan.extra, plan.yearly) + '&euro;</td> \
</tr> \
<tr style="background-color: #eee; border-top: 1px solid lightgrey; border-bottom: 1px solid lightgrey; display: ' + (plan.name === 'business' && plan.yearly ? 'visible' : 'none') + ';"> \
<td style="padding: 5px 16px; text-align: right;">Sous-total</td> \
<td style="padding: 5px 16px; text-align: right;">+ ' + _compute(plan.base + (plan.name === 'business' ? plan.extra : 0), plan.yearly) + '&euro;</td> \
</tr> \
<tr style="display: ' + (plan.yearly ? 'visible' : 'none') + ';"> \
<td style="padding: 5px 16px;">Facturation <em>annuelle</em></td> \
<td style="padding: 5px 16px; text-align: right;">&minus; ' + plan.base + '&euro;</td> \
</tr> \
<tr style="background-color: #eee; border-top: 1px solid lightgrey;"> \
<td style="padding: 5px 16px; text-align: right;">Total ' + (plan.yearly ? 'annuel' : 'mensuel') + '</td> \
<td style="padding: 5px 16px; text-align: right;">+ ' + _compute(plan.base + (plan.name === 'business' ? plan.extra : 0), plan.yearly, true) + '&euro;</td> \
</tr> \
</tbody> \
</table> \
<small style="float: right;">Prix exprim&eacute;s hors taxe</small>');
messageBody += invoice;
messageBody += '\
<p style="text-align: justify;"> \
Merci pour l\'int&eacute;r&ecirc;t que vous portez &agrave; cette solution. \
<br />Notre &eacute;quipe prendra contact avec vous d&egrave;s que possible pour discuter de votre projet. \
</p> \
<hr /> \
<p><small>Ceci est un e-mail automatis&eacute; &mdash; ne pas y r&eacute;pondre.</small></p>';
messageBody = '<div style="font-family: sans-serif;">' + messageBody + '</div>';
if (debug) {
//notifyMe(recipient, invoice, (err, data) => err ? error(err) : success(data));
mock(messageBody);
} else {
send(recipient.address, messageTitle, messageBody, function (err, data) {
if (err) {
error(err);
return;
}
notifyMe(recipient, invoice, success.bind(success, recipient.address));
});
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment