├── .gitignore
├── README.MD
├── countitems
├── event.json
└── index.js
├── dataset
├── import.sh
└── restaurants.json
├── email
├── .gitignore
├── Templates
│ └── Restaurants.html
├── config.js
├── event.json
├── index.js
├── package.json
└── zip.sh
├── exec_sf.sh
├── restaurants
├── .gitignore
├── event.json
├── index.js
├── ll.sh
├── package.json
└── zip.sh
├── sms
├── .gitignore
├── config.js
├── event.json
├── index.js
├── package.json
└── zip.sh
└── statemachine.json
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | *.zip
3 |
4 | restaurants/ll_local.sh
5 |
6 | statemachine_local.json
7 |
8 | sms/_index.js
9 |
10 | email/ll_local.sh
11 |
12 | API_Gateway_TestInput.txt
13 |
14 | exec_sf_local.sh
15 |
16 | restaurants/claudia.json
17 | restaurants/package-lock.json
18 |
--------------------------------------------------------------------------------
/README.MD:
--------------------------------------------------------------------------------
1 | # AWS Step Function Sample State Machine with MongoDB Atlas
2 |
3 | This repository provides the source code of 3 [AWS Lambda](https://aws.amazon.com/lambda/) functions written in Node.js and combined into one AWS Step Function State Machine to illustrate the workflow and orchestration capabilities of [AWS Step Functions](https://aws.amazon.com/step-functions).
4 |
5 | 
6 |
7 | ## Software Requirements
8 |
9 | In order to use this repository, you will need:
10 |
11 | 1. A local computer with [Node.js](https://nodejs.org)
12 | 2. An [Amazon Web Services](https://aws.amazon.com/) account
13 | 3. A [MongoDB Atlas](https://www.mongodb.com/atlas?jmp=adref) cluster (see this [video](https://youtu.be/_d8CBOtadRA) to get started with a free M0 cluster)
14 | 4. A [Twilio](https://www.twilio.com/) account. A trial account is fine, but you will need to add the phone numbers you'd like to send text messages to on the [Verified Called IDs page](https://www.twilio.com/console/phone-numbers/verified).
15 |
16 | __Note__: this repository was built and tested on Mac OS X Sierra and Node.js 6.9.4
17 |
18 | ## Setup
19 |
20 | ### MongoDB Atlas
21 |
22 | Since this Step Function relies on an existing dataset of restaurants, you must first import a sample dataset to your MongoDB Atlas cluster:
23 |
24 | 1. Customize the [import.sh](/dataset/import.sh) script to import the [restaurants.json](/dataset/restaurants.json) file.
25 | 1. Alternatively, you can download [this dataset](https://raw.githubusercontent.com/mongodb/docs-assets/primer-dataset/primer-dataset.json) (also used in the [MongoDB Shell Tutorial](https://docs.mongodb.com/getting-started/shell/import-data)) and follow the next steps below.
26 | 1. Edit the following command line by replacing the ``$`` parameters with your own values:
27 | ``mongoimport --host $ATLAS_CLUSTER_URI --ssl -u $ATLAS_ADMIN -p $ATLAS_PWD --authenticationDatabase admin --db travel --collection restaurants --drop --file primer-dataset.json``
28 | 1. Run your customized ``mongoimport`` command above to import the dataset to your Atlas cluster.
29 | 1. (Optional) verify with [MongoDB Compass](https://www.mongodb.com/download-center?jmp=adref#compass) that your ``restaurants`` collection was properly imported. The [MongoDB Compass with Atlas](https://www.mongodb.com/blog/post/your-mongodb-atlas-toolkit-logging-into-mongodb-atlas-with-compass?jmp=adref) blog post might help you configure Compass with MongoDB Atlas.
30 |
31 | ### Amazon Web Services
32 | This repository is made of the following Lambda functions:
33 |
34 | - A **[GetRestaurants](https://github.com/raphaellondner-mongodb/aws-stepfunctions-samples/tree/master/restaurants)** function that queries a collection of restaurants stored in a [MongoDB Atlas](https://www.mongodb.com/atlas?jmp=adref) database.
35 | - A **[CountItems](https://github.com/raphaellondner-mongodb/aws-stepfunctions-samples/tree/master/countitems)** helper function whose role is solely to count the number of items in an array.
36 | - A **[SendBySMS](https://github.com/raphaellondner-mongodb/aws-stepfunctions-samples/tree/master/sms)** function that sends a text message using [SMS by Twilio](https://www.twilio.com/sms).
37 | - A **[SendByEmail](https://github.com/raphaellondner-mongodb/aws-stepfunctions-samples/tree/master/email)** function that sends an email using [AWS Simple Email Service](https://aws.amazon.com/ses).
38 |
39 | The __Common setup tasks__ below lists the tasks that you must perform for each of the 3 Lambda functions.
40 |
41 | The __Function-specific configuration tasks__ section below lists the tasks that are specific to each Lambda function.
42 |
43 | #### Common setup tasks
44 |
45 | For __each__ of the 4 Lambda functions listed above (except the CountItems function), please perform the following tasks:
46 |
47 | 1. Navigate to the root folder of the Lambda function (i.e. _restaurants_, _email_ and _sms_) and run ``npm install`` from a Terminal console to restore the required Node.js dependencies.
48 | 1. Run the ``zip.sh`` script to zip all the files required by AWS Lambda (you might have to run ``chmod 744 zip.sh`` first to allow the ``zip.sh`` file to run). This will generate an ``archive.zip`` file in every function folder.
49 | 1. Create a new AWS Lambda function and use the ``archive.zip`` file as the *Upload a .ZIP file* **Code entry type**. Refer to [this AWS Lambda tutorial](https://www.mongodb.com/blog/post/serverless-development-with-nodejs-aws-lambda-mongodb-atlas) for additional help on AWS Lambda development and deployment.
50 | 4. Increase the timeout to 5 or 6s (optional but strongly recommended)
51 |
52 | #### Function-specific configuration tasks
53 |
54 | Here are the specific configuration tasks you must make for each Lambda function:
55 |
56 | - **GetRestaurants** function:
57 | - Add a ``MONGODB_ATLAS_CLUSTER_URI`` environment variable with the value of your MongoDB Atlas connection string (see [this blog post](https://www.mongodb.com/blog/post/serverless-development-with-nodejs-aws-lambda-mongodb-atlas) for further details).
58 | - Copy/paste the contents of the [``event.json``](https://github.com/rlondner/aws-stepfunctions-samples/blob/master/restaurants/event.json) file in the Lambda *Input test event* window.
59 | - Note how the Node.js code is using [performance optimization enhancements](http://bit.ly/lambdaperf) to speed up queries after the first call to the Lambda function.
60 | - **CountItems** function:
61 | - Create a blank Lambda function and copy/paste the content of the [``index.js``](https://github.com/rlondner/aws-stepfunctions-samples/blob/master/countitems/index.js) file into the AWS Lambda code editor.
62 | - Copy/paste the contents of the [``event.json``](https://github.com/rlondner/aws-stepfunctions-samples/blob/master/countitems/index.js) file in the Lambda *Input test event* window.
63 | - **SendBySMS** function:
64 | - Add a ``TWILIO_ACCOUNT_SID`` environment variable with the value of your Twilio account ID.
65 | - Add a ``TWILIO_AUTH_TOKEN`` environment variable with the value of your Twilio Auth Token.
66 | - Add a ``TWILIO_PHONE_NUMBER`` environment variable with the value of your Twilio Phone Number.
67 | - Copy/paste the contents of the [``event.json``](https://github.com/rlondner/aws-stepfunctions-samples/blob/master/sms/event.json) file in the Lambda *Input test event* window.
68 | - **SendByEmail** function:
69 | - You must first set up [AWS Simple Email Service](https://aws.amazon.com/ses/) in order for it to accept your sender email address as well as the recipient adress(es) you are planning to use.
70 | - Create an S3 bucket and in that S3 bucket, create a *Templates* folder. Upload the [__*Templates/Restaurants.html*__](https://github.com/rlondner/aws-stepfunctions-samples/blob/master/email/Templates/Restaurants.html) file into this *Templates* bucket.
71 | - Add an ``S3_BUCKET`` environment variable with the value of the S3 bucket you created in the previous step.
72 | - Add an ``FROM_ADDRESS`` environment variable with the value of SES-verified email address you want to use as your sender email address.
73 | - Copy/paste the [``event.json``](https://github.com/rlondner/aws-stepfunctions-samples/blob/master/email/event.json) file as your test event input and at least update the ``emailTo`` attribute with an SES-verified email address (and potentially the ``firstnameTo`` attribute too).
74 | - Make sure the IAM role you use to run your Lambda function includes the following permissions (created as an inline or managed policy):
75 |
76 | ```json
77 | {
78 | "Version": "2012-10-17",
79 | "Statement": [
80 | {
81 | "Effect": "Allow",
82 | "Action": [
83 | "ses:SendEmail"
84 | ],
85 | "Resource": "*"
86 | },
87 | {
88 | "Effect": "Allow",
89 | "Action": [
90 | "s3:GetObject"
91 | ],
92 | "Resource": "*"
93 | }
94 | ]
95 | }
96 | ```
97 |
98 | ### AWS Step Functions
99 |
100 | Once you have successfully created and tested the 4 Lambda functions above, head over (or go back) to the [AWS Step Functions setup and code walkthrough](http://bit.ly/mdbawssf) for the last mile!
101 |
--------------------------------------------------------------------------------
/countitems/event.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "address": {
4 | "building": "235-237",
5 | "street": "West 48 Street"
6 | },
7 | "borough": "Manhattan",
8 | "name": "La Masseria"
9 | },
10 | {
11 | "address": {
12 | "building": "315",
13 | "street": "West 48 Street"
14 | },
15 | "borough": "Manhattan",
16 | "name": "Maria'S Mont Blanc Restaurant"
17 | },
18 | {
19 | "address": {
20 | "building": "654",
21 | "street": "9 Avenue"
22 | },
23 | "borough": "Manhattan",
24 | "name": "Cara Mia"
25 | }
26 | ]
--------------------------------------------------------------------------------
/countitems/index.js:
--------------------------------------------------------------------------------
1 | exports.handler = (event, context, callback) => {
2 | console.log(event.length)
3 | callback(null, event.length);
4 | };
--------------------------------------------------------------------------------
/dataset/import.sh:
--------------------------------------------------------------------------------
1 | ATLAS_PRIMARY="%CLUSTER_NAME%-shard-00-00-%SUFFIX%.mongodb.net:27017"
2 | ATLAS_ADMIN="%ATLAS_CLUSTER_ADMIN_USERNAME%"
3 | ATLAS_PWD="%ATLAS_CLUSTER_ADMIN_PASSWORD%"
4 |
5 | mongoimport --host $ATLAS_PRIMARY --ssl -u $ATLAS_ADMIN -p $ATLAS_PWD --authenticationDatabase admin --db travel --collection restaurants --drop --file restaurants.json
--------------------------------------------------------------------------------
/email/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | node_modules/
3 |
--------------------------------------------------------------------------------
/email/Templates/Restaurants.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Matching Restaurants List
7 |
8 |
9 |
10 |
11 | Here is the list of restaurants matching your request!
12 |
13 |
14 |
15 | Name |
16 | Address |
17 | Zip |
18 | Borough/City |
19 |
20 | {{#restaurants}}
21 |
22 | {{name}} |
23 | {{address.building}} {{address.street}} |
24 | {{address.zipcode}} |
25 | {{borough}} |
26 |
27 | {{/restaurants}}
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/email/config.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var config = {
4 | "templateKey" : "Templates/Restaurants.html",
5 | "defaultSubject" : "Your list of restaurants"
6 | }
7 |
8 | module.exports = config
--------------------------------------------------------------------------------
/email/event.json:
--------------------------------------------------------------------------------
1 | {
2 | "firstnameTo": "Raphael",
3 | "emailTo": "raphael@example.com",
4 | "subject": "List of restaurants for {{firstnameTo}}",
5 | "restaurants": [
6 | {
7 | "address": {
8 | "building": "235-237",
9 | "street": "West 48 Street"
10 | },
11 | "borough": "Manhattan",
12 | "name": "La Masseria"
13 | },
14 | {
15 | "address": {
16 | "building": "315",
17 | "street": "West 48 Street"
18 | },
19 | "borough": "Manhattan",
20 | "name": "Maria'S Mont Blanc Restaurant"
21 | },
22 | {
23 | "address": {
24 | "building": "654",
25 | "street": "9 Avenue"
26 | },
27 | "borough": "Manhattan",
28 | "name": "Cara Mia"
29 | }
30 | ]
31 | }
--------------------------------------------------------------------------------
/email/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | var aws = require('aws-sdk');
4 | var Mustache = require('mustache');
5 |
6 | exports.handler = function (event, context, callback) {
7 |
8 | console.log("Event: " + JSON.stringify(event));
9 | var aws_region = process.env['AWS_DEFAULT_REGION'];
10 | if(aws_region == undefined) {
11 | aws_region = process.env['AWS_REGION_LOCAL'];
12 | }
13 | var s3_bucket = process.env['S3_BUCKET'];
14 | var from_address = process.env['FROM_ADDRESS'];
15 |
16 | aws.config.update({ region: aws_region });
17 |
18 | var config = require('./config.js');
19 |
20 | console.log('Loading template from ' + config.templateKey + ' in ' + s3_bucket);
21 |
22 | var s3 = new aws.S3();
23 | // Read the template file from S3
24 | s3.getObject({
25 | Bucket: s3_bucket,
26 | Key: config.templateKey
27 | }, function (err, data) {
28 | if (err) {
29 | // Error
30 | console.log(err, err.stack);
31 | context.fail('Internal Error: Failed to load template from s3.')
32 | } else {
33 | var templateBody = data.Body.toString();
34 |
35 | //generate dynamic content based on Mustache tags
36 | var subject = Mustache.render(event.subject, event);
37 | var message = Mustache.render(templateBody, event);
38 |
39 |
40 | var params = {
41 | Destination: {
42 | ToAddresses: [
43 | event.emailTo
44 | ]
45 | },
46 | Message: {
47 | Subject: {
48 | Data: subject,
49 | Charset: 'UTF-8'
50 | }
51 | },
52 | Source: from_address,
53 | ReplyToAddresses: [
54 | from_address
55 | ]
56 | };
57 |
58 | var fileExtension = config.templateKey.split(".").pop();
59 | if (fileExtension.toLowerCase() == 'html') {
60 | params.Message.Body = {
61 | Html: {
62 | Data: message,
63 | Charset: 'UTF-8'
64 | }
65 | };
66 | } else if (fileExtension.toLowerCase() == 'txt') {
67 | params.Message.Body = {
68 | Text: {
69 | Data: message,
70 | Charset: 'UTF-8'
71 | }
72 | };
73 | } else {
74 | context.fail('Internal Error: Unrecognized template file extension: ' + fileExtension);
75 | return;
76 | }
77 |
78 | var ses = new aws.SES();
79 |
80 | // Send the email
81 | ses.sendEmail(params, function (err, data) {
82 | if (err) {
83 | console.log(err, err.stack);
84 | callback('Internal Error: The email could not be sent. ' + err.message);
85 | } else {
86 | console.log(data); // successful response
87 | callback(null, 'The email was successfully sent to ' + event.emailTo);
88 | }
89 | });
90 | }
91 | });
92 | };
--------------------------------------------------------------------------------
/email/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "awslambda-email",
3 | "version": "1.0.0",
4 | "description": "An AWS Lambda function that sends an email through AWS SES (Simple Email Service)",
5 | "main": "email.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "Raphael Londner",
10 | "license": "Apache-2.0",
11 | "dependencies": {
12 | "mustache": "^2.3.0"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/email/zip.sh:
--------------------------------------------------------------------------------
1 | zip -r archive.zip node_modules/ index.js config.js
--------------------------------------------------------------------------------
/exec_sf.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # Courtesy of Paul Sears, Amazon Web Services
3 |
4 | EMAIL="test@example.com"
5 | PHONE="+15555555555"
6 | NAME="TestUser"
7 | STATEARN="arn:aws:states:us-west-2:ACCOUNT_ID:stateMachine:WhatsThisRestaurantAgain"
8 | API="https://API_GATEWAY_PREFIX.execute-api.us-west-2.amazonaws.com/STAGE_NAME/execution"
9 | STARTS=$1
10 | CU=$2
11 | ZIP=$3
12 |
13 | EXECUTIONNAME=`uuidgen`
14 |
15 | curl -X POST -d '{"input": "{ \"startsWith\": \"'$STARTS'\", \"cuisine\": \"'$CU'\", \"zipcode\": \"'$ZIP'\", \"phoneTo\": \"'$PHONE'\", \"firstnameTo\": \"'$NAME'\", \"emailTo\": \"'$EMAIL'\", \"subject\": \"List of restaurants for {{firstnameTo}}\" }","name": "'.$EXECUTIONNAME'","stateMachineArn": "'$STATEARN'"}' $API
16 |
--------------------------------------------------------------------------------
/restaurants/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | node_modules/
3 |
--------------------------------------------------------------------------------
/restaurants/event.json:
--------------------------------------------------------------------------------
1 | {
2 | "startsWith": "M",
3 | "cuisine": "Italian",
4 | "zipcode": "10036",
5 | "phoneTo": "+15555555555",
6 | "firstnameTo": "Raphael",
7 | "emailTo": "raphael@example.com",
8 | "subject": "List of restaurants for {{firstnameTo}}"
9 | }
--------------------------------------------------------------------------------
/restaurants/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | //const AWS = require('aws-sdk');
3 |
4 | var MongoClient = require('mongodb').MongoClient;
5 |
6 | //Performance optimization Step 1: declare the database connection object outside the handler method
7 | let cachedDb = null;
8 |
9 | let atlas_connection_uri = null;
10 |
11 | exports.handler = (event, context, callback) => {
12 |
13 | var uri = process.env['MONGODB_ATLAS_CLUSTER_URI'];
14 |
15 | console.log('remaining time =', context.getRemainingTimeInMillis());
16 | console.log('functionName =', context.functionName);
17 | console.log('AWSrequestID =', context.awsRequestId);
18 | console.log('logGroupName =', context.logGroupName);
19 | console.log('logStreamName =', context.logStreamName);
20 | console.log('clientContext =', context.clientContext);
21 |
22 | //Performance optimization Step 2: set context.callbackWaitsForEmptyEventLoop to false to prevent the Lambda function from waiting for all resources (such as the database connection) to be released before returning it
23 | context.callbackWaitsForEmptyEventLoop = false;
24 |
25 | if (atlas_connection_uri == null) {
26 | atlas_connection_uri = uri;
27 | /*
28 | const kms = new AWS.KMS();
29 | kms.decrypt({ CiphertextBlob: new Buffer(uri, 'base64') }, (err, data) => {
30 | if (err) {
31 | console.log('Decrypt error:', err);
32 | return callback(err);
33 | }
34 |
35 | atlas_connection_uri = data.Plaintext.toString('ascii');
36 | });
37 | */
38 | }
39 | processEvent(event, context, callback);
40 | };
41 |
42 | function connectToDatabase(uri) {
43 |
44 | //Performance optimization Step 3: test that database connection exists and is valid
45 | //before re-using it
46 | if (cachedDb && cachedDb.serverConfig.isConnected()) {
47 | console.log('=> using cached database instance');
48 | return Promise.resolve(cachedDb);
49 | }
50 | const dbName = 'travel';
51 | return MongoClient.connect(uri)
52 | .then(client => { cachedDb = client.db(dbName); return cachedDb; });
53 | }
54 |
55 | function processEvent(event, context, callback) {
56 | connectToDatabase(atlas_connection_uri)
57 | .then(db => queryDatabase(db, event))
58 | .then(result => {
59 | console.log('query results: ', result);
60 | callback(null, result);
61 | })
62 | .catch(err => {
63 | console.log('=> an error occurred: ', err);
64 | callback(err);
65 | });
66 | }
67 |
68 | function queryDatabase(db, event) {
69 | var jsonContents = JSON.parse(JSON.stringify(event));
70 |
71 | //handling API Gateway input where the event is embedded into the 'body' element
72 | if (event.body !== null && event.body !== undefined) {
73 | console.log('retrieving payload from event.body');
74 | jsonContents = JSON.parse(event.body);
75 | }
76 |
77 | console.log('query parameters: ', jsonContents);
78 | return db.collection('restaurants').aggregate([{ $match: { "address.zipcode": jsonContents.zipcode, "cuisine": jsonContents.cuisine, "name": new RegExp(jsonContents.startsWith) } },
79 | { $project: { "_id": 0, "name": 1, "address.building": 1, "address.street": 1, "borough": 1, "address.zipcode": 1, "healthScoreAverage": { $avg: "$grades.score" }, "healthScoreWorst": { $max: "$grades.score" } } }
80 | ]).toArray()
81 | .then(docs => { return docs;})
82 | .catch(err => { return err; });
83 | }
--------------------------------------------------------------------------------
/restaurants/ll.sh:
--------------------------------------------------------------------------------
1 | ATLAS_USERNAME=""
2 | ATLAS_PASSWORD=""
3 | ATLAS_CLUSTER_NAME=""
4 | ATLAS_CLUSTER_SUFFIX=""
5 | DATABASE_NAME="travel"
6 |
7 | ATLAS_URI="\"mongodb://$ATLAS_USERNAME:$ATLAS_PASSWORD@$ATLAS_CLUSTER_NAME-shard-00-00-$ATLAS_CLUSTER_SUFFIX.mongodb.net:27017,$ATLAS_CLUSTER_NAME-shard-00-01-$ATLAS_CLUSTER_SUFFIX.mongodb.net:27017,$ATLAS_CLUSTER_NAME-shard-00-02-$ATLAS_CLUSTER_SUFFIX.mongodb.net:27017/$DATABASE_NAME?ssl=true&replicaSet=$ATLAS_CLUSTER_NAME-shard-0&authSource=admin\""
8 |
9 | #echo $ATLAS_URI
10 | lambda-local -l index.js -e event.json -E {\"MONGODB_ATLAS_CLUSTER_URI\":$ATLAS_URI}
11 |
--------------------------------------------------------------------------------
/restaurants/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "awslambda-restaurants",
3 | "version": "1.0.0",
4 | "description": "An AWS Lambda function that retrieves a list of restaurants from a database hosted in MongoDB Atlas",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "",
10 | "license": "Apache-2.0",
11 | "dependencies": {
12 | "claudia-api-builder": "^2.5.1",
13 | "mongodb": "^3.0.5"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/restaurants/zip.sh:
--------------------------------------------------------------------------------
1 | zip -r archive.zip node_modules/ index.js
--------------------------------------------------------------------------------
/sms/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | node_modules/
3 | .env
4 |
--------------------------------------------------------------------------------
/sms/config.js:
--------------------------------------------------------------------------------
1 | var cfg = {};
2 |
3 | cfg.smsTemplate = "Here's the restaurant you're looking for: {{#restaurants}}{{name}}, {{address.building}} {{address.street}}, {{borough}} {{address.zipcode}}{{/restaurants}}";
4 |
5 | module.exports = cfg;
6 |
--------------------------------------------------------------------------------
/sms/event.json:
--------------------------------------------------------------------------------
1 | {
2 | "phoneTo" : "+1555-555-5555",
3 | "firstnameTo": "Raphael",
4 | "emailTo": "raphael@example.com",
5 | "subject": "List of restaurants for {{firstnameTo}}",
6 | "restaurants": [
7 | {
8 | "address": {
9 | "building": "235-237",
10 | "street": "West 48 Street"
11 | },
12 | "borough": "Manhattan",
13 | "name": "La Masseria"
14 | }]
15 | }
--------------------------------------------------------------------------------
/sms/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | var cfg = require("./config.js")
4 | var Mustache = require('mustache');
5 |
6 | exports.handler = (event, context, callback) => {
7 |
8 | var twilioAccoundSid = process.env.TWILIO_ACCOUNT_SID;
9 | var twilioAuthToken = process.env.TWILIO_AUTH_TOKEN;
10 | var twilioPhoneNumber = process.env.TWILIO_PHONE_NUMBER;
11 |
12 | var client = require('twilio')(
13 | twilioAccoundSid,
14 | twilioAuthToken
15 | );
16 |
17 | var message = Mustache.render(cfg.smsTemplate, event);
18 | console.log(message);
19 |
20 | client.messages.create({
21 | from: twilioPhoneNumber,
22 | to: event.phoneTo,
23 | body: message
24 | }, function (err, message) {
25 | if (err) {
26 | console.error(err.message);
27 | callback(err);
28 | }
29 | else {
30 | callback(null, "An SMS was properly sent to " + event.phoneTo);
31 | }
32 | });
33 |
34 | };
--------------------------------------------------------------------------------
/sms/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "awslambda-twilio",
3 | "version": "1.0.0",
4 | "description": "An AWS Lambda function that sends a text message through Twilio's SMS Service",
5 | "main": "sms.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "",
10 | "license": "Apache-2.0",
11 | "dependencies": {
12 | "mustache": "^2.3.0",
13 | "twilio": "^2.11.1"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/sms/zip.sh:
--------------------------------------------------------------------------------
1 | zip -r archive.zip node_modules/ index.js config.js
--------------------------------------------------------------------------------
/statemachine.json:
--------------------------------------------------------------------------------
1 | {
2 | "Comment": "A state machine showcasing the use of MongoDB Atlas to notify a user by text message or email depending on the number of returned restaurants",
3 | "StartAt": "GetRestaurants",
4 | "States": {
5 | "GetRestaurants": {
6 | "Type": "Task",
7 | "Resource": "",
8 | "ResultPath": "$.restaurants",
9 | "Next": "CountItems"
10 | },
11 | "CountItems": {
12 | "Type": "Task",
13 | "Resource": "",
14 | "InputPath": "$.restaurants",
15 | "ResultPath": "$.count",
16 | "Next": "NotificationMethodChoice"
17 | },
18 | "NotificationMethodChoice": {
19 | "Type": "Choice",
20 | "Choices": [
21 | {
22 | "Variable": "$.count",
23 | "NumericGreaterThan": 1,
24 | "Next": "SendByEmail"
25 | },
26 | {
27 | "Variable": "$.count",
28 | "NumericLessThanEquals": 1,
29 | "Next": "SendBySMS"
30 | }
31 | ],
32 | "Default": "SendByEmail"
33 | },
34 | "SendByEmail": {
35 | "Type": "Task",
36 | "Resource": "",
37 | "End": true
38 | },
39 | "SendBySMS": {
40 | "Type": "Task",
41 | "Resource": "",
42 | "End": true
43 | }
44 | }
45 | }
--------------------------------------------------------------------------------