Skip to content
This repository was archived by the owner on Oct 22, 2021. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 3 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,11 @@ This example demonstrates receiving votes via text message from users via a phon

Step 1 – Create an AWS CloudFormation stack with the [template](https://s3.amazonaws.com/awslambda-reference-architectures/web-app/lambda_webapp.template) using a lowercase name for the stack.

Step 2 – Visit the [API Gateway dashboard](https://console.aws.amazon.com/apigateway/home) in your AWS account and create a new resource with a `/vote` endpoint. Assign a POST method that has the `Integration Request` type of "Lambda Function," and point to the Lambda function created by the AWS CloudFormation script that receives votes from your third-party voting service (in this example, Twilio).
Step 2 – Visit the [Amazon Cognito dashboard](https://console.aws.amazon.com/cognito/create) and create a new identity pool that allows access to unauthenticated identities. Modify the policy document to allow `GetItem` and `Scan` access to the aggregates DynamoDB table created by the AWS CloudFormation script above. This allows unauthenticated users to retrieve data from the vote aggregation table in DynamoDB. Amazon Cognito will provide sample code for the JavaScript platform. Note the value for identity pool ID; you'll need it in step 4.

Under `Body Mapping Templates`, set the "Content-Type" to `application/x-www-form-urlencoded`, and add [this mapping template](apigateway-mappingtemplate.txt).
Step 3 – In the __VoteApp__ table in DynamoDB, create a new [Trigger](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Streams.Lambda.html), and point it to the existing Lambda function to [aggregate votes](lambda-functions/aggregate-votes/app.js). This function will monitor any changes to your __VoteApp__ table, writing new, aggregate values into __VoteAppAggregates__.

Step 3 – Visit the [Amazon Cognito dashboard](https://console.aws.amazon.com/cognito/home) and create a new identity pool that allows access to unauthenticated identities. Modify the policy document to allow `GetItem` and `Scan` access to the aggregates DynamoDB table created by the AWS CloudFormation script above. This allows unauthenticated users to retrieve data from the vote aggregation table in DynamoDB. Amazon Cognito will provide sample code for the JavaScript platform. Note the value for identity pool ID; you'll need it in step 5.

Step 4 – In the __VoteApp__ table in DynamoDB, create a new [Trigger](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Streams.Lambda.html), and point it to the existing Lambda function to [aggregate votes](lambda-functions/aggregate-votes/app.js). This function will monitor any changes to your __VoteApp__ table, writing new, aggregate values into __VoteAppAggregates__.

Step 5 – Copy the HTML, CSS, and JS files from this repo and into the static S3 bucket that was created to hold your dashboard. You'll need to open `refresh.js` and replace default values of `region` and `identity-pool-id` with your own values.
Step 4 – Copy the HTML, CSS, and JS files from this repo and into the static S3 bucket that was created to hold your dashboard. You'll need to open `refresh.js` and replace default values of `region` and `identity-pool-id` with your own values, and your SMS phone number in index.html.

Congratulations! You now should have a working example of the reference architecture. You are able to receive votes in real time, tune your DynamoDB table to handle various levels of incoming traffic, and watch your results change on your dashboard in real time!

Expand Down
221 changes: 219 additions & 2 deletions lambda_webapp.template
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,224 @@
},

"Resources": {
"ApiGatewayResource": {
"Type" : "AWS::ApiGateway::Resource",
"Properties": {
"RestApiId": { "Ref": "ApiGatewayRestApi" },
"ParentId": { "Fn::GetAtt": ["ApiGatewayRestApi", "RootResourceId"] },
"PathPart": "vote"
}
},
"CloudWatchRole": {
"Type": "AWS::IAM::Role",
"Properties": {
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "Service": [ "apigateway.amazonaws.com" ] },
"Action": "sts:AssumeRole"
}]
},
"Path": "/",
"ManagedPolicyArns": ["arn:aws:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs"]
}
},
"ApiGatewayAccount": {
"Type" : "AWS::ApiGateway::Account",
"Properties" : {
"CloudWatchRoleArn": { "Fn::GetAtt": ["CloudWatchRole", "Arn"] }
}
},
"ApiGatewayDeployment": {
"Type": "AWS::ApiGateway::Deployment",
"Properties": {
"RestApiId": { "Ref": "ApiGatewayRestApi" },
"Description": "Production deployment",
"StageName": "production"
},
"DependsOn": [
"ApiGatewayRestApi",
"ApiGatewayResource",
"ApiGatewayMethod",
"ApiGatewayLambdaPermission"
]
},

"ApiLoggableStage": {
"DependsOn" : ["ApiGatewayAccount"],
"Type": "AWS::ApiGateway::Stage",
"Properties": {
"DeploymentId": {"Ref": "ApiGatewayDeployment"},
"MethodSettings": [{
"DataTraceEnabled": true,
"HttpMethod": "*",
"LoggingLevel": "INFO",
"ResourcePath": "/*"
}],
"RestApiId": {"Ref": "ApiGatewayRestApi"},
"StageName": "prod_loggable"
}
},

