├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── backend ├── api │ └── locations │ │ ├── get.js │ │ ├── local │ │ ├── getTest.js │ │ ├── getTestEvent.json │ │ ├── postTest.js │ │ └── postTestEvent.json │ │ ├── package.json │ │ └── post.js ├── layers │ └── sharp-layer │ │ ├── package.json │ │ └── template.yaml ├── realtime.yaml ├── streams │ └── ddb │ │ ├── app.js │ │ ├── localTest.js │ │ ├── localTestEvent.json │ │ └── package.json ├── template.yaml └── upload │ ├── complete.js │ ├── local │ ├── localTest.js │ └── localTestEvent.json │ ├── package.json │ └── request.js ├── frontend ├── README.md ├── auth_config.json ├── babel.config.js ├── package.json ├── public │ ├── favicon.ico │ └── index.html ├── src │ ├── App.vue │ ├── assets │ │ └── logo.png │ ├── auth │ │ └── index.js │ ├── components │ │ ├── IoT.vue │ │ └── Snackbar.vue │ ├── main.js │ ├── plugins │ │ └── vuetify.js │ ├── router │ │ └── index.js │ ├── store │ │ └── index.js │ └── views │ │ ├── HomeView.vue │ │ └── PlaceDetail.vue └── vue.config.js ├── localTesting ├── events │ ├── ddbevent-processed.json │ ├── ddbevent-rejection.json │ └── event.json └── test.js └── workflows ├── functions ├── analyze │ ├── dimensions │ │ ├── app.js │ │ └── package.json │ ├── moderator │ │ ├── app.js │ │ └── package.json │ └── people │ │ ├── app.js │ │ └── package.json ├── publish │ ├── app.js │ └── package.json └── resizer │ ├── app.js │ └── package.json ├── statemachines ├── v1.asl.json ├── v2.asl.json ├── v3.asl.json └── v4.asl.json └── templates ├── v1 └── template.yaml ├── v2 └── template.yaml ├── v3 └── template.yaml └── v4 └── template.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/osx,node,linux,windows 3 | 4 | ### Linux ### 5 | *~ 6 | 7 | # temporary files which can be created if a process still has a handle open of a deleted file 8 | .fuse_hidden* 9 | 10 | # KDE directory preferences 11 | .directory 12 | 13 | # Linux trash folder which might appear on any partition or disk 14 | .Trash-* 15 | 16 | # .nfs files are created when an open file is removed but is still being accessed 17 | .nfs* 18 | 19 | ### Node ### 20 | # Logs 21 | logs 22 | *.log 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # Runtime data 28 | pids 29 | *.pid 30 | *.seed 31 | *.pid.lock 32 | 33 | # Directory for instrumented libs generated by jscoverage/JSCover 34 | lib-cov 35 | 36 | # Coverage directory used by tools like istanbul 37 | coverage 38 | 39 | # nyc test coverage 40 | .nyc_output 41 | 42 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 43 | .grunt 44 | 45 | # Bower dependency directory (https://bower.io/) 46 | bower_components 47 | 48 | # node-waf configuration 49 | .lock-wscript 50 | 51 | # Compiled binary addons (http://nodejs.org/api/addons.html) 52 | build/Release 53 | 54 | # Dependency directories 55 | node_modules/ 56 | jspm_packages/ 57 | 58 | # Typescript v1 declaration files 59 | typings/ 60 | 61 | # Optional npm cache directory 62 | .npm 63 | 64 | # Optional eslint cache 65 | .eslintcache 66 | 67 | # Optional REPL history 68 | .node_repl_history 69 | 70 | # Output of 'npm pack' 71 | *.tgz 72 | 73 | # Yarn Integrity file 74 | .yarn-integrity 75 | 76 | # dotenv environment variables file 77 | .env 78 | 79 | 80 | ### OSX ### 81 | *.DS_Store 82 | .AppleDouble 83 | .LSOverride 84 | 85 | # Icon must end with two \r 86 | Icon 87 | 88 | # Thumbnails 89 | ._* 90 | 91 | # Files that might appear in the root of a volume 92 | .DocumentRevisions-V100 93 | .fseventsd 94 | .Spotlight-V100 95 | .TemporaryItems 96 | .Trashes 97 | .VolumeIcon.icns 98 | .com.apple.timemachine.donotpresent 99 | 100 | # Directories potentially created on remote AFP share 101 | .AppleDB 102 | .AppleDesktop 103 | Network Trash Folder 104 | Temporary Items 105 | .apdisk 106 | 107 | ### Windows ### 108 | # Windows thumbnail cache files 109 | Thumbs.db 110 | ehthumbs.db 111 | ehthumbs_vista.db 112 | 113 | # Folder config file 114 | Desktop.ini 115 | 116 | # Recycle Bin used on file shares 117 | $RECYCLE.BIN/ 118 | 119 | # Windows Installer files 120 | *.cab 121 | *.msi 122 | *.msm 123 | *.msp 124 | 125 | # Windows shortcuts 126 | *.lnk 127 | 128 | 129 | # End of https://www.gitignore.io/api/osx,node,linux,windows 130 | 131 | # Editor directories and files 132 | .idea 133 | .vscode 134 | *.suo 135 | *.ntvs* 136 | *.njsproj 137 | *.sln 138 | *.sw? 139 | 140 | #AWS/SAM 141 | .aws-sam 142 | packaged.yaml 143 | samconfig.toml 144 | 145 | #jbesw-specific 146 | testData/ 147 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *master* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | 61 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. 62 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 10 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 11 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 12 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 13 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 14 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Happy Path - Building flexible serverless backends for web apps 2 | 3 | This example application shows how to build flexible serverless backends for web apps. This sample application is called *Happy Path*. 4 | 5 | This app is designed to help state parks and nonprofit organizations digitalize printed materials, such as flyers and maps. It allows visitors to capture images of documents and photos of hiking trails. They can share these with other users to reduce printed waste. 6 | 7 | The frontend displays and captures images for different locations, and the backend processes this data according to a set of business rules. This web application is designed for smartphones so it’s used while visitors are at the locations. 8 | 9 | To learn more about how this application works, see the 3-part series on the AWS Compute Blog: 10 | * Part 1: https://aws.amazon.com/blogs/compute/using-serverless-backends-to-iterate-quickly-on-web-apps-part-1/. 11 | * Part 2: https://aws.amazon.com/blogs/compute/using-serverless-backends-to-iterate-quickly-on-web-apps-part-2/. 12 | * Part 3: https://aws.amazon.com/blogs/compute/using-serverless-backends-to-iterate-quickly-on-web-apps-part-3/. 13 | 14 | Important: this application uses various AWS services and there are costs associated with these services after the Free Tier usage - please see the [AWS Pricing page](https://aws.amazon.com/pricing/) for details. You are responsible for any AWS costs incurred. No warranty is implied in this example. 15 | 16 | ```bash 17 | . 18 | ├── README.MD <-- This instructions file 19 | ├── frontend <-- Source code for the Vue.js 20 | ├── backend <-- Source code for the serverless backend 21 | ├── workflows <-- Step Functions applications 22 | ├── localTesting <-- Test harness/test events for local debugging 23 | ``` 24 | 25 | ## Requirements 26 | 27 | * AWS CLI already configured with Administrator permission 28 | * [AWS SAM CLI installed](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) - **minimum version 0.48**. 29 | * [NodeJS 12.x installed](https://nodejs.org/en/download/) 30 | * [Vue.js and Vue CLI installed](https://vuejs.org/v2/guide/installation.html) 31 | * [Google Maps API key](https://developers.google.com/maps/documentation/javascript/get-api-key) 32 | * Sign up for an [Auth0 account](https://auth0.com/) 33 | 34 | ## Auth0 configuration 35 | 36 | 1. Sign up for an Auth0 account at https://auth0.com. 37 | 2. Select Applications from the menu, then choose **Create Application**. 38 | 3. Enter the name *Happy Path* and select *Single Page Web Applications* for application type. Choose **Create**. 39 | 4. Select the *Settings* tab, and note the `Domain` and `ClientID` for installation of the application backend and frontend. 40 | 5. Under *Allowed Callback URLs*, *Allowed Logout URLs* and *Allowed Web Origins* and *Allowed Origins (CORS)*, enter **http://localhost:8080**. Choose **Save Changes**. 41 | 6. Select APIS from the menu, then choose **Create API**. 42 | 7. Enter the name *Happy Path*, and set the *Identifier* to **https://auth0-jwt-authorizer**. Choose **Create**. 43 | 44 | Auth0 is now configured for you to use. The backend uses the `domain` value to validate the JWT token. The frontend uses the identifier (also known as the audience), together with the *Client ID* to validate authentication for this single application. For more information, see the [Auth0 documentation](https://auth0.com/docs/api/authentication). 45 | 46 | ## Installation Instructions 47 | 48 | 1. [Create an AWS account](https://portal.aws.amazon.com/gp/aws/developer/registration/index.html) if you do not already have one and login. 49 | 50 | 2. Clone the repo onto your local development machine: 51 | ``` 52 | git clone https://github.com/aws-samples/happy-path 53 | ``` 54 | 55 | ### 1. Installing the realtime stack 56 | 57 | 1. From the command line, install the realtime messaging stack: 58 | ``` 59 | cd backend 60 | sam deploy --guided --template-file realtime.yaml 61 | ``` 62 | During the prompts, enter `happy-path-realtime` for the Stack Name, enter your preferred Region, and accept the defaults for the remaining questions. 63 | 64 | 2. Retrieve the IoT endpointAddress - note this for the frontend installation: 65 | ``` 66 | aws iot describe-endpoint --endpoint-type iot:Data-ATS 67 | ``` 68 | 3. Retrieve the Cognito Pool ID - note this for the frontend installation: 69 | ``` 70 | aws cognito-identity list-identity-pools --max-results 10 71 | ``` 72 | 73 | ### 2. Create the Lambda layer 74 | 75 | Note that you must run these instructions on a Linux x64 platform to ensure the correct Sharp binaries are included. If you are using another operating system, see the [AWS Lambda Sharp installation instructions](https://sharp.pixelplumbing.com/install#aws-lambda). Alternatively, use [AWS Cloud9](https://aws.amazon.com/cloud9/) to complete this step to ensure compatibility. 76 | 77 | 1. Change to the layer directory: 78 | ``` 79 | cd ./backend/layers/sharp-layer 80 | ``` 81 | 2. Install and prepare package: 82 | ``` 83 | npm install 84 | mkdir -p ./layer/nodejs 85 | mv ./node_modules ./layer/nodejs 86 | ``` 87 | 3. Deploy the SAM template to create the layer: 88 | ``` 89 | sam deploy --guided 90 | ``` 91 | 4. When prompted for parameters, enter: 92 | - Stack Name: lambda-layer-sharp 93 | - AWS Region: your preferred AWS Region (e.g. us-east-1) 94 | - Accept all other defaults. 95 | 96 | Take a note of the output ARN - it is used in subsequent deployments wherever you see the `SharpLayerARN` parameter. 97 | 98 | ### 3. Installing the backend application 99 | 100 | From the command line, deploy the SAM template. Note that your SAM version must be at least 0.48 - if you receive build errors, it is likely that your SAM CLI version is not up to date. 101 | Run: 102 | 103 | ``` 104 | cd ../.. 105 | sam build 106 | sam deploy --guided 107 | ``` 108 | 109 | When prompted for parameters, enter: 110 | - Stack Name: happy-path-backend 111 | - AWS Region: your preferred AWS Region (e.g. us-east-1) 112 | - ApplicationTableName: leave as default 113 | - S3UploadBucketName: enter a unique name for your bucket. 114 | - S3DistributionBucketName: enter a unique name for your bucket. 115 | - IoTDataEndpoint: the IoT endpointAddress noted in the realtime stack installation. 116 | - Auth0issuer: this is the URL for the Auth0 account (the format is https://dev-abc12345.auth0.com/). 117 | - Accept all other defaults. 118 | 119 | This takes several minutes to deploy. At the end of the deployment, note the output values, as you need these in the frontend installation. 120 | 121 | ### 4. Installing the frontend application 122 | 123 | The frontend code is saved in the `frontend` subdirectory. 124 | 125 | 1. Before running, you need to set environment variables in the `src\main.js` file: 126 | 127 | - GoogleMapsKey: [sign up](https://developers.google.com/maps/documentation/javascript/get-api-key) for a Google Maps API key and enter the value here. 128 | - APIurl: this is the `APIendpoint` value from the last section. 129 | - PoolId: your Cognito pool ID from earlier. 130 | - Host: your IoT endpoint from earlier. 131 | - Region: your preferred AWS Region (e.g. us-east-1). 132 | 133 | 2. Open the `src\auth_config.json` and enter the values from your Auth0 account: 134 | - domain: this is your account identifier, in the format `dev-12345678.auth0.com`. 135 | - clientId: a unique string identifying the client application. 136 | - audience: set to https://auth0-jwt-authorizer. 137 | 138 | 3. Change directory into the frontend code, and run the NPM installation: 139 | 140 | ``` 141 | cd ../frontend 142 | npm install 143 | ``` 144 | 4. After installation is complete, you can run the application locally: 145 | 146 | ``` 147 | npm run serve 148 | ``` 149 | 150 | ### 5. Installing the workflows 151 | 152 | To show the progression of increasingly complex workflows, this repo uses four separate AWS Step Functions state machines. Part 3 of the blog series shows how to deploy each one. The deployment pattern is the same for each version. 153 | 154 | - Workflow SAM templates are in `workflows/templates`. 155 | - State machine definitions are stored in `workflows/state machines`. 156 | 157 | To deploy: 158 | 1. Remove any previous workflow version by deleting their CloudFormation stack. 159 | 2. Change directory to the version of the workflow you want to deploy, e.g.: 160 | ``` 161 | cd workflows/templates/v1 162 | ``` 163 | 3. Build and deploy: 164 | ``` 165 | sam build 166 | sam deploy --guided 167 | ``` 168 | 4. When prompted for parameters, enter the parameters noted as outputs in the previous deployments. 169 | 170 | ## Next steps 171 | 172 | [A sample photo dataset](https://jbesw-public-downloads.s3.us-east-2.amazonaws.com/happy-path-photos-dataset.zip) is provided for download. These photos are provided by the blog author ([James Beswick](https://twitter.com/jbesw)) under the [Creative Commons Attribution 4.0 International license](http://creativecommons.org/licenses/by/4.0/). 173 | 174 | The AWS Compute Blog series and video link at the top of this README file contains additional information about the application design and architecture. 175 | 176 | If you have any questions, please contact the author or raise an issue in the GitHub repo. 177 | 178 | ============================================== 179 | 180 | Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 181 | 182 | SPDX-License-Identifier: MIT-0 183 | -------------------------------------------------------------------------------- /backend/api/locations/get.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 4 | software and associated documentation files (the "Software"), to deal in the Software 5 | without restriction, including without limitation the rights to use, copy, modify, 6 | merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 7 | permit persons to whom the Software is furnished to do so. 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 9 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 11 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 12 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 13 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 14 | */ 15 | 16 | const AWS = require('aws-sdk') 17 | AWS.config.update({region: process.env.AWS_REGION}) 18 | const documentClient = new AWS.DynamoDB.DocumentClient() 19 | 20 | // Returns details of a Place ID where the app has user-generated content. 21 | 22 | exports.handler = async (event) => { 23 | console.log(event) 24 | if (!event.queryStringParameters) { 25 | return { 26 | statusCode: 422, 27 | body: JSON.stringify("Missing parameters") 28 | } 29 | } 30 | 31 | const placeId = event.queryStringParameters.placeId 32 | console.log(`Searching for: ${placeId}`) 33 | 34 | // Use query method to retrieve all values for a given Primary Key. 35 | // See https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GettingStarted.NodeJs.04.html 36 | 37 | const place = await documentClient.query({ 38 | TableName: process.env.TableName, 39 | KeyConditionExpression: "#pk = :pk", 40 | ExpressionAttributeNames: { 41 | "#pk": "PK" 42 | }, 43 | ExpressionAttributeValues: { 44 | ":pk": placeId 45 | } 46 | }).promise() 47 | console.log('Place: ', place) 48 | 49 | return { 50 | statusCode: 200, 51 | body: JSON.stringify(place.Items) 52 | } 53 | } -------------------------------------------------------------------------------- /backend/api/locations/local/getTest.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 4 | software and associated documentation files (the "Software"), to deal in the Software 5 | without restriction, including without limitation the rights to use, copy, modify, 6 | merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 7 | permit persons to whom the Software is furnished to do so. 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 9 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 11 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 12 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 13 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 14 | */ 15 | 16 | // Mock event 17 | const event = require('./getTestEvent') 18 | 19 | // Mock environment variables 20 | process.env.AWS_REGION = 'us-east-1' 21 | process.env.localTest = true 22 | process.env.TableName = 'hp-application' 23 | 24 | // Lambda handler 25 | const { handler } = require('../get') 26 | 27 | const main = async () => { 28 | console.time('localTest') 29 | console.dir(await handler(event)) 30 | console.timeEnd('localTest') 31 | } 32 | 33 | main().catch(error => console.error(error)) -------------------------------------------------------------------------------- /backend/api/locations/local/getTestEvent.json: -------------------------------------------------------------------------------- 1 | { 2 | "body": "eyJ0ZXN0IjoiYm9keSJ9", 3 | "resource": "/{proxy+}", 4 | "path": "/path/to/resource", 5 | "httpMethod": "POST", 6 | "isBase64Encoded": true, 7 | "queryStringParameters": { 8 | "placeId": "ChIJDZ4oVn3A4okR2NFbt8kHZwU" 9 | }, 10 | "pathParameters": { 11 | "proxy": "/path/to/resource" 12 | }, 13 | "stageVariables": { 14 | "baz": "qux" 15 | }, 16 | "headers": { 17 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", 18 | "Accept-Encoding": "gzip, deflate, sdch", 19 | "Accept-Language": "en-US,en;q=0.8", 20 | "Cache-Control": "max-age=0", 21 | "CloudFront-Forwarded-Proto": "https", 22 | "CloudFront-Is-Desktop-Viewer": "true", 23 | "CloudFront-Is-Mobile-Viewer": "false", 24 | "CloudFront-Is-SmartTV-Viewer": "false", 25 | "CloudFront-Is-Tablet-Viewer": "false", 26 | "CloudFront-Viewer-Country": "US", 27 | "Host": "1234567890.execute-api.us-east-1.amazonaws.com", 28 | "Upgrade-Insecure-Requests": "1", 29 | "User-Agent": "Custom User Agent String", 30 | "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", 31 | "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==", 32 | "X-Forwarded-For": "127.0.0.1, 127.0.0.2", 33 | "X-Forwarded-Port": "443", 34 | "X-Forwarded-Proto": "https" 35 | }, 36 | "multiValueHeaders": { 37 | "Accept": [ 38 | "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8" 39 | ], 40 | "Accept-Encoding": [ 41 | "gzip, deflate, sdch" 42 | ], 43 | "Accept-Language": [ 44 | "en-US,en;q=0.8" 45 | ], 46 | "Cache-Control": [ 47 | "max-age=0" 48 | ], 49 | "CloudFront-Forwarded-Proto": [ 50 | "https" 51 | ], 52 | "CloudFront-Is-Desktop-Viewer": [ 53 | "true" 54 | ], 55 | "CloudFront-Is-Mobile-Viewer": [ 56 | "false" 57 | ], 58 | "CloudFront-Is-SmartTV-Viewer": [ 59 | "false" 60 | ], 61 | "CloudFront-Is-Tablet-Viewer": [ 62 | "false" 63 | ], 64 | "CloudFront-Viewer-Country": [ 65 | "US" 66 | ], 67 | "Host": [ 68 | "0123456789.execute-api.us-east-1.amazonaws.com" 69 | ], 70 | "Upgrade-Insecure-Requests": [ 71 | "1" 72 | ], 73 | "User-Agent": [ 74 | "Custom User Agent String" 75 | ], 76 | "Via": [ 77 | "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)" 78 | ], 79 | "X-Amz-Cf-Id": [ 80 | "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==" 81 | ], 82 | "X-Forwarded-For": [ 83 | "127.0.0.1, 127.0.0.2" 84 | ], 85 | "X-Forwarded-Port": [ 86 | "443" 87 | ], 88 | "X-Forwarded-Proto": [ 89 | "https" 90 | ] 91 | }, 92 | "requestContext": { 93 | "accountId": "123456789012", 94 | "resourceId": "123456", 95 | "stage": "prod", 96 | "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", 97 | "requestTime": "09/Apr/2015:12:34:56 +0000", 98 | "requestTimeEpoch": 1428582896000, 99 | "identity": { 100 | "cognitoIdentityPoolId": null, 101 | "accountId": null, 102 | "cognitoIdentityId": null, 103 | "caller": null, 104 | "accessKey": null, 105 | "sourceIp": "127.0.0.1", 106 | "cognitoAuthenticationType": null, 107 | "cognitoAuthenticationProvider": null, 108 | "userArn": null, 109 | "userAgent": "Custom User Agent String", 110 | "user": null 111 | }, 112 | "path": "/prod/path/to/resource", 113 | "resourcePath": "/{proxy+}", 114 | "httpMethod": "POST", 115 | "apiId": "1234567890", 116 | "protocol": "HTTP/1.1" 117 | } 118 | } -------------------------------------------------------------------------------- /backend/api/locations/local/postTest.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 4 | software and associated documentation files (the "Software"), to deal in the Software 5 | without restriction, including without limitation the rights to use, copy, modify, 6 | merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 7 | permit persons to whom the Software is furnished to do so. 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 9 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 11 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 12 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 13 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 14 | */ 15 | 16 | // Mock event 17 | const event = require('./postTestEvent') 18 | 19 | // Mock environment variables 20 | process.env.AWS_REGION = 'us-east-1' 21 | process.env.localTest = true 22 | process.env.TableName = 'hp-application' 23 | 24 | // Lambda handler 25 | const { handler } = require('../app') 26 | 27 | const main = async () => { 28 | console.time('localTest') 29 | console.dir(await handler(event)) 30 | console.timeEnd('localTest') 31 | } 32 | 33 | main().catch(error => console.error(error)) -------------------------------------------------------------------------------- /backend/api/locations/local/postTestEvent.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0", 3 | "routeKey": "POST /locations", 4 | "rawPath": "/locations", 5 | "rawQueryString": "", 6 | "headers": { 7 | "accept": "application/json, text/plain, */*", 8 | "accept-encoding": "gzip, deflate, br", 9 | "accept-language": "en-US,en;q=0.9", 10 | "content-length": "149", 11 | "content-type": "application/json;charset=UTF-8", 12 | "host": "bly6y4jqaj.execute-api.us-east-1.amazonaws.com", 13 | "origin": "http://localhost:8080", 14 | "referer": "http://localhost:8080/", 15 | "sec-fetch-dest": "empty", 16 | "sec-fetch-mode": "cors", 17 | "sec-fetch-site": "cross-site", 18 | "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36", 19 | "x-amzn-trace-id": "Root=1-5efb40c7-0768bc2a44049b3a7fdaf583", 20 | "x-forwarded-for": "72.21.196.65", 21 | "x-forwarded-port": "443", 22 | "x-forwarded-proto": "https" 23 | }, 24 | "requestContext": { 25 | "accountId": "123456789012", 26 | "apiId": "bly6y4jqaj", 27 | "domainName": "bly6y4jqaj.execute-api.us-east-1.amazonaws.com", 28 | "domainPrefix": "bly6y4jqaj", 29 | "http": { 30 | "method": "POST", 31 | "path": "/locations", 32 | "protocol": "HTTP/1.1", 33 | "sourceIp": "72.21.196.65", 34 | "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36" 35 | }, 36 | "requestId": "O8cPNh7boAMEVwQ=", 37 | "routeKey": "POST /locations", 38 | "stage": "$default", 39 | "time": "30/Jun/2020:13:40:23 +0000", 40 | "timeEpoch": 1593524423660 41 | }, 42 | "body": "{\"place_id\":\"ChIJsbfYOBXA4okRAzASPj3c4iU\",\"name\":\"Camp Seawood\",\"vicinity\":\"350 Banfield Road, Portsmouth\",\"lat\":43.04326679999999,\"lng\":-70.7892576}", 43 | "isBase64Encoded": false 44 | } -------------------------------------------------------------------------------- /backend/api/locations/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "api-locations", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "James Beswick, AWS Serverless", 11 | "license": "MIT-0", 12 | "dependencies": { 13 | "aws-sdk": "^2.658.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /backend/api/locations/post.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 4 | software and associated documentation files (the "Software"), to deal in the Software 5 | without restriction, including without limitation the rights to use, copy, modify, 6 | merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 7 | permit persons to whom the Software is furnished to do so. 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 9 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 11 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 12 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 13 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 14 | */ 15 | 16 | const AWS = require('aws-sdk') 17 | AWS.config.update({region: process.env.AWS_REGION}) 18 | const documentClient = new AWS.DynamoDB.DocumentClient() 19 | 20 | // Save place info to database 21 | 22 | exports.handler = async (event) => { 23 | // console.log(JSON.stringify(event, null, 0)) 24 | 25 | if (!event.body) { 26 | return { 27 | statusCode: 422, 28 | body: JSON.stringify("Missing body") 29 | } 30 | } 31 | 32 | const body = JSON.parse(event.body) 33 | const Item = { 34 | PK: body.place_id, 35 | SK: "listing", 36 | name: body.name, 37 | type: body.type, 38 | lat: body.lat, 39 | lng: body.lng, 40 | vicinity: body.vicinity, 41 | created: event.requestContext.timeEpoch 42 | } 43 | 44 | const result = await documentClient.put({ 45 | TableName: process.env.TableName, 46 | Item 47 | }).promise() 48 | console.log(result) 49 | 50 | return { 51 | statusCode: 200, 52 | body: JSON.stringify(Item) 53 | } 54 | } -------------------------------------------------------------------------------- /backend/layers/sharp-layer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sharp-layer", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "James Beswick, AWS Serverless", 11 | "license": "MIT-0", 12 | "dependencies": { 13 | "sharp": ">=0.32.6" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /backend/layers/sharp-layer/template.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Transform: AWS::Serverless-2016-10-31 3 | Description: AWS SDK Layer 4 | 5 | Resources: 6 | SDKlayer: 7 | Type: AWS::Serverless::LayerVersion 8 | Properties: 9 | LayerName: Sharp 10 | Description: Sharp NPM package. 11 | ContentUri: './layer' 12 | CompatibleRuntimes: 13 | - nodejs12.x 14 | LicenseInfo: 'Available under the Apache-2.0 license.' 15 | RetentionPolicy: Retain 16 | 17 | -------------------------------------------------------------------------------- /backend/realtime.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: 2010-09-09 2 | Transform: AWS::Serverless-2016-10-31 3 | Description: Happy Path - Realtime messaging 4 | 5 | Resources: 6 | ########################################## 7 | # Resources for realtime messaging # 8 | ########################################## 9 | HappyPathRealtime: 10 | Type: "AWS::IoT::Thing" 11 | Properties: 12 | ThingName: "happypath-realtime" 13 | AttributePayload: 14 | Attributes: {} 15 | 16 | UserPool: 17 | Type: "AWS::Cognito::UserPool" 18 | Properties: 19 | UserPoolName: HappyPathUserPool 20 | MfaConfiguration: "OFF" 21 | Schema: 22 | - Name: email 23 | AttributeDataType: String 24 | Mutable: false 25 | Required: true 26 | 27 | # Creates a User Pool Client to be used by the identity pool 28 | UserPoolClient: 29 | Type: "AWS::Cognito::UserPoolClient" 30 | Properties: 31 | ClientName: HappyPathUserPoolClient 32 | GenerateSecret: false 33 | UserPoolId: !Ref UserPool 34 | 35 | # Creates a federated Identity pool 36 | IdentityPool: 37 | Type: "AWS::Cognito::IdentityPool" 38 | Properties: 39 | IdentityPoolName: HappyPathIdentityPool 40 | AllowUnauthenticatedIdentities: true 41 | CognitoIdentityProviders: 42 | - ClientId: !Ref UserPoolClient 43 | ProviderName: !GetAtt UserPool.ProviderName 44 | 45 | # Create a role for unauthorized access to AWS resources. 46 | CognitoUnAuthorizedRole: 47 | Type: "AWS::IAM::Role" 48 | Properties: 49 | AssumeRolePolicyDocument: 50 | Version: "2012-10-17" 51 | Statement: 52 | - Effect: "Allow" 53 | Principal: 54 | Federated: "cognito-identity.amazonaws.com" 55 | Action: 56 | - "sts:AssumeRoleWithWebIdentity" 57 | Condition: 58 | StringEquals: 59 | "cognito-identity.amazonaws.com:aud": !Ref IdentityPool 60 | "ForAnyValue:StringLike": 61 | "cognito-identity.amazonaws.com:amr": unauthenticated 62 | Policies: 63 | - PolicyName: "CognitoUnauthorizedPolicy" 64 | PolicyDocument: 65 | Version: "2012-10-17" 66 | Statement: 67 | - Effect: "Allow" 68 | Action: 69 | - "cognito-sync:*" 70 | Resource: !Join [ "", [ "arn:aws:cognito-sync:", !Ref "AWS::Region", ":", !Ref "AWS::AccountId", ":identitypool/", !Ref IdentityPool] ] 71 | - Effect: Allow 72 | Action: 73 | - iot:Connect 74 | Resource: !Join [ "", [ "arn:aws:iot:", !Ref "AWS::Region", ":", !Ref "AWS::AccountId", ":client/happyPath-*" ] ] 75 | - Effect: Allow 76 | Action: 77 | - iot:Subscribe 78 | Resource: "*" 79 | - Effect: Allow 80 | Action: 81 | - iot:Receive 82 | Resource: !Join [ "", [ "arn:aws:iot:", !Ref "AWS::Region", ":", !Ref "AWS::AccountId", ":topic/*" ] ] 83 | 84 | # Create a role for authorized acces to AWS resources. 85 | CognitoAuthorizedRole: 86 | Type: "AWS::IAM::Role" 87 | Properties: 88 | AssumeRolePolicyDocument: 89 | Version: "2012-10-17" 90 | Statement: 91 | - Effect: "Allow" 92 | Principal: 93 | Federated: "cognito-identity.amazonaws.com" 94 | Action: 95 | - "sts:AssumeRoleWithWebIdentity" 96 | Condition: 97 | StringEquals: 98 | "cognito-identity.amazonaws.com:aud": !Ref IdentityPool 99 | "ForAnyValue:StringLike": 100 | "cognito-identity.amazonaws.com:amr": authenticated 101 | Policies: 102 | - PolicyName: "CognitoAuthorizedPolicy" 103 | PolicyDocument: 104 | Version: "2012-10-17" 105 | Statement: 106 | - Effect: "Allow" 107 | Action: 108 | - "cognito-sync:*" 109 | Resource: !Join [ "", [ "arn:aws:cognito-sync:", !Ref "AWS::Region", ":", !Ref "AWS::AccountId", ":identitypool/", !Ref IdentityPool] ] 110 | - Effect: Allow 111 | Action: 112 | - iot:Connect 113 | Resource: !Join [ "", [ "arn:aws:iot:", !Ref "AWS::Region", ":", !Ref "AWS::AccountId", ":client/happyPath-*" ] ] 114 | - Effect: Allow 115 | Action: 116 | - iot:Subscribe 117 | Resource: "*" 118 | - Effect: Allow 119 | Action: 120 | - iot:Receive 121 | Resource: !Join [ "", [ "arn:aws:iot:", !Ref "AWS::Region", ":", !Ref "AWS::AccountId", ":topic/*" ] ] 122 | # Assigns the roles to the Identity Pool 123 | IdentityPoolRoleMapping: 124 | Type: "AWS::Cognito::IdentityPoolRoleAttachment" 125 | Properties: 126 | IdentityPoolId: !Ref IdentityPool 127 | Roles: 128 | authenticated: !GetAtt CognitoAuthorizedRole.Arn 129 | unauthenticated: !GetAtt CognitoUnAuthorizedRole.Arn 130 | 131 | Outputs: 132 | IotEndpoint: 133 | Value: !Ref HappyPathRealtime 134 | -------------------------------------------------------------------------------- /backend/streams/ddb/app.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 4 | software and associated documentation files (the "Software"), to deal in the Software 5 | without restriction, including without limitation the rights to use, copy, modify, 6 | merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 7 | permit persons to whom the Software is furnished to do so. 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 9 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 11 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 12 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 13 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 14 | */ 15 | 16 | 'use strict' 17 | 18 | const AWS = require('aws-sdk') 19 | AWS.config.region = process.env.AWS_REGION 20 | const iotdata = new AWS.IotData({ endpoint: process.env.IOT_DATA_ENDPOINT }) 21 | 22 | // The standard Lambda handler 23 | exports.handler = async (event) => { 24 | console.log (JSON.stringify(event, null, 2)) 25 | 26 | await Promise.all( 27 | event.Records.map(async (record) => { 28 | try { 29 | if (!record.dynamodb.NewImage) return // Deletion 30 | 31 | const data = record.dynamodb.NewImage 32 | 33 | // This function only handles workflow completions 34 | if (data.PK.S === 'workflow') { 35 | if (data.objStatus.S === 'REJECTED') { 36 | console.log('Item rejection') 37 | const iotTopic = data.placeId.S 38 | data.result = 'REJECTED' 39 | await iotPublish(iotTopic, data) 40 | } 41 | } 42 | if (data.PK.S != 'workflow' && data.SK.S != 'listing') { 43 | console.log('Item processed') 44 | const iotTopic = data.PK.S 45 | data.result = 'PROCESSED' 46 | await iotPublish(iotTopic, data) 47 | } 48 | } catch (err) { 49 | console.error('Error: ', err) 50 | } 51 | }) 52 | ) 53 | } 54 | 55 | // Publishes the message to the IoT topic 56 | const iotPublish = async function (topic, message) { 57 | console.log('iotPublish: ', topic, message) 58 | try { 59 | await iotdata.publish({ 60 | topic, 61 | qos: 0, 62 | payload: JSON.stringify(message) 63 | }).promise(); 64 | console.log('iotPublish success to topic: ', topic, message) 65 | } catch (err) { 66 | console.error('iotPublish error:', err) 67 | } 68 | } 69 | 70 | -------------------------------------------------------------------------------- /backend/streams/ddb/localTest.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 4 | software and associated documentation files (the "Software"), to deal in the Software 5 | without restriction, including without limitation the rights to use, copy, modify, 6 | merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 7 | permit persons to whom the Software is furnished to do so. 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 9 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 11 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 12 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 13 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 14 | */ 15 | 16 | // Mock event 17 | const event = require('./localTestEvent') 18 | 19 | // Mock environment variables 20 | process.env.AWS_REGION = 'us-east-1' 21 | process.env.localTest = true 22 | process.env.TableName = 'aamQuestions' 23 | process.env.IOT_DATA_ENDPOINT = 'a2ty1m17b5znw2-ats.iot.us-east-1.amazonaws.com' 24 | 25 | // Lambda handler 26 | const { handler } = require('./app') 27 | 28 | const main = async () => { 29 | console.time('localTest') 30 | await handler(event) 31 | console.timeEnd('localTest') 32 | } 33 | 34 | main().catch(error => console.error(error)) 35 | -------------------------------------------------------------------------------- /backend/streams/ddb/localTestEvent.json: -------------------------------------------------------------------------------- 1 | { 2 | "Records": [ 3 | { 4 | "eventID": "902bcd5816d1a8654c4826f731ca2b33", 5 | "eventName": "MODIFY", 6 | "eventVersion": "1.1", 7 | "eventSource": "aws:dynamodb", 8 | "awsRegion": "us-east-1", 9 | "dynamodb": { 10 | "ApproximateCreationDateTime": 1593631095, 11 | "Keys": { 12 | "SK": { 13 | "S": "listing" 14 | }, 15 | "PK": { 16 | "S": "ChIJ26rBvg2_4okRqS0h3-hOhhE" 17 | } 18 | }, 19 | "NewImage": { 20 | "lng": { 21 | "N": "-70.754409" 22 | }, 23 | "created": { 24 | "N": "1593526807832" 25 | }, 26 | "SK": { 27 | "S": "listing" 28 | }, 29 | "name": { 30 | "S": "Aldrich Parkk" 31 | }, 32 | "vicinity": { 33 | "S": "371 Court Street, Portsmouth" 34 | }, 35 | "PK": { 36 | "S": "ChIJ26rBvg2_4okRqS0h3-hOhhE" 37 | }, 38 | "lat": { 39 | "N": "43.0766217" 40 | } 41 | }, 42 | "OldImage": { 43 | "lng": { 44 | "N": "-70.754409" 45 | }, 46 | "created": { 47 | "N": "1593526807832" 48 | }, 49 | "SK": { 50 | "S": "listing" 51 | }, 52 | "name": { 53 | "S": "Aldrich Park" 54 | }, 55 | "vicinity": { 56 | "S": "371 Court Street, Portsmouth" 57 | }, 58 | "PK": { 59 | "S": "ChIJ26rBvg2_4okRqS0h3-hOhhE" 60 | }, 61 | "lat": { 62 | "N": "43.0766217" 63 | } 64 | }, 65 | "SequenceNumber": "10440400000000018778526598", 66 | "SizeBytes": 285, 67 | "StreamViewType": "NEW_AND_OLD_IMAGES" 68 | }, 69 | "eventSourceARN": "arn:aws:dynamodb:us-east-1:123456789012:table/hp-application/stream/2020-06-29T14:29:10.716" 70 | } 71 | ] 72 | } -------------------------------------------------------------------------------- /backend/streams/ddb/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "process-answers-stream", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "James Beswick", 11 | "license": "MIT-0", 12 | "dependencies": { 13 | "aws-sdk": "^2.663.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /backend/template.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: 2010-09-09 2 | Transform: AWS::Serverless-2016-10-31 3 | Description: Happy Path - Example application 4 | 5 | Parameters: 6 | ApplicationTableName: 7 | Type: String 8 | Description: Name of application's DynamoDB table 9 | Default: hp-application 10 | 11 | IoTDataEndpoint: 12 | Type: String 13 | Description: The IoT data endpoint for the application. 14 | Default: a2ty1m17b5znw2-ats.iot.us-east-1.amazonaws.com 15 | 16 | Auth0issuer: 17 | Type: String 18 | Description: The issuer URL from your Auth0 account. 19 | Default: https://dev-y6clwyi3.us.auth0.com/ 20 | 21 | S3UploadBucketName: 22 | Type: String 23 | Description: The name for the S3 upload bucket. 24 | Default: 'happy-path-upload' 25 | 26 | S3DistributionBucketName: 27 | Type: String 28 | Description: The name for the S3 upload bucket. 29 | Default: 'happy-path-distribution' 30 | 31 | Globals: 32 | Function: 33 | Timeout: 5 34 | Runtime: nodejs12.x 35 | Tags: 36 | Application: HappyPath 37 | 38 | Resources: 39 | # HTTP API 40 | MyApi: 41 | Type: AWS::Serverless::HttpApi 42 | Properties: 43 | # HTTP API authorizer - see https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-property-httpapi-httpapiauth.html 44 | Auth: 45 | Authorizers: 46 | MyAuthorizer: 47 | JwtConfiguration: 48 | issuer: !Ref Auth0issuer 49 | audience: 50 | - https://auth0-jwt-authorizer 51 | IdentitySource: "$request.header.Authorization" 52 | DefaultAuthorizer: MyAuthorizer 53 | 54 | # CORS configuration - this is open for development only and should be restricted in prod. 55 | # See https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-property-httpapi-httpapicorsconfiguration.html 56 | CorsConfiguration: 57 | AllowMethods: 58 | - GET 59 | - POST 60 | - DELETE 61 | - OPTIONS 62 | AllowHeaders: 63 | - "*" 64 | AllowOrigins: 65 | - "*" 66 | 67 | ## Lambda functions 68 | UploadRequestFunction: 69 | # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction 70 | Type: AWS::Serverless::Function 71 | Properties: 72 | CodeUri: upload/ 73 | Handler: request.handler 74 | Runtime: nodejs12.x 75 | MemorySize: 128 76 | Environment: 77 | Variables: 78 | AWS_NODEJS_CONNECTION_REUSE_ENABLED: 1 79 | UploadBucket: !Ref S3UploadBucketName 80 | TableName: !Ref ApplicationTableName 81 | Policies: 82 | - S3CrudPolicy: 83 | BucketName: !Ref S3UploadBucketName 84 | - DynamoDBWritePolicy: 85 | TableName: !Ref ApplicationTableName 86 | Events: 87 | UploadAssetAPI: 88 | Type: HttpApi 89 | Properties: 90 | Path: /images 91 | Method: get 92 | ApiId: !Ref MyApi 93 | 94 | UploadCompleteFunction: 95 | Type: AWS::Serverless::Function 96 | Properties: 97 | CodeUri: upload/ 98 | Handler: complete.handler 99 | MemorySize: 128 100 | Policies: 101 | - Statement: 102 | - Effect: Allow 103 | Resource: '*' 104 | Action: 105 | - events:PutEvents 106 | Events: 107 | FileUpload: 108 | Type: S3 109 | Properties: 110 | Bucket: !Ref S3UploadBucket 111 | Events: s3:ObjectCreated:* 112 | 113 | PostLocationFunction: 114 | Type: AWS::Serverless::Function 115 | Properties: 116 | CodeUri: api/locations/ 117 | Handler: post.handler 118 | MemorySize: 128 119 | Description: Post new location to database 120 | Policies: 121 | - DynamoDBWritePolicy: 122 | TableName: !Ref ApplicationTableName 123 | Environment: 124 | Variables: 125 | AWS_NODEJS_CONNECTION_REUSE_ENABLED: 1 126 | TableName: !Ref ApplicationTableName 127 | Events: 128 | GetLocations: 129 | Type: HttpApi 130 | Properties: 131 | Path: /locations 132 | Method: post 133 | ApiId: !Ref MyApi 134 | #If you have a default authorizer but want to leave one route open, you can override that here. 135 | #Learn more at https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-property-function-httpapifunctionauth.html 136 | Auth: 137 | Authorizer: NONE 138 | 139 | GetLocationsFunction: 140 | Type: AWS::Serverless::Function 141 | Properties: 142 | CodeUri: api/locations/ 143 | Handler: get.handler 144 | MemorySize: 128 145 | Description: Returns list of locations from lat/lng query params 146 | Policies: 147 | - DynamoDBReadPolicy: 148 | TableName: !Ref ApplicationTableName 149 | Environment: 150 | Variables: 151 | AWS_NODEJS_CONNECTION_REUSE_ENABLED: 1 152 | TableName: !Ref ApplicationTableName 153 | Events: 154 | GetLocations: 155 | Type: HttpApi 156 | Properties: 157 | Path: /locations 158 | Method: get 159 | ApiId: !Ref MyApi 160 | #If you have a default authorizer but want to leave one route open, you can override that here. 161 | #Learn more at https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-property-function-httpapifunctionauth.html 162 | Auth: 163 | Authorizer: NONE 164 | 165 | ## DDB stream processor 166 | ProcessDynamoDBStream: 167 | Type: AWS::Serverless::Function 168 | Properties: 169 | CodeUri: streams/ddb/ 170 | Handler: app.handler 171 | Description: Updates IoT topics with changes to Questions DDB table 172 | ReservedConcurrentExecutions: 1 173 | Environment: 174 | Variables: 175 | IOT_DATA_ENDPOINT: !Ref IoTDataEndpoint 176 | Policies: 177 | - DynamoDBReadPolicy: 178 | TableName: !Ref ApplicationTableName 179 | - Statement: 180 | - Effect: "Allow" 181 | Action: 182 | - "iot:*" 183 | Resource: "*" 184 | Events: 185 | Stream: 186 | Type: DynamoDB 187 | Properties: 188 | Stream: !GetAtt ApplicationTable.StreamArn 189 | BatchSize: 100 190 | StartingPosition: TRIM_HORIZON 191 | 192 | ## DynamoDB table 193 | ApplicationTable: 194 | Type: AWS::DynamoDB::Table 195 | Properties: 196 | TableName: !Ref ApplicationTableName 197 | AttributeDefinitions: 198 | - AttributeName: PK 199 | AttributeType: S 200 | - AttributeName: SK 201 | AttributeType: S 202 | KeySchema: 203 | - AttributeName: PK 204 | KeyType: HASH 205 | - AttributeName: SK 206 | KeyType: RANGE 207 | BillingMode: PAY_PER_REQUEST 208 | StreamSpecification: 209 | StreamViewType: NEW_AND_OLD_IMAGES 210 | 211 | ## S3 buckets 212 | S3UploadBucket: 213 | Type: AWS::S3::Bucket 214 | Properties: 215 | BucketName: !Ref S3UploadBucketName 216 | CorsConfiguration: 217 | CorsRules: 218 | - AllowedHeaders: 219 | - "*" 220 | AllowedMethods: 221 | - GET 222 | - PUT 223 | - POST 224 | - DELETE 225 | - HEAD 226 | AllowedOrigins: 227 | - "*" 228 | 229 | S3DistributionBucket: 230 | Type: AWS::S3::Bucket 231 | Properties: 232 | BucketName: !Ref S3DistributionBucketName 233 | 234 | # S3BucketPolicy: 235 | # Type: AWS::S3::BucketPolicy 236 | # Properties: 237 | # BucketName: !Ref S3DistributionBucketName 238 | # Policies: 239 | # - Statement: 240 | # - Effect: Allow 241 | # Action: 's3:GetObject' 242 | # Resource: 243 | # - !Sub "arn:aws:s3:::${S3DistributionBucketName}/*" 244 | # Principal: 245 | # AWS: !Sub "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity ${CloudFrontOriginAccessIdentity}" 246 | 247 | ## CloudFront distribution 248 | CloudFrontOriginAccessIdentity: 249 | Type: AWS::CloudFront::CloudFrontOriginAccessIdentity 250 | Properties: 251 | CloudFrontOriginAccessIdentityConfig: 252 | Comment: 'Happy Path CloudFront distribution' 253 | 254 | CloudFrontDistribution: 255 | Type: AWS::CloudFront::Distribution 256 | Properties: 257 | DistributionConfig: 258 | Comment: "CloudFront distribution for Happy Path assets" 259 | Enabled: true 260 | HttpVersion: http2 261 | # List of origins for CloudFront distribution 262 | Origins: 263 | - Id: happy-path-distribution-s3-bucket 264 | DomainName: !GetAtt S3DistributionBucket.DomainName 265 | S3OriginConfig: 266 | # Restrict bucket access using an origin access identity 267 | OriginAccessIdentity: 268 | Fn::Sub: 'origin-access-identity/cloudfront/${CloudFrontOriginAccessIdentity}' 269 | DefaultCacheBehavior: 270 | Compress: true 271 | AllowedMethods: 272 | - GET 273 | - HEAD 274 | ForwardedValues: 275 | QueryString: false 276 | TargetOriginId: happy-path-distribution-s3-bucket 277 | ViewerProtocolPolicy : redirect-to-https 278 | 279 | ## Take a note of the outputs for deploying the workflow templates in this sample application 280 | Outputs: 281 | APIendpoint: 282 | Description: HTTP API endpoint URL. 283 | Value: !Sub "https://${MyApi}.execute-api.${AWS::Region}.amazonaws.com" 284 | DynamoDBstreamARN: 285 | Description: Stream ARN used for workflows. 286 | Value: !GetAtt ApplicationTable.StreamArn 287 | CloudFrontEndpoint: 288 | Description: Endpoint for CloudFront distribution. 289 | Value: !Ref 'CloudFrontDistribution' 290 | -------------------------------------------------------------------------------- /backend/upload/complete.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 4 | software and associated documentation files (the "Software"), to deal in the Software 5 | without restriction, including without limitation the rights to use, copy, modify, 6 | merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 7 | permit persons to whom the Software is furnished to do so. 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 9 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 11 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 12 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 13 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 14 | */ 15 | 16 | const AWS = require('aws-sdk') 17 | AWS.config.region = process.env.AWS_REGION 18 | const eventbridge = new AWS.EventBridge() 19 | 20 | // The standard Lambda handler 21 | exports.handler = async (event) => { 22 | console.log(JSON.stringify(event, null, 2)) 23 | 24 | // Handle each record in the event 25 | await Promise.all( 26 | event.Records.map(async (event) => { 27 | try { 28 | const key = event.s3.object.key.split('/')[1] 29 | await eventbridge.putEvents({ 30 | Entries: [{ 31 | // Event envelope fields 32 | Source: 'custom.happyPath', 33 | EventBusName: 'default', 34 | DetailType: 'uploadComplete', 35 | Time: new Date(), 36 | 37 | // Main event body 38 | Detail: JSON.stringify({ 39 | bucket: event.s3.bucket.name, 40 | key: event.s3.object.key, 41 | 42 | // Parse place ID and workflow ID from event 43 | placeId: event.s3.object.key.split('/')[0], 44 | ID: key.split('.')[0] 45 | }) 46 | }] 47 | }).promise() 48 | } catch (err) { 49 | console.error(`Handler error: ${err}`) 50 | } 51 | }) 52 | ) 53 | } -------------------------------------------------------------------------------- /backend/upload/local/localTest.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 4 | software and associated documentation files (the "Software"), to deal in the Software 5 | without restriction, including without limitation the rights to use, copy, modify, 6 | merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 7 | permit persons to whom the Software is furnished to do so. 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 9 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 11 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 12 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 13 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 14 | */ 15 | 16 | // Mock event 17 | const event = require('./localTestEvent') 18 | 19 | // Mock environment variables 20 | process.env.AWS_REGION = 'us-east-1' 21 | process.env.localTest = true 22 | process.env.TableName = 'hp-application' 23 | process.env.UploadBucket = 'process.env.UploadBucket' 24 | 25 | // Lambda handler 26 | const { handler } = require('../complete') 27 | 28 | const main = async () => { 29 | console.time('localTest') 30 | console.dir(await handler(event)) 31 | console.timeEnd('localTest') 32 | } 33 | 34 | main().catch(error => console.error(error)) -------------------------------------------------------------------------------- /backend/upload/local/localTestEvent.json: -------------------------------------------------------------------------------- 1 | { 2 | "Records": [ 3 | { 4 | "eventVersion": "2.1", 5 | "eventSource": "aws:s3", 6 | "awsRegion": "us-east-1", 7 | "eventTime": "2020-07-08T17:24:42.909Z", 8 | "eventName": "ObjectCreated:Put", 9 | "userIdentity": { 10 | "principalId": "AWS:AROA3DTKMGNKP7OCJBACT:happypath-backend-UploadRequestFunction-S5KLA32CXWFB" 11 | }, 12 | "requestParameters": { 13 | "sourceIPAddress": "72.21.196.68" 14 | }, 15 | "responseElements": { 16 | "x-amz-request-id": "8Y8N4Z8N4M8H4H5W", 17 | "x-amz-id-2": "RZ0tHxk/wawNBiyVBc71ia1pX4HBL0uQWuAndjRAd2pt17M38poXj42VGXzmkmc6dUD6faP6brC1HXg7IK8V9biZEJsfrjfi" 18 | }, 19 | "s3": { 20 | "s3SchemaVersion": "1.0", 21 | "configurationId": "2e28f59c-8532-42fa-a6f0-9dff1f308286", 22 | "bucket": { 23 | "name": "happy-path-upload", 24 | "ownerIdentity": { 25 | "principalId": "ASRUZ4ZWI5GFR" 26 | }, 27 | "arn": "arn:aws:s3:::happy-path-upload" 28 | }, 29 | "object": { 30 | "key": "ChIJn6yiduG6j4ARBP9ci2gGMws/e6233bf8-fc82-490c-8f24-5b4390e3ae59.jpg", 31 | "size": 302458, 32 | "eTag": "716ff2cb7efd112f606d02746288c788", 33 | "sequencer": "005F060162357DD438" 34 | } 35 | } 36 | } 37 | ] 38 | } -------------------------------------------------------------------------------- /backend/upload/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "upload-process", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "James Beswick", 11 | "license": "MIT-0", 12 | "dependencies": { 13 | "aws-sdk": "^2.708.0", 14 | "uuid": "^8.2.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /backend/upload/request.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 4 | software and associated documentation files (the "Software"), to deal in the Software 5 | without restriction, including without limitation the rights to use, copy, modify, 6 | merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 7 | permit persons to whom the Software is furnished to do so. 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 9 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 11 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 12 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 13 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 14 | */ 15 | 16 | 'use strict' 17 | 18 | const { v4: uuidv4 } = require('uuid') 19 | const AWS = require('aws-sdk') 20 | AWS.config.update({ region: process.env.AWS_REGION }) 21 | const documentClient = new AWS.DynamoDB.DocumentClient() 22 | const s3 = new AWS.S3() 23 | 24 | const URL_EXPIRATION_SECONDS = 60 25 | 26 | // Main Lambda entry point 27 | exports.handler = async (event) => { 28 | const result = await getUploadURL(event) 29 | console.log('Result: ', result) 30 | return result 31 | } 32 | 33 | const getUserId = (event) => { 34 | try { 35 | return event.requestContext.authorizer.jwt.claims.sub 36 | } catch (err) { 37 | return 'test-user' 38 | } 39 | } 40 | 41 | const getUploadURL = async function(event) { 42 | console.log(event) 43 | const actionId = uuidv4() 44 | 45 | if (!event.queryStringParameters) { 46 | return { 47 | statusCode: 422, 48 | body: JSON.stringify("Missing parameters") 49 | } 50 | } 51 | 52 | const placeId = event.queryStringParameters.placeId 53 | const key = `${actionId}.jpg` 54 | const userId = getUserId(event) 55 | 56 | // Add to DynamoDB table 57 | const result = await documentClient.put({ 58 | TableName: process.env.TableName, 59 | Item: { 60 | PK: 'workflow', 61 | SK: actionId, 62 | placeId, 63 | userId, 64 | objStatus: 'PENDING_UPLOAD', 65 | created: Date.now() 66 | } 67 | }).promise() 68 | console.log(result) 69 | 70 | // Get signed URL from S3 71 | 72 | const s3Params = { 73 | Bucket: process.env.UploadBucket, 74 | Key: `${placeId}/${actionId}.jpg`, 75 | Expires: URL_EXPIRATION_SECONDS, 76 | ContentType: 'image/jpeg' 77 | } 78 | 79 | console.log('Params: ', s3Params) 80 | const uploadURL = await s3.getSignedUrlPromise('putObject', s3Params) 81 | 82 | return JSON.stringify({ 83 | "uploadURL": uploadURL, 84 | "photoFilename": `${actionId}.jpg` 85 | }) 86 | } -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # happy-path 2 | 3 | ## Project setup 4 | ``` 5 | npm install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | npm run serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | npm run build 16 | ``` 17 | 18 | ### Lints and fixes files 19 | ``` 20 | npm run lint 21 | ``` 22 | 23 | ### Customize configuration 24 | See [Configuration Reference](https://cli.vuejs.org/config/). 25 | -------------------------------------------------------------------------------- /frontend/auth_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "domain": "<< ENTER YOUR ID>>", 3 | "clientId": "<< ENTER YOUR ID>>", 4 | "audience": "https://auth0-jwt-authorizer" 5 | } -------------------------------------------------------------------------------- /frontend/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-app", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "@auth0/auth0-spa-js": "^1.13.5", 12 | "aws-iot-device-sdk": "^2.2.6", 13 | "aws-sdk": "^2.823.0", 14 | "axios": "^1.6.0", 15 | "core-js": "^3.8.2", 16 | "gmaps-heatmap": "^1.0.2", 17 | "moment": "^2.29.1", 18 | "ngeohash": "^0.6.3", 19 | "nodes2ts": "^2.0.0", 20 | "vue": "^2.6.12", 21 | "vue-router": "^3.4.9", 22 | "vue2-gmap-custom-marker": "^6.0.0", 23 | "vue2-google-maps": "^0.10.7", 24 | "vuetify": "^2.4.2", 25 | "vuex": "^3.6.0" 26 | }, 27 | "devDependencies": { 28 | "@vue/cli-plugin-babel": "~4.5.10", 29 | "@vue/cli-plugin-eslint": "~4.5.10", 30 | "@vue/cli-plugin-router": "^4.5.10", 31 | "@vue/cli-service": "~4.5.10", 32 | "babel-eslint": "^10.1.0", 33 | "eslint": "^7.17.0", 34 | "eslint-plugin-vue": "^7.4.1", 35 | "sass": "^1.32.1", 36 | "sass-loader": "^10.1.0", 37 | "vue-cli-plugin-vuetify": "~2.0.9", 38 | "vue-template-compiler": "^2.6.12", 39 | "vuetify-loader": "^1.6.0" 40 | }, 41 | "eslintConfig": { 42 | "root": true, 43 | "env": { 44 | "node": true 45 | }, 46 | "extends": [ 47 | "plugin:vue/essential", 48 | "eslint:recommended" 49 | ], 50 | "parserOptions": { 51 | "parser": "babel-eslint" 52 | }, 53 | "rules": {} 54 | }, 55 | "browserslist": [ 56 | "> 1%", 57 | "last 2 versions", 58 | "not dead" 59 | ] 60 | } 61 | -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/happy-path/368ab10506c9834b8056a4973a461134d982d764/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 |