├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md └── a.txt ├── .gitignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.txt ├── NOTICE.txt ├── README.md ├── deployment ├── build-s3-dist.sh ├── custom-resources-stack.template ├── elasticsearchkibana.template ├── location-based-notifications-using-amazon-pinpoint.template ├── run-unit-tests.sh └── website-stack.template └── source ├── architecture.png ├── cognitoPosConfirmation ├── cognitoPosConfirmation.py └── test_cognitoPosConfirmation.py ├── es-custom-resource-js ├── cfn-response.js ├── es-requests.js ├── kibana-objects.ndjson ├── package-lock.json └── package.json ├── getCoordsFromAddress ├── getCoordsFromAddress.py ├── requirements.txt └── test_getCoordsFromAddress.py ├── getCurrentAddress ├── getCurrentAddress.py ├── requirements.txt └── test_getCurrentAddress.py ├── indexDdbDataToEs ├── indexDdbDataToEs.py └── requirements.txt ├── lambda-custom-resource ├── cfnResponse.py ├── lambdaDeploy.py └── requirements.txt ├── manageMessages ├── manageMessages.py └── test_manageMessages.py ├── sendMessage ├── sendMessage.py └── test_sendMessage.py ├── website-contents ├── README.md ├── amplify │ ├── .config │ │ └── project-config.json │ └── backend │ │ └── backend-config.json ├── package-lock.json ├── package.json ├── postbuild.sh ├── public │ ├── favicon.ico │ ├── index.html │ ├── manifest.json │ └── robots.txt └── src │ ├── App.css │ ├── App.js │ ├── aws-exports.js │ ├── components │ ├── CustomSignUp.js │ ├── GeofenceDetails.js │ ├── GeofenceEdit.js │ ├── GeofenceForm.js │ ├── GeofenceHome.js │ ├── GeofenceNavBar.js │ └── GeofenceTable.js │ ├── graphql │ ├── mutations.js │ └── queries.js │ ├── index.css │ ├── index.js │ └── serviceWorker.js └── website-custom-resource ├── cfnResponse.py ├── requirements.txt └── websiteDeploy.py /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior. 15 | 16 | **Expected behavior** 17 | A clear and concise description of what you expected to happen. 18 | 19 | **Please complete the following information about the solution:** 20 | - [ ] Version: [e.g. v1.1.0] 21 | 22 | To get the version of the solution, you can look at the description of the created CloudFormation stack. For example, "(SO0099a) - The AWS Cloudformation template for deploymnet of the Location-based Notifications Using Amazon Pinpoint solution. Version v1.0.0". 23 | 24 | - [ ] Region: [e.g. us-east-1] 25 | - [ ] Was the solution modified from the version published on this repository? 26 | - [ ] If the answer to the previous question was yes, are the changes available on GitHub? 27 | - [ ] Have you checked your [service quotas](https://docs.aws.amazon.com/general/latest/gr/aws_service_limits.html) for the sevices this solution uses? 28 | - [ ] Were there any errors in the CloudWatch Logs? 29 | 30 | **Screenshots** 31 | If applicable, add screenshots to help explain your problem (please **DO NOT include sensitive information**). 32 | 33 | **Additional context** 34 | Add any other context about the problem here. -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this solution 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the feature you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | *Issue #, if available:* 2 | 3 | *Description of changes:* 4 | 5 | By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice. 6 | -------------------------------------------------------------------------------- /.github/a.txt: -------------------------------------------------------------------------------- 1 | a 2 | b 3 | c 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ########################################################################################################################################### 2 | # Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved # 3 | # # 4 | # Licensed under the MIT No Attribution License (MIT-0) (the ‘License’). You may not use this file except in compliance # 5 | # with the License. A copy of the License is located at # 6 | # # 7 | # https://opensource.org/licenses/MIT-0 # 8 | # # 9 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files # 10 | # (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, # 11 | # publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so. # 12 | # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # 13 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR # 14 | # ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH # 15 | # THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # 16 | ############################################################################################################################################ 17 | 18 | # compiled output 19 | **/dist 20 | **/global-s3-assets 21 | **/regional-s3-assets 22 | **/open-source 23 | **/.zip 24 | **/tmp 25 | **/out-tsc 26 | 27 | # dependencies 28 | **/node_modules 29 | 30 | # e2e 31 | **/e2e/*.js 32 | **/e2e/*.map 33 | 34 | # misc 35 | **/npm-debug.log 36 | **/testem.log 37 | **/.vscode/settings.json 38 | 39 | # System Files 40 | **/.DS_Store 41 | **/.vscode 42 | 43 | Config 44 | **/geofence-a*.yaml 45 | **/geofence-a*.json 46 | 47 | # amplify 48 | **/amplify/team-provider-info.json 49 | **/amplify/\#current-cloud-backend 50 | **/amplify/.config/local-* 51 | **/amplify/mock-data 52 | **/amplify/backend/amplify-meta.json 53 | **/amplify/backend/awscloudformation 54 | 55 | # temp files to ignore 56 | template.drawio 57 | temp-CR-ESLogs.yaml 58 | sync-directories.sh 59 | *.code-workspace -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [1.0.0] - 2020-06-08 8 | ### Added 9 | - initial repository version 10 | -------------------------------------------------------------------------------- /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](https://github.com/awslabs/location-based-notifications-using-amazon-pinpoint/issues), or [recently closed](https://github.com/awslabs/location-based-notifications-using-amazon-pinpoint/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20), 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'](https://github.com/awslabs/location-based-notifications-using-amazon-pinpoint/labels/help%20wanted) 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](https://github.com/awslabs/location-based-notifications-using-amazon-pinpoint/blob/master/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.txt: -------------------------------------------------------------------------------- 1 | Copyright 2020 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 this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so. 4 | 5 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /NOTICE.txt: -------------------------------------------------------------------------------- 1 | Location-based Notifications Using Amazon Pinpoint 2 | Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | Licensed under the Apache License Version 2.0 (the "License"). You may not use this file except 4 | in compliance with the License. A copy of the License is located at http://www.apache.org/licenses/ 5 | or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, 6 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, express or implied. See the License for the 7 | specific language governing permissions and limitations under the License. 8 | 9 | ********************** 10 | THIRD PARTY COMPONENTS 11 | ********************** 12 | This software includes third party software subject to the following copyrights: 13 | 14 | AWS SDK under the Apache License Version 2.0 15 | herepy under the Massachusetts Institute of Technology (MIT) license 16 | request_aws4auth under the Massachusetts Institute of Technology (MIT) license 17 | form-data under the Massachusetts Institute of Technology (MIT) license 18 | https under the Massachusetts Institute of Technology (MIT) license 19 | url under the Massachusetts Institute of Technology (MIT) license 20 | material-ui under the Massachusetts Institute of Technology (MIT) license 21 | aws-amplify under the Apache License Version 2.0 22 | aws-amplify-react under the Apache License Version 2.0 23 | react under the Massachusetts Institute of Technology (MIT) license 24 | react-dom under the Massachusetts Institute of Technology (MIT) license 25 | react-router-dom under the Massachusetts Institute of Technology (MIT) license 26 | react-scripts under the Massachusetts Institute of Technology (MIT) license 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | *Deprecation Notice*: This AWS Solution has been archived and is no longer maintained by AWS. To discover other solutions, please visit the [AWS Solutions Library](https://aws.amazon.com/solutions/). 2 | 3 | # location-based-notifications-using-amazon-pinpoint 4 | 5 | The Location-Based Notifications Using Amazon Pinpoint solution allows you to engage with your customers based on their location. You can use the solution to create and manage geographic boundaries (geofences) around any businesses or points of interest. These geofences help you customize your engagement campaigns by sending push notifications to your customers when they enter or exit a geofence. For example, you can send a notification when a customer is walking to meet their rideshare, or when they enter an airport to check-in for a flight. 6 | 7 | The solution provides the following features: 8 | 9 | * A new Amazon Pinpoint project with push notifications enabled (both iOS and Android devices are supported out-of-the-box) 10 | * An Amazon Cognito user pool created to provide authentication to the solution’s APIs and administration portal 11 | * A set of secure GraphQL APIs in AWS AppSync 12 | * A secure administration portal (website) that allows you to quickly and easily manage geofences and create personalized message templates for each location 13 | * A Kibana dashboard to display analytics and provide near real-time feedback 14 | 15 | *** 16 | 17 | ## Architecture 18 | 19 | The AWS CloudFormation template deploys a new Amazon Pinpoint project with push notifications enabled and an event stream that enables Amazon Kinesis Data Firehose to store event data in Amazon Simple Storage Service (Amazon S3). 20 | 21 | The template creates a set of secure GraphQL APIs in AWS AppSync, which can be used by a mobile application to interact the solution. Amazon Cognito User Pools provide authentication and authorization to the APIs and the administrator portal. 22 | 23 | In AWS AppSync, a set of resolvers are created to provide the implementation for the APIs, including an Amazon DynamoDB table that stores the geofence’s location. The APIs that interact with this DynamoDB table are used by the website and any application that you connect to this solution. Four additional resolvers are deployed with AWS Lambda as data sources. 24 | 25 | The solution includes an administration portal for managing the solution. The portal is hosted using Amazon Simple Storage Service (Amazon S3) as an origin to an Amazon CloudFront distribution. Authentication and authorization are provided by the same Amazon Cognito User Pool, but through a different group of users. An AWS Lambda function is used as a postConfirmation trigger inside the Amazon Cognito user pool. 26 | 27 | The template provides the choice to deploy an Amazon Elasticsearch domain with Kibana. If you chose to deploy it, the Kibana portal authentication is backed by Amazon Cognito and uses the same group of users as those registered in the administration portal. When a new record is written to the Amazon DynamoDB table, or a modification of an existing record occurs, a DynamoDB Streams is triggered and calls an AWS Lambda to index data into this Amazon Elasticsearch domain. The solution includes a sample Kibana dashboard as a starting point to demonstrate analytic capabilities. 28 | 29 | ![architecture](source/architecture.png) 30 | 31 | *** 32 | 33 | ## File Structure 34 | 35 | Upon successfully cloning the repository into your local development environment but **prior** to running locally, you will see the following file structure in your editor: 36 | 37 | ``` 38 | ├── deployment [folder containing build scripts]│ 39 | │ ├── build-s3-dist.sh [A script to prepare the solution for deploying from source code] 40 | │ ├── run-unit-tests.sh [A script to run unit tests on the AWS Lambda functions] 41 | │ ├── location-based-notifications-using-amazon-pinpoint.template [Main AWS CloudFormation template for the solution] 42 | │ ├── website-stack.template [Nested stack to deploy the admin website] 43 | │ ├── custom-resources-stack.template [Nested stack to deploy the AWS Lambda@Edge function in us-east-1] 44 | │ ├── elasticsearchkibana.template [Nested stack to deploy the analytics stack] 45 | ├── source [Source code containing AWS Lambda functions and the admin portal] 46 | │ ├── cognitoPosConfirmation [Cognito pos-confirmation trigger AWS Lambda function] 47 | │ ├── es-custom-resource-js [AWS CloudFormation custom resource Lambda function to deploy Kibana assets] 48 | │ ├── getCoordsFromAddress [GetCoordsFromAddress AWS Lambda function used as AWS AppSync datasource] 49 | │ ├── getCurrentAddress [GetCurrentAddress AWS Lambda function used as AWS AppSync datasource] 50 | │ ├── indexDdbDataToEs [AWS Lambda function used with DynamoDB streams to index data into Amazon Elasticsearch] 51 | │ ├── lambda-custom-resource [AWs CloudFormation custom resource Lambda function to deploy Lambda@Edge function] 52 | │ ├── manageMessages [ManageMessages AWS Lambda function used as AWS AppSync datasource] 53 | │ ├── sendMessage [SendMessage AWS Lambda function used as AWS AppSync datasource] 54 | │ ├── website-contents [Admin portal react website source code] 55 | │ ├── website-custom-resource [AWs CloudFormation custom resource Lambda function to deploy the admin portal to S3] 56 | ├── .gitignore 57 | ├── CHANGELOG.md [required for every solution to include changes based on version to auto[uild release notes] 58 | ├── CODE_OF_CONDUCT.md [standardized open source file for all solutions] 59 | ├── CONTRIBUTING.md [standardized open source file for all solutions] 60 | ├── LICENSE.txt [required open source file for all solutions - should contain the MIT No Attribution License (MIT-0) license] 61 | ├── NOTICE.txt [required open source file for all solutions - should contain references to all 3rd party libraries] 62 | └── README.md [required file for all solutions] 63 | ``` 64 | 65 | *** 66 | 67 | ## Building the solution 68 | 69 | ### 1. Get source code 70 | 71 | Clone this git repository. 72 | 73 | `git clone https://github.com/awslabs/` 74 | 75 | *** 76 | 77 | ### 2. Running Unit Tests 78 | 79 | The `/deployment/run-unit-tests.sh ` script is the centralized script for running all unit tests for AWS Lambda functions. 80 | 81 | - Note: It is the developer's responsibility to ensure that all test commands are called in this script, and that it is kept up to date. 82 | 83 | This script is called from the solution build scripts to ensure that specified tests are passing while performing build, validation and publishing tasks via the pipeline. 84 | 85 | *** 86 | 87 | ### 3. Building Project Distributable 88 | 89 | * Configure the bucket name of your target Amazon S3 distribution bucket 90 | ``` 91 | export DIST_OUTPUT_BUCKET=my-bucket-name # bucket where customized code will reside 92 | export SOLUTION_NAME=my-solution-name 93 | export VERSION=my-version # version number for the customized code 94 | ``` 95 | _Note:_ You would have to create an S3 bucket with the prefix 'my-bucket-name-'; aws_region is where you are testing the customized solution. Also, the assets in bucket should be publicly accessible. 96 | 97 | * Now build the distributable: 98 | ``` 99 | cd deployment/ 100 | chmod +x ./build-s3-dist.sh \n 101 | ./build-s3-dist.sh $DIST_OUTPUT_BUCKET $SOLUTION_NAME $VERSION \n 102 | ``` 103 | 104 | * Deploy the distributable to an Amazon S3 bucket in your account. _Note:_ you must have the AWS Command Line Interface installed. 105 | 106 | ``` 107 | aws s3 cp ./deployment/ s3://my-bucket-name-/$SOLUTION_NAME/$VERSION/ --recursive --acl bucket-owner-full-control --profile aws-cred-profile-name \n 108 | ``` 109 | 110 | ### 4. Creating the required parameters 111 | 112 | * There are some parameters that should be created before you can deploy the solution. Make sure you navigate to the AWS Console within the region you want to deploy the solution and follow the steps below. 113 | 114 | * On the AWS Console, navigate to the AWS Secrets Manager console and create the following parameters: 115 | - APNS Certificate 116 | - APNS Private Key 117 | - FCM API Key 118 | - HERE API Key 119 | 120 | * In order to generate some of those keys you will have to access the services on the Apple Notification Service (APNS) website, the Firebase Cloud Messaging (FCM) and the HERE geocoding solution. Please refer to each website to find the proper instructions on how to do so. 121 | 122 | * To create the parameters, go to AWS SSM Parameter Store and follow the procedure below for each secret mentioned above. Even though Secrets Manager supports multiple keys inside the same secret, this solution uses a single key per secret: 123 | - Click on `Store a new secret` 124 | - Choose `Other type of secret` 125 | - On the Key box name it however you choose and paste the correct value on the Value box. 126 | - Click on `Next` and name your secret properly. Remember that name because you are going to need it to deploy the solution. 127 | - Click on `Next` and leave all the defaults on the rotation page. 128 | - Click on `Next` and review the parameters of your secret and click on `Store` when you are finished. 129 | 130 | ### 5. Deploy the Cloudformation template 131 | 132 | * Get the link of the solution template uploaded to your Amazon S3 bucket. It should be located inside your bucket under the name `$SOLUTION_NAME/$VERSION/location-based-notifications-using-amazon-pinpoint.template`. 133 | * Deploy the solution to your account by launching a new AWS CloudFormation stack using the link of the solution template in Amazon S3. 134 | 135 | 136 | *** 137 | 138 | Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved 139 | 140 | Licensed under the MIT No Attribution License (MIT-0) (the ‘License’). You may not use this file except in compliance 141 | with the License. A copy of the License is located at 142 | 143 | https://opensource.org/licenses/MIT-0 144 | 145 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files 146 | (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, 147 | publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so. 148 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 149 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR 150 | ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH 151 | THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 152 | -------------------------------------------------------------------------------- /deployment/custom-resources-stack.template: -------------------------------------------------------------------------------- 1 | ########################################################################################################################################### 2 | # Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved # 3 | # # 4 | # Licensed under the MIT No Attribution License (MIT-0) (the ‘License’). You may not use this file except in compliance # 5 | # with the License. A copy of the License is located at # 6 | # # 7 | # https://opensource.org/licenses/MIT-0 # 8 | # # 9 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files # 10 | # (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, # 11 | # publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so. # 12 | # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # 13 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR # 14 | # ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH # 15 | # THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # 16 | ############################################################################################################################################ 17 | 18 | # Location-based Notifications Using Amazon Pinpoint solution 19 | # 20 | # template for location-based-notifications-using-amazon-pinpoint 21 | # **DO NOT DELETE** 22 | # 23 | # author: 24 | # - Ciro Santos (cirosant@) 25 | # - Paulo Aragão (paragao@) 26 | # - aws-solutions-builder@ 27 | 28 | AWSTemplateFormatVersion: '2010-09-09' 29 | 30 | Description: "(SO0099b) - The AWS Cloudformation template for deploymnet of the Location-based Notifications Using Amazon Pinpoint solution" 31 | 32 | Parameters: 33 | SolutionsBucket: 34 | Description: original solutions bucket 35 | Type: String 36 | SolutionsPrefix: 37 | Description: original solutions prefix 38 | Type: String 39 | GeofenceApiUrl: 40 | Description: Geofence API GraphQL URL created in the main stack 41 | Type: String 42 | 43 | Resources: 44 | LambdaEdgeCustomResourceLambdaRole: 45 | Type: 'AWS::IAM::Role' 46 | UpdateReplacePolicy: Delete 47 | DeletionPolicy: Delete 48 | Properties: 49 | AssumeRolePolicyDocument: 50 | Statement: 51 | - Action: 'sts:AssumeRole' 52 | Effect: Allow 53 | Principal: 54 | Service: 55 | - 'lambda.amazonaws.com' 56 | - 'edgelambda.amazonaws.com' 57 | Version: '2012-10-17' 58 | Policies: 59 | - PolicyName: LambdaBasicExecutionPolicy 60 | PolicyDocument: 61 | Version: '2012-10-17' 62 | Statement: 63 | - Effect: Allow 64 | Action: 65 | - 'logs:CreateLogGroup' 66 | Resource: 67 | - !Sub arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:* 68 | - Effect: Allow 69 | Action: 70 | - 'logs:CreateLogStream' 71 | - 'logs:PutLogEvents' 72 | Resource: 73 | - !Sub arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/*:* 74 | - PolicyName: LambdaAtEdgeExecutionPolicy 75 | PolicyDocument: 76 | Version: '2012-10-17' 77 | Statement: 78 | - Effect: Allow 79 | Action: 80 | - 'lambda:GetFunction' 81 | - 'lambda:CreateFunction' 82 | - 'lambda:DeleteFunction' 83 | - 'lambda:DisableReplication' 84 | - 'lambda:EnableReplication*' 85 | - 'lambda:GetLayerVersion' 86 | - 'lambda:PublishVersion' 87 | - 'cloudfront:UpdateDistribution' 88 | - 'cloudfront:CreateDistribution' 89 | - 'cloudfront:ListDistributionsByLambdaFunction' 90 | Resource: 91 | - !Sub arn:${AWS::Partition}:lambda:us-east-1:${AWS::AccountId}:function:GeofencesSecHeadersLambda* 92 | - !Sub arn:${AWS::Partition}:cloudfront::${AWS::AccountId}:distribution/* 93 | - PolicyName: AllowDeletionLambdaEdge 94 | PolicyDocument: 95 | Version: '2012-10-17' 96 | Statement: 97 | - Effect: Allow 98 | Action: 99 | - 'iam:CreateServiceLinkedRole' 100 | - 'iam:PassRole' 101 | - 'iam:CreateRole' 102 | - 'iam:CreatePolicy' 103 | - 'iam:AttachRolePolicy' 104 | - 'iam:DetachRolePolicy' 105 | - 'iam:DeleteRole' 106 | - 'iam:DeletePolicy' 107 | - 'iam:DeleteServiceLinkedRole' 108 | Resource: 109 | - !Sub arn:${AWS::Partition}:iam::${AWS::AccountId}:role/service-role/GeofencesSecHeadersLambdaRole* 110 | - !Sub arn:${AWS::Partition}:iam::${AWS::AccountId}:policy/GeofencesSecHeadersLambdaPolicy* 111 | - !Sub arn:${AWS::Partition}:lambda:us-east-1:${AWS::AccountId}:function:GeofencesSecHeadersLambda* 112 | 113 | LambdaEdgeCustomResourceLambdaPolicy: 114 | Type: 'AWS::IAM::Policy' 115 | UpdateReplacePolicy: Delete 116 | DeletionPolicy: Delete 117 | Properties: 118 | PolicyDocument: 119 | Statement: 120 | - Action: 121 | - 'ssm:PutParameter' 122 | - 'ssm:DeleteParameter' 123 | - 'ssm:GetParameterHistory' 124 | - 'ssm:GetParametersByPath' 125 | - 'ssm:GetParameters' 126 | - 'ssm:GetParameter' 127 | - 'ssm:DeleteParameters' 128 | Effect: Allow 129 | Resource: !Sub arn:${AWS::Partition}:ssm:us-east-1:${AWS::AccountId}:parameter/geofences-region* 130 | Version: '2012-10-17' 131 | PolicyName: !Sub LambdaEdgeCustomResourceLambdaPolicy-${AWS::Region}-${AWS::AccountId} 132 | Roles: 133 | - !Ref LambdaEdgeCustomResourceLambdaRole 134 | 135 | LambdaEdgeCustomResourceLambda: 136 | Type: 'AWS::Lambda::Function' 137 | UpdateReplacePolicy: Delete 138 | DeletionPolicy: Delete 139 | Properties: 140 | Code: 141 | S3Bucket: !Ref SolutionsBucket 142 | S3Key: !Sub ${SolutionsPrefix}lambda-custom-resource.zip 143 | Handler: lambdaDeploy.handler 144 | Role: !GetAtt 145 | - LambdaEdgeCustomResourceLambdaRole 146 | - Arn 147 | Runtime: python3.7 148 | Timeout: 300 149 | DependsOn: 150 | - LambdaEdgeCustomResourceLambdaPolicy 151 | - LambdaEdgeCustomResourceLambdaRole 152 | Metadata: 153 | cfn_nag: 154 | rules_to_suppress: 155 | - id: W58 156 | reason: using a policy that allows write permission to CloudWatch Logs. 157 | 158 | LambdaEdgeCustomResource: 159 | Type: 'AWS::CloudFormation::CustomResource' 160 | UpdateReplacePolicy: Delete 161 | DeletionPolicy: Delete 162 | Properties: 163 | ServiceToken: !GetAtt 164 | - LambdaEdgeCustomResourceLambda 165 | - Arn 166 | Requests: 167 | - awsRegion: !Ref 'AWS::Region' 168 | lambdaName: !Sub 169 | - GeofencesSecHeadersLambda${Hash} 170 | - { Hash: !Select [4, !Split ['-', !Select [2, !Split ['/', !Ref 'AWS::StackId']]]] } 171 | lambdaRoleName: !Sub 172 | - GeofencesSecHeadersLambdaRole${Hash} 173 | - { Hash: !Select [4, !Split ['-', !Select [2, !Split ['/', !Ref 'AWS::StackId']]]] } 174 | lambdaPolicyName: !Sub 175 | - GeofencesSecHeadersLambdaPolicy${Hash} 176 | - { Hash: !Select [4, !Split ['-', !Select [2, !Split ['/', !Ref 'AWS::StackId']]]] } 177 | ssmParamName: !Sub 178 | - geofences-region${Hash} 179 | - { Hash: !Select [4, !Split ['-', !Select [2, !Split ['/', !Ref 'AWS::StackId']]]] } 180 | appSyncEndpoint: !Ref GeofenceApiUrl 181 | 182 | Outputs: 183 | LambdaEdgeCustomResourceArn: 184 | Description: parameter passed to the website-stack.yaml 185 | Value: !GetAtt LambdaEdgeCustomResource.lambdaArn -------------------------------------------------------------------------------- /deployment/run-unit-tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ########################################################################################################################################### 4 | # Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved # 5 | # # 6 | # Licensed under the MIT No Attribution License (MIT-0) (the ‘License’). You may not use this file except in compliance # 7 | # with the License. A copy of the License is located at # 8 | # # 9 | # https://opensource.org/licenses/MIT-0 # 10 | # # 11 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files # 12 | # (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, # 13 | # publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so. # 14 | # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # 15 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR # 16 | # ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH # 17 | # THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # 18 | ############################################################################################################################################ 19 | # 20 | # This assumes all of the OS-level configuration has been completed and git repo has already been cloned 21 | # 22 | # This script should be run from the repo's deployment directory 23 | # cd deployment 24 | # ./run-unit-tests.sh 25 | # 26 | 27 | # Get reference for all important folders 28 | template_dir="$PWD" 29 | source_dir="$template_dir/../source" 30 | 31 | function clean_up { 32 | find . -maxdepth 1 -not -name "*.py" -not -name "requirements.txt" -not -name "package.json" -not -name "." -not -name "*.js" -not -name "*.ndjson" -not -name "*.yaml"| xargs -I {} rm -rf {} 33 | } 34 | 35 | function executeUnitTests { 36 | FUNCTION_NAME=$1 37 | DEPENDENCY=$2 38 | 39 | echo "------------------------------------------------------------------------------" 40 | echo "[Test] Services - $1" 41 | echo "------------------------------------------------------------------------------" 42 | 43 | cd $source_dir/$FUNCTION_NAME 44 | 45 | if [ -f "requirements.txt" ]; then 46 | pip install -r requirements.txt -t . 47 | fi 48 | 49 | if [[ -n "$DEPENDENCY" ]]; then 50 | pip install $DEPENDENCY 51 | fi 52 | 53 | python -m unittest 54 | clean_up 55 | } 56 | 57 | executeUnitTests getCurrentAddress 58 | executeUnitTests getCoordsFromAddress 59 | executeUnitTests cognitoPosConfirmation moto 60 | executeUnitTests manageMessages 61 | executeUnitTests sendMessage 62 | -------------------------------------------------------------------------------- /deployment/website-stack.template: -------------------------------------------------------------------------------- 1 | ########################################################################################################################################### 2 | # Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved # 3 | # # 4 | # Licensed under the MIT No Attribution License (MIT-0) (the ‘License’). You may not use this file except in compliance # 5 | # with the License. A copy of the License is located at # 6 | # # 7 | # https://opensource.org/licenses/MIT-0 # 8 | # # 9 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files # 10 | # (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, # 11 | # publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so. # 12 | # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # 13 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR # 14 | # ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH # 15 | # THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # 16 | ############################################################################################################################################ 17 | 18 | # author: 19 | # - Ciro Santos (cirosant@) 20 | # - Paulo Aragão (paragao@) 21 | # - aws-solutions-builder@ 22 | 23 | AWSTemplateFormatVersion: '2010-09-09' 24 | 25 | Description: "(SO0099d) - The AWS Cloudformation template for deploymnet of the Location-based Notifications Using Amazon Pinpoint solution" 26 | 27 | Parameters: 28 | SolutionsBucket: 29 | Description: original solutions bucket 30 | Type: String 31 | SolutionsPrefix: 32 | Description: original solutions prefix 33 | Type: String 34 | UserPoolId: 35 | Description: user pool id 36 | Type: String 37 | IdentityPoolId: 38 | Description: identity pool id 39 | Type: String 40 | AppClientId: 41 | Description: app client id 42 | Type: String 43 | PinpointAppId: 44 | Description: Pinpoint project id 45 | Type: String 46 | AppSyncEndpoint: 47 | Description: AppSync Endpoint id 48 | Type: String 49 | S3LogBucket: 50 | Description: Logging bucket from the main stack 51 | Type: String 52 | LambdaEdgeArn: 53 | Description: LambdaEdgeCustomeResource Arn from the custom-resources-stack 54 | Type: String 55 | 56 | Resources: 57 | WebSiteCustomResourceLambdaRole: 58 | Type: 'AWS::IAM::Role' 59 | Properties: 60 | AssumeRolePolicyDocument: 61 | Statement: 62 | - Action: 'sts:AssumeRole' 63 | Effect: Allow 64 | Principal: 65 | Service: lambda.amazonaws.com 66 | Version: '2012-10-17' 67 | Policies: 68 | - PolicyName: LambdaBasicExecutionPolicy 69 | PolicyDocument: 70 | Version: '2012-10-17' 71 | Statement: 72 | - Effect: Allow 73 | Action: 74 | - 'logs:CreateLogGroup' 75 | Resource: 76 | - !Sub arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:* 77 | - Effect: Allow 78 | Action: 79 | - 'logs:CreateLogStream' 80 | - 'logs:PutLogEvents' 81 | Resource: 82 | - !Sub arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/*:* 83 | Metadata: 84 | cfn_nag: 85 | rules_to_suppress: 86 | - id: W58 87 | reason: using an inline policy that allows for the lambda to publish the logs 88 | 89 | WebSiteCustomResourceLambdaPolicy: 90 | Type: 'AWS::IAM::Policy' 91 | Properties: 92 | PolicyDocument: 93 | Statement: 94 | - Action: 95 | - 's3:AbortMultipartUpload' 96 | - 's3:GetBucketLocation' 97 | - 's3:GetObject' 98 | - 's3:HeadObject' 99 | - 's3:ListBucket' 100 | - 's3:ListBucketMultipartUploads' 101 | - 's3:PutObject' 102 | - 's3:DeleteObject' 103 | Effect: Allow 104 | Resource: 105 | - !Sub arn:${AWS::Partition}:s3:::${SolutionsBucket}/${SolutionsPrefix} 106 | - !Sub arn:${AWS::Partition}:s3:::${SolutionsBucket}/${SolutionsPrefix}* 107 | - !Sub arn:${AWS::Partition}:s3:::${GeofenceAdminWebSiteBucket} 108 | - !Sub arn:${AWS::Partition}:s3:::${GeofenceAdminWebSiteBucket}/* 109 | Version: '2012-10-17' 110 | PolicyName: 'WebSiteCustomResourceLambdaPolicy' 111 | Roles: 112 | - !Ref WebSiteCustomResourceLambdaRole 113 | 114 | WebSiteCustomResourceLambda: 115 | Type: 'AWS::Lambda::Function' 116 | Properties: 117 | Code: 118 | S3Bucket: !Ref SolutionsBucket 119 | S3Key: !Sub ${SolutionsPrefix}website-custom-resource.zip 120 | Handler: websiteDeploy.handler 121 | Role: !GetAtt 122 | - WebSiteCustomResourceLambdaRole 123 | - Arn 124 | Runtime: python3.7 125 | Environment: 126 | Variables: 127 | REGION: !Ref 'AWS::Region' 128 | Timeout: 600 129 | DependsOn: 130 | - WebSiteCustomResourceLambdaPolicy 131 | - WebSiteCustomResourceLambdaRole 132 | Metadata: 133 | cfn_nag: 134 | rules_to_suppress: 135 | - id: W58 136 | reason: using an inline policy that allows to write to CloudWatch Logs. 137 | 138 | WebSiteCustomResource: 139 | Type: 'AWS::CloudFormation::CustomResource' 140 | Properties: 141 | ServiceToken: !GetAtt 142 | - WebSiteCustomResourceLambda 143 | - Arn 144 | Requests: 145 | - websiteBucket: !Ref GeofenceAdminWebSiteBucket 146 | originBucket: !Ref SolutionsBucket 147 | originPrefix: !Ref SolutionsPrefix 148 | userPoolId: !Ref UserPoolId 149 | appClientId: !Ref AppClientId 150 | identityPoolId: !Ref IdentityPoolId 151 | pinpointAppId: !Ref PinpointAppId 152 | appSyncEndpoint: !Ref AppSyncEndpoint 153 | DependsOn: 154 | - WebSiteCustomResourceLambda 155 | - GeofenceAdminWebsiteCFDistribution 156 | UpdateReplacePolicy: Delete 157 | DeletionPolicy: Delete 158 | 159 | GeofenceAdminWebSiteBucket: 160 | Type: 'AWS::S3::Bucket' 161 | Properties: 162 | AccessControl: LogDeliveryWrite 163 | LoggingConfiguration: 164 | DestinationBucketName: !Ref S3LogBucket 165 | LogFilePrefix: adminwebsite-bucket-access-logs/ 166 | VersioningConfiguration: 167 | Status: Enabled 168 | BucketEncryption: 169 | ServerSideEncryptionConfiguration: 170 | - ServerSideEncryptionByDefault: 171 | SSEAlgorithm: AES256 172 | BucketName: !Sub 173 | - geofences-admin-website-${Hash} 174 | - { Hash: !Select [4, !Split ['-', !Select [2, !Split ['/', !Ref 'AWS::StackId']]]] } 175 | WebsiteConfiguration: 176 | ErrorDocument: error.html 177 | IndexDocument: index.html 178 | UpdateReplacePolicy: Retain 179 | DeletionPolicy: Retain 180 | 181 | GeofenceAdminWebSiteBucketPolicy: 182 | Type: 'AWS::S3::BucketPolicy' 183 | Properties: 184 | Bucket: !Ref GeofenceAdminWebSiteBucket 185 | PolicyDocument: 186 | Statement: 187 | - Action: 's3:GetObject' 188 | Effect: Allow 189 | Principal: 190 | CanonicalUser: !GetAtt 191 | - GeofenceCFOAI 192 | - S3CanonicalUserId 193 | Condition: 194 | Bool: 195 | aws:SecureTransport: 196 | - 'True' 197 | Resource: !Sub ${GeofenceAdminWebSiteBucket.Arn}/* 198 | - Action: 199 | - 's3:GetObject*' 200 | - 's3:GetBucket*' 201 | - 's3:List*' 202 | Effect: Allow 203 | Principal: 204 | CanonicalUser: !GetAtt 205 | - GeofenceCFOAI 206 | - S3CanonicalUserId 207 | Condition: 208 | Bool: 209 | aws:SecureTransport: 210 | - 'True' 211 | Resource: 212 | - !Sub ${GeofenceAdminWebSiteBucket.Arn} 213 | - !Sub ${GeofenceAdminWebSiteBucket.Arn}/* 214 | Version: '2012-10-17' 215 | 216 | GeofenceCFOAI: 217 | Type: 'AWS::CloudFront::CloudFrontOriginAccessIdentity' 218 | Properties: 219 | CloudFrontOriginAccessIdentityConfig: 220 | Comment: Allows CloudFront to reach the bucket 221 | 222 | GeofenceAdminWebsiteCFDistribution: 223 | Type: 'AWS::CloudFront::Distribution' 224 | DependsOn: 225 | - GeofenceAdminWebSiteBucket 226 | Properties: 227 | DistributionConfig: 228 | Logging: 229 | Bucket: !Sub ${S3LogBucket}.s3.amazonaws.com 230 | Prefix: cloudfront-logs/ 231 | DefaultCacheBehavior: 232 | AllowedMethods: 233 | - GET 234 | - HEAD 235 | CachedMethods: 236 | - GET 237 | - HEAD 238 | Compress: true 239 | ForwardedValues: 240 | Cookies: 241 | Forward: none 242 | QueryString: false 243 | TargetOriginId: origin1 244 | ViewerProtocolPolicy: redirect-to-https 245 | LambdaFunctionAssociations: 246 | - EventType: 'origin-response' 247 | LambdaFunctionARN: !Ref LambdaEdgeArn 248 | DefaultRootObject: index.html 249 | Enabled: true 250 | HttpVersion: http2 251 | IPV6Enabled: true 252 | Origins: 253 | - DomainName: !GetAtt 254 | - GeofenceAdminWebSiteBucket 255 | - RegionalDomainName 256 | Id: origin1 257 | S3OriginConfig: 258 | OriginAccessIdentity: !Sub origin-access-identity/cloudfront/${GeofenceCFOAI} 259 | PriceClass: PriceClass_All 260 | ViewerCertificate: 261 | CloudFrontDefaultCertificate: true 262 | Metadata: 263 | cfn_nag: 264 | rules_to_suppress: 265 | - id: W70 266 | reason: Enforcing usage of Http/2.0 and HTTPS which enforces the usage of TLS1.2 267 | 268 | Outputs: 269 | AdministrationWebsiteURL: 270 | Description: URL for the administration website 271 | Value: !GetAtt 272 | - GeofenceAdminWebsiteCFDistribution 273 | - DomainName 274 | -------------------------------------------------------------------------------- /source/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awslabs/location-based-notifications-using-amazon-pinpoint/8cd68d5a1fe11f32b752739e34f9250453e418c7/source/architecture.png -------------------------------------------------------------------------------- /source/cognitoPosConfirmation/cognitoPosConfirmation.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved 3 | 4 | Licensed under the MIT No Attribution License (MIT-0) (the ‘License’). You may not use this file except in compliance 5 | with the License. A copy of the License is located at 6 | 7 | https://opensource.org/licenses/MIT-0 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files 10 | (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, 11 | publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so. 12 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 13 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR 14 | ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH 15 | THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | """ 17 | 18 | """ 19 | Post Confirmation Lambda Trigger function that will be associated with the Cognito User Pool for the application. 20 | 21 | Amazon Cognito invokes this trigger after a new user is confirmed, allowing you to send custom messages or to add custom logic. 22 | """ 23 | 24 | import json 25 | import boto3 26 | from botocore.exceptions import ClientError 27 | 28 | def handler(event, context): 29 | """ 30 | Gets information about the user, including the username and the type of user its been created, also in which user pool. 31 | Then, it adds the user in the proper Cognito User Pool group. If it is an administrator user, it will be added into the 32 | geofence-admin group, otherwise it will add into the geofence-mobile group. 33 | """ 34 | 35 | print('event => : {}'.format(json.dumps(event, indent = 4))) 36 | 37 | userpool_id = event['userPoolId'] 38 | username = event['userName'] 39 | user_type = event['request']['userAttributes']['custom:userType'] if 'custom:userType' in event['request']['userAttributes'] else '' 40 | 41 | if (user_type == 'ADMIN'): 42 | add_user_to_cognito_group( 43 | userpool_id =userpool_id, 44 | username = username, 45 | group_name = 'geofence-admin' 46 | ) 47 | else: 48 | add_user_to_cognito_group( 49 | userpool_id =userpool_id, 50 | username = username, 51 | group_name = 'geofence-mobile' 52 | ) 53 | 54 | return event 55 | 56 | def add_user_to_cognito_group(userpool_id, username, group_name): 57 | """ 58 | Calls the AWS SDK to add a given user into a Cognito User Pool group. 59 | """ 60 | 61 | print(f'Saving {username} to the {group_name} group') 62 | try: 63 | cognito_client = boto3.client('cognito-idp') 64 | response_add_to_group = cognito_client.admin_add_user_to_group( 65 | UserPoolId = userpool_id, 66 | Username = username, 67 | GroupName = group_name 68 | ) 69 | print('response: {}'.format(json.dumps(response_add_to_group, indent = 4))) 70 | response = 'SUCESS' 71 | 72 | except ClientError as ex: 73 | print(f'ClientError: {ex}') 74 | response = 'ERROR' 75 | 76 | return response 77 | -------------------------------------------------------------------------------- /source/cognitoPosConfirmation/test_cognitoPosConfirmation.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved 3 | 4 | Licensed under the MIT No Attribution License (MIT-0) (the ‘License’). You may not use this file except in compliance 5 | with the License. A copy of the License is located at 6 | 7 | https://opensource.org/licenses/MIT-0 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files 10 | (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, 11 | publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so. 12 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 13 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR 14 | ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH 15 | THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | """ 17 | 18 | import unittest 19 | from unittest.mock import Mock, patch 20 | from moto import mock_cognitoidp 21 | 22 | import boto3 23 | import uuid 24 | import os 25 | import json 26 | import random 27 | import cognitoPosConfirmation 28 | 29 | @mock_cognitoidp 30 | class TestCognitoPosConfirmation(unittest.TestCase): 31 | """ 32 | Test class for the CognitoPosConfirmation function 33 | """ 34 | 35 | def test_cognito_pos_confirmation_add_mobile_user_with_succes(self): 36 | """ 37 | Test when cognito is able to add a mobile user into the geofence-mobile group 38 | """ 39 | 40 | user_pool_name = 'sample-mock-userpool' 41 | mobile_username = 'mobile_user' 42 | role_arn = "arn:aws:iam:::role/my-iam-role" 43 | 44 | conn = boto3.client("cognito-idp") 45 | 46 | user_pool_id = conn.create_user_pool(PoolName=user_pool_name)["UserPool"]["Id"] 47 | 48 | mobile_group_name = 'geofence-mobile' 49 | role_arn = "arn:aws:iam:::role/my-iam-role" 50 | 51 | result_create_group = conn.create_group( 52 | GroupName=mobile_group_name, 53 | UserPoolId=user_pool_id, 54 | Description=str(uuid.uuid4()), 55 | RoleArn=role_arn, 56 | Precedence=random.randint(0, 100000), 57 | ) 58 | 59 | conn.admin_create_user(UserPoolId=user_pool_id, Username=mobile_username) 60 | 61 | event = { 62 | 'version': '1', 63 | 'region': 'us-east-1', 64 | 'userPoolId': user_pool_id, 65 | 'userName': mobile_username, 66 | 'callerContext': { 67 | 'awsSdkVersion': 'aws-sdk-unknown-unknown', 68 | 'clientId': 'some_client_id' 69 | }, 70 | 'triggerSource': 'PostConfirmation_ConfirmSignUp', 71 | 'request': { 72 | 'userAttributes': { 73 | 'sub': 'some_user_sub', 74 | 'cognito:user_status': 'CONFIRMED', 75 | 'email_verified': 'true', 76 | 'email': 'someuser@somemail.com' 77 | } 78 | }, 79 | 'response': {} 80 | } 81 | 82 | response = cognitoPosConfirmation.handler(event, None) 83 | 84 | self.assertTrue(response) 85 | self.assertEqual(response['userPoolId'], user_pool_id) 86 | self.assertFalse(response['response']) 87 | 88 | def test_cognito_pos_confirmation_add_admin_user_with_succes(self): 89 | """ 90 | Test when cognito is able to add an admin user into the geofence-admin group 91 | """ 92 | 93 | user_pool_name = 'sample-mock-userpool' 94 | admin_username = 'admin_user' 95 | role_arn = "arn:aws:iam:::role/my-iam-role" 96 | 97 | conn = boto3.client("cognito-idp") 98 | 99 | user_pool_id = conn.create_user_pool(PoolName=user_pool_name)["UserPool"]["Id"] 100 | 101 | admin_group_name = 'geofence-admin' 102 | role_arn = "arn:aws:iam:::role/my-iam-role" 103 | 104 | result_create_group = conn.create_group( 105 | GroupName=admin_group_name, 106 | UserPoolId=user_pool_id, 107 | Description=str(uuid.uuid4()), 108 | RoleArn=role_arn, 109 | Precedence=random.randint(0, 100000), 110 | ) 111 | 112 | conn.admin_create_user(UserPoolId=user_pool_id, Username=admin_username) 113 | 114 | event = { 115 | 'version': '1', 116 | 'region': 'us-east-1', 117 | 'userPoolId': user_pool_id, 118 | 'userName': admin_username, 119 | 'callerContext': { 120 | 'awsSdkVersion': 'aws-sdk-unknown-unknown', 121 | 'clientId': 'some_client_id' 122 | }, 123 | 'triggerSource': 'PostConfirmation_ConfirmSignUp', 124 | 'request': { 125 | 'userAttributes': { 126 | 'sub': 'some_user_sub', 127 | 'cognito:user_status': 'CONFIRMED', 128 | 'email_verified': 'true', 129 | 'email': 'someuser@somemail.com', 130 | 'custom:userType': 'ADMIN' 131 | } 132 | }, 133 | 'response': {} 134 | } 135 | 136 | response = cognitoPosConfirmation.handler(event, None) 137 | 138 | self.assertTrue(response) 139 | self.assertEqual(response['userPoolId'], user_pool_id) 140 | self.assertTrue(response['request']['userAttributes']['custom:userType']) 141 | self.assertEqual(response['request']['userAttributes']['custom:userType'], 'ADMIN') 142 | self.assertFalse(response['response']) 143 | 144 | if __name__ == '__main__': 145 | unittest.main() -------------------------------------------------------------------------------- /source/es-custom-resource-js/cfn-response.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************************************************************************* 2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved * 3 | * * 4 | * Licensed under the MIT No Attribution License (MIT-0) (the ‘License’). You may not use this file except in compliance * 5 | * with the License. A copy of the License is located at * 6 | * * 7 | * https://opensource.org/licenses/MIT-0 * 8 | * * 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files * 10 | * (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, * 11 | * publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so. * 12 | * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * 13 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR * 14 | * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH * 15 | * THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * 16 | *******************************************************************************************************************************************/ 17 | 18 | exports.SUCCESS = "SUCCESS"; 19 | exports.FAILED = "FAILED"; 20 | 21 | /** 22 | * derived from: https://github.com/awsdocs/aws-cloudformation-user-guide/blob/master/doc_source/cfn-lambda-function-code-cfnresponsemodule.md#module-source-code 23 | * adopted for calls from within an async function 24 | */ 25 | exports.send = function (event, context, responseStatus, responseData, physicalResourceId, noEcho) { 26 | 27 | return new Promise((resolve, reject) => { 28 | 29 | var responseBody = JSON.stringify({ 30 | Status: responseStatus, 31 | Reason: "See the details in CloudWatch Log Stream: " + context.logStreamName, 32 | PhysicalResourceId: physicalResourceId || context.logStreamName, 33 | StackId: event.StackId, 34 | RequestId: event.RequestId, 35 | LogicalResourceId: event.LogicalResourceId, 36 | NoEcho: noEcho || false, 37 | Data: responseData 38 | }); 39 | 40 | console.log("Response body:\n", responseBody); 41 | 42 | var https = require("https"); 43 | var url = require("url"); 44 | 45 | var parsedUrl = url.parse(event.ResponseURL); 46 | var options = { 47 | hostname: parsedUrl.hostname, 48 | port: 443, 49 | path: parsedUrl.path, 50 | method: "PUT", 51 | headers: { 52 | "content-type": "", 53 | "content-length": responseBody.length 54 | } 55 | }; 56 | 57 | var request = https.request(options, function (response) { 58 | console.log("Status code: " + response.statusCode); 59 | console.log("Status message: " + response.statusMessage); 60 | resolve(response); 61 | }); 62 | 63 | request.on("error", function (error) { 64 | console.error("send(..) failed executing https.request(..): " + error); 65 | reject(error); 66 | }); 67 | 68 | request.write(responseBody); 69 | request.end(); 70 | }); 71 | } 72 | -------------------------------------------------------------------------------- /source/es-custom-resource-js/es-requests.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************************************************************************* 2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved * 3 | * * 4 | * Licensed under the MIT No Attribution License (MIT-0) (the ‘License’). You may not use this file except in compliance * 5 | * with the License. A copy of the License is located at * 6 | * * 7 | * https://opensource.org/licenses/MIT-0 * 8 | * * 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files * 10 | * (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, * 11 | * publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so. * 12 | * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * 13 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR * 14 | * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH * 15 | * THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * 16 | *******************************************************************************************************************************************/ 17 | 18 | const AWS = require('aws-sdk'); 19 | const FormData = require('form-data'); 20 | const fs = require('fs'); 21 | const cfn_response = require('./cfn-response.js'); 22 | 23 | const region = process.env.REGION; 24 | const domain = process.env.DOMAIN; 25 | 26 | /** 27 | * 28 | * Custom resource function to be invoked by the CloudFormation stack in oder to create the proper dashboards inside Kibana 29 | * 30 | * This function loads a pre-defined ndjson file with a sample dashboard that it will ne deployed at the stack creating time. 31 | * This custom resource will be processed only for the Create event, otherwise it will move forward without any actios. 32 | */ 33 | exports.handler = async function (event, context) { 34 | console.log("Event:", event); 35 | const physicalId = "ESCustomResourceId"; 36 | var requestSuccessful = true; 37 | const requests = event.ResourceProperties.Requests; 38 | const requestType = event.RequestType; 39 | 40 | if (requestType === 'Create') { 41 | await requests.reduce(async (previousPromise, request) => { 42 | return previousPromise 43 | .then(_result => { return sendDocument(request.method, request.path) }) 44 | .then(handleSuccess); 45 | }, Promise.resolve()) 46 | .catch(error => { 47 | console.error({ error }); 48 | requestSuccessful = false; 49 | }); 50 | 51 | if (event.ResponseURL) { 52 | console.log({requestSuccessful}); 53 | const status = requestSuccessful ? cfn_response.SUCCESS : cfn_response.FAILED; 54 | await cfn_response.send(event, context, status, {}, physicalId); 55 | } 56 | } else { 57 | await cfn_response.send(event, context, cfn_response.SUCCESS, {}, physicalId); 58 | } 59 | }; 60 | 61 | /** 62 | * 63 | * Creates a request object to ElasticSearch passing the sample dashboards as body. 64 | */ 65 | function sendDocument(httpMethod, path) { 66 | return new Promise(function (resolve, reject) { 67 | var endpoint = new AWS.Endpoint(domain); 68 | var request = new AWS.HttpRequest(endpoint, region); 69 | 70 | var body = new FormData(); 71 | body.append('file', fs.readFileSync('./kibana-objects.ndjson', 'utf8'), 'kibana-objects.ndjson') 72 | 73 | request.method = httpMethod; 74 | request.headers = body.getHeaders(); 75 | request.headers['kbn-xsrf'] = 'kibana' 76 | request.headers['host'] = domain; 77 | request.path += path; 78 | console.log(body.getBuffer().toString('utf-8')) 79 | request.body = body.getBuffer(); 80 | 81 | var credentials = new AWS.EnvironmentCredentials('AWS'); 82 | var signer = new AWS.Signers.V4(request, 'es'); 83 | signer.addAuthorization(credentials, new Date()); 84 | 85 | var client = new AWS.HttpClient(); 86 | client.handleRequest(request, null, function (response) { 87 | var responseBody = ''; 88 | response.on('data', function (chunk) { 89 | responseBody += chunk; 90 | }); 91 | response.on('end', function (_chunk) { 92 | resolve({ "status": response.statusCode, "body": responseBody }); 93 | }); 94 | }, function (error) { 95 | console.error('Error: ' + error); 96 | reject(error); 97 | }); 98 | }); 99 | } 100 | 101 | /** 102 | * Validates if the request was successfull, it throws an exception otherwise. 103 | */ 104 | function handleSuccess(response) { 105 | if (response.status >= 200 && response.status < 300) { 106 | console.log("Successful request:", response); 107 | } else { 108 | throw new Error("Request failed: " + JSON.stringify(response)); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /source/es-custom-resource-js/kibana-objects.ndjson: -------------------------------------------------------------------------------- 1 | {"attributes":{"fields":"[{\"name\":\"_id\",\"type\":\"string\",\"esTypes\":[\"_id\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"esTypes\":[\"_index\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"esTypes\":[\"_source\"],\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"esTypes\":[\"_type\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"address\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"address.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"address\",\"subType\":\"multi\"},{\"name\":\"branch\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"branch.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"branch\",\"subType\":\"multi\"},{\"name\":\"city\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"city.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"city\",\"subType\":\"multi\"},{\"name\":\"country\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"country.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"country\",\"subType\":\"multi\"},{\"name\":\"createdAt\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"id\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"id.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"id\",\"subType\":\"multi\"},{\"name\":\"latitude\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"longitude\",\"type\":\"number\",\"esTypes\":[\"float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"name\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"name.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"name\",\"subType\":\"multi\"},{\"name\":\"region\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"region.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"region\",\"subType\":\"multi\"},{\"name\":\"updatedAt\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"visits\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true}]","timeFieldName":"createdAt","title":"index-geofences*"},"id":"efbdf240-8193-11ea-8d34-015c18a5f8f3","migrationVersion":{"index-pattern":"6.5.0"},"references":[],"type":"index-pattern","updated_at":"2020-04-18T16:44:59.728Z","version":"WzcsMV0="} 2 | {"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"},"title":"Visits per Geofence Branch","uiStateJSON":"{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":null,\"direction\":null}}}}","version":1,"visState":"{\"title\":\"Visits per Geofence Branch\",\"type\":\"table\",\"params\":{\"perPage\":10,\"showPartialRows\":false,\"showMetricsAtAllLevels\":false,\"sort\":{\"columnIndex\":null,\"direction\":null},\"showTotal\":false,\"totalFunc\":\"sum\",\"percentageCol\":\"\",\"dimensions\":{\"metrics\":[{\"accessor\":1,\"format\":{\"id\":\"number\"},\"params\":{},\"aggType\":\"count\"}],\"buckets\":[{\"accessor\":0,\"format\":{\"id\":\"terms\",\"params\":{\"id\":\"string\",\"otherBucketLabel\":\"Other\",\"missingBucketLabel\":\"Missing\"}},\"params\":{},\"aggType\":\"terms\"}]}},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"top_hits\",\"schema\":\"metric\",\"params\":{\"field\":\"visits\",\"aggregate\":\"concat\",\"size\":1,\"sortField\":\"createdAt\",\"sortOrder\":\"desc\",\"customLabel\":\"Amount of Visits\"}},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"bucket\",\"params\":{\"field\":\"branch.keyword\",\"orderBy\":\"_key\",\"order\":\"asc\",\"size\":50,\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":false,\"missingBucketLabel\":\"Missing\",\"customLabel\":\"Branch Name\"}}]}"},"id":"94ebe1e0-8276-11ea-8d34-015c18a5f8f3","migrationVersion":{"visualization":"7.4.2"},"references":[{"id":"efbdf240-8193-11ea-8d34-015c18a5f8f3","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"}],"type":"visualization","updated_at":"2020-04-19T19:47:20.445Z","version":"WzgsMV0="} 3 | {"attributes":{"description":"","hits":0,"kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"language\":\"kuery\",\"query\":\"\"},\"filter\":[]}"},"optionsJSON":"{\"hidePanelTitles\":false,\"useMargins\":true}","panelsJSON":"[{\"version\":\"7.4.2\",\"gridData\":{\"x\":0,\"y\":0,\"w\":26,\"h\":15,\"i\":\"feb53d08-3d25-4911-9daa-13b144e4f875\"},\"panelIndex\":\"feb53d08-3d25-4911-9daa-13b144e4f875\",\"embeddableConfig\":{},\"panelRefName\":\"panel_0\"}]","timeRestore":false,"title":"Dashboard - Visits per Geofence Branch","version":1},"id":"acd196b0-8276-11ea-8d34-015c18a5f8f3","migrationVersion":{"dashboard":"7.3.0"},"references":[{"id":"94ebe1e0-8276-11ea-8d34-015c18a5f8f3","name":"panel_0","type":"visualization"}],"type":"dashboard","updated_at":"2020-04-19T19:48:00.539Z","version":"WzksMV0="} -------------------------------------------------------------------------------- /source/es-custom-resource-js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "es-custom-resource", 3 | "version": "1.0.0", 4 | "description": "code for lambda function to make requests to ES", 5 | "main": "es-requests.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "AWS Solution Builders", 10 | "license": "MIT-0", 11 | "dependencies": { 12 | "aws-sdk": "^2.661.0", 13 | "form-data": "^3.0.0", 14 | "https": "^1.0.0", 15 | "url": "^0.11.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /source/getCoordsFromAddress/getCoordsFromAddress.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved 3 | 4 | Licensed under the MIT No Attribution License (MIT-0) (the ‘License’). You may not use this file except in compliance 5 | with the License. A copy of the License is located at 6 | 7 | https://opensource.org/licenses/MIT-0 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files 10 | (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, 11 | publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so. 12 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 13 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR 14 | ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH 15 | THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | """ 17 | 18 | """ 19 | Lambda function used as an AWS AppSync datasource to return information about an address based in a given address passed as parameter. 20 | In the address object to be returned, sets the coordinates for the address. 21 | """ 22 | 23 | import json 24 | import uuid 25 | import os 26 | import herepy 27 | 28 | def handler(event, context): 29 | """ 30 | Handler for the Lambda function. 31 | 32 | Gets the address as input from the AWS AppSync API, then gets the coordinates and other information about the given address using the Geocoder HERE API. 33 | """ 34 | 35 | print('request: {}'.format(json.dumps(event))) 36 | search_text = event['arguments']['address'] 37 | 38 | here_api_key = os.environ['HERE_API_KEY'] 39 | geocoder_api = herepy.GeocoderApi(here_api_key) 40 | response_here = geocoder_api.free_form(search_text) 41 | 42 | response_location = response_here.items[0] 43 | response_address = response_location['address'] 44 | response_location = response_location['position'] 45 | 46 | address = { 47 | 'street': response_address['label'], 48 | 'city': response_address['city'], 49 | 'state': response_address['state'], 50 | 'country': response_address['countryCode'], 51 | 'latitude': response_location['lat'], 52 | 'longitude': response_location['lng'] 53 | } 54 | 55 | print('response: {}'.format(json.dumps(address))) 56 | return address 57 | -------------------------------------------------------------------------------- /source/getCoordsFromAddress/requirements.txt: -------------------------------------------------------------------------------- 1 | ########################################################################################################################################### 2 | # Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved # 3 | # # 4 | # Licensed under the MIT No Attribution License (MIT-0) (the ‘License’). You may not use this file except in compliance # 5 | # with the License. A copy of the License is located at # 6 | # # 7 | # https://opensource.org/licenses/MIT-0 # 8 | # # 9 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files # 10 | # (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, # 11 | # publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so. # 12 | # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # 13 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR # 14 | # ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH # 15 | # THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # 16 | ############################################################################################################################################ 17 | 18 | herepy==3.0.1 -------------------------------------------------------------------------------- /source/getCoordsFromAddress/test_getCoordsFromAddress.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved 3 | 4 | Licensed under the MIT No Attribution License (MIT-0) (the ‘License’). You may not use this file except in compliance 5 | with the License. A copy of the License is located at 6 | 7 | https://opensource.org/licenses/MIT-0 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files 10 | (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, 11 | publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so. 12 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 13 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR 14 | ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH 15 | THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | """ 17 | 18 | import unittest 19 | from unittest.mock import Mock, patch 20 | 21 | import os 22 | import json 23 | import getCoordsFromAddress 24 | from herepy.models import GeocoderResponse 25 | 26 | class TestGetCoordsFromAddress(unittest.TestCase): 27 | """ 28 | Test class for the GetCoordsFromAddress function 29 | """ 30 | 31 | ENV_HERE_API_KEY = 'HERE_API_KEY' 32 | HERE_API_KEY = 'some-sample-key' 33 | 34 | def setUp(self): 35 | """ 36 | Setting up the test case 37 | """ 38 | os.environ[TestGetCoordsFromAddress.ENV_HERE_API_KEY] = TestGetCoordsFromAddress.HERE_API_KEY 39 | 40 | @patch('herepy.GeocoderApi') 41 | def test_get_coords_from_address_successfully(self, mock_GeocoderApi): 42 | """ 43 | Test when getting the coordinates from an address successfully 44 | """ 45 | 46 | event = { 47 | 'arguments': { 48 | 'address': 'some-address' 49 | } 50 | } 51 | 52 | returned_address = { 53 | 'items': [{ 54 | 'address': { 55 | 'label': 'some_address', 56 | 'city': 'some_city', 57 | 'state': 'some_state', 58 | 'countryCode': 'some_country_code' 59 | }, 60 | 'position': { 61 | 'lat': -11.1234567, 62 | 'lng': -99.0987654 63 | } 64 | }] 65 | } 66 | 67 | geocoderResponse = GeocoderResponse().new_from_jsondict(returned_address) 68 | mock_GeocoderApi().free_form.return_value = geocoderResponse 69 | 70 | returned_address = getCoordsFromAddress.handler(event, None) 71 | 72 | expected_address = { 73 | 'street': 'some_address', 74 | 'city': 'some_city', 75 | 'state': 'some_state', 76 | 'country': 'some_country_code', 77 | 'latitude': -11.1234567, 78 | 'longitude': -99.0987654 79 | } 80 | 81 | self.assertTrue(returned_address) 82 | self.assertEqual(returned_address['street'], 'some_address') 83 | self.assertEqual(returned_address['city'], 'some_city') 84 | self.assertEqual(returned_address['state'], 'some_state') 85 | self.assertEqual(returned_address['country'], 'some_country_code') 86 | self.assertEqual(returned_address['latitude'], -11.1234567) 87 | self.assertEqual(returned_address['longitude'], -99.0987654) 88 | 89 | if __name__ == '__main__': 90 | unittest.main() 91 | -------------------------------------------------------------------------------- /source/getCurrentAddress/getCurrentAddress.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved 3 | 4 | Licensed under the MIT No Attribution License (MIT-0) (the ‘License’). You may not use this file except in compliance 5 | with the License. A copy of the License is located at 6 | 7 | https://opensource.org/licenses/MIT-0 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files 10 | (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, 11 | publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so. 12 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 13 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR 14 | ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH 15 | THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | """ 17 | 18 | """ 19 | Lambda function used as an AWS AppSync datasource to return an address based in the coordinates passed as parameter. 20 | """ 21 | 22 | import json 23 | import uuid 24 | import os 25 | import herepy 26 | 27 | def handler(event, context): 28 | """ 29 | Handler for the Lambda function. 30 | 31 | Gets the coordinates as input from the AWS AppSync API, then gets the address information using the Geocoder Reverse HERE API. 32 | """ 33 | 34 | print('request: {}'.format(json.dumps(event))) 35 | latitude = float(event['arguments']['coordinates']['latitude']) 36 | longitude = float(event['arguments']['coordinates']['longitude']) 37 | 38 | here_api_key = os.environ['HERE_API_KEY'] 39 | geocoder_reverse_api = herepy.GeocoderReverseApi(here_api_key) 40 | response_here = geocoder_reverse_api.retrieve_addresses([latitude,longitude]) 41 | 42 | response_location = response_here.items[0] 43 | response_address = response_location['address'] 44 | response_location = response_location['position'] 45 | 46 | address = { 47 | 'street': response_address['label'], 48 | 'city': response_address['city'], 49 | 'state': response_address['state'], 50 | 'country': response_address['countryCode'], 51 | 'latitude': response_location['lat'], 52 | 'longitude': response_location['lng'] 53 | } 54 | 55 | print('response: {}'.format(json.dumps(address))) 56 | return address 57 | -------------------------------------------------------------------------------- /source/getCurrentAddress/requirements.txt: -------------------------------------------------------------------------------- 1 | ########################################################################################################################################### 2 | # Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved # 3 | # # 4 | # Licensed under the MIT No Attribution License (MIT-0) (the ‘License’). You may not use this file except in compliance # 5 | # with the License. A copy of the License is located at # 6 | # # 7 | # https://opensource.org/licenses/MIT-0 # 8 | # # 9 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files # 10 | # (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, # 11 | # publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so. # 12 | # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # 13 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR # 14 | # ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH # 15 | # THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # 16 | ############################################################################################################################################ 17 | 18 | herepy==3.0.1 -------------------------------------------------------------------------------- /source/getCurrentAddress/test_getCurrentAddress.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved 3 | 4 | Licensed under the MIT No Attribution License (MIT-0) (the ‘License’). You may not use this file except in compliance 5 | with the License. A copy of the License is located at 6 | 7 | https://opensource.org/licenses/MIT-0 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files 10 | (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, 11 | publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so. 12 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 13 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR 14 | ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH 15 | THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | """ 17 | 18 | import unittest 19 | from unittest.mock import Mock, patch 20 | 21 | import os 22 | import json 23 | import getCurrentAddress 24 | from herepy.models import GeocoderReverseResponse 25 | 26 | class TestGetCurrentAddress(unittest.TestCase): 27 | """ 28 | Test class for the GetCurrentAddressLambda function 29 | """ 30 | 31 | ENV_HERE_API_KEY = 'HERE_API_KEY' 32 | HERE_API_KEY = 'some-sample-key' 33 | 34 | def setUp(self): 35 | """ 36 | Setting up the test case 37 | """ 38 | os.environ[TestGetCurrentAddress.ENV_HERE_API_KEY] = TestGetCurrentAddress.HERE_API_KEY 39 | 40 | @patch('herepy.GeocoderReverseApi') 41 | def test_get_current_address_successfully(self, mock_GeocoderReverseApi): 42 | """ 43 | Test when getting the current address successfully 44 | """ 45 | 46 | event = { 47 | 'arguments': { 48 | 'coordinates': { 49 | 'latitude': -11.1234567, 50 | 'longitude': -99.0987654 51 | } 52 | } 53 | } 54 | 55 | returned_address = { 56 | 'items': [{ 57 | 'address': { 58 | 'label': 'some_address', 59 | 'city': 'some_city', 60 | 'state': 'some_state', 61 | 'countryCode': 'some_country_code' 62 | }, 63 | 'position': { 64 | 'lat': -11.1234567, 65 | 'lng': -99.0987654 66 | } 67 | }] 68 | } 69 | 70 | geocoderResponse = GeocoderReverseResponse().new_from_jsondict(returned_address) 71 | mock_GeocoderReverseApi().retrieve_addresses.return_value = geocoderResponse 72 | 73 | returned_address = getCurrentAddress.handler(event, None) 74 | 75 | expected_address = { 76 | 'street': 'some_address', 77 | 'city': 'some_city', 78 | 'state': 'some_state', 79 | 'country': 'some_country_code', 80 | 'latitude': -11.1234567, 81 | 'longitude': -99.0987654 82 | } 83 | 84 | self.assertTrue(returned_address) 85 | self.assertEqual(returned_address['street'], 'some_address') 86 | self.assertEqual(returned_address['city'], 'some_city') 87 | self.assertEqual(returned_address['state'], 'some_state') 88 | self.assertEqual(returned_address['country'], 'some_country_code') 89 | self.assertEqual(returned_address['latitude'], -11.1234567) 90 | self.assertEqual(returned_address['longitude'], -99.0987654) 91 | 92 | if __name__ == '__main__': 93 | unittest.main() 94 | -------------------------------------------------------------------------------- /source/indexDdbDataToEs/indexDdbDataToEs.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved 3 | 4 | Licensed under the MIT No Attribution License (MIT-0) (the ‘License’). You may not use this file except in compliance 5 | with the License. A copy of the License is located at 6 | 7 | https://opensource.org/licenses/MIT-0 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files 10 | (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, 11 | publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so. 12 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 13 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR 14 | ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH 15 | THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | """ 17 | 18 | """ 19 | Lambda function used together with DynamoDB Streams to index data into Amazon ElasticSearch. 20 | """ 21 | 22 | import json 23 | import os 24 | import decimal 25 | 26 | import boto3 27 | from boto3.dynamodb.types import TypeDeserializer 28 | from elasticsearch import Elasticsearch, RequestsHttpConnection 29 | from requests_aws4auth import AWS4Auth 30 | 31 | region = os.environ['REGION'] 32 | host = os.environ['ES_HOST'] 33 | 34 | service = 'es' 35 | credentials = boto3.Session().get_credentials() 36 | awsauth = AWS4Auth(credentials.access_key, credentials.secret_key, region, service, session_token=credentials.token) 37 | 38 | def handler(event, context): 39 | """ 40 | Main handler function that gets the data from the request and perform operations into Amazon ElasticSearch to create of update index. 41 | """ 42 | 43 | print('Request: {}'.format(json.dumps(event, indent = 4))) 44 | count = 0 45 | index_name = 'index-geofences' 46 | 47 | es = Elasticsearch( 48 | hosts = [{'host': host, 'port': 443}], 49 | http_auth = awsauth, 50 | use_ssl = True, 51 | verify_certs = True, 52 | connection_class = RequestsHttpConnection 53 | ) 54 | 55 | print('Cluster Info: {}'.format(json.dumps(es.info(), indent = 4))) 56 | 57 | for record in event['Records']: 58 | try: 59 | # to test if the indexed data was properly indexed 60 | # uncomment the line below and comment the if/else block 61 | #get_geofence(es, record, index_name) 62 | 63 | if record['eventName'] == 'INSERT' or record['eventName'] == 'MODIFY': 64 | index_geofence(es, record, index_name) 65 | elif record['eventName'] == 'REMOVE': 66 | unindex_geofence(es, record, index_name) 67 | 68 | except Exception as e: 69 | print("Failed to process:") 70 | print('Record: {}'.format(json.dumps(record, indent = 4))) 71 | print("ERROR: " + repr(e)) 72 | continue 73 | 74 | count += 1 75 | return f'{count} records processed.' 76 | 77 | def index_geofence(es, record, index_name): 78 | """ 79 | Index data into the Amazon ElstichSearch cluster 80 | """ 81 | 82 | print('Indexing data into ES...') 83 | 84 | if es.indices.exists(index_name) == False: 85 | print(f'Index {index_name} not found. Creating it...') 86 | 87 | es.indices.create( 88 | index_name, 89 | body='{"settings": { "index.mapping.coerce": true } }') 90 | 91 | print(f'Index {index_name} created successfuly') 92 | 93 | else: 94 | print(f'Index {index_name} already exists...') 95 | 96 | geofence_to_index_id = get_id(record) 97 | print('geofence_to_index_id: {}'.format(geofence_to_index_id)) 98 | 99 | geofence_to_index = convert_from_dbb_format_to_obj(record['dynamodb']['NewImage']) 100 | print('geofence_to_index: {}'.format(geofence_to_index)) 101 | 102 | es.index( 103 | index = index_name, 104 | body = geofence_to_index, 105 | id = geofence_to_index_id, 106 | doc_type = index_name, 107 | refresh = True 108 | ) 109 | print(f'Successly inserted geofence ID {geofence_to_index_id} to index {index_name}') 110 | 111 | def unindex_geofence(es, record, index_name): 112 | """ 113 | Removes indexed data from the Amazon ElstichSearch cluster 114 | """ 115 | 116 | print('Removing indexed data from ES...') 117 | geofence_to_index_id = get_id(record) 118 | print('geofence_to_index_id: {}'.format(geofence_to_index_id)) 119 | 120 | es.delete( 121 | index = index_name, 122 | id = geofence_to_index_id, 123 | doc_type = index_name, 124 | refresh = True 125 | ) 126 | 127 | print(f'Successly removed geofence ID {geofence_to_index_id} to index {index_name}') 128 | 129 | def get_geofence(es, record, index_name): 130 | """ 131 | Gets the indexed data from the Amazon ElstichSearch cluster for testing purposes. 132 | """ 133 | print('Getting data into ES for testing purposes...') 134 | geofence_to_index_id = get_id(record) 135 | print('geofence_to_index_id: {}'.format(geofence_to_index_id)) 136 | 137 | response_geofence = es.get( 138 | index = index_name, 139 | doc_type = index_name, 140 | id = geofence_to_index_id 141 | ) 142 | 143 | print('response_geofence: {}'.format(json.dumps(response_geofence, indent = 4))) 144 | 145 | def get_id(record): 146 | """ 147 | Returns the Amazon DynamoDB key. 148 | """ 149 | 150 | return record['dynamodb']['Keys']['id']['S'] 151 | 152 | def convert_from_dbb_format_to_obj(data): 153 | """ 154 | Normalizes the object coming from Amazon DynamoDB in order to index a clean object in Amazon ElasticSearch. 155 | """ 156 | 157 | return {k: normalize_values(TypeDeserializer().deserialize(v)) for k,v in data.items()} 158 | 159 | def normalize_values(value): 160 | """ 161 | Normalize values coming from Amazon DynamoDB. 162 | """ 163 | 164 | if isinstance(value, decimal.Decimal): 165 | if value % 1 == 0: 166 | return int(value) 167 | else: 168 | return float(value) 169 | return value 170 | -------------------------------------------------------------------------------- /source/indexDdbDataToEs/requirements.txt: -------------------------------------------------------------------------------- 1 | ########################################################################################################################################### 2 | # Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved # 3 | # # 4 | # Licensed under the MIT No Attribution License (MIT-0) (the ‘License’). You may not use this file except in compliance # 5 | # with the License. A copy of the License is located at # 6 | # # 7 | # https://opensource.org/licenses/MIT-0 # 8 | # # 9 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files # 10 | # (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, # 11 | # publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so. # 12 | # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # 13 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR # 14 | # ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH # 15 | # THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # 16 | ############################################################################################################################################ 17 | 18 | requests_aws4auth 19 | elasticsearch -------------------------------------------------------------------------------- /source/lambda-custom-resource/cfnResponse.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved 3 | 4 | Licensed under the MIT No Attribution License (MIT-0) (the ‘License’). You may not use this file except in compliance 5 | with the License. A copy of the License is located at 6 | 7 | https://opensource.org/licenses/MIT-0 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files 10 | (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, 11 | publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so. 12 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 13 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR 14 | ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH 15 | THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | """ 17 | 18 | import requests 19 | import json 20 | 21 | SUCCESS = "SUCCESS" 22 | FAILED = "FAILED" 23 | 24 | def send(event, context, responseStatus, responseData, physicalResourceId=None, noEcho=False): 25 | """ 26 | It creates a response object with proper parameters to invoke a CloudFormation API to send a signal to finish the CustomResource invocation. 27 | """ 28 | 29 | responseUrl = event['ResponseURL'] 30 | 31 | print(responseUrl) 32 | 33 | responseBody = {} 34 | responseBody['Status'] = responseStatus 35 | responseBody['Reason'] = 'See the details in CloudWatch Log Stream: ' + context.log_stream_name 36 | responseBody['PhysicalResourceId'] = physicalResourceId or context.log_stream_name 37 | responseBody['StackId'] = event['StackId'] 38 | responseBody['RequestId'] = event['RequestId'] 39 | responseBody['LogicalResourceId'] = event['LogicalResourceId'] 40 | responseBody['NoEcho'] = noEcho 41 | responseBody['Data'] = responseData 42 | 43 | json_responseBody = json.dumps(responseBody) 44 | 45 | print("Response body:\n" + json_responseBody) 46 | 47 | headers = { 48 | 'content-type' : '', 49 | 'content-length' : str(len(json_responseBody)) 50 | } 51 | 52 | try: 53 | response = requests.put(responseUrl, 54 | data=json_responseBody, 55 | headers=headers) 56 | print("Status code: " + response.reason) 57 | except Exception as e: 58 | print("send(..) failed executing requests.put(..): " + str(e)) 59 | -------------------------------------------------------------------------------- /source/lambda-custom-resource/requirements.txt: -------------------------------------------------------------------------------- 1 | ########################################################################################################################################### 2 | # Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved # 3 | # # 4 | # Licensed under the MIT No Attribution License (MIT-0) (the ‘License’). You may not use this file except in compliance # 5 | # with the License. A copy of the License is located at # 6 | # # 7 | # https://opensource.org/licenses/MIT-0 # 8 | # # 9 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files # 10 | # (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, # 11 | # publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so. # 12 | # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # 13 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR # 14 | # ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH # 15 | # THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # 16 | ############################################################################################################################################ 17 | 18 | requests 19 | -------------------------------------------------------------------------------- /source/manageMessages/manageMessages.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved 3 | 4 | Licensed under the MIT No Attribution License (MIT-0) (the ‘License’). You may not use this file except in compliance 5 | with the License. A copy of the License is located at 6 | 7 | https://opensource.org/licenses/MIT-0 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files 10 | (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, 11 | publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so. 12 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 13 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR 14 | ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH 15 | THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | """ 17 | 18 | """ 19 | Lambda function used as an AWS AppSync datasource to handle push notification message templates operations. 20 | Message template is a feature in Amazon Pinpoint, so all requests are handled using the AWS Pinpoint SDK. 21 | """ 22 | 23 | import json 24 | import boto3 25 | from botocore.exceptions import ClientError 26 | 27 | def handler(event, context): 28 | """ 29 | Main handler function that get the input messaged passed as parameter along with the operation to be performed. 30 | The operation is passed via AWS AppSync API, then execute the proper operation. 31 | """ 32 | print('request: {}'.format(json.dumps(event, indent = 4))) 33 | 34 | pinpoint_client = boto3.client('pinpoint') 35 | 36 | message_input = { 37 | 'template_name': event['arguments']['template'] 38 | } 39 | 40 | if 'input' in event['arguments']: 41 | message_input['service'] = event['arguments']['input']['service'] 42 | message_input['action'] = event['arguments']['input']['action'] 43 | message_input['title'] = event['arguments']['input']['title'] 44 | message_input['body'] = event['arguments']['input']['body'] 45 | 46 | operations = { 47 | 'getMessage': get_message, 48 | 'createMessage': create_message, 49 | 'deleteMessage': delete_message 50 | } 51 | 52 | response = operations[event['operation']](pinpoint_client, message_input) 53 | print('response: {}'.format(json.dumps(response, indent = 4))) 54 | return response 55 | 56 | def get_message(pinpoint_client, message_input): 57 | """ 58 | Based on a message passed as parameter, it gets a push notification message template 59 | in Pinpoint and creates a message Payload to be returned 60 | """ 61 | 62 | try: 63 | response_get_template = pinpoint_client.get_push_template( 64 | TemplateName = message_input['template_name'] 65 | ) 66 | 67 | push_template = response_get_template['PushNotificationTemplateResponse'] 68 | 69 | if push_template['APNS']: 70 | service = 'APNS' 71 | elif push_template['GCM']: 72 | service = 'GCM' 73 | 74 | response = { 75 | 'status': 'MESSAGE_OK', 76 | 'message': { 77 | 'service': service, 78 | 'action': push_template[service]['Action'], 79 | 'title': push_template[service]['Title'], 80 | 'body': push_template[service]['Body'] 81 | } 82 | } 83 | 84 | except ClientError as ex: 85 | response = create_error_payload( 86 | exception = 'ClientError', 87 | message = f'Unexpected error: {ex}', 88 | endpoint_id = '' 89 | ) 90 | 91 | return response 92 | 93 | def create_message(pinpoint_client, message_input): 94 | """ 95 | Based on a message passed as parameter, it creates a push notification 96 | message template in Pinpoint 97 | """ 98 | 99 | try: 100 | template = message_input['template_name'] 101 | service = message_input['service'] 102 | 103 | response_create_template = pinpoint_client.create_push_template( 104 | TemplateName = template, 105 | PushNotificationTemplateRequest = { 106 | service: { 107 | 'Action': message_input['action'], 108 | 'Title': message_input['title'], 109 | 'Body': message_input['body'] 110 | } 111 | } 112 | ) 113 | 114 | response = { 115 | 'status': 'MESSAGE_CREATED', 116 | 'message': f'Personalized {service} push message created for geofence {template}' 117 | } 118 | 119 | except ClientError as ex: 120 | response = create_error_payload( 121 | exception = 'ClientError', 122 | message = f'Unexpected error: {ex}', 123 | endpoint_id = '' 124 | ) 125 | 126 | return response 127 | 128 | def delete_message(pinpoint_client, message_input): 129 | """ 130 | Based on a message passed as parameter, it deletes a push notification 131 | message template in Pinpoint 132 | """ 133 | try: 134 | template = message_input['template_name'] 135 | 136 | response_delete_template = pinpoint_client.delete_push_template( 137 | TemplateName = template 138 | ) 139 | 140 | response = { 141 | 'status': 'MESSAGE_DELETED', 142 | 'message': f'Personalized message deleted for geofence {template}' 143 | } 144 | 145 | except ClientError as ex: 146 | response = create_error_payload( 147 | exception = 'ClientError', 148 | message = f'Unexpected error: {ex}', 149 | endpoint_id = '' 150 | ) 151 | 152 | return response 153 | 154 | def create_error_payload(exception, message, endpoint_id): 155 | """ 156 | Formats an error message to be added in case of failure 157 | """ 158 | 159 | print(f'{exception}: {message}') 160 | error_payload = { 161 | 'status': 'MESSAGE_ERROR', 162 | 'message': f'{exception}: {message}' 163 | } 164 | return error_payload 165 | -------------------------------------------------------------------------------- /source/manageMessages/test_manageMessages.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved 3 | 4 | Licensed under the MIT No Attribution License (MIT-0) (the ‘License’). You may not use this file except in compliance 5 | with the License. A copy of the License is located at 6 | 7 | https://opensource.org/licenses/MIT-0 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files 10 | (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, 11 | publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so. 12 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 13 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR 14 | ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH 15 | THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | """ 17 | 18 | import unittest 19 | from unittest.mock import Mock, patch 20 | 21 | import boto3 22 | import uuid 23 | import os 24 | import json 25 | import random 26 | import manageMessages 27 | 28 | class TestManageMessages(unittest.TestCase): 29 | """ 30 | Test class for the ManageMessages function 31 | """ 32 | 33 | @patch('boto3.client') 34 | def test_create_message_with_succes(self, mock_client): 35 | """ 36 | Test when the lambda is able to create a message template on Pinpoint 37 | """ 38 | 39 | event = { 40 | 'operation': 'createMessage', 41 | 'arguments': { 42 | 'template': 'my-sample-geofence-id', 43 | 'input': { 44 | 'service': 'APNS', 45 | 'action': 'OPEN_APP', 46 | 'title': 'Sample Title', 47 | 'body': 'This is a sample body' 48 | } 49 | } 50 | } 51 | 52 | response = { 53 | "Arn": f'arn:aws:mobiletargeting:eus-east-1:SOME_ACCOUNT_ID:templates/my-sample-geofence-id/PUSH', 54 | "RequestID": "some-request-id", 55 | "Message": 'some message' 56 | } 57 | 58 | mock_client().create_push_template.return_value = response 59 | response = manageMessages.handler(event, None) 60 | 61 | self.assertTrue(response) 62 | self.assertEqual(response['status'], 'MESSAGE_CREATED') 63 | 64 | @patch('boto3.client') 65 | def test_get_apns_message_with_succes(self, mock_client): 66 | """ 67 | Test when the lambda is able to get an APNS message template on Pinpoint 68 | """ 69 | 70 | event = { 71 | 'operation': 'getMessage', 72 | 'arguments': { 73 | 'template': 'my-sample-geofence-id', 74 | } 75 | } 76 | 77 | response = { 78 | "PushNotificationTemplateResponse": { 79 | 'APNS': { 80 | 'Action': 'OPEN_APP', 81 | 'Title': 'Sample Title', 82 | 'Body': 'This is a sample body' 83 | } 84 | } 85 | } 86 | 87 | mock_client().get_push_template.return_value = response 88 | response = manageMessages.handler(event, None) 89 | 90 | self.assertTrue(response) 91 | self.assertEqual(response['status'], 'MESSAGE_OK') 92 | self.assertEqual(response['message']['service'], 'APNS') 93 | 94 | @patch('boto3.client') 95 | def test_delete_message_with_succes(self, mock_client): 96 | """ 97 | Test when the lambda is able to delete a message template on Pinpoint 98 | """ 99 | 100 | event = { 101 | 'operation': 'deleteMessage', 102 | 'arguments': { 103 | 'template': 'my-sample-geofence-id', 104 | } 105 | } 106 | 107 | response = { 108 | "Arn": f'arn:aws:mobiletargeting:eus-east-1:SOME_ACCOUNT_ID:templates/my-sample-geofence-id/PUSH', 109 | "RequestID": "some-request-id", 110 | "Message": 'some message' 111 | } 112 | 113 | mock_client().delete_push_template.return_value = response 114 | response = manageMessages.handler(event, None) 115 | 116 | self.assertTrue(response) 117 | self.assertEqual(response['status'], 'MESSAGE_DELETED') 118 | 119 | if __name__ == '__main__': 120 | unittest.main() -------------------------------------------------------------------------------- /source/sendMessage/test_sendMessage.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved 3 | 4 | Licensed under the MIT No Attribution License (MIT-0) (the ‘License’). You may not use this file except in compliance 5 | with the License. A copy of the License is located at 6 | 7 | https://opensource.org/licenses/MIT-0 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files 10 | (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, 11 | publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so. 12 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 13 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR 14 | ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH 15 | THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | """ 17 | 18 | import unittest 19 | from unittest.mock import Mock, patch 20 | 21 | import boto3 22 | import uuid 23 | import os 24 | import json 25 | import random 26 | import sendMessage 27 | 28 | class TestSendMessage(unittest.TestCase): 29 | """ 30 | Test class for the SendMessage function 31 | """ 32 | 33 | ENV_DBB_TABLE_NAME = 'DBB_TABLE_NAME' 34 | DDB_TABLE_NAME = 'geofence-ddb-table' 35 | 36 | def setUp(self): 37 | """ 38 | Setting up the test case 39 | """ 40 | os.environ[TestSendMessage.ENV_DBB_TABLE_NAME] = TestSendMessage.DDB_TABLE_NAME 41 | 42 | @patch('boto3.client') 43 | def test_send_message_with_succes(self, mock_client): 44 | """ 45 | Test when the lambda is able to send a push notification 46 | """ 47 | 48 | event = { 49 | 'arguments': { 50 | 'input': { 51 | 'applicationId': 'pinpoint-app-id', 52 | 'geofenceId': 'geofence-id', 53 | 'userId': 'user-id' 54 | } 55 | } 56 | } 57 | 58 | response_endpoint = { 59 | 'EndpointsResponse': { 60 | 'Item': [{ 61 | 'Id': 'endpoint-id', 62 | 'ChannelType': 'APNS', 63 | 'Address': 'endpoint-address', 64 | 'Attributes': [] 65 | }] 66 | } 67 | } 68 | 69 | response_template = { 70 | "PushNotificationTemplateResponse": { 71 | 'APNS': { 72 | 'Action': 'OPEN_APP', 73 | 'Title': 'Sample Title', 74 | 'Body': 'This is a sample body' 75 | } 76 | } 77 | } 78 | 79 | response_send_message = { 80 | "MessageResponse": { 81 | 'Result': { 82 | 'endpoint-address': { 83 | 'DeliveryStatus': 'SUCCESSFUL' 84 | } 85 | } 86 | } 87 | } 88 | 89 | response_ddb_update = { 90 | "ResponseMetadata": { 91 | 'HTTPStatusCode': 200 92 | } 93 | } 94 | 95 | response_update_endpoint = { 96 | "ResponseMetadata": { 97 | 'HTTPStatusCode': 202 98 | } 99 | } 100 | 101 | mock_client().get_user_endpoints.return_value = response_endpoint 102 | mock_client().get_push_template.return_value = response_template 103 | mock_client().send_messages.return_value = response_send_message 104 | mock_client().update_item.return_value = response_ddb_update 105 | mock_client().update_endpoint.return_value = response_update_endpoint 106 | 107 | response = sendMessage.handler(event, None) 108 | 109 | self.assertTrue(response) 110 | self.assertEqual(response['status'],'MESSAGE_SENT') 111 | self.assertEqual(response['endpointId'],'endpoint-id') 112 | 113 | if __name__ == '__main__': 114 | unittest.main() -------------------------------------------------------------------------------- /source/website-contents/README.md: -------------------------------------------------------------------------------- 1 | # admin-portal-website 2 | 3 | This is the administrator website for the location-based-notifications-using-amazon-pinpoint. 4 | 5 | All backend and amplify references are configured once the solution is deployed using the CloudFormation. 6 | 7 | If you want to run this web site locally, you will need to update the backend references inside the `src/aws-exports.js` file. 8 | 9 | For more information about the backend services and resources, please check the solution implementation guide. 10 | 11 | *** 12 | 13 | ## Comands available 14 | 15 | ### `yarn start` 16 | 17 | Runs the admin portal website in development mode. 18 | 19 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 20 | 21 | The page will reload if you make edits. 22 | 23 | ### `yarn build` 24 | 25 | Builds the app for production into the `build` folder. 26 | 27 | It correctly bundles React in production mode and optimizes the build for the best performance. 28 | 29 | The build is minified and the filenames include the hashes. 30 | 31 | Your app is ready to be deployed! 32 | 33 | *** 34 | 35 | Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved 36 | 37 | Licensed under the MIT No Attribution License (MIT-0) (the ‘License’). You may not use this file except in compliance 38 | with the License. A copy of the License is located at 39 | 40 | https://opensource.org/licenses/MIT-0 41 | 42 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files 43 | (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, 44 | publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so. 45 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 46 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR 47 | ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH 48 | THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 49 | -------------------------------------------------------------------------------- /source/website-contents/amplify/.config/project-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "GeofenceAdminWebsite", 3 | "version": "3.0", 4 | "frontend": "javascript", 5 | "javascript": { 6 | "framework": "react", 7 | "config": { 8 | "SourceDir": "app", 9 | "DistributionDir": "public", 10 | "BuildCommand": "npm run build", 11 | "StartCommand": "npm start" 12 | } 13 | }, 14 | "providers": [ 15 | "awscloudformation" 16 | ] 17 | } -------------------------------------------------------------------------------- /source/website-contents/amplify/backend/backend-config.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /source/website-contents/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-admin-portal", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@material-ui/core": "^4.9.10", 7 | "@material-ui/icons": "^4.9.1", 8 | "aws-amplify": "^3.0.5", 9 | "aws-amplify-react": "^4.1.4", 10 | "react": "^16.13.1", 11 | "react-dom": "^16.13.1", 12 | "react-router-dom": "^5.1.2", 13 | "react-scripts": "3.4.1" 14 | }, 15 | "scripts": { 16 | "start": "react-scripts start", 17 | "build": "react-scripts build", 18 | "postbuild": "./postbuild.sh", 19 | "test": "react-scripts test", 20 | "eject": "react-scripts eject" 21 | }, 22 | "eslintConfig": { 23 | "extends": "react-app" 24 | }, 25 | "browserslist": { 26 | "production": [ 27 | ">0.2%", 28 | "not dead", 29 | "not op_mini all" 30 | ], 31 | "development": [ 32 | "last 1 chrome version", 33 | "last 1 firefox version", 34 | "last 1 safari version" 35 | ] 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /source/website-contents/postbuild.sh: -------------------------------------------------------------------------------- 1 | ########################################################################################################################################### 2 | # Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved # 3 | # # 4 | # Licensed under the MIT No Attribution License (MIT-0) (the ‘License’). You may not use this file except in compliance # 5 | # with the License. A copy of the License is located at # 6 | # # 7 | # https://opensource.org/licenses/MIT-0 # 8 | # # 9 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files # 10 | # (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, # 11 | # publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so. # 12 | # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # 13 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR # 14 | # ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH # 15 | # THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # 16 | ############################################################################################################################################ 17 | 18 | #!/bin/bash 19 | 20 | echo "=> Moving build content to s3 assets..." 21 | cd build 22 | zip -9r ../website-contents.zip . 23 | if [ $? -ne 0 ]; then 24 | echo "Did not zip the website correctly." 25 | exit 26 | fi 27 | 28 | echo "=> Deleting build folder..." 29 | rm -rf build 30 | if [ $? -ne 0 ]; then 31 | echo "Did not delete the build directory." 32 | exit 33 | fi -------------------------------------------------------------------------------- /source/website-contents/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awslabs/location-based-notifications-using-amazon-pinpoint/8cd68d5a1fe11f32b752739e34f9250453e418c7/source/website-contents/public/favicon.ico -------------------------------------------------------------------------------- /source/website-contents/public/index.html: -------------------------------------------------------------------------------- 1 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 34 | Geofence - Admin Portal 35 | 36 | 37 | 38 |
39 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /source/website-contents/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /source/website-contents/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /source/website-contents/src/App.css: -------------------------------------------------------------------------------- 1 | /******************************************************************************************************************************************* 2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved * 3 | * * 4 | * Licensed under the MIT No Attribution License (MIT-0) (the ‘License’). You may not use this file except in compliance * 5 | * with the License. A copy of the License is located at * 6 | * * 7 | * https://opensource.org/licenses/MIT-0 * 8 | * * 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files * 10 | * (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, * 11 | * publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so. * 12 | * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * 13 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR * 14 | * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH * 15 | * THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * 16 | *******************************************************************************************************************************************/ 17 | 18 | .App { 19 | text-align: center; 20 | } 21 | 22 | .App-logo { 23 | animation: App-logo-spin infinite 20s linear; 24 | height: 80px; 25 | } 26 | 27 | .App-header { 28 | background-color: #222; 29 | height: 150px; 30 | padding: 20px; 31 | color: white; 32 | } 33 | 34 | .App-intro { 35 | font-size: large; 36 | } 37 | 38 | @keyframes App-logo-spin { 39 | from { transform: rotate(0deg); } 40 | to { transform: rotate(360deg); } 41 | } 42 | -------------------------------------------------------------------------------- /source/website-contents/src/App.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************************************************************************* 2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved * 3 | * * 4 | * Licensed under the MIT No Attribution License (MIT-0) (the ‘License’). You may not use this file except in compliance * 5 | * with the License. A copy of the License is located at * 6 | * * 7 | * https://opensource.org/licenses/MIT-0 * 8 | * * 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files * 10 | * (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, * 11 | * publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so. * 12 | * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * 13 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR * 14 | * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH * 15 | * THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * 16 | *******************************************************************************************************************************************/ 17 | 18 | import React, { Component }from 'react'; 19 | import { BrowserRouter as Router, Switch, Route } from 'react-router-dom'; 20 | 21 | import "./App.css"; 22 | import "@aws-amplify/ui/dist/style.css"; 23 | 24 | import Amplify, {Auth} from 'aws-amplify'; 25 | import aws_exports from './aws-exports'; 26 | 27 | import { 28 | ConfirmSignIn, 29 | ConfirmSignUp, 30 | ForgotPassword, 31 | RequireNewPassword, 32 | SignIn, 33 | VerifyContact, 34 | withAuthenticator , 35 | AmplifyTheme 36 | } from 'aws-amplify-react'; 37 | 38 | import CustomSignUp from './components/CustomSignUp' 39 | import GeofenceNavBar from './components/GeofenceNavBar' 40 | import GeofenceHome from './components/GeofenceHome'; 41 | import GeofenceDetails from './components/GeofenceDetails'; 42 | import GeofenceEdit from './components/GeofenceEdit'; 43 | 44 | Amplify.configure({ 45 | Auth: { 46 | region: aws_exports.aws_project_region, 47 | userPoolId: aws_exports.user_pool_id, 48 | userPoolWebClientId: aws_exports.user_pool_web_client_id, 49 | identityPoolId: aws_exports.identity_pool_id, 50 | identityPoolRegion: aws_exports.aws_project_region 51 | }, 52 | Analytics: { 53 | AWSPinpoint: { 54 | appId: aws_exports.pinpoint_app_id, 55 | region: aws_exports.aws_project_region, 56 | } 57 | }, 58 | API: { 59 | aws_appsync_graphqlEndpoint: aws_exports.aws_appsync_graphqlEndpoint, 60 | aws_appsync_region: aws_exports.aws_project_region, 61 | aws_appsync_authenticationType: aws_exports.aws_appsync_authenticationType 62 | } 63 | }) 64 | 65 | class App extends Component { 66 | state = { 67 | loggedUser: "" 68 | }; 69 | 70 | async componentDidMount() { 71 | await Auth.currentAuthenticatedUser().then(user => { 72 | this.setState({ 73 | loggedUser: user.username 74 | }); 75 | }); 76 | } 77 | 78 | render() { 79 | return ( 80 |
81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 |
91 | ); 92 | } 93 | } 94 | 95 | const MyTheme = { 96 | ...AmplifyTheme 97 | }; 98 | 99 | export default withAuthenticator( 100 | App, 101 | true, 102 | [ 103 | , 104 | , 105 | , 106 | , 107 | , 108 | , 109 | 110 | ], 111 | null, 112 | MyTheme 113 | ); 114 | -------------------------------------------------------------------------------- /source/website-contents/src/aws-exports.js: -------------------------------------------------------------------------------- 1 | // WARNING: DO NOT EDIT. This file is automatically generated by AWS Amplify. It will be overwritten. 2 | 3 | const awsmobile = { 4 | "aws_project_region": "REPLACE_AWS_REGION", 5 | "user_pool_id": "REPLACE_USER_POOL_ID", 6 | "user_pool_web_client_id": "REPLACE_APP_CLIENT_ID", 7 | "identity_pool_id": "REPLACE_IDENTITY_POOL_ID", 8 | "pinpoint_app_id": "REPLACE_PINPOINT_APP_ID", 9 | "aws_appsync_graphqlEndpoint": "REPLACE_APPSYNC_ENDPOINT", 10 | "aws_appsync_authenticationType": "AMAZON_COGNITO_USER_POOLS" 11 | }; 12 | 13 | export default awsmobile; -------------------------------------------------------------------------------- /source/website-contents/src/components/CustomSignUp.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************************************************************************* 2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved * 3 | * * 4 | * Licensed under the MIT No Attribution License (MIT-0) (the ‘License’). You may not use this file except in compliance * 5 | * with the License. A copy of the License is located at * 6 | * * 7 | * https://opensource.org/licenses/MIT-0 * 8 | * * 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files * 10 | * (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, * 11 | * publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so. * 12 | * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * 13 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR * 14 | * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH * 15 | * THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * 16 | *******************************************************************************************************************************************/ 17 | 18 | import React from 'react'; 19 | 20 | import { Auth } from 'aws-amplify'; 21 | import { 22 | SignUp, 23 | FormSection, 24 | SectionHeader, 25 | SectionBody, 26 | SectionFooter, 27 | InputRow, 28 | Link, 29 | Button 30 | } from 'aws-amplify-react'; 31 | 32 | export default class MySignUp extends SignUp { 33 | 34 | signUp() { 35 | const { username, password, email } = this.inputs; 36 | 37 | const param = { 38 | "username": username, 39 | "password": password, 40 | "attributes": { 41 | "email": email, 42 | "custom:userType" : "ADMIN" 43 | } 44 | } 45 | 46 | Auth.signUp(param) 47 | .then(() => this.changeState('confirmSignUp', username)) 48 | .catch(err => this.error(err)); 49 | } 50 | 51 | showComponent(theme) { 52 | const { hide } = this.props; 53 | if (hide && hide.includes(MySignUp)) { return null; } 54 | 55 | return ( 56 |
57 | 58 | {'Sign Up Account'} 59 | 60 | 68 | 76 | 83 | {/* 84 | {'Sign Up'} 85 | */} 86 | 87 | 88 | 89 | 90 | 91 | {'Have an account? '} 92 | this.changeState('signIn')} > 95 | Sign In 96 | 97 | 98 | 99 | 104 | 105 | 106 | 107 | 108 | {/* 109 |
110 | this.changeState('confirmSignUp')}> 111 | {'Confirm a Code'} 112 | 113 |
114 |
115 | this.changeState('signIn')}> 116 | {'Sign In'} 117 | 118 |
119 |
*/} 120 |
121 |
122 | ) 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /source/website-contents/src/components/GeofenceEdit.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************************************************************************* 2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved * 3 | * * 4 | * Licensed under the MIT No Attribution License (MIT-0) (the ‘License’). You may not use this file except in compliance * 5 | * with the License. A copy of the License is located at * 6 | * * 7 | * https://opensource.org/licenses/MIT-0 * 8 | * * 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files * 10 | * (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, * 11 | * publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so. * 12 | * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * 13 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR * 14 | * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH * 15 | * THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * 16 | *******************************************************************************************************************************************/ 17 | 18 | import React, { Component } from 'react'; 19 | 20 | import KeyboardBackspaceIcon from '@material-ui/icons/KeyboardBackspace'; 21 | import TextField from '@material-ui/core/TextField' 22 | import Button from '@material-ui/core/Button'; 23 | import SaveIcon from '@material-ui/icons/Save'; 24 | import Grid from '@material-ui/core/Grid'; 25 | import Card from '@material-ui/core/Card'; 26 | import CardContent from '@material-ui/core/CardContent'; 27 | import Typography from '@material-ui/core/Typography'; 28 | import Select from '@material-ui/core/Select'; 29 | import MenuItem from '@material-ui/core/MenuItem'; 30 | import InputLabel from '@material-ui/core/InputLabel'; 31 | import FormControl from '@material-ui/core/FormControl'; 32 | 33 | import { Link } from 'react-router-dom'; 34 | import { API, graphqlOperation } from 'aws-amplify'; 35 | import { updateGeofence } from '../graphql/mutations' 36 | 37 | class GeofenceEdit extends Component { 38 | 39 | state = { 40 | id: "", 41 | name: "", 42 | branch: "", 43 | definition: "", 44 | defType: "", 45 | defValue: "" 46 | }; 47 | 48 | componentDidMount() { 49 | if (this.props.location.obj != null) { 50 | let def = this.props.location.obj.definition 51 | let splitDef 52 | 53 | if (def != null) { 54 | splitDef = def.split(':') 55 | } 56 | 57 | this.setState({ 58 | id: this.props.location.obj.id, 59 | name: this.props.location.obj.name, 60 | branch: this.props.location.obj.branch, 61 | defType: splitDef[0], 62 | defValue: splitDef[1].slice(1,-1) 63 | }) 64 | } 65 | } 66 | 67 | updateGeofence = async () => { 68 | let geofenceUpdateResponse = await API.graphql(graphqlOperation( 69 | updateGeofence, { 70 | input: { 71 | id: this.state.id, 72 | name: this.state.name, 73 | branch: this.state.branch, 74 | definition: this.state.defType + ':(' + this.state.defValue + ')', 75 | } 76 | } 77 | )); 78 | console.log('Geofence Update Response =>' + JSON.stringify(geofenceUpdateResponse, null, 4)); 79 | 80 | this.props.location.queryGeofences() 81 | }; 82 | 83 | change = e => { 84 | this.setState({ 85 | [e.target.name]: e.target.value 86 | }); 87 | }; 88 | 89 | render() { 90 | return ( 91 |
92 |
93 |
94 |
95 | 96 | 104 | 105 | 106 |
107 |
108 | 109 | 113 | 114 | 115 | 116 | 117 |
118 | this.change(e)} 124 | /> 125 |
126 | this.change(e)} 132 | /> 133 |
134 | 138 | 139 | 140 | Type 141 | 150 | 151 | 152 | 153 | this.change(e)} 159 | /> 160 |
161 |
162 |
163 | 164 | 170 | 171 | 172 |
173 | 181 | 182 |
183 | 184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 | ); 193 | } 194 | } 195 | 196 | export default GeofenceEdit; -------------------------------------------------------------------------------- /source/website-contents/src/components/GeofenceForm.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************************************************************************* 2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved * 3 | * * 4 | * Licensed under the MIT No Attribution License (MIT-0) (the ‘License’). You may not use this file except in compliance * 5 | * with the License. A copy of the License is located at * 6 | * * 7 | * https://opensource.org/licenses/MIT-0 * 8 | * * 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files * 10 | * (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, * 11 | * publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so. * 12 | * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * 13 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR * 14 | * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH * 15 | * THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * 16 | *******************************************************************************************************************************************/ 17 | 18 | import React from "react"; 19 | import TextField from '@material-ui/core/TextField' 20 | import Button from '@material-ui/core/Button'; 21 | import SaveIcon from '@material-ui/icons/Save'; 22 | import Grid from '@material-ui/core/Grid'; 23 | import SearchIcon from '@material-ui/icons/Search'; 24 | import IconButton from '@material-ui/core/IconButton'; 25 | import Box from '@material-ui/core/Box'; 26 | import Select from '@material-ui/core/Select'; 27 | import MenuItem from '@material-ui/core/MenuItem'; 28 | import FormControl from '@material-ui/core/FormControl'; 29 | import InputLabel from '@material-ui/core/InputLabel'; 30 | 31 | import { API, graphqlOperation } from 'aws-amplify'; 32 | import { getCoordsFromAddress } from '../graphql/queries'; 33 | import { createGeofence } from '../graphql/mutations' 34 | 35 | export default class GeofenceForm extends React.Component { 36 | state = { 37 | address: "", 38 | name: "", 39 | branch: "", 40 | city: "", 41 | country: "", 42 | region: "", 43 | latitude: "", 44 | longitude: "", 45 | defType: "", 46 | defValue: "", 47 | searchText: "", 48 | hideFields: false 49 | }; 50 | 51 | change = e => { 52 | this.setState({ 53 | [e.target.name]: e.target.value 54 | }); 55 | }; 56 | 57 | onSubmit = async (e) => { 58 | e.preventDefault(); 59 | 60 | let resultcreateGeofence = await API.graphql(graphqlOperation( 61 | createGeofence, { 62 | input: { 63 | name: this.state.name, 64 | branch: this.state.branch, 65 | address: this.state.address, 66 | city: this.state.city, 67 | country: this.state.country, 68 | region: this.state.region, 69 | latitude: parseFloat(this.state.latitude), 70 | longitude: parseFloat(this.state.longitude), 71 | definition: this.state.defType + ':(' + this.state.defValue + ')', 72 | visits: 0 73 | } 74 | } 75 | )); 76 | 77 | console.log('Create Geofence Response =>' + JSON.stringify(resultcreateGeofence, null, 4)); 78 | 79 | this.setState({ 80 | address: "", 81 | name: "", 82 | branch: "", 83 | city: "", 84 | country: "", 85 | region: "", 86 | latitude: "", 87 | longitude: "", 88 | defType: "", 89 | defValue: "", 90 | searchText: "", 91 | hideFields: false, 92 | errorGetAddress: "" 93 | }); 94 | 95 | this.props.queryGeofences() 96 | }; 97 | 98 | searchAddress = async () => { 99 | try { 100 | let responseddress = await API.graphql(graphqlOperation( 101 | getCoordsFromAddress, { 102 | address: this.state.searchText 103 | } 104 | )); 105 | 106 | this.setState({ 107 | address: responseddress.data.getCoordsFromAddress.street, 108 | city: responseddress.data.getCoordsFromAddress.city, 109 | country: responseddress.data.getCoordsFromAddress.country, 110 | region: responseddress.data.getCoordsFromAddress.state, 111 | latitude: responseddress.data.getCoordsFromAddress.latitude, 112 | longitude: responseddress.data.getCoordsFromAddress.longitude, 113 | hideFields: true, 114 | errorGetAddress: "" 115 | }) 116 | } catch (err) { 117 | console.error('Err Getting the address => ' + JSON.stringify(err, null, 4)) 118 | this.setState({ 119 | errorGetAddress: 'Address not found. Please try again!' 120 | }) 121 | } 122 | } 123 | 124 | render() { 125 | return ( 126 |
127 |
128 | 129 | 136 | this.change(e)} 143 | /> 144 | 145 | this.searchAddress(e)} color="primary"> 146 | 147 | 148 | 149 | 150 |

{this.state.errorGetAddress}

151 | 152 | { this.state.hideFields ? 153 | 154 |
155 |

156 | 164 | 165 |

166 | Fill up the fields below: 167 |

168 | 169 | 173 | 174 | this.change(e)} 180 | /> 181 | this.change(e)} 187 | /> 188 | 189 | 190 | 191 | 195 | 196 | 197 | Type 198 | 207 | 208 | 209 | 210 | this.change(e)} 216 | /> 217 |
218 |
219 |
220 |
221 |

222 | 223 | 224 | 225 | this.change(e)} 231 | /> 232 | 233 | this.change(e)} 239 | /> 240 | 241 | this.change(e)} 247 | /> 248 | 249 | 250 | 251 | this.change(e)} 257 | /> 258 | 259 | this.change(e)} 265 | /> 266 | 267 | this.change(e)} 273 | /> 274 | 275 | 276 | 277 | 278 |
279 | 286 |
287 |
288 | 289 | : null } 290 | 291 |
292 | ); 293 | } 294 | } -------------------------------------------------------------------------------- /source/website-contents/src/components/GeofenceHome.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************************************************************************* 2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved * 3 | * * 4 | * Licensed under the MIT No Attribution License (MIT-0) (the ‘License’). You may not use this file except in compliance * 5 | * with the License. A copy of the License is located at * 6 | * * 7 | * https://opensource.org/licenses/MIT-0 * 8 | * * 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files * 10 | * (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, * 11 | * publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so. * 12 | * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * 13 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR * 14 | * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH * 15 | * THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * 16 | *******************************************************************************************************************************************/ 17 | 18 | import React, { Component } from 'react'; 19 | 20 | import { ThemeProvider as MuiThemeProvider, createMuiTheme } from '@material-ui/core/styles'; 21 | import { blue } from '@material-ui/core/colors'; 22 | 23 | import { API, graphqlOperation } from 'aws-amplify'; 24 | 25 | import { listGeofences } from '../graphql/queries'; 26 | 27 | import GeofenceForm from "./GeofenceForm"; 28 | import GeofenceTable from "./GeofenceTable"; 29 | 30 | const theme = createMuiTheme({ 31 | status: { 32 | danger: blue[500], 33 | }, 34 | }); 35 | 36 | class GeofenceHome extends Component { 37 | state = { 38 | data:[] 39 | }; 40 | 41 | queryGeofences = async () => { 42 | console.log('queryGeofences') 43 | try { 44 | let allGeofences = await API.graphql(graphqlOperation( 45 | listGeofences 46 | )); 47 | 48 | let items = await allGeofences.data.listGeofences.items; 49 | this.setState({ data: items }); 50 | } catch (err) { 51 | console.error('Err List Geofences => ' + JSON.stringify(err, null, 4)) 52 | } 53 | }; 54 | 55 | async componentDidMount() { 56 | await this.queryGeofences(); 57 | } 58 | 59 | render() { 60 | return ( 61 | 62 |
63 | 64 |

65 | 83 |

84 |
85 |
86 | ); 87 | } 88 | 89 | } 90 | 91 | export default GeofenceHome; 92 | -------------------------------------------------------------------------------- /source/website-contents/src/components/GeofenceNavBar.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************************************************************************* 2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved * 3 | * * 4 | * Licensed under the MIT No Attribution License (MIT-0) (the ‘License’). You may not use this file except in compliance * 5 | * with the License. A copy of the License is located at * 6 | * * 7 | * https://opensource.org/licenses/MIT-0 * 8 | * * 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files * 10 | * (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, * 11 | * publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so. * 12 | * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * 13 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR * 14 | * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH * 15 | * THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * 16 | *******************************************************************************************************************************************/ 17 | 18 | import React from 'react' 19 | import AppBar from '@material-ui/core/AppBar' 20 | import Toolbar from '@material-ui/core/Toolbar' 21 | import Typography from '@material-ui/core/Typography' 22 | import MenuItem from '@material-ui/core/MenuItem'; 23 | import Menu from '@material-ui/core/Menu'; 24 | import IconButton from '@material-ui/core/IconButton'; 25 | import AccountCircle from '@material-ui/icons/AccountCircle'; 26 | import { makeStyles } from '@material-ui/core/styles'; 27 | 28 | import {Auth} from 'aws-amplify'; 29 | 30 | const useStyles = makeStyles((theme) => ({ 31 | root: { 32 | flexGrow: 1, 33 | }, 34 | menuButton: { 35 | marginRight: theme.spacing(2), 36 | }, 37 | title: { 38 | flexGrow: 1, 39 | }, 40 | })); 41 | 42 | const GeofenceNavBar = ({loggedUser}) => { 43 | const classes = useStyles(); 44 | 45 | const [anchorEl, setAnchorEl] = React.useState(null); 46 | const open = Boolean(anchorEl); 47 | 48 | const signOut = () => { 49 | Auth.signOut().then(() => { 50 | console.log("signout executed"); 51 | }); 52 | }; 53 | 54 | const handleMenu = (event) => { 55 | setAnchorEl(event.currentTarget); 56 | }; 57 | 58 | const handleClose = () => { 59 | setAnchorEl(null); 60 | }; 61 | 62 | return( 63 |
64 | 65 | 66 | 67 | Geofence - Portal Admin 68 | 69 | 70 | 71 | 72 |
73 | 80 | 81 | 82 | 97 | User: {loggedUser} 98 | Logout 99 | 100 |
101 | 102 | 103 | 104 |
105 |
106 |
107 | ) 108 | } 109 | 110 | export default GeofenceNavBar; 111 | -------------------------------------------------------------------------------- /source/website-contents/src/components/GeofenceTable.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************************************************************************* 2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved * 3 | * * 4 | * Licensed under the MIT No Attribution License (MIT-0) (the ‘License’). You may not use this file except in compliance * 5 | * with the License. A copy of the License is located at * 6 | * * 7 | * https://opensource.org/licenses/MIT-0 * 8 | * * 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files * 10 | * (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, * 11 | * publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so. * 12 | * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * 13 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR * 14 | * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH * 15 | * THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * 16 | *******************************************************************************************************************************************/ 17 | 18 | import React from 'react'; 19 | import { withStyles, makeStyles } from '@material-ui/core/styles'; 20 | import Table from '@material-ui/core/Table'; 21 | import TableBody from '@material-ui/core/TableBody'; 22 | import TableCell from '@material-ui/core/TableCell'; 23 | import TableContainer from '@material-ui/core/TableContainer'; 24 | import TableHead from '@material-ui/core/TableHead'; 25 | import TableRow from '@material-ui/core/TableRow'; 26 | import TablePagination from '@material-ui/core/TablePagination'; 27 | import VisibilityIcon from '@material-ui/icons/Visibility'; 28 | import DeleteIcon from '@material-ui/icons/Delete'; 29 | import EditIcon from '@material-ui/icons/Edit'; 30 | import IconButton from '@material-ui/core/IconButton'; 31 | 32 | import { API, graphqlOperation } from 'aws-amplify'; 33 | import { deleteGeofence, deleteGeofenceMessage } from '../graphql/mutations' 34 | import { Link } from 'react-router-dom'; 35 | import { indigo } from '@material-ui/core/colors'; 36 | 37 | const useStyles = makeStyles({ 38 | table: { 39 | maxWidth: 900, 40 | margin: "auto" 41 | }, 42 | }); 43 | 44 | const StyledTableCell = withStyles((theme) => ({ 45 | head: { 46 | backgroundColor: indigo[200], 47 | color: theme.palette.common.black, 48 | }, 49 | body: { 50 | fontSize: 14, 51 | }, 52 | }))(TableCell); 53 | 54 | const row = (x, i, deleteGeofence, queryGeofences) => ( 55 | 56 | 57 | {x['name']} 58 | 59 | 60 | {x['branch']} 61 | 62 | 63 | {x['city']} 64 | 65 | 66 | 70 | 75 | 76 | 77 | 78 | 79 | 80 | 85 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | deleteGeofence(e, x['id'])} 101 | > 102 | 103 | 104 | 105 | 106 | 107 | ); 108 | 109 | export default function GeofenceTable( {header, data, queryGeofences} ) { 110 | const classes = useStyles(); 111 | 112 | const [rowsPerPage, setRowsPerPage] = React.useState(5); 113 | const [page, setPage] = React.useState(0); 114 | 115 | React.useEffect(() => { 116 | queryGeofences(); 117 | }, []); 118 | 119 | //const emptyRows = rowsPerPage - Math.min(rowsPerPage, rows.length - page * rowsPerPage); 120 | 121 | const handleChangePage = async (event, newPage) => { 122 | setPage(newPage); 123 | }; 124 | 125 | const handleChangeRowsPerPage = async (event) => { 126 | setRowsPerPage(parseInt(event.target.value, 10)); 127 | setPage(0); 128 | }; 129 | 130 | const removeGeofence = async (e, id) => { 131 | e.preventDefault(); 132 | 133 | try { 134 | let resultDeleteGeofenceMessage = await API.graphql(graphqlOperation( 135 | deleteGeofenceMessage, { 136 | template: id 137 | } 138 | )); 139 | console.log('Delete Geofence Message Response =>' + JSON.stringify(resultDeleteGeofenceMessage, null, 4)); 140 | } catch (err) { 141 | console.error('Err Delete Geofence => ' + JSON.stringify(err, null, 4)) 142 | } 143 | 144 | try { 145 | let resultDeletePremiumGeofenceMessage = await API.graphql(graphqlOperation( 146 | deleteGeofenceMessage, { 147 | template: `${id}-PREMIUM` 148 | } 149 | )); 150 | console.log('Delete Premium Geofence Message Response =>' + JSON.stringify(resultDeletePremiumGeofenceMessage, null, 4)); 151 | } catch (err) { 152 | console.error('Err Delete Geofence => ' + JSON.stringify(err, null, 4)) 153 | } 154 | 155 | try { 156 | let resultDeleteGeofence = await API.graphql(graphqlOperation( 157 | deleteGeofence, { 158 | input: { 159 | id: id 160 | } 161 | } 162 | )); 163 | console.log('Delete Geofence Response =>' + JSON.stringify(resultDeleteGeofence, null, 4)); 164 | } catch (err) { 165 | console.error('Err Delete Geofence => ' + JSON.stringify(err, null, 4)) 166 | } 167 | 168 | queryGeofences(); 169 | }; 170 | 171 | return ( 172 |
173 | 174 | 175 | 176 | 177 | Name 178 | Branch 179 | City 180 | Details 181 | Edit 182 | Delete 183 | 184 | 185 | 186 | { data 187 | .slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) 188 | .map((x, i) => row(x, i, removeGeofence, queryGeofences)) 189 | } 190 | 191 |
192 |
193 | 194 | 204 | 205 |
206 | ); 207 | } 208 | -------------------------------------------------------------------------------- /source/website-contents/src/graphql/mutations.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************************************************************************* 2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved * 3 | * * 4 | * Licensed under the MIT No Attribution License (MIT-0) (the ‘License’). You may not use this file except in compliance * 5 | * with the License. A copy of the License is located at * 6 | * * 7 | * https://opensource.org/licenses/MIT-0 * 8 | * * 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files * 10 | * (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, * 11 | * publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so. * 12 | * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * 13 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR * 14 | * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH * 15 | * THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * 16 | *******************************************************************************************************************************************/ 17 | 18 | export const createGeofence = `mutation CreateGeofence($input: CreateGeofenceInput!) { 19 | createGeofence(input: $input) { 20 | id 21 | name 22 | branch 23 | address 24 | city 25 | country 26 | region 27 | latitude 28 | longitude 29 | definition 30 | visits 31 | } 32 | } 33 | `; 34 | 35 | export const updateGeofence = `mutation updateGeofence($input: UpdateGeofenceInput!) { 36 | updateGeofence(input: $input) { 37 | id 38 | name 39 | branch 40 | address 41 | city 42 | country 43 | region 44 | latitude 45 | longitude 46 | definition 47 | visits 48 | } 49 | } 50 | `; 51 | 52 | export const deleteGeofence = `mutation deleteGeofence($input: DeleteGeofenceInput!) { 53 | deleteGeofence(input: $input) { 54 | id 55 | } 56 | } 57 | `; 58 | 59 | export const createGeofenceMessage = `mutation createGeofenceMessage($template: String!, $input: GeofenceMessageInput!) { 60 | createGeofenceMessage( 61 | template: $template, 62 | input: $input 63 | ) { 64 | status 65 | message 66 | } 67 | } 68 | `; 69 | 70 | export const deleteGeofenceMessage = `mutation deleteGeofenceMessage($template: String!) { 71 | deleteGeofenceMessage( 72 | template: $template 73 | ) { 74 | status 75 | message 76 | } 77 | } 78 | `; 79 | -------------------------------------------------------------------------------- /source/website-contents/src/graphql/queries.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************************************************************************* 2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved * 3 | * * 4 | * Licensed under the MIT No Attribution License (MIT-0) (the ‘License’). You may not use this file except in compliance * 5 | * with the License. A copy of the License is located at * 6 | * * 7 | * https://opensource.org/licenses/MIT-0 * 8 | * * 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files * 10 | * (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, * 11 | * publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so. * 12 | * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * 13 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR * 14 | * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH * 15 | * THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * 16 | *******************************************************************************************************************************************/ 17 | 18 | export const listGeofences = `query listGeofences($limit: Int, $nextToken: String) { 19 | listGeofences( 20 | limit: $limit, 21 | nextToken: $nextToken 22 | ) { 23 | items { 24 | id 25 | name 26 | branch 27 | address 28 | city 29 | country 30 | region 31 | latitude 32 | longitude 33 | definition 34 | visits 35 | } 36 | nextToken 37 | } 38 | } 39 | `; 40 | 41 | export const getCoordsFromAddress = `query getCoordsFromAddress($address: String!) { 42 | getCoordsFromAddress( 43 | address: $address 44 | ) { 45 | street 46 | city 47 | state 48 | country 49 | latitude 50 | longitude 51 | } 52 | } 53 | `; 54 | 55 | export const getGeofenceMessage = `query getGeofenceMessage($template: String!) { 56 | getGeofenceMessage( 57 | template: $template 58 | ) { 59 | status 60 | message { 61 | service 62 | action 63 | title 64 | body 65 | } 66 | } 67 | } 68 | `; -------------------------------------------------------------------------------- /source/website-contents/src/index.css: -------------------------------------------------------------------------------- 1 | /******************************************************************************************************************************************* 2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved * 3 | * * 4 | * Licensed under the MIT No Attribution License (MIT-0) (the ‘License’). You may not use this file except in compliance * 5 | * with the License. A copy of the License is located at * 6 | * * 7 | * https://opensource.org/licenses/MIT-0 * 8 | * * 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files * 10 | * (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, * 11 | * publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so. * 12 | * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * 13 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR * 14 | * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH * 15 | * THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * 16 | *******************************************************************************************************************************************/ 17 | 18 | body { 19 | margin: 0; 20 | padding: 0; 21 | font-family: sans-serif; 22 | } -------------------------------------------------------------------------------- /source/website-contents/src/index.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************************************************************************* 2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved * 3 | * * 4 | * Licensed under the MIT No Attribution License (MIT-0) (the ‘License’). You may not use this file except in compliance * 5 | * with the License. A copy of the License is located at * 6 | * * 7 | * https://opensource.org/licenses/MIT-0 * 8 | * * 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files * 10 | * (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, * 11 | * publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so. * 12 | * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * 13 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR * 14 | * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH * 15 | * THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * 16 | *******************************************************************************************************************************************/ 17 | 18 | import React from 'react'; 19 | import ReactDOM from 'react-dom'; 20 | import './index.css'; 21 | import App from './App'; 22 | import * as serviceWorker from './serviceWorker'; 23 | 24 | /*ReactDOM.render( 25 | 26 | 27 | , 28 | document.getElementById('root') 29 | ); 30 | */ 31 | 32 | ReactDOM.render(, document.getElementById('root')); 33 | 34 | // If you want your app to work offline and load faster, you can change 35 | // unregister() to register() below. Note this comes with some pitfalls. 36 | // Learn more about service workers: https://bit.ly/CRA-PWA 37 | serviceWorker.unregister(); 38 | -------------------------------------------------------------------------------- /source/website-contents/src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************************************************************************* 2 | * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved * 3 | * * 4 | * Licensed under the MIT No Attribution License (MIT-0) (the ‘License’). You may not use this file except in compliance * 5 | * with the License. A copy of the License is located at * 6 | * * 7 | * https://opensource.org/licenses/MIT-0 * 8 | * * 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files * 10 | * (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, * 11 | * publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so. * 12 | * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * 13 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR * 14 | * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH * 15 | * THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * 16 | *******************************************************************************************************************************************/ 17 | 18 | // This optional code is used to register a service worker. 19 | // register() is not called by default. 20 | 21 | // This lets the app load faster on subsequent visits in production, and gives 22 | // it offline capabilities. However, it also means that developers (and users) 23 | // will only see deployed updates on subsequent visits to a page, after all the 24 | // existing tabs open on the page have been closed, since previously cached 25 | // resources are updated in the background. 26 | 27 | // To learn more about the benefits of this model and instructions on how to 28 | // opt-in, read https://bit.ly/CRA-PWA 29 | 30 | const isLocalhost = Boolean( 31 | window.location.hostname === 'localhost' || 32 | // [::1] is the IPv6 localhost address. 33 | window.location.hostname === '[::1]' || 34 | // 127.0.0.0/8 are considered localhost for IPv4. 35 | window.location.hostname.match( 36 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 37 | ) 38 | ); 39 | 40 | export function register(config) { 41 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 42 | // The URL constructor is available in all browsers that support SW. 43 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 44 | if (publicUrl.origin !== window.location.origin) { 45 | // Our service worker won't work if PUBLIC_URL is on a different origin 46 | // from what our page is served on. This might happen if a CDN is used to 47 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 48 | return; 49 | } 50 | 51 | window.addEventListener('load', () => { 52 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 53 | 54 | if (isLocalhost) { 55 | // This is running on localhost. Let's check if a service worker still exists or not. 56 | checkValidServiceWorker(swUrl, config); 57 | 58 | // Add some additional logging to localhost, pointing developers to the 59 | // service worker/PWA documentation. 60 | navigator.serviceWorker.ready.then(() => { 61 | console.log( 62 | 'This web app is being served cache-first by a service ' + 63 | 'worker. To learn more, visit https://bit.ly/CRA-PWA' 64 | ); 65 | }); 66 | } else { 67 | // Is not localhost. Just register service worker 68 | registerValidSW(swUrl, config); 69 | } 70 | }); 71 | } 72 | } 73 | 74 | function registerValidSW(swUrl, config) { 75 | navigator.serviceWorker 76 | .register(swUrl) 77 | .then(registration => { 78 | registration.onupdatefound = () => { 79 | const installingWorker = registration.installing; 80 | if (installingWorker == null) { 81 | return; 82 | } 83 | installingWorker.onstatechange = () => { 84 | if (installingWorker.state === 'installed') { 85 | if (navigator.serviceWorker.controller) { 86 | // At this point, the updated precached content has been fetched, 87 | // but the previous service worker will still serve the older 88 | // content until all client tabs are closed. 89 | console.log( 90 | 'New content is available and will be used when all ' + 91 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' 92 | ); 93 | 94 | // Execute callback 95 | if (config && config.onUpdate) { 96 | config.onUpdate(registration); 97 | } 98 | } else { 99 | // At this point, everything has been precached. 100 | // It's the perfect time to display a 101 | // "Content is cached for offline use." message. 102 | console.log('Content is cached for offline use.'); 103 | 104 | // Execute callback 105 | if (config && config.onSuccess) { 106 | config.onSuccess(registration); 107 | } 108 | } 109 | } 110 | }; 111 | }; 112 | }) 113 | .catch(error => { 114 | console.error('Error during service worker registration:', error); 115 | }); 116 | } 117 | 118 | function checkValidServiceWorker(swUrl, config) { 119 | // Check if the service worker can be found. If it can't reload the page. 120 | fetch(swUrl, { 121 | headers: { 'Service-Worker': 'script' }, 122 | }) 123 | .then(response => { 124 | // Ensure service worker exists, and that we really are getting a JS file. 125 | const contentType = response.headers.get('content-type'); 126 | if ( 127 | response.status === 404 || 128 | (contentType != null && contentType.indexOf('javascript') === -1) 129 | ) { 130 | // No service worker found. Probably a different app. Reload the page. 131 | navigator.serviceWorker.ready.then(registration => { 132 | registration.unregister().then(() => { 133 | window.location.reload(); 134 | }); 135 | }); 136 | } else { 137 | // Service worker found. Proceed as normal. 138 | registerValidSW(swUrl, config); 139 | } 140 | }) 141 | .catch(() => { 142 | console.error( 143 | 'No internet connection found. App is running in offline mode.' 144 | ); 145 | }); 146 | } 147 | 148 | export function unregister() { 149 | if ('serviceWorker' in navigator) { 150 | navigator.serviceWorker.ready 151 | .then(registration => { 152 | registration.unregister(); 153 | }) 154 | .catch(error => { 155 | console.error(error.message); 156 | }); 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /source/website-custom-resource/cfnResponse.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved 3 | 4 | Licensed under the MIT No Attribution License (MIT-0) (the ‘License’). You may not use this file except in compliance 5 | with the License. A copy of the License is located at 6 | 7 | https://opensource.org/licenses/MIT-0 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files 10 | (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, 11 | publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so. 12 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 13 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR 14 | ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH 15 | THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | """ 17 | 18 | import requests 19 | import json 20 | 21 | SUCCESS = "SUCCESS" 22 | FAILED = "FAILED" 23 | 24 | def send(event, context, responseStatus, responseData, physicalResourceId=None, noEcho=False): 25 | """ 26 | It creates a response object with proper parameters to invoke a CloudFormation API to send a signal to finish the CustomResource invocation. 27 | """ 28 | 29 | responseUrl = event['ResponseURL'] 30 | 31 | print(responseUrl) 32 | 33 | responseBody = {} 34 | responseBody['Status'] = responseStatus 35 | responseBody['Reason'] = 'See the details in CloudWatch Log Stream: ' + context.log_stream_name 36 | responseBody['PhysicalResourceId'] = physicalResourceId or context.log_stream_name 37 | responseBody['StackId'] = event['StackId'] 38 | responseBody['RequestId'] = event['RequestId'] 39 | responseBody['LogicalResourceId'] = event['LogicalResourceId'] 40 | responseBody['NoEcho'] = noEcho 41 | responseBody['Data'] = responseData 42 | 43 | json_responseBody = json.dumps(responseBody) 44 | 45 | print("Response body:\n" + json_responseBody) 46 | 47 | headers = { 48 | 'content-type' : '', 49 | 'content-length' : str(len(json_responseBody)) 50 | } 51 | 52 | try: 53 | response = requests.put(responseUrl, 54 | data=json_responseBody, 55 | headers=headers) 56 | print("Status code: " + response.reason) 57 | except Exception as e: 58 | print("send(..) failed executing requests.put(..): " + str(e)) 59 | -------------------------------------------------------------------------------- /source/website-custom-resource/requirements.txt: -------------------------------------------------------------------------------- 1 | ########################################################################################################################################### 2 | # Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved # 3 | # # 4 | # Licensed under the MIT No Attribution License (MIT-0) (the ‘License’). You may not use this file except in compliance # 5 | # with the License. A copy of the License is located at # 6 | # # 7 | # https://opensource.org/licenses/MIT-0 # 8 | # # 9 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files # 10 | # (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, # 11 | # publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so. # 12 | # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # 13 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR # 14 | # ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH # 15 | # THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # 16 | ############################################################################################################################################ 17 | 18 | requests -------------------------------------------------------------------------------- /source/website-custom-resource/websiteDeploy.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved 3 | 4 | Licensed under the MIT No Attribution License (MIT-0) (the ‘License’). You may not use this file except in compliance 5 | with the License. A copy of the License is located at 6 | 7 | https://opensource.org/licenses/MIT-0 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files 10 | (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, 11 | publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so. 12 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 13 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR 14 | ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH 15 | THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | """ 17 | 18 | """ 19 | Custom resource function to be invoked by the CloudFormation stack in oder to deploy the administrator website. 20 | 21 | This function deploys the administrator website into a S3 bucket configured as a website in the Create event. For 22 | the Delete event, the function cleans up the website bucket to be removed by the CloudFormation stack. 23 | 24 | This custom resource will be processed only for the Create and Delete events. 25 | """ 26 | 27 | import json 28 | import os 29 | import boto3 30 | import zipfile 31 | import cfnResponse 32 | import mimetypes 33 | from botocore.exceptions import ClientError 34 | 35 | s3 = boto3.resource('s3') 36 | 37 | def handler(event, context): 38 | """ 39 | Main handler to control wether to process the Custom Resource in the event of a stack creation or deletion. 40 | """ 41 | 42 | print('request: {}'.format(json.dumps(event, indent = 4))) 43 | requests = event['ResourceProperties']['Requests'][0] 44 | 45 | origin_bucket = requests['originBucket'] 46 | origin_prefix = requests['originPrefix'] 47 | website_bucket = requests['websiteBucket'] 48 | print('Bucket Origin: ' + origin_bucket) 49 | print('Bucket Prefix: ' + origin_prefix) 50 | print('Bucket Target: ' + website_bucket) 51 | 52 | if event['RequestType'] == 'Create': 53 | print('Creating the Stack...') 54 | aws_resources = { 55 | 'aws_region': os.environ['REGION'], 56 | 'user_pool_id': requests['userPoolId'], 57 | 'app_client_id': requests['appClientId'], 58 | 'identity_pool_id': requests['identityPoolId'], 59 | 'pinpoint_app_id': requests['pinpointAppId'], 60 | 'appsync_endpoint': requests['appSyncEndpoint'] 61 | } 62 | 63 | content, content_to_replace = get_website_content_from_origin_bucket( 64 | event = event, 65 | context = context, 66 | origin_bucket = origin_bucket, 67 | origin_prefix = origin_prefix 68 | ) 69 | 70 | deploy_website_to_target_bucket( 71 | event = event, 72 | context = context, 73 | target_bucket = website_bucket, 74 | files = content 75 | ) 76 | 77 | replace_aws_resources( 78 | event = event, 79 | context = context, 80 | target_bucket = website_bucket, 81 | files = content_to_replace, 82 | aws_resources = aws_resources 83 | ) 84 | 85 | cfnResponse.send(event, context, cfnResponse.SUCCESS, {}, "CustomResourcePhysicalID") 86 | 87 | elif event['RequestType'] == 'Delete': 88 | print('Deleting Stack. ') 89 | cfnResponse.send(event, context, cfnResponse.SUCCESS, {}, "CustomResourcePhysicalID") 90 | 91 | ''' 92 | # In case you want to clean up the website bucket during deletion. Default behavior is to 93 | # keep the s3 bucket and its contents. 94 | 95 | try: 96 | print('Deleting the Stack...') 97 | bucket = s3.Bucket(website_bucket) 98 | 99 | if is_bucket_empty(bucket): 100 | print(f'Bucket {website_bucket} is empty. No need to clean up') 101 | else: 102 | bucket.objects.all().delete() 103 | print (f'Bucket {website_bucket} was cleaned up with success') 104 | 105 | cfnResponse.send(event, context, cfnResponse.SUCCESS, {}, "CustomResourcePhysicalID") 106 | 107 | except ClientError as ex: 108 | print(f'Target Bucket {website_bucket} with error: {ex}') 109 | cfnResponse.send(event, context, cfnResponse.FAILED, {}, "CustomResourcePhysicalID") 110 | ''' 111 | 112 | else: 113 | print('Updating Stack. ') 114 | cfnResponse.send(event, context, cfnResponse.SUCCESS, {}, "CustomResourcePhysicalID") 115 | 116 | def replace_aws_resources(event, context, target_bucket, files, aws_resources): 117 | """ 118 | Replace all placeholders at deployment time with the newly created resources. Then sends the files 119 | to the S3 website bucket 120 | """ 121 | 122 | print(f'Setting up AWS resources to the admin website') 123 | 124 | try: 125 | for webSiteFile in files: 126 | with open(webSiteFile) as f: 127 | content = f.read() 128 | 129 | content = content.replace("REPLACE_AWS_REGION", aws_resources['aws_region']) 130 | content = content.replace("REPLACE_USER_POOL_ID", aws_resources['user_pool_id']) 131 | content = content.replace("REPLACE_APP_CLIENT_ID", aws_resources['app_client_id']) 132 | content = content.replace("REPLACE_IDENTITY_POOL_ID", aws_resources['identity_pool_id']) 133 | content = content.replace("REPLACE_PINPOINT_APP_ID", aws_resources['pinpoint_app_id']) 134 | content = content.replace("REPLACE_APPSYNC_ENDPOINT", aws_resources['appsync_endpoint']) 135 | 136 | encoded_string = content.encode("utf-8") 137 | website_key = os.path.relpath(webSiteFile, '/tmp/website-contents') 138 | guessed_mime_type = mimetypes.guess_type(webSiteFile) 139 | 140 | if website_key.startswith('../'): 141 | file_key = website_key[len('../'):] 142 | else: 143 | file_key = website_key 144 | 145 | if guessed_mime_type is None: 146 | raise Exception("Failed to guess mimetype") 147 | 148 | mime_type = guessed_mime_type[0] 149 | 150 | if mime_type is None: 151 | mime_type = 'binary/octet-stream' 152 | 153 | s3.Bucket(target_bucket).put_object( 154 | Key=file_key, 155 | Body=encoded_string, 156 | ContentType=mime_type 157 | ) 158 | 159 | print(f'{file_key} uploaded to {target_bucket}') 160 | 161 | print(f'AWS Resources set and deployed successfully to {target_bucket} bucket') 162 | except ClientError as ex: 163 | print(f'Target Bucket {target_bucket} with error: {ex}') 164 | cfnResponse.send(event, context, cfnResponse.FAILED, {}, "CustomResourcePhysicalID") 165 | 166 | def deploy_website_to_target_bucket(event, context, target_bucket, files): 167 | """ 168 | Deploys the website files into the S3 website bucket 169 | """ 170 | 171 | print(f'Starting admin website deployment to {target_bucket} bucket') 172 | 173 | try: 174 | for webSiteFile in files: 175 | with open(webSiteFile) as f: 176 | content = f.read() 177 | 178 | encoded_string = content.encode("utf-8") 179 | website_key = os.path.relpath(webSiteFile, '/tmp/website-contents') 180 | guessed_mime_type = mimetypes.guess_type(webSiteFile) 181 | 182 | if website_key.startswith('../'): 183 | file_key = website_key[len('../'):] 184 | else: 185 | file_key = website_key 186 | 187 | print('Key being uploaded to S3: ' + file_key) 188 | 189 | if guessed_mime_type is None: 190 | raise Exception("Failed to guess mimetype") 191 | 192 | mime_type = guessed_mime_type[0] 193 | 194 | if mime_type is None: 195 | mime_type = 'binary/octet-stream' 196 | 197 | s3.Bucket(target_bucket).put_object( 198 | Key=file_key, 199 | Body=encoded_string, 200 | ContentType=mime_type 201 | ) 202 | 203 | print(f'{file_key} uploaded to {target_bucket}') 204 | 205 | print(f'Admin website deployed successfully to {target_bucket} bucket') 206 | except ClientError as ex: 207 | print(f'Target Bucket {target_bucket} with error: {ex}') 208 | cfnResponse.send(event, context, cfnResponse.FAILED, {}, "CustomResourcePhysicalID") 209 | 210 | def get_website_content_from_origin_bucket(event, context, origin_bucket, origin_prefix): 211 | """ 212 | Gets the website raw content and stores in the Lambda tmp directory to be processed 213 | """ 214 | 215 | print(f'Getting website files from {origin_bucket} bucket') 216 | 217 | try: 218 | key = 'website-contents.zip' 219 | full_key = origin_prefix + key 220 | tmp_dir = '/tmp/' 221 | local_file_name = tmp_dir + key 222 | 223 | s3.Bucket(origin_bucket).download_file(full_key, local_file_name) 224 | print(f'File {key} downloaded to {local_file_name}') 225 | 226 | print(f'Extracting file {key} to {tmp_dir}') 227 | with zipfile.ZipFile(local_file_name, 'r') as zip_ref: 228 | zip_ref.extractall(tmp_dir) 229 | 230 | print(f'Deleting {local_file_name}') 231 | os.remove(local_file_name) 232 | 233 | files = [] 234 | files_to_replace = [] 235 | 236 | for r, d, f in os.walk(tmp_dir): 237 | for file in f: 238 | if file.startswith('main') and all(x in file for x in 'js') and '.ico' not in file: 239 | files_to_replace.append(os.path.join(r, file)) 240 | elif '.ico' not in file: 241 | files.append(os.path.join(r, file)) 242 | 243 | return files, files_to_replace 244 | 245 | except ClientError as ex: 246 | print(f'Origin Bucket {origin_bucket} with error: {ex}') 247 | cfnResponse.send(event, context, cfnResponse.FAILED, {}, "CustomResourcePhysicalID") 248 | 249 | def is_bucket_empty(bucket): 250 | """ 251 | Returns true if the S3 website bucket is empty, false otherwise 252 | """ 253 | 254 | total_obj = len(list(bucket.objects.all())) 255 | return total_obj == 0 256 | 257 | def is_bucket_exist(bucket): 258 | """ 259 | Returns true if the S3 website bucket exists, false otherwise 260 | """ 261 | 262 | if bucket.creation_date: 263 | return True 264 | else: 265 | return False 266 | --------------------------------------------------------------------------------