"ApiGatewayRestApi": {
"Type": "AWS::ApiGateway::RestApi",
"Properties": {
"Description": "RESTful API endpoint",
"Name": {
"Fn::Join": [
"",
[
"ApiGatewayRestApi-",
{ "Ref": "AWS::StackName" }
]
]
}
}
},
"ApiGatewayLambdaPermission": {
"Type": "AWS::Lambda::Permission",
"Properties": {
"FunctionName" : { "Fn::GetAtt" : [ "LambdaVoteFunction", "Arn" ] },
"Action": "lambda:InvokeFunction",
"Principal": "apigateway.amazonaws.com",
"SourceArn": {
"Fn::Join": [
"",
[
"arn:aws:execute-api:",
{ "Ref" : "AWS::Region" },
":",
{ "Ref" : "AWS::AccountId" },
":",
{ "Ref" : "ApiGatewayRestApi" },
"/*/POST/vote"
]
]
}
}
},
"ApiGatewayMethod": {
"DependsOn": ["LambdaVoteFunction","ApiGatewayRestApi"],
"Type": "AWS::ApiGateway::Method",
"Properties": {
"RestApiId": { "Ref": "ApiGatewayRestApi" },
"ResourceId": { "Ref": "ApiGatewayResource" },
"HttpMethod": "POST",
"AuthorizationType": "NONE",
"Integration": {
"Type": "AWS",
"IntegrationHttpMethod": "POST",
"Uri": {
"Fn::Join": [
"",
[
"arn:aws:apigateway:",
{ "Ref" : "AWS::Region" },
":lambda:path/2015-03-31/functions/",
{ "Fn::GetAtt" : [ "LambdaVoteFunction", "Arn" ] },
"/invocations"
]
]
},
"RequestTemplates": {
"application/x-www-form-urlencoded": {"Fn::Join": [
"\n",
[
"## convert x-www-form-urlencoded to JSON",
"##",
"{",
" #foreach( $token in $input.path('$').split('&') )",
" #set( $keyVal = $token.split('=') )",
" #set( $keyValSize = $keyVal.size() )",
" #if( $keyValSize >= 1 )",
" #set( $key = $util.urlDecode($keyVal[0]) )",
" #if( $keyValSize >= 2 )",
" #set( $val = $util.urlDecode($keyVal[1]) )",
" #else",
" #set( $val = '' )",
" #end",
" \"$key\": \"$val\"#if($foreach.hasNext),#end",
" #end",
" #end",
"}"
]]
}
}
}
}
},
"OptionsApiMethod": {
"Type": "AWS::ApiGateway::Method",
"Properties": {
"RestApiId": { "Ref": "ApiGatewayRestApi" },
"ResourceId": { "Ref": "ApiGatewayResource" },
"HttpMethod": "OPTIONS",
"AuthorizationType": "NONE",
"Integration": {
"Type": "MOCK",
"RequestTemplates": {
"application/json": "{ \"statusCode\": 200 }"
},
"IntegrationResponses": [
{
"StatusCode": "200",
"ResponseParameters" : {
"method.response.header.Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'",
"method.response.header.Access-Control-Allow-Methods": "'POST,OPTIONS'",
"method.response.header.Access-Control-Allow-Origin": "'*'"
}
}
]
},
"MethodResponses": [
{
"StatusCode": "200",
"ResponseParameters" : {
"method.response.header.Access-Control-Allow-Headers": true,
"method.response.header.Access-Control-Allow-Methods": true,
"method.response.header.Access-Control-Allow-Origin": true
}
}
]
}
},
"LambdaCallableFromApiGatewayPolicy": {
"Type": "AWS::IAM::ManagedPolicy",
"Properties": {
"Description": "Managed Policy for API Gateway Lambda function",
"Path": "/lambda/apigateway/",
"PolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:*:*:*"
},
{
"Effect": "Allow",
"Action": [
"apigateway:*"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"iam:PassRole",
"iam:GetServerCertificate"
],
"Resource": "*"
}
]
}
}
},
"DynamoDBTable": {
"Type": "AWS::DynamoDB::Table",
"Properties": {
Expand Down Expand Up @@ -173,7 +390,7 @@
"Ref": "LambdaReceiveS3Key"
}
},
"Runtime": "nodejs",
"Runtime": "nodejs4.3",
"Description": "Receives votes from Twilio and adds to DynamoDB",
"Handler": "app.handler",
"Role": {
Expand All @@ -197,7 +414,7 @@
"Ref": "LambdaAggregateS3Key"
}
},
"Runtime": "nodejs",
"Runtime": "nodejs4.3",
"Description": "Receives updated items from DynamoDB streams for aggregation",
"Handler": "app.handler",
"Role": {
Expand Down