From 9418228e293a7673d73c576d43ccca6168420628 Mon Sep 17 00:00:00 2001 From: Mike Apted Date: Fri, 26 Jun 2020 15:48:51 -0400 Subject: [PATCH] Architecure update to SAM, Amplify Console, Node.js, and Well Architected review --- .github/ISSUE_TEMPLATE/bug_report.md | 47 + .github/ISSUE_TEMPLATE/feature_request.md | 45 + .github/PULL_REQUEST_TEMPLATE.md | 5 + .gitignore | 181 +- .vscode/launch.json | 12 + CODE_OF_CONDUCT.md | 5 + CONTRIBUTING.md | 95 + Gruntfile.js | 36 - LICENSE | 31 +- NOTICE.txt | 4 - README.md | 253 +- cloudformation/api-creation-helper.template | 1083 ------- .../cognito-creation-helper.template | 352 --- cloudformation/config-helper.template | 115 - .../lambda-creation-helper.template | 257 -- cloudformation/serverless-web-master.template | 782 ----- deploy.sh | 38 + env.json | 26 + img/serverless-refarch-webapp.png | Bin 0 -> 100972 bytes img/serverless-refarch-webapp.xml | 1 + lambda-functions/build.gradle | 49 - .../gradle/wrapper/gradle-wrapper.jar | Bin 53638 -> 0 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 - lambda-functions/gradlew | 160 - lambda-functions/gradlew.bat | 90 - lambda-functions/settings.gradle | 18 - .../blog/comments/handlers/FindComment.java | 39 - .../blog/comments/handlers/FindComments.java | 40 - .../blog/comments/handlers/SaveComment.java | 38 - .../java/blog/comments/models/Comment.java | 81 - .../comments/models/FindCommentRequest.java | 51 - .../DynamoDBCommentRepository.java | 47 - .../ApplicationConfiguration.java | 111 - .../ApplicationConfigurationStore.java | 29 - .../configuration/RequestConfiguration.java | 28 - .../java/blog/forums/handlers/FindForums.java | 36 - .../blog/forums/models/FindForumsRequest.java | 30 - .../main/java/blog/forums/models/Forum.java | 59 - .../repositories/DynamoDBForumRepository.java | 31 - .../java/blog/posts/handlers/FindPost.java | 39 - .../java/blog/posts/handlers/FindPosts.java | 42 - .../java/blog/posts/handlers/SavePost.java | 42 - .../blog/posts/models/FindPostRequest.java | 51 - .../blog/posts/models/FindPostsRequest.java | 38 - .../blog/posts/models/LatestPostByForum.java | 78 - .../src/main/java/blog/posts/models/Post.java | 100 - .../repositories/DynamoDBPostRepository.java | 137 - .../blog/users/handlers/AuthenticateUser.java | 38 - .../java/blog/users/handlers/SaveUser.java | 42 - .../src/main/java/blog/users/models/User.java | 127 - .../repositories/DynamoDBUserRepository.java | 115 - .../blog/users/repositories/PasswordUtil.java | 66 - .../src/main/resources/log4j.properties | 7 - launch.json | 12 + package.json | 19 - template.yaml | 379 +++ todo-src/.npmignore | 1 + todo-src/addTodo/app.js | 76 + todo-src/addTodo/event.json | 138 + todo-src/addTodo/package.json | 14 + todo-src/completeTodo/app.js | 69 + todo-src/completeTodo/event.json | 135 + todo-src/completeTodo/package.json | 13 + todo-src/deleteTodo/app.js | 66 + todo-src/deleteTodo/event.json | 135 + todo-src/deleteTodo/package.json | 13 + todo-src/getAllTodo/app.js | 53 + todo-src/getAllTodo/event.json | 135 + todo-src/getAllTodo/package.json | 13 + todo-src/getTodo/app.js | 68 + todo-src/getTodo/event.json | 135 + todo-src/getTodo/package.json | 13 + todo-src/package.json | 19 + todo-src/test/environment/mac.json | 28 + todo-src/test/unit/test-handler.js | 22 + todo-src/updateTodo/app.js | 88 + todo-src/updateTodo/event.json | 138 + todo-src/updateTodo/package.json | 13 + tsconfig.json | 10 + website/.bowerrc | 3 - website/.editorconfig | 13 - website/.eslintrc | 13 - website/.gitignore | 7 - website/.yo-rc.json | 80 - website/Readme.md | 114 - website/bower.json | 27 - website/e2e/.eslintrc | 9 - website/e2e/main.po.js | 15 - website/e2e/main.spec.js | 23 - website/gulp/.eslintrc | 5 - website/gulp/build.js | 97 - website/gulp/conf.js | 41 - website/gulp/e2e-tests.js | 38 - website/gulp/inject.js | 53 - website/gulp/scripts.js | 29 - website/gulp/server.js | 63 - website/gulp/unit-tests.js | 52 - website/gulp/watch.js | 36 - website/gulpfile.js | 29 - website/karma.conf.js | 111 - website/package.json | 55 - website/protractor.conf.js | 27 - website/src/app/components/aws/aws.api.js | 332 -- website/src/app/components/aws/aws.auth.js | 152 - website/src/app/components/aws/aws.config.js | 19 - .../facebook/facebook-login.directive.js | 71 - .../src/app/components/forums/forum-select.js | 33 - .../app/components/jumbotron/jumbotron.css | 10 - .../jumbotron/jumbotron.directive.js | 30 - .../app/components/jumbotron/jumbotron.html | 16 - website/src/app/components/navbar/navbar.css | 3 - .../app/components/navbar/navbar.directive.js | 32 - website/src/app/components/navbar/navbar.html | 10 - .../src/app/components/post/post.directive.js | 40 - .../sidenav/menu-button.directive.js | 40 - .../components/sidenav/sidenav.directive.js | 106 - .../src/app/components/sidenav/sidenav.html | 66 - website/src/app/index.config.js | 24 - website/src/app/index.constants.js | 9 - website/src/app/index.css | 2803 ----------------- website/src/app/index.module.js | 17 - website/src/app/index.route.js | 44 - website/src/app/index.run.js | 13 - website/src/app/main/main.controller.js | 84 - website/src/app/main/main.controller.spec.js | 29 - website/src/app/main/main.css | 22 - website/src/app/main/main.html | 39 - website/src/app/post/create.controller.js | 64 - website/src/app/post/create.html | 43 - website/src/app/post/post.controller.js | 92 - website/src/app/post/post.controller.spec.js | 0 website/src/app/post/post.css | 35 - website/src/app/post/post.html | 75 - .../src/assets/images/angular-material.png | Bin 2119 -> 0 bytes website/src/assets/images/angular.png | Bin 13522 -> 0 bytes website/src/assets/images/aws-cloud.png | Bin 28328 -> 0 bytes website/src/assets/images/aws.png | Bin 55178 -> 0 bytes website/src/assets/images/browsersync.png | Bin 11615 -> 0 bytes website/src/assets/images/gulp.png | Bin 10678 -> 0 bytes website/src/assets/images/jasmine.png | Bin 15828 -> 0 bytes website/src/assets/images/karma.png | Bin 10130 -> 0 bytes website/src/assets/images/protractor.png | Bin 10186 -> 0 bytes website/src/assets/images/yeoman.png | Bin 13501 -> 0 bytes website/src/favicon.ico | Bin 1150 -> 0 bytes website/src/index.html | 54 - website/src/lib/aws-api-client/README.md | 78 - website/src/lib/aws-api-client/apigClient.js | 375 --- .../lib/CryptoJS/components/enc-base64.js | 109 - .../lib/CryptoJS/components/hmac.js | 131 - .../lib/CryptoJS/rollups/hmac-sha256.js | 18 - .../lib/CryptoJS/rollups/sha256.js | 16 - .../lib/apiGatewayCore/apiGatewayClient.js | 53 - .../lib/apiGatewayCore/sigV4Client.js | 213 -- .../lib/apiGatewayCore/simpleHttpClient.js | 81 - .../lib/apiGatewayCore/utils.js | 80 - .../lib/axios/dist/axios.standalone.js | 1089 ------- .../lib/url-template/url-template.js | 438 --- .../src/lib/aws-cognito/amazon-cognito.min.js | 20 - .../aws-cognito/amazon-cognito.min.js.map.txt | 1 - .../aws-sdk-mobile-analytics.min.js | 1 - well-architected.md | 55 + www/package.json | 36 + www/public/index.html | 41 + www/src/App.css | 18 + www/src/App.js | 181 ++ www/src/ToDo.css | 17 + www/src/ToDo.js | 58 + www/src/aws.png | Bin 0 -> 3850 bytes www/src/config.default.js | 13 + www/src/config.js | 13 + www/src/index.css | 15 + www/src/index.js | 14 + www/src/serviceWorker.js | 135 + 173 files changed, 2921 insertions(+), 12763 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .vscode/launch.json create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md delete mode 100644 Gruntfile.js delete mode 100644 NOTICE.txt delete mode 100644 cloudformation/api-creation-helper.template delete mode 100644 cloudformation/cognito-creation-helper.template delete mode 100644 cloudformation/config-helper.template delete mode 100644 cloudformation/lambda-creation-helper.template delete mode 100644 cloudformation/serverless-web-master.template create mode 100755 deploy.sh create mode 100644 env.json create mode 100644 img/serverless-refarch-webapp.png create mode 100644 img/serverless-refarch-webapp.xml delete mode 100644 lambda-functions/build.gradle delete mode 100644 lambda-functions/gradle/wrapper/gradle-wrapper.jar delete mode 100644 lambda-functions/gradle/wrapper/gradle-wrapper.properties delete mode 100755 lambda-functions/gradlew delete mode 100644 lambda-functions/gradlew.bat delete mode 100644 lambda-functions/settings.gradle delete mode 100644 lambda-functions/src/main/java/blog/comments/handlers/FindComment.java delete mode 100644 lambda-functions/src/main/java/blog/comments/handlers/FindComments.java delete mode 100644 lambda-functions/src/main/java/blog/comments/handlers/SaveComment.java delete mode 100644 lambda-functions/src/main/java/blog/comments/models/Comment.java delete mode 100644 lambda-functions/src/main/java/blog/comments/models/FindCommentRequest.java delete mode 100644 lambda-functions/src/main/java/blog/comments/repositories/DynamoDBCommentRepository.java delete mode 100644 lambda-functions/src/main/java/blog/configuration/ApplicationConfiguration.java delete mode 100644 lambda-functions/src/main/java/blog/configuration/ApplicationConfigurationStore.java delete mode 100644 lambda-functions/src/main/java/blog/configuration/RequestConfiguration.java delete mode 100644 lambda-functions/src/main/java/blog/forums/handlers/FindForums.java delete mode 100644 lambda-functions/src/main/java/blog/forums/models/FindForumsRequest.java delete mode 100644 lambda-functions/src/main/java/blog/forums/models/Forum.java delete mode 100644 lambda-functions/src/main/java/blog/forums/repositories/DynamoDBForumRepository.java delete mode 100644 lambda-functions/src/main/java/blog/posts/handlers/FindPost.java delete mode 100644 lambda-functions/src/main/java/blog/posts/handlers/FindPosts.java delete mode 100644 lambda-functions/src/main/java/blog/posts/handlers/SavePost.java delete mode 100644 lambda-functions/src/main/java/blog/posts/models/FindPostRequest.java delete mode 100644 lambda-functions/src/main/java/blog/posts/models/FindPostsRequest.java delete mode 100644 lambda-functions/src/main/java/blog/posts/models/LatestPostByForum.java delete mode 100644 lambda-functions/src/main/java/blog/posts/models/Post.java delete mode 100644 lambda-functions/src/main/java/blog/posts/repositories/DynamoDBPostRepository.java delete mode 100644 lambda-functions/src/main/java/blog/users/handlers/AuthenticateUser.java delete mode 100644 lambda-functions/src/main/java/blog/users/handlers/SaveUser.java delete mode 100644 lambda-functions/src/main/java/blog/users/models/User.java delete mode 100644 lambda-functions/src/main/java/blog/users/repositories/DynamoDBUserRepository.java delete mode 100644 lambda-functions/src/main/java/blog/users/repositories/PasswordUtil.java delete mode 100644 lambda-functions/src/main/resources/log4j.properties create mode 100644 launch.json delete mode 100644 package.json create mode 100644 template.yaml create mode 100644 todo-src/.npmignore create mode 100644 todo-src/addTodo/app.js create mode 100644 todo-src/addTodo/event.json create mode 100644 todo-src/addTodo/package.json create mode 100644 todo-src/completeTodo/app.js create mode 100644 todo-src/completeTodo/event.json create mode 100644 todo-src/completeTodo/package.json create mode 100644 todo-src/deleteTodo/app.js create mode 100644 todo-src/deleteTodo/event.json create mode 100644 todo-src/deleteTodo/package.json create mode 100644 todo-src/getAllTodo/app.js create mode 100644 todo-src/getAllTodo/event.json create mode 100644 todo-src/getAllTodo/package.json create mode 100644 todo-src/getTodo/app.js create mode 100644 todo-src/getTodo/event.json create mode 100644 todo-src/getTodo/package.json create mode 100644 todo-src/package.json create mode 100644 todo-src/test/environment/mac.json create mode 100644 todo-src/test/unit/test-handler.js create mode 100644 todo-src/updateTodo/app.js create mode 100644 todo-src/updateTodo/event.json create mode 100644 todo-src/updateTodo/package.json create mode 100644 tsconfig.json delete mode 100644 website/.bowerrc delete mode 100644 website/.editorconfig delete mode 100644 website/.eslintrc delete mode 100644 website/.gitignore delete mode 100644 website/.yo-rc.json delete mode 100644 website/Readme.md delete mode 100644 website/bower.json delete mode 100644 website/e2e/.eslintrc delete mode 100644 website/e2e/main.po.js delete mode 100644 website/e2e/main.spec.js delete mode 100644 website/gulp/.eslintrc delete mode 100644 website/gulp/build.js delete mode 100644 website/gulp/conf.js delete mode 100644 website/gulp/e2e-tests.js delete mode 100644 website/gulp/inject.js delete mode 100644 website/gulp/scripts.js delete mode 100644 website/gulp/server.js delete mode 100644 website/gulp/unit-tests.js delete mode 100644 website/gulp/watch.js delete mode 100644 website/gulpfile.js delete mode 100644 website/karma.conf.js delete mode 100644 website/package.json delete mode 100644 website/protractor.conf.js delete mode 100644 website/src/app/components/aws/aws.api.js delete mode 100644 website/src/app/components/aws/aws.auth.js delete mode 100644 website/src/app/components/aws/aws.config.js delete mode 100644 website/src/app/components/facebook/facebook-login.directive.js delete mode 100644 website/src/app/components/forums/forum-select.js delete mode 100644 website/src/app/components/jumbotron/jumbotron.css delete mode 100644 website/src/app/components/jumbotron/jumbotron.directive.js delete mode 100644 website/src/app/components/jumbotron/jumbotron.html delete mode 100644 website/src/app/components/navbar/navbar.css delete mode 100644 website/src/app/components/navbar/navbar.directive.js delete mode 100644 website/src/app/components/navbar/navbar.html delete mode 100644 website/src/app/components/post/post.directive.js delete mode 100644 website/src/app/components/sidenav/menu-button.directive.js delete mode 100644 website/src/app/components/sidenav/sidenav.directive.js delete mode 100644 website/src/app/components/sidenav/sidenav.html delete mode 100644 website/src/app/index.config.js delete mode 100644 website/src/app/index.constants.js delete mode 100644 website/src/app/index.css delete mode 100644 website/src/app/index.module.js delete mode 100644 website/src/app/index.route.js delete mode 100644 website/src/app/index.run.js delete mode 100644 website/src/app/main/main.controller.js delete mode 100644 website/src/app/main/main.controller.spec.js delete mode 100644 website/src/app/main/main.css delete mode 100644 website/src/app/main/main.html delete mode 100644 website/src/app/post/create.controller.js delete mode 100644 website/src/app/post/create.html delete mode 100644 website/src/app/post/post.controller.js delete mode 100644 website/src/app/post/post.controller.spec.js delete mode 100644 website/src/app/post/post.css delete mode 100644 website/src/app/post/post.html delete mode 100644 website/src/assets/images/angular-material.png delete mode 100644 website/src/assets/images/angular.png delete mode 100644 website/src/assets/images/aws-cloud.png delete mode 100644 website/src/assets/images/aws.png delete mode 100644 website/src/assets/images/browsersync.png delete mode 100644 website/src/assets/images/gulp.png delete mode 100644 website/src/assets/images/jasmine.png delete mode 100644 website/src/assets/images/karma.png delete mode 100644 website/src/assets/images/protractor.png delete mode 100644 website/src/assets/images/yeoman.png delete mode 100644 website/src/favicon.ico delete mode 100644 website/src/index.html delete mode 100755 website/src/lib/aws-api-client/README.md delete mode 100755 website/src/lib/aws-api-client/apigClient.js delete mode 100755 website/src/lib/aws-api-client/lib/CryptoJS/components/enc-base64.js delete mode 100755 website/src/lib/aws-api-client/lib/CryptoJS/components/hmac.js delete mode 100755 website/src/lib/aws-api-client/lib/CryptoJS/rollups/hmac-sha256.js delete mode 100755 website/src/lib/aws-api-client/lib/CryptoJS/rollups/sha256.js delete mode 100755 website/src/lib/aws-api-client/lib/apiGatewayCore/apiGatewayClient.js delete mode 100755 website/src/lib/aws-api-client/lib/apiGatewayCore/sigV4Client.js delete mode 100755 website/src/lib/aws-api-client/lib/apiGatewayCore/simpleHttpClient.js delete mode 100755 website/src/lib/aws-api-client/lib/apiGatewayCore/utils.js delete mode 100755 website/src/lib/aws-api-client/lib/axios/dist/axios.standalone.js delete mode 100755 website/src/lib/aws-api-client/lib/url-template/url-template.js delete mode 100644 website/src/lib/aws-cognito/amazon-cognito.min.js delete mode 100644 website/src/lib/aws-cognito/amazon-cognito.min.js.map.txt delete mode 100644 website/src/lib/aws-mobile-analytics/aws-sdk-mobile-analytics.min.js create mode 100644 well-architected.md create mode 100644 www/package.json create mode 100644 www/public/index.html create mode 100644 www/src/App.css create mode 100644 www/src/App.js create mode 100644 www/src/ToDo.css create mode 100644 www/src/ToDo.js create mode 100644 www/src/aws.png create mode 100644 www/src/config.default.js create mode 100644 www/src/config.js create mode 100644 www/src/index.css create mode 100644 www/src/index.js create mode 100644 www/src/serviceWorker.js diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..b9fb3e0d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,47 @@ +--- +name: "\U0001F41B Bug Report" +about: Report a bug +labels: bug, needs-triage +--- + + + + + + +### Reproduction Steps + + + + + + +### Error Log + + + + + + +### Environment + + - **SAM CLI Version :** + - **OS :** + - **Language :** + +### Other + + + + + + +--- + +This is :bug: Bug Report \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..75df8e8d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,45 @@ +--- +name: "\U0001F680 Feature Request" +about: Request a new feature +labels: feature-request, needs-triage +--- + + + + + + + +### Use Case + + + + + + + +### Proposed Solution + + + + + + + +### Other + + + + + + + +* [ ] :wave: I may be able to implement this feature request +* [ ] :warning: This feature might incur a breaking change + +--- + +This is a :rocket: Feature Request \ No newline at end of file diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..de50e4d9 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,5 @@ +*Issue #, if available:* + +*Description of changes:* + +By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice. diff --git a/.gitignore b/.gitignore index 3951b606..333e8743 100644 --- a/.gitignore +++ b/.gitignore @@ -1,75 +1,152 @@ -### Eclipse ### -.metadata -bin/ -tmp/ -*.tmp -*.bak -*.swp -*~.nib -local.properties -.settings/ -.loadpath -.recommenders +# Created by https://www.gitignore.io/api/osx,node,linux,windows -# Eclipse Core -.project +### Linux ### +*~ -# External tool builders -.externalToolBuilders/ +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* -# Locally stored "Eclipse launch configurations" -*.launch +# KDE directory preferences +.directory -# PyDev specific (Python IDE for Eclipse) -*.pydevproject +# Linux trash folder which might appear on any partition or disk +.Trash-* -# CDT-specific (C/C++ Development Tooling) -.cproject +# .nfs files are created when an open file is removed but is still being accessed +.nfs* -# JDT-specific (Eclipse Java Development Tools) -.classpath +### Node ### +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* -# Java annotation processor (APT) -.factorypath +# Runtime data +pids +*.pid +*.seed +*.pid.lock -# PDT-specific (PHP Development Tools) -.buildpath +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov -# sbteclipse plugin -.target +# Coverage directory used by tools like istanbul +coverage -# Tern plugin -.tern-project +# nyc test coverage +.nyc_output -# TeXlipse plugin -.texlipse +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt -# STS (Spring Tool Suite) -.springBeans +# Bower dependency directory (https://bower.io/) +bower_components -# Code Recommenders -.recommenders/ +# node-waf configuration +.lock-wscript +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release -### Gradle ### -.gradle -build/ +# Dependency directories +node_modules/ +jspm_packages/ -# Ignore Gradle GUI config -gradle-app.setting +# Typescript v1 declaration files +typings/ -# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) -!gradle-wrapper.jar +# Optional npm cache directory +.npm -# Cache of project -.gradletasknamecache +# Optional eslint cache +.eslintcache -# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 -# gradle/wrapper/gradle-wrapper.properties +# Optional REPL history +.node_repl_history -node_modules/ +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + + +### OSX ### +*.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# SAM package file +packaged.yaml +.aws-sam +samconfig.toml + +# End of https://www.gitignore.io/api/osx,node,linux,windows +# node_modules # +################ +**/node_modules +package-lock.json +packaged.yaml +# OS generated files # +###################### .DS_Store -/dist/ -/website/dist/ +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Front end config and production build +www/build +.idea/ diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..f147e691 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,12 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "chrome", + "request": "launch", + "name": "Launch Chrome", + "url": "http://localhost:8080", + "webRoot": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..21634a08 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,5 @@ +# Code of Conduct + +This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). +For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact +opensource-codeofconduct@amazon.com with any additional questions or comments. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..5b34cff2 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,95 @@ +# Contributing Guidelines + +Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional +documentation, we greatly value feedback and contributions from our community. + +Please read through this document before submitting any issues or pull requests to ensure we have all the necessary +information to effectively respond to your bug report or contribution. + +## Reporting Bugs/Feature Requests + +We welcome you to use the GitHub issue tracker to report bugs or suggest features. + +When filing an issue, please check [existing open](https://github.com/awslabs/aws-solutions-constructs/issues), or [recently closed](https://github.com/awslabs/aws-solutions-constructs/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20), issues to make sure somebody else hasn't already +reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: + +* A reproducible test case or series of steps +* The version of our code being used +* Any modifications you've made relevant to the bug +* Anything unusual about your environment or deployment + +## Contributing via Pull Requests + +### Pull Request Checklist + +* [ ] Title and Description + - __Change type__: title prefixed with **fix**, **feat** and module name in parens, which will appear in changelog + - __Title__: use lower-case and doesn't end with a period + - __Breaking?__: last paragraph: "BREAKING CHANGE: " + - __Issues__: Indicate issues fixed via: "**Fixes #xxx**" or "**Closes #xxx**" + +--- + +### Step 1: Open Issue + +If there isn't one already, open an issue describing what you intend to contribute. It's useful to communicate in +advance, because sometimes, someone is already working in this space, so maybe it's worth collaborating with them +instead of duplicating the efforts. + + +### Step 2: Commit + +Create a commit with the proposed changes: + +* Commit title and message (and PR title and description) must adhere to [conventionalcommits](https://www.conventionalcommits.org). + * The title must begin with `feat(module): title`, `fix(module): title`, `refactor(module): title` or + `chore(module): title`. + * Title should be lowercase. + * No period at the end of the title. + +* Commit message should describe _motivation_. Think about your code reviewers and what information they need in + order to understand what you did. If it's a big commit (hopefully not), try to provide some good entry points so + it will be easier to follow. + +* Commit message should indicate which issues are fixed: `fixes #` or `closes #`. + +* Shout out to collaborators. + +* If not obvious (i.e. from unit tests), describe how you verified that your change works. + +* If this commit includes breaking changes, they must be listed at the end in the following format (notice how multiple breaking changes should be formatted): + +``` +BREAKING CHANGE: Description of what broke and how to achieve this behavior now +* **module-name:** Another breaking change +* **module-name:** Yet another breaking change +``` + +### Step 3: Pull Request + +* Push to a GitHub fork or to a branch (naming convention: `/`) +* Submit a Pull Requests on GitHub. +* Please follow the PR checklist written below. We trust our contributors to self-check, and this helps that process! +* Discuss review comments and iterate until you get at least one “Approve”. When iterating, push new commits to the + same branch. Usually all these are going to be squashed when you merge to master. The commit messages should be hints + for you when you finalize your merge commit message. +* Make sure to update the PR title/description if things change. The PR title/description are going to be used as the + commit title/message and will appear in the CHANGELOG, so maintain them all the way throughout the process. + +### Step 4 + +## Code of Conduct +This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). +For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact +opensource-codeofconduct@amazon.com with any additional questions or comments. + + +## Security issue notifications +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. + + +## Licensing + +See the [LICENSE](https://github.com/awslabs/aws-solutions-constructs/blob/master/LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. + +We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. \ No newline at end of file diff --git a/Gruntfile.js b/Gruntfile.js deleted file mode 100644 index a78d58ed..00000000 --- a/Gruntfile.js +++ /dev/null @@ -1,36 +0,0 @@ -module.exports = function(grunt) { - grunt.initConfig({ - pkg: grunt.file.readJSON('package.json'), - - // Begin grunt-phantomas config - // https://github.com/stefanjudis/grunt-phantomas/blob/master/README.md - phantomas: { - index: { - options: { - indexPath : './phantomas/index/', - // phantomas options passed directly to exec - // https://github.com/macbre/phantomas - options: { - timeout: 30, - 'film-strip': false - }, - url: 'http://serverless-blog.s3-website-us-east-1.amazonaws.com', - buildUi: true, - numberOfRuns: 30 - } - }, - login: { - options: { - indexPath: './phantomas/login/', - options: {}, - url: 'https://n3a93skkc9.execute-api.us-east-1.amazonaws.com/prod/login', - buildUi: true - } - } - } - // End grunt-phantomas config - }); - - grunt.loadNpmTasks('grunt-phantomas'); - grunt.registerTask('default', ['phantomas:index']); -}; \ No newline at end of file diff --git a/LICENSE b/LICENSE index 8f71f43f..19dc35b2 100644 --- a/LICENSE +++ b/LICENSE @@ -1,3 +1,4 @@ + Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ @@ -171,32 +172,4 @@ of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright {yyyy} {name of copyright owner} - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - + of your accepting any such warranty or additional liability. \ No newline at end of file diff --git a/NOTICE.txt b/NOTICE.txt deleted file mode 100644 index 16dbf6ca..00000000 --- a/NOTICE.txt +++ /dev/null @@ -1,4 +0,0 @@ -AWS Lambda Reference Architecture: Web Application -lambda-refarch-webapp -Copyright 2015 Amazon.com, Inc. or its affiliates. -All Rights Reserved. diff --git a/README.md b/README.md index b5b64090..f72aa162 100644 --- a/README.md +++ b/README.md @@ -1,211 +1,226 @@ -# Serverless Reference Architecture: Web Application +# Serverless Reference Architecture: Web Application -## Note: The Vote Application serverless reference architecture was originally in the lambda-refarch-webapp repository and has since moved to the lambda-refarch-voteapp repository. +The Web Application reference architecture is a general-purpose, event-driven, web application back-end that uses [AWS Lambda](https://aws.amazon.com/lambda), [Amazon API Gateway](https://aws.amazon.com/apigateway) for its business logic. It also uses [Amazon DynamoDB](https://aws.amazon.com/dynamodb) as its database and [Amazon Cognito](https://aws.amazon.com/cognito) for user management. All static content is hosted using [AWS Amplify Console](https://aws.amazon.com/amplify/console). -The Serverless Web Application ([diagram](https://s3.amazonaws.com/aws-lambda-serverless-web-refarch/RefArch_BlogApp_Serverless.png)) demonstrates how to use [AWS Lambda](http://aws.amazon.com/lambda/) in conjunction with [Amazon API Gateway](http://aws.amazon.com/api-gateway/), [Amazon DynamoDB](http://aws.amazon.com/dynamodb/), [Amazon S3](http://aws.amazon.com/s3/), and [Amazon Cognito](http://aws.amazon.com/cognito/) to build a serverless web application. +This application implements a simple To Do app, in which a registered user can create, update, view the existing items, and eventually, delete them. -The site is a simple blog application that allows users to log in and create posts and comments. By leveraging these services, you can build cost-efficient web applications that don't require the overhead of managing servers. +## Architectural Diagram -This repository contains sample code for all the Lambda functions that make up the back end of the application, as well as an AWS CloudFormation template for creating the functions, API, DynamoDB tables, Amazon Cognito identity pool, and related resources. +![Reference Architecture - Web Application](img/serverless-refarch-webapp.png) -## Running the example +## Application Components -The entire example system can be deployed in us-east-1 using the provided CloudFormation template and an S3 bucket. +The Web Application is built from 3 different components. -Choose **Launch Stack** to launch the template in the us-east-1 region in your account: +### Front End Application -[![Launch Serverless Web Application into North Virginia with CloudFormation](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/images/cloudformation-launch-stack-button.png)](https://console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/new?stackName=serverless-web-refarch&templateURL=https://s3.amazonaws.com/aws-lambda-serverless-web-refarch/serverless-web-master.template) +The front-end application is all the static content (HTML files, CSS files, JavaScript files and images) that are generated by `create-react-app`. All these objects are hosted on AWS Amplify Console. -After the stack is successfully created, you need to finish the configuration. +When a user connects to the web site, the needed resources are downloaded to their browser and start to run there. When the application needs to communicate with the backend it does so by issuing REST API calls to the backend. -- Follow the instructions to minify the website code and push it to S3. See the [website readme](http://s3.amazonaws.com/aws-lambda-serverless-web-refarch/website/Readme.md) for step-by-step instructions. Follow the **Updating the API Gateway SDK**, **Updating the Amazon Cognito identity ID and AWS region**, **Building**, and **Production Build** sections. -- After you have done this, upload the website to the S3 bucket that you created via the CloudFormation script (that is, the bucket you specified for the `Hosting Bucket` parameter). +### Back End Application (Business Logic) -## Testing the example +The backend application is where the actual business logic is implemented. The code is implemented using Lambda functions fronted by an API Gateway REST API. In our case, we have different Lambda functions, each handling a different aspect of the application: list the to-do items, get details about a specific item, update an item, create a new item, mark an item as complete and delete an existing item. The application saves all items in a DynamoDB table. -After you've successfully uploaded the updated website to S3, go to the URL for the website. You can find this URL listed in the outputs for the CloudFormation stack you ran earlier, listed as **WebsiteURL**. At this point, your website is up and running. Feel free to interact with it, create posts, comments, etc. +### User Registration and Authentication -## Cleaning up the example resources +As the ToDo application contains personal information (the user's ToDo items), access is restricted only to registered and authenticated users. Each user can access only their own items. -To remove all resources created by this example, do the following: +To accomplish this, we are using Cognito User Pools, which allows users to register to the application, authenticate and so on. Only after a user is authenticated, the client will receive a JWT token which it should then use when making the REST API calls. -1. Delete all objects from the `Hosting Bucket` created by the CloudFormation stack. -1. Delete the CloudFormation stack. -1. Delete all CloudWatch log groups for each of the Lambda functions in the stack. -1. Delete the Amazon Cognito identity pool. +## Running the Example -## CloudFormation template resources +Fork this repository to your own GitHub account, as you will need to create a Personal Access Token in Github for the Amplify console, as well as provide your GitHub repository URL in the deployment. -The following sections explain all of the resources created by the CloudFormation template provided with this example. +You can use the provided [AWS SAM template](./template.yml) to launch a stack that shown here on this Serverless reference architecture. Details about the resources created by this template are provided in the *SAM Template Resources* section of this document. -### Website +## Generating your Github Access Token -- **WebsiteBucket** - An S3 bucket for the static assets of the web application. The front-end JavaScript is uploaded to this bucket. +In order for use the Amplify Console, you need to generate a personal access token. +Once created, an access token should be stored in a safe place, as it may not be available to be seen the next time and may need to regenerate a new one again. +In order to setup your access token, go to [New personal access page](https://github.com/settings/tokens/new) in GitHub. -### Lambda functions +Note that you might need to enter your password in order to proceed. -- **LambdaCreationHelperStack** - A sub-stack that creates a custom resource for writing entries to `ConfigTable`. This stack creates a Lambda function and execution role that grants UpdateItem permission on `ConfigTable`. +### Using SAM and the Amplify Console to Build and Deploy the Full Stack Application -- **SaveCommentFunction** - A Lambda function that saves a comment to `DDBCommentTable`. +You can deploy the full stack application using the deployment script: -- **FindCommentsFunction** - A Lambda function that finds the comments in `DDBCommentTable` for a particular post. +```bash +export AWS_DEFAULT_REGION= +export STACK_NAME= +./deploy.sh +``` -- **FindCommentFunction** - A Lambda function that finds a single comment in `DDBCommentTable`. +The script will use the SAM CLI to build your backend functions, and then the guided deployment feature of the SAM CLI for the initial backend deployment. You will be prompted for a set of parameters, and can accept the defaults for all parameters with the exception of the GitHub Repository URL and the GitHub OAuth token. -- **SavePostFunction** - A Lambda function that saves a post to `DDBPostTable`. +### Building the Application Step by Step -- **FindForumsFunction** - A Lambda function that finds all the forums in `DDBForumTable`. +Alternatively, you could run the build steps yourself in the CLI: -- **FindPostsFunction** - A Lambda function that finds all the latest posts for a forum in the `DDBLatestPostTable`. +#### Build the backend functions -- **FindPostFunction** - A Lambda function that finds a single post in `DDBPostTable`. +The AWS SAM CLI comes with abstractions for a number of Lambda runtimes to build your dependencies, and copies the source code into staging folders so that everything is ready to be packaged and deployed. The `sam build` command builds any dependencies that your application has, and copies your application source code to folders under aws-sam/build to be zipped and uploaded to Lambda. -- **SaveUserFunction** - A Lambda function that saves a user to `DDBUserTable`. +```bash +sam build --use-container +``` -- **AuthenticateUserFunction** - A Lambda function that authenticates a user against `DDBPostTable`. +#### Package the backend -### Function roles +Next, run *sam package*. This command takes your Lambda handler source code and any third-party dependencies, zips everything, and uploads the zip file to your Amazon S3 bucket. That bucket and file location are then noted in the packaged.yaml file. You use the generated `packaged.yaml` file to deploy the application in the next step. -- **LambdaToDynamoDBUserTableRole** - An AWS Identity and Access Management (IAM) role assumed by the `SaveUserFunction` and `AuthenticateUserFunction` functions. This role provides logging permissions and access to the `DDBUserTable` and the `DDBConfigTable` tables. It also enables the function to call Amazon Cognito and get an Open ID token for the user. +```bash +sam package \ + --output-template-file packaged.yml \ + --s3-bucket $DEPLOYMENT_BUCKET +``` -- **LambdaToDynamoDBPostTableRole** - An IAM role assumed by the `SavePostFunction`, `FindPostsFunction`, and `FindPostFunction` functions. This role provides logging permissions and access to the `DDBPostTable`, `DDBConfigTable`, and the `DDBLatestPostTable` tables. +#### Deploy the backend -- **LambdaToDynamoDBCommentTableRole** - An IAM role assumed by the `SaveCommentFunction`, `FindCommentsFunction`, and `FindCommentFunction` functions. This role provides logging permissions and access to the `DDBCommentTable` and the `DDBConfigTable` tables. +This command deploys your application to the AWS Cloud. It's important that this command explicitly includes both of the following: -- **LambdaToDynamoDBForumTableRole** - An IAM role assumed by the _____ function. This role provides logging permissions and access to the `DDBForumTable` and the `DDBConfigTable` table. +* The AWS Region to deploy to. This Region must match the Region of the Amazon S3 source bucket. -### API Gateway resources +* The CAPABILITY_IAM parameter, because creating new Lambda functions involves creating new IAM roles. -- **ApiCreationHelperStack** - A sub-stack that creates all the API Gateway resources, methods, and mapping templates. +```bash +sam deploy \ + --template-file packaged.yml \ + --stack-name $STACK_NAME \ + --capabilities CAPABILITY_IAM +``` -- **APIGWToLambda** - An IAM role that gives API Gateway permissions to execute the Lambda functions. +#### Testing locally (Optional) -- **APIGWRESTAPI** - Creates the API. +To run lambda function , API Gateway and dynamodb locally follow the steps -- **APIGWRESTAPIlogin** - The login resource. +To run the dynamodb table locally -- **APIGWRESTAPIloginPOST** - The POST method on the login resource. +```bash +docker run -p 8000:8000 amazon/dynamodb-local +``` -- **APIGWRESTAPIloginOPTIONS** - The OPTIONS method on the login resource. +Create a table in local Dynamodb environment -- **APIGWRESTAPIuser** - The user resource. +```bash +aws dynamodb create-table --table-name TodoTable --attribute-definitions AttributeName=id,AttributeType=S --key-schema AttributeName=id,KeyType=HASH --billing-mode PAY_PER_REQUEST --endpoint-url http://127.0.0.1:8000 +``` -- **APIGWRESTAPIuserPOST** - The POST method on the user resource. +Run the sam local module to test the application locally -- **APIGWRESTAPIuserOPTIONS** - The OPTIONS method on the user resource. - -- **APIGWRESTAPIforums** - The forums resource. - -- **APIGWRESTAPIforumsGET** - The GET method that returns all forums. - -- **APIGWRESTAPIforumsOPTIONS** - The OPTIONS method on the forums resource. - -- **APIGWRESTAPIforum** - The {id} resource representing a forum. - -- **APIGWRESTAPIforumposts** - The {id}/posts resource representing posts within a forum. - -- **APIGWRESTAPIforumpostsGET** - The GET method on the {id}/posts resource - -- **APIGWRESTAPIforumpostsPOST** - The POST method on the {id}/posts resource - -- **APIGWRESTAPIforumpostsOPTIONS** - The OPTIONS method on the {id}/posts resource - -- **APIGWRESTAPIposts** - The post resource. - -- **APIGWRESTAPIpost** - The posts/{id} resource representing a post. +```bash +sam local start-api --env-vars todo-src/test/environment/mac.json +``` -- **APIGWRESTAPIpostGET** - The GET method on the posts/{id} resource. +Sample file of mac os is `todo-src/test/environment/mac.json` -- **APIGWRESTAPIpostOPTIONS** - The OPTIONS method on the posts/{id} resource. +#### Updating the Front-End Application -- **APIGWRESTAPIcomments** - The {id}/comment resource. +Once you deploy the infrastructure using SAM you will need to create a configuration file for your front-end web application. You can view the necessary values by describing your deployed stack: -- **APIGWRESTAPIcommentsGET** - The GET method on the {id}/comment resource. +```bash +aws cloudformation describe-stacks --stack-name $STACK_NAME --query "Stacks[0].Outputs[]" +``` -- **APIGWRESTAPIcommentsPOST** - The POST method on the {id}/comment resource. +Copy the default config file and update the values from the output above: -- **APIGWRESTAPIcommentsOPTIONS** - The OPTIONS method on the {id}/comment resource. +```bash +cp www/src/config.default.js www/src/config.js +``` -- **APIGWRESTAPIcomment** - The {id}/comment/{created-at} resource. +You can run the front end locally for testing by setting the `redirect_url` value to `https://localhost:8080` and running: -- **APIGWRESTAPIcommentGET** - The GET method on the {id}/comment/{created-at} resource. +```bash +cd www/src +npm start +``` -- **APIGWRESTAPIcommentOPTIONS** - The OPTIONS method on the {id}/comment/{created-at} resource. +You can run the front end locally for testing and use the local api by setting the `api_base_url` value to `http://127.0.0.1:8080` -- **APIGWRESTAPIDeployment** - The deployment of the specified stage for the API. +#### Deploy the frontend +Deploy your application by checking in your update to the `config.js` file and pushing that commit to your repo. Amplify Console will automatically deploy the update from there. -### DynamoDB tables +```bash +git add www/src/config.js +git commit -m 'Update frontend config' +git push +``` -- **DDBPostTable** - DynamoDB table that stores the post data. +You can view the deployment process in the Amplify Console web console. -- **DDBCommentTable** - DynamoDB table that stores the comment data. +## Cleaning Up the Example Resources -- **DDBUserTable** - DynamoDB table that stores the user data. +### Delete the CloudFormation Stack -- **DDBForumTable** - DynamoDB table that stores the forum data. +```bash +aws cloudformation delete-stack \ +--stack-name $STACK_NAME +``` -- **DDBLatestPostTable** - DynamoDB table that stores information on the latest posts for a forum. +### Delete the CloudWatch Log Groups -### Amazon Cognito +```bash +for log_group in $(aws logs describe-log-groups --log-group-name-prefix '/aws/lambda/'$STACK_NAME --query "logGroups[*].logGroupName" --output text); do + echo "Removing log group ${log_group}..." + aws logs delete-log-group --log-group-name ${log_group} + echo +done +``` -- **AuthenticatedBlogUserPolicy** - IAM policy containing the list of API endpoints on which authenticated users in Cognito can call. +## SAM Template Resources -- **UnauthenticatedBlogUserPolicy** - IAM policy containing the list of API endpoints on which unauthenticated users in Cognito can call. +### Resources -- **CognitoCreationHelperStack** - A sub-stack that creates the IAM roles for Amazon Cognito and has custom resources for creating the identity pool. +[The provided template](./template.yaml) +creates the following resources: -- **CognitoServerlessBlogUnauthenticatedRole** - IAM role that users who are NOT authenticated assume when interacting with the blog site. This role has the `UnauthenticatedBlogUserPolicy` policy attached. +* **TodoUserPool** - A Cognito UserPool that holds all the application users -- **CognitoServerlessBlogAuthenticatedRole** - IAM role that users who are authenticated assume when interacting with the blog site. This role has the `UnauthenticatedBlogUserPolicy` and ` AuthenticatedBlogUserPolicy` policies attached. +* **TodoUserPoolTokenClient** - A Cognito UserPool Client used by the web application -- **LambdaCognitoExecutionRole** - IAM role that the custom resource Lambda function executes under. +* **TodoDomain** - The Cogito UserPool domain name -- **CreateCognitoPoolResource** - Custom resource that calls the Lambda function `AddCognitoIdentityPool`. +* **TodoTable** - The DynamoDB table used to hold all the ToDo items for all users -- **AddCognitoIdentityPool** - Lambda function for creating the Amazon Cognito identity pool. Also updates LambdaToDynamoDBUserTableRole to add permissions to call the GetOpenIdTokenForDeveloperIdentity function on the Amazon Cognito identity pool just created. +* **TodoApi** - The REST API that is used to expose the ToDo application functionality -- **UpdateCognitoPoolResource** - Custom resource that calls the Lambda function `UpdateCognitoIdentityPool`. +* **GetTodoFunction** - The Lambda function used to retrieve a single ToDo item -- **UpdateCognitoIdentityPool** - Lambda function that updates the Amazon Cognito identity pool with the unauthenticated and authenticated IAM roles, `CognitoServerlessBlogUnauthenticatedRole` and ` CognitoServerlessBlogAuthenticatedRole` respectively. +* **GetAllTodoFunction** - The Lambda function used to retrieve all the ToDo items -### Other resources +* **CompleteTodoFunction** - The Lambda function used to set the state of an item to complete -- **KmsCMK** - The customer master key (CMK) in KMS for encrypting user data in DynamoDB +* **AddTodoFunction** - The Lambda function used to create a new ToDo item -### Configuration +* **UpdateTodoFunction** - The Lambda function used to update the content of a ToDo item -- **DDBConfigTable** - A DynamoDB table to hold configuration values read by the various Lambda functions. The name of this table, "aws-serverless-config", is hard-coded into each function's code and cannot be modified without updating the code as well. +* **DeleteTodoFunction** - The Lambda function used to delete a ToDo item -- **ConfigHelperStack** - A sub-stack that creates a custom resource for writing entries to `ConfigTable`. This stack creates a Lambda function and execution role that grants UpdateItem permission on `ConfigTable`. +* **ApiGatewayPushToCloudWatchRole** - An IAM role that allows API Gateway to send log events to CloudWatch Logs -- **DDBPostTableConfig** - Configures the DynamoDB post table name for the current environment. +* **ApiAccessLogGroup** - The CloudWatch Logs Log Group used by API Gateway for its log messages -- **DDBCommentTableConfig** - Configures the DynamoDB comment table name for the current environment. +* **AmplifyApp** - Amplify Console application that will manage deployment of frontend updates based on pushes to GitHub -- **DDBUserTableConfig** - Configures the DynamoDB user table name for the current environment. +* **AmplifyBranch** - Connecting a GitHub branch to the Amplify Console application -- **DDBForumTableConfig** - Configures the DynamoDB forum table name for the current environment. +* **AmplifyRole** - An IAM role that allows the Amplify Console to perform build and deployment actions -- **DDBLatestPostTableConfig** - Configures the DynamoDB latest post table name for the current environment. +### Notes -- **CognitoPoolIdConfig** - Configures the Amazon Cognito identity pool ID for the current environment. +By default, the default Node.js HTTP/HTTPS agent creates a new TCP connection for every new request. To avoid the cost of establishing a new connection, you can reuse an existing connection. -- **CognitoPoolDeveloperIdConfig** - Configures the Developer Provider Name for the Amazon Cognito identity pool for the current environment. +For short-lived operations, such as DynamoDB queries, the latency overhead of setting up a TCP connection might be greater than the operation itself. Additionally, since DynamoDB [encryption at rest](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/encryption.howitworks.html) is integrated with [AWS KMS](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/encryption.howitworks.html), you may experience latencies from the database having to re-establish new AWS KMS cache entries for each operation. -- **KMSIdConfig** - Configures the key ID for the KMS CMK used for encrypting data in DynamoDB. +The easiest way to configure SDK for JavaScript to reuse TCP connections is to set the `AWS_NODEJS_CONNECTION_REUSE_ENABLED` environment variable to 1. This feature was added in the [2.463.0](https://github.com/aws/aws-sdk-js/blob/master/CHANGELOG.md#24630) release. -- **PopulateForumsTable** - Custom CloudFormation resource which calls `PopulateForumsTableResource` +Read more about this feature on our [Developer Guide](https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/node-reusing-connections.html). -- **PopulateForumsTableResource** - Lambda function which populates the DynamoDB forum table with generic values +## Well-Architected Review -### Outputs - -- **API_endpoint** - This is the endpoint URL for your API Gateway deployed by the CloudFormation stack. - -- **WebsiteURL** - Once the website code is uploaded, this is the location of the website running on S3. - -- **CognitoIdentityPoolId** - This is the ID for the Cognito Identity Pool. +We have conducted a [Well-Architected](https://aws.amazon.com/architecture/well-architected/) review for this application using the [Serverless Application Lens](https://d1.awsstatic.com/whitepapers/architecture/AWS-Serverless-Applications-Lens.pdf). The results of this review can be found [here](well-architected.md). ## License diff --git a/cloudformation/api-creation-helper.template b/cloudformation/api-creation-helper.template deleted file mode 100644 index 6ee1ccf5..00000000 --- a/cloudformation/api-creation-helper.template +++ /dev/null @@ -1,1083 +0,0 @@ -{ - "AWSTemplateFormatVersion": "2010-09-09", - "Parameters": { - - "APIStageName": { - "Description": "", - "Type": "String" - }, - "AuthenticateUserFunction": { - "Description": "", - "Type": "String" - }, - "SaveUserFunction": { - "Description": "", - "Type": "String" - }, - "SavePostFunction": { - "Description": "", - "Type": "String" - }, - "SaveCommentFunction": { - "Description": "", - "Type": "String" - }, - "FindForumsFunction":{ - "Description":"", - "Type": "String" - }, - "FindPostFunction": { - "Description": "", - "Type": "String" - }, - "FindPostsFunction": { - "Description": "", - "Type": "String" - }, - "FindCommentsFunction": { - "Description": "", - "Type": "String" - }, - "FindCommentFunction": { - "Description": "", - "Type": "String" - } - }, - - "Resources": - { - "APIGWToLambda": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Version": "2012-10-17", - "Statement": [{ - "Effect": "Allow", - "Principal": { - "Service": [ - "apigateway.amazonaws.com" - ] - }, - "Action": [ - "sts:AssumeRole" - ] - }] - }, - "Path": "/", - "Policies": [{ - "PolicyName": "ServerlessBlogAPI-CallLambdaFunctions", - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [{ - "Sid": "AllowLambda", - "Effect": "Allow", - "Action": [ - "lambda:InvokeFunction" - ], - "Resource": "*" - }] - } - }] - } - }, - - - "APIGWRESTAPI": { - "Type": "AWS::ApiGateway::RestApi", - "Properties": { - "Description": "API for serverless blog", - "Name": "serverless-blog-api" - } - }, - - - "APIGWRESTAPIlogin": { - "Type" : "AWS::ApiGateway::Resource", - "Properties" : { - "ParentId": { "Fn::GetAtt": ["APIGWRESTAPI", "RootResourceId"] }, - "PathPart" : "login", - "RestApiId": { "Ref": "APIGWRESTAPI" } - } - }, - - "APIGWRESTAPIloginPOST": { - "Type": "AWS::ApiGateway::Method", - "Properties": { - "RestApiId": { "Ref": "APIGWRESTAPI" }, - "ResourceId": { "Ref": "APIGWRESTAPIlogin" }, - "HttpMethod": "POST", - "AuthorizationType": "AWS_IAM", - "MethodResponses" : [{ - "StatusCode" : "200", - "ResponseParameters" : { - "method.response.header.Access-Control-Allow-Origin": "true" - } - }], - "Integration":{ - "Type" : "AWS", - "IntegrationHttpMethod" : "POST", - "Credentials" : - { - "Fn::GetAtt": ["APIGWToLambda","Arn"] - }, - "RequestTemplates" : { - "application/json" : { - "Fn::Join": ["\n", [ - "{", - " \"email\": \"$input.path('$.email')\",", - " \"password\": \"$input.path('$.password')\",", - " \"config\": { \"stageName\" : \"$context.stage\"}", - "}" - ]] - } - }, - "IntegrationResponses" : [ - { - "SelectionPattern" : "", - "StatusCode" : "200", - "ResponseParameters" : - { - "method.response.header.Access-Control-Allow-Origin": "'*'" - }, - "ResponseTemplates" : - { - "application/json" : "" - } - }], - "Uri" : - { - "Fn::Join": [ - "", - [ - "arn:aws:apigateway:", - {"Ref": "AWS::Region"}, - ":lambda:path/2015-03-31/functions/", - {"Ref": "AuthenticateUserFunction"}, - "/invocations" - ] - ]} - } - } - }, - - "APIGWRESTAPIloginOPTIONS": { - "Type": "AWS::ApiGateway::Method", - "Properties": { - "RestApiId": { "Ref": "APIGWRESTAPI" }, - "ResourceId": { "Ref": "APIGWRESTAPIlogin" }, - "HttpMethod": "OPTIONS", - "AuthorizationType": "NONE", - "MethodResponses" : [{ - "ResponseModels" : { "application/json" : "Empty" }, - "ResponseParameters" : { - "method.response.header.Access-Control-Allow-Methods": "true", - "method.response.header.Access-Control-Allow-Headers": "true", - "method.response.header.Access-Control-Allow-Origin": "true" - }, - "StatusCode" : "200" - }], - "Integration" : - { - "Type" : "MOCK", - "RequestTemplates" : { - "application/json" : "{\"statusCode\": 200}" - }, - "IntegrationResponses" : [ - { - "SelectionPattern" : "", - "StatusCode" : "200", - "ResponseParameters" : { - "method.response.header.Access-Control-Allow-Methods": "'POST,OPTIONS'", - "method.response.header.Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'", - "method.response.header.Access-Control-Allow-Origin": "'*'" - } - } - ] - } - } - }, - - "APIGWRESTAPIuser": { - "Type" : "AWS::ApiGateway::Resource", - "Properties" : { - "ParentId": { "Fn::GetAtt": ["APIGWRESTAPI", "RootResourceId"] }, - "PathPart" : "users", - "RestApiId": { "Ref": "APIGWRESTAPI" } - } - }, - - "APIGWRESTAPIuserPOST": { - "Type": "AWS::ApiGateway::Method", - "Properties": { - "RestApiId": { "Ref": "APIGWRESTAPI" }, - "ResourceId": { "Ref": "APIGWRESTAPIuser" }, - "HttpMethod": "POST", - "AuthorizationType": "AWS_IAM", - "MethodResponses" : [{ - "StatusCode" : "200", - "ResponseParameters" : { - "method.response.header.Access-Control-Allow-Origin": "true" - } - }], - "Integration":{ - "Type" : "AWS", - "IntegrationHttpMethod" : "POST", - "Credentials" : - { - "Fn::GetAtt": ["APIGWToLambda","Arn"] - }, - "RequestTemplates" : { - - "application/json" : { - "Fn::Join": ["\n", [ - "{", - " \"email\": \"$input.path('$.email')\",", - " \"password\": \"$input.path('$.password')\",", - " \"config\": { \"stageName\" : \"$context.stage\"}", - "}" - ]] - } - }, - "IntegrationResponses" : [ - { - "SelectionPattern" : "", - "StatusCode" : "200", - "ResponseParameters" : - { - "method.response.header.Access-Control-Allow-Origin": "'*'" - }, - "ResponseTemplates" : - { - "application/json" : "" - } - }], - "Uri" : - { - "Fn::Join": [ - "", - [ - "arn:aws:apigateway:", - {"Ref": "AWS::Region"}, - ":lambda:path/2015-03-31/functions/", - {"Ref": "SaveUserFunction"}, - "/invocations" - ] - ]} - } - } - }, - - "APIGWRESTAPIuserOPTIONS": { - "Type": "AWS::ApiGateway::Method", - "Properties": { - "RestApiId": { "Ref": "APIGWRESTAPI" }, - "ResourceId": { "Ref": "APIGWRESTAPIuser" }, - "HttpMethod": "OPTIONS", - "AuthorizationType": "NONE", - "MethodResponses" : [{ - "ResponseModels" : { "application/json" : "Empty" }, - "ResponseParameters" : { - "method.response.header.Access-Control-Allow-Methods": "true", - "method.response.header.Access-Control-Allow-Headers": "true", - "method.response.header.Access-Control-Allow-Origin": "true" - }, - "StatusCode" : "200" - }], - "Integration" : - { - "Type" : "MOCK", - "RequestTemplates" : { - "application/json" : "{\"statusCode\": 200}" - }, - "IntegrationResponses" : [ - { - "SelectionPattern" : "", - "StatusCode" : "200", - "ResponseParameters" : { - "method.response.header.Access-Control-Allow-Methods": "'POST,OPTIONS'", - "method.response.header.Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'", - "method.response.header.Access-Control-Allow-Origin": "'*'" - } - } - ] - } - } - }, - - "APIGWRESTAPIforums": { - "Type" : "AWS::ApiGateway::Resource", - "Properties" : { - "ParentId": { "Fn::GetAtt": ["APIGWRESTAPI", "RootResourceId"] }, - "PathPart" : "forums", - "RestApiId": { "Ref": "APIGWRESTAPI" } - } - }, - - "APIGWRESTAPIforumsGET": { - "Type": "AWS::ApiGateway::Method", - "Properties": { - "RestApiId": { "Ref": "APIGWRESTAPI" }, - "ResourceId": { "Ref": "APIGWRESTAPIforums" }, - "HttpMethod": "GET", - "AuthorizationType": "AWS_IAM", - "MethodResponses" : [{ - "StatusCode" : "200", - "ResponseParameters" : { - "method.response.header.Access-Control-Allow-Origin": "true" - } - }], - "Integration":{ - "Type" : "AWS", - "IntegrationHttpMethod" : "POST", - "Credentials" : - { - "Fn::GetAtt": ["APIGWToLambda","Arn"] - }, - "RequestTemplates" : { - - "application/json" : { - - "Fn::Join": ["", [ - - "{\n", - " \"config\": { \"stageName\" : \"$context.stage\"}\n", - "}" - ]] - } - }, - "IntegrationResponses" : [ - { - "SelectionPattern" : "", - "StatusCode" : "200", - "ResponseParameters" : - { - "method.response.header.Access-Control-Allow-Origin": "'*'" - }, - "ResponseTemplates" : - { - "application/json" : "" - } - }], - "Uri" : - { - "Fn::Join": [ - "", - [ - "arn:aws:apigateway:", - {"Ref" : "AWS::Region" }, - ":lambda:path/2015-03-31/functions/", - {"Ref": "FindForumsFunction"}, - "/invocations" - ] - ]} - } - } - }, - - "APIGWRESTAPIforumsOPTIONS": { - "Type": "AWS::ApiGateway::Method", - "Properties": { - "RestApiId": { "Ref": "APIGWRESTAPI" }, - "ResourceId": { "Ref": "APIGWRESTAPIforums" }, - "HttpMethod": "OPTIONS", - "AuthorizationType": "NONE", - "MethodResponses" : [{ - "ResponseModels" : { "application/json" : "Empty" }, - "ResponseParameters" : { - "method.response.header.Access-Control-Allow-Methods": "true", - "method.response.header.Access-Control-Allow-Headers": "true", - "method.response.header.Access-Control-Allow-Origin": "true" - }, - "StatusCode" : "200" - }], - "RequestParameters": { - "method.request.path.id": true - }, - "Integration" : - { - "Type" : "MOCK", - "RequestTemplates" : { - "application/json" : "{\"statusCode\": 200}" - }, - "IntegrationResponses" : [ - { - "SelectionPattern" : "", - "StatusCode" : "200", - "ResponseParameters" : { - "method.response.header.Access-Control-Allow-Methods": "'GET,OPTIONS'", - "method.response.header.Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'", - "method.response.header.Access-Control-Allow-Origin": "'*'" - } - } - ] - } - } - }, - - "APIGWRESTAPIforum": { - "Type" : "AWS::ApiGateway::Resource", - "Properties" : { - "ParentId": { "Ref": "APIGWRESTAPIforums" }, - "PathPart" : "{id}", - "RestApiId": { "Ref": "APIGWRESTAPI" } - } - }, - - "APIGWRESTAPIforumposts": { - "Type" : "AWS::ApiGateway::Resource", - "Properties" : { - "ParentId": { "Ref": "APIGWRESTAPIforum" }, - "PathPart" : "posts", - "RestApiId": { "Ref": "APIGWRESTAPI" } - } - }, - - "APIGWRESTAPIforumpostsGET": { - "Type": "AWS::ApiGateway::Method", - "Properties": { - "RestApiId": { "Ref": "APIGWRESTAPI" }, - "ResourceId": { "Ref": "APIGWRESTAPIforumposts" }, - "HttpMethod": "GET", - "AuthorizationType": "AWS_IAM", - "MethodResponses" : [{ - "StatusCode" : "200", - "ResponseParameters" : { - "method.response.header.Access-Control-Allow-Origin": "true" - } - }], - "RequestParameters": { - "method.request.path.id": true - }, - "Integration":{ - "Type" : "AWS", - "IntegrationHttpMethod" : "POST", - "Credentials" : - { - "Fn::GetAtt": ["APIGWToLambda","Arn"] - }, - "RequestTemplates" : { - - "application/json" : { - - "Fn::Join": ["", [ - - "{\n", - " \"forumId\" : \"$input.params('id')\",\n", - " \"config\": { \"stageName\" : \"$context.stage\"}\n", - "}" - ]] - } - }, - "IntegrationResponses" : [ - { - "SelectionPattern" : "", - "StatusCode" : "200", - "ResponseParameters" : - { - "method.response.header.Access-Control-Allow-Origin": "'*'" - }, - "ResponseTemplates" : - { - "application/json" : "" - } - }], - "Uri" : - { - "Fn::Join": [ - "", - [ - "arn:aws:apigateway:", - {"Ref" : "AWS::Region" }, - ":lambda:path/2015-03-31/functions/", - {"Ref": "FindPostsFunction"}, - "/invocations" - ] - ]} - } - } - }, - - "APIGWRESTAPIforumpostsPOST": { - "Type": "AWS::ApiGateway::Method", - "Properties": { - "RestApiId": { "Ref": "APIGWRESTAPI" }, - "ResourceId": { "Ref": "APIGWRESTAPIforumposts" }, - "HttpMethod": "POST", - "AuthorizationType": "AWS_IAM", - "MethodResponses" : [{ - "StatusCode" : "200", - "ResponseParameters" : { - "method.response.header.Access-Control-Allow-Origin": "true" - } - }], - "RequestParameters": { - "method.request.path.id": true - }, - "Integration":{ - "Type" : "AWS", - "IntegrationHttpMethod" : "POST", - "Credentials" : - { - "Fn::GetAtt": ["APIGWToLambda","Arn"] - }, - "RequestTemplates" : { - - "application/json" : { - - "Fn::Join": ["", [ - - "{\n", - " \"forumId\": \"$input.params('id')\",\n", - " \"email\": \"$input.path('$.email')\",\n", - " \"title\": \"$input.path('$.title')\",\n", - " \"body\": \"$input.path('$.body')\",\n", - " \"config\": { \"stageName\" : \"$context.stage\"}\n", - "}" - ]] - } - }, - "IntegrationResponses" : [ - { - "SelectionPattern" : "", - "StatusCode" : "200", - "ResponseParameters" : - { - "method.response.header.Access-Control-Allow-Origin": "'*'" - }, - "ResponseTemplates" : - { - "application/json" : "" - } - }], - "Uri" : - { - "Fn::Join": [ - "", - [ - "arn:aws:apigateway:", - {"Ref": "AWS::Region"}, - ":lambda:path/2015-03-31/functions/", - {"Ref": "SavePostFunction"}, - "/invocations" - ] - ]} - } - } - }, - - "APIGWRESTAPIforumpostsOPTIONS": { - "Type": "AWS::ApiGateway::Method", - "Properties": { - "RestApiId": { "Ref": "APIGWRESTAPI" }, - "ResourceId": { "Ref": "APIGWRESTAPIforumposts" }, - "HttpMethod": "OPTIONS", - "AuthorizationType": "NONE", - "MethodResponses" : [{ - "ResponseModels" : { "application/json" : "Empty" }, - "ResponseParameters" : { - "method.response.header.Access-Control-Allow-Methods": "true", - "method.response.header.Access-Control-Allow-Headers": "true", - "method.response.header.Access-Control-Allow-Origin": "true" - }, - "StatusCode" : "200" - }], - "RequestParameters": { - "method.request.path.id": true - }, - "Integration" : - { - "Type" : "MOCK", - "RequestTemplates" : { - "application/json" : "{\"statusCode\": 200}" - }, - "IntegrationResponses" : [ - { - "SelectionPattern" : "", - "StatusCode" : "200", - "ResponseParameters" : { - "method.response.header.Access-Control-Allow-Methods": "'POST,GET,OPTIONS'", - "method.response.header.Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'", - "method.response.header.Access-Control-Allow-Origin": "'*'" - } - } - ] - } - } - }, - - "APIGWRESTAPIposts": { - "Type" : "AWS::ApiGateway::Resource", - "Properties" : { - "ParentId": { "Fn::GetAtt": ["APIGWRESTAPI", "RootResourceId"] }, - "PathPart" : "posts", - "RestApiId": { "Ref": "APIGWRESTAPI" } - } - }, - - "APIGWRESTAPIpost": { - "Type" : "AWS::ApiGateway::Resource", - "Properties" : { - "ParentId": { "Ref": "APIGWRESTAPIposts" }, - "PathPart" : "{id}", - "RestApiId": { "Ref": "APIGWRESTAPI" } - } - }, - - "APIGWRESTAPIpostGET": { - "Type": "AWS::ApiGateway::Method", - "Properties": { - "RestApiId": { "Ref": "APIGWRESTAPI" }, - "ResourceId": { "Ref": "APIGWRESTAPIpost" }, - "HttpMethod": "GET", - "AuthorizationType": "AWS_IAM", - "MethodResponses" : [{ - "StatusCode" : "200", - "ResponseParameters" : { - "method.response.header.Access-Control-Allow-Origin": "true" - } - }], - "RequestParameters": { - "method.request.path.id": true - }, - "Integration":{ - "Type" : "AWS", - "IntegrationHttpMethod" : "POST", - "Credentials" : - { - "Fn::GetAtt": ["APIGWToLambda","Arn"] - }, - "RequestTemplates" : { - - "application/json" : { - - "Fn::Join": ["", [ - - "{\n", - " \"id\" : \"$input.params('id')\",\n", - " \"config\": { \"stageName\" : \"$context.stage\"}\n", - "}" - ]] - } - }, - "IntegrationResponses" : [ - { - "SelectionPattern" : "", - "StatusCode" : "200", - "ResponseParameters" : - { - "method.response.header.Access-Control-Allow-Origin": "'*'" - }, - "ResponseTemplates" : - { - "application/json" : "" - } - }], - "Uri" : - { - "Fn::Join": [ - "", - [ - "arn:aws:apigateway:", - {"Ref" : "AWS::Region" }, - ":lambda:path/2015-03-31/functions/", - {"Ref": "FindPostFunction"}, - "/invocations" - ] - ]} - } - } - }, - - - "APIGWRESTAPIpostOPTIONS": { - "Type": "AWS::ApiGateway::Method", - "Properties": { - "RestApiId": { "Ref": "APIGWRESTAPI" }, - "ResourceId": { "Ref": "APIGWRESTAPIpost" }, - "HttpMethod": "OPTIONS", - "AuthorizationType": "NONE", - "MethodResponses" : [{ - "ResponseModels" : { "application/json" : "Empty" }, - "ResponseParameters" : { - "method.response.header.Access-Control-Allow-Methods": "true", - "method.response.header.Access-Control-Allow-Headers": "true", - "method.response.header.Access-Control-Allow-Origin": "true" - }, - "StatusCode" : "200" - }], - "RequestParameters": { - "method.request.path.id": true - }, - "Integration" : - { - "Type" : "MOCK", - "RequestTemplates" : { - "application/json" : "{\"statusCode\": 200}" - }, - "IntegrationResponses" : [ - { - "SelectionPattern" : "", - "StatusCode" : "200", - "ResponseParameters" : { - "method.response.header.Access-Control-Allow-Methods": "'GET,OPTIONS'", - "method.response.header.Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'", - "method.response.header.Access-Control-Allow-Origin": "'*'" - } - } - ] - } - } - }, - - "APIGWRESTAPIcomments": { - "Type" : "AWS::ApiGateway::Resource", - "Properties" : { - "ParentId": { "Ref": "APIGWRESTAPIpost" }, - "PathPart" : "comments", - "RestApiId": { "Ref": "APIGWRESTAPI" } - } - }, - - "APIGWRESTAPIcommentsPOST": { - "Type": "AWS::ApiGateway::Method", - "Properties": { - "RestApiId": { "Ref": "APIGWRESTAPI" }, - "ResourceId": { "Ref": "APIGWRESTAPIcomments" }, - "HttpMethod": "POST", - "AuthorizationType": "AWS_IAM", - "MethodResponses" : [{ - "StatusCode" : "200", - "ResponseParameters" : { - "method.response.header.Access-Control-Allow-Origin": "true" - } - }], - "RequestParameters": { - "method.request.path.id": true - }, - "Integration":{ - "Type" : "AWS", - "IntegrationHttpMethod" : "POST", - "Credentials" : - { - "Fn::GetAtt": ["APIGWToLambda","Arn"] - }, - "RequestTemplates" : { - - "application/json" : { - - "Fn::Join": ["", [ - - "{\n", - " \"postId\" : \"$input.params('id')\",\n", - " \"message\" : $input.json('$.message'),\n", - " \"email\" : $input.json('$.email'),\n", - " \"config\": { \"stageName\" : \"$context.stage\"}\n", - "}" - ]] - } - }, - "IntegrationResponses" : [ - { - "SelectionPattern" : "", - "StatusCode" : "200", - "ResponseParameters" : - { - "method.response.header.Access-Control-Allow-Origin": "'*'" - }, - "ResponseTemplates" : - { - "application/json" : "" - } - }], - "Uri" : - { - "Fn::Join": [ - "", - [ - "arn:aws:apigateway:", - {"Ref": "AWS::Region"}, - ":lambda:path/2015-03-31/functions/", - {"Ref": "SaveCommentFunction"}, - "/invocations" - ] - ]} - } - } - }, - - - "APIGWRESTAPIcommentsGET": { - "Type": "AWS::ApiGateway::Method", - "Properties": { - "RestApiId": { "Ref": "APIGWRESTAPI" }, - "ResourceId": { "Ref": "APIGWRESTAPIcomments" }, - "HttpMethod": "GET", - "AuthorizationType": "AWS_IAM", - "MethodResponses" : [{ - "StatusCode" : "200", - "ResponseParameters" : { - "method.response.header.Access-Control-Allow-Origin": "true" - } - }], - "RequestParameters": { - "method.request.path.id": true - }, - "Integration":{ - "Type" : "AWS", - "IntegrationHttpMethod" : "POST", - "Credentials" : - { - "Fn::GetAtt": ["APIGWToLambda","Arn"] - }, - "RequestTemplates" : { - - "application/json" : { - - "Fn::Join": ["", [ - - "{\n", - " \"postId\": \"$input.params('id')\",\n", - " \"config\": { \"stageName\" : \"$context.stage\"}\n", - "}" - ]] - } - }, - "IntegrationResponses" : [ - { - "SelectionPattern" : "", - "StatusCode" : "200", - "ResponseParameters" : - { - "method.response.header.Access-Control-Allow-Origin": "'*'" - }, - "ResponseTemplates" : - { - "application/json" : "" - } - }], - "Uri" : - { - "Fn::Join": [ - "", - [ - "arn:aws:apigateway:", - {"Ref": "AWS::Region"}, - ":lambda:path/2015-03-31/functions/", - {"Ref": "FindCommentsFunction"}, - "/invocations" - ] - ]} - } - } - }, - - "APIGWRESTAPIcommentsOPTIONS": { - "Type": "AWS::ApiGateway::Method", - "Properties": { - "RestApiId": { "Ref": "APIGWRESTAPI" }, - "ResourceId": { "Ref": "APIGWRESTAPIcomments" }, - "HttpMethod": "OPTIONS", - "AuthorizationType": "NONE", - "MethodResponses" : [{ - "ResponseModels" : { "application/json" : "Empty" }, - "ResponseParameters" : { - "method.response.header.Access-Control-Allow-Methods": "true", - "method.response.header.Access-Control-Allow-Headers": "true", - "method.response.header.Access-Control-Allow-Origin": "true" - }, - "StatusCode" : "200" - }], - "RequestParameters": { - "method.request.path.id": true - }, - "Integration" : - { - "Type" : "MOCK", - "RequestTemplates" : { - "application/json" : "{\"statusCode\": 200}" - }, - "IntegrationResponses" : [ - { - "SelectionPattern" : "", - "StatusCode" : "200", - "ResponseParameters" : { - "method.response.header.Access-Control-Allow-Methods": "'POST,GET,OPTIONS'", - "method.response.header.Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'", - "method.response.header.Access-Control-Allow-Origin": "'*'" - } - } - ] - } - } - }, - - "APIGWRESTAPIcomment": { - "Type" : "AWS::ApiGateway::Resource", - "Properties" : { - "ParentId": { "Ref": "APIGWRESTAPIcomments" }, - "PathPart" : "{created-at}", - "RestApiId": { "Ref": "APIGWRESTAPI" } - } - }, - - "APIGWRESTAPIcommentGET": { - "Type": "AWS::ApiGateway::Method", - "Properties": { - "RestApiId": { "Ref": "APIGWRESTAPI" }, - "ResourceId": { "Ref": "APIGWRESTAPIcomment" }, - "HttpMethod": "GET", - "AuthorizationType": "AWS_IAM", - "MethodResponses" : [{ - "StatusCode" : "200", - "ResponseParameters" : { - "method.response.header.Access-Control-Allow-Origin": "true" - } - }], - "RequestParameters": { - "method.request.path.id": true, - "method.request.path.created-at": true - }, - "Integration":{ - "Type" : "AWS", - "IntegrationHttpMethod" : "POST", - "Credentials" : - { - "Fn::GetAtt": ["APIGWToLambda","Arn"] - }, - "RequestTemplates" : { - - "application/json" : { - - "Fn::Join": ["", [ - - "{\n", - " \"postId\" : \"$input.params('id')\",\n", - " \"date\" : \"$input.params('created-at')\",\n", - " \"config\": { \"stageName\" : \"$context.stage\"}\n", - "}" - ]] - } - }, - "IntegrationResponses" : [ - { - "SelectionPattern" : "", - "StatusCode" : "200", - "ResponseParameters" : - { - "method.response.header.Access-Control-Allow-Origin": "'*'" - }, - "ResponseTemplates" : - { - "application/json" : "" - } - }], - "Uri" : - { - "Fn::Join": [ - "", - [ - "arn:aws:apigateway:", - {"Ref": "AWS::Region"}, - ":lambda:path/2015-03-31/functions/", - {"Ref": "FindCommentFunction"}, - "/invocations" - ] - ]} - } - } - }, - - "APIGWRESTAPIcommentOPTIONS": { - "Type": "AWS::ApiGateway::Method", - "Properties": { - "RestApiId": { "Ref": "APIGWRESTAPI" }, - "ResourceId": { "Ref": "APIGWRESTAPIcomment" }, - "HttpMethod": "OPTIONS", - "AuthorizationType": "NONE", - "MethodResponses" : [{ - "ResponseModels" : { "application/json" : "Empty" }, - "ResponseParameters" : { - "method.response.header.Access-Control-Allow-Methods": "true", - "method.response.header.Access-Control-Allow-Headers": "true", - "method.response.header.Access-Control-Allow-Origin": "true" - }, - "StatusCode" : "200" - }], - "RequestParameters": { - "method.request.path.id": true, - "method.request.path.created-at": true - }, - "Integration" : - { - "Type" : "MOCK", - "RequestTemplates" : { - "application/json" : "{\"statusCode\": 200}" - }, - "IntegrationResponses" : [ - { - "SelectionPattern" : "", - "StatusCode" : "200", - "ResponseParameters" : { - "method.response.header.Access-Control-Allow-Methods": "'GET,OPTIONS'", - "method.response.header.Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'", - "method.response.header.Access-Control-Allow-Origin": "'*'" - } - } - ] - } - } - }, - - "APIGWRESTAPIDeployment": - { - "Type": "AWS::ApiGateway::Deployment", - "DependsOn": [ - "APIGWRESTAPI", - "APIGWRESTAPIlogin", - "APIGWRESTAPIloginPOST", - "APIGWRESTAPIloginOPTIONS", - "APIGWRESTAPIuser", - "APIGWRESTAPIuserPOST", - "APIGWRESTAPIuserOPTIONS", - "APIGWRESTAPIforums", - "APIGWRESTAPIforumsGET", - "APIGWRESTAPIforumsOPTIONS", - "APIGWRESTAPIforum", - "APIGWRESTAPIforumposts", - "APIGWRESTAPIforumpostsGET", - "APIGWRESTAPIforumpostsPOST", - "APIGWRESTAPIforumpostsOPTIONS", - "APIGWRESTAPIposts", - "APIGWRESTAPIpost", - "APIGWRESTAPIpostGET", - "APIGWRESTAPIpostOPTIONS", - "APIGWRESTAPIcomments", - "APIGWRESTAPIcommentsGET", - "APIGWRESTAPIcommentsPOST", - "APIGWRESTAPIcommentsOPTIONS", - "APIGWRESTAPIcomment", - "APIGWRESTAPIcommentGET", - "APIGWRESTAPIcommentOPTIONS" - ], - "Properties": { - "RestApiId": { "Ref": "APIGWRESTAPI" }, - "Description": "Initial deployment", - "StageName": { "Ref": "APIStageName"}, - "StageDescription": - { - "StageName": {"Ref": "APIStageName"}, - "Description": "Prod Stage" - } - } - } - }, - - "Outputs": { - - "APIID": { - "Value": - {"Ref": "APIGWRESTAPI"}, - "Description": "Prod stage deployment URL" - } - } -} diff --git a/cloudformation/cognito-creation-helper.template b/cloudformation/cognito-creation-helper.template deleted file mode 100644 index 13e2fb54..00000000 --- a/cloudformation/cognito-creation-helper.template +++ /dev/null @@ -1,352 +0,0 @@ -{ - "AWSTemplateFormatVersion": "2010-09-09", - "Parameters": { - - "CognitoPoolDevID": { - "Description": "developer provider name for cognito identity pool", - "Type": "String" - }, - "UnauthenticatedBlogUserPolicy": { - "Description": "IAM policy for unauth user", - "Type": "String" - }, - "AuthenticatedBlogUserPolicy": { - "Description": "IAM policy for Authenticated user", - "Type": "String" - }, - "LambdaUserRole": { - "Type": "String" - } - }, - - "Resources": - { - - "CognitoServerlessBlogUnauthenticatedRole": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Principal": { - "Federated": "cognito-identity.amazonaws.com" - }, - "Action": "sts:AssumeRoleWithWebIdentity", - "Condition": { - "StringEquals": { - "cognito-identity.amazonaws.com:aud" : { "Fn::Join" : [ "", [ - "", - { "Fn::GetAtt": [ "CreateCognitoPoolResource", "identityPoolId" ] } - ]]} - }, - "ForAnyValue:StringLike": { - "cognito-identity.amazonaws.com:amr": "unauthenticated" - } - } - } - ] - }, - "ManagedPolicyArns": [ {"Ref": "UnauthenticatedBlogUserPolicy"}], - "Path": "/", - "Policies": [ { - "PolicyName": "root", - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "mobileanalytics:PutEvents", - "cognito-sync:*", - "cognito-identity:*" - ], - "Resource": [ - "*" - ] - } - ] - } - } ] - } - }, - - "CognitoServerlessBlogAuthenticatedRole": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Principal": { - "Federated": "cognito-identity.amazonaws.com" - }, - "Action": "sts:AssumeRoleWithWebIdentity", - "Condition": { - "StringEquals": { - "cognito-identity.amazonaws.com:aud" : { "Fn::Join" : [ "", [ - "", - { "Fn::GetAtt": [ "CreateCognitoPoolResource", "identityPoolId" ] } - ]]} - }, - "ForAnyValue:StringLike": { - "cognito-identity.amazonaws.com:amr": "authenticated" - } - } - } - ] - }, - "ManagedPolicyArns": [ {"Ref": "UnauthenticatedBlogUserPolicy"}, {"Ref": "AuthenticatedBlogUserPolicy"}], - "Path": "/", - "Policies": [ { - "PolicyName": "root", - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "mobileanalytics:PutEvents", - "cognito-sync:*", - "cognito-identity:*" - ], - "Resource": [ - "*" - ] - } - ] - } - } ] - } - }, - - "LambdaCreateCognitoExecutionRole": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Version": "2012-10-17", - "Statement": [{ - "Effect": "Allow", - "Principal": {"Service": ["lambda.amazonaws.com"]}, - "Action": ["sts:AssumeRole"] - }] - }, - "Path": "/", - "Policies": [{ - "PolicyName": "root", - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [{ - "Effect": "Allow", - "Action": ["logs:CreateLogGroup","logs:CreateLogStream","logs:PutLogEvents"], - "Resource": "arn:aws:logs:*:*:*" - }, - { - "Effect": "Allow", - "Action": ["cognito-identity:CreateIdentityPool","cognito-identity:SetIdentityPoolRoles","cognito-identity:UpdateIdentityPool"], - "Resource": "*" - }, - { - "Effect": "Allow", - "Action": ["iam:putRolePolicy"], - "Resource": {"Ref": "LambdaUserRole"} - }] - } - }] - } - }, - - "LambdaUpdateCognitoExecutionRole": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Version": "2012-10-17", - "Statement": [{ - "Effect": "Allow", - "Principal": {"Service": ["lambda.amazonaws.com"]}, - "Action": ["sts:AssumeRole"] - }] - }, - "Path": "/", - "Policies": [{ - "PolicyName": "root", - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [{ - "Effect": "Allow", - "Action": ["logs:CreateLogGroup","logs:CreateLogStream","logs:PutLogEvents"], - "Resource": "arn:aws:logs:*:*:*" - }, - { - "Effect": "Allow", - "Action": ["cognito-identity:CreateIdentityPool","cognito-identity:SetIdentityPoolRoles","cognito-identity:UpdateIdentityPool"], - "Resource": "*" - }, - { - "Effect": "Allow", - "Action": ["iam:putRolePolicy"], - "Resource": {"Ref": "LambdaUserRole"} - }, - { - "Effect": "Allow", - "Action": ["iam:passRole"], - "Resource": [{ "Fn::GetAtt" : ["CognitoServerlessBlogAuthenticatedRole", "Arn"] }, - { "Fn::GetAtt" : ["CognitoServerlessBlogUnauthenticatedRole", "Arn"] }] - }] - } - }] - } - }, - - "CreateCognitoPoolResource": { - "Type": "Custom::CreateCognitoPoolResource", - "Properties": { - "ServiceToken": { "Fn::GetAtt" : ["AddCognitoIdentityPool", "Arn"] }, - "CognitoDevId": { - "Ref": "CognitoPoolDevID" - }, - "StackName": { - "Ref":"AWS::StackName" - }, - "LambdaIAMRole": { - "Ref":"LambdaUserRole" - } - } - }, - - "AddCognitoIdentityPool": { - "DependsOn": "LambdaCreateCognitoExecutionRole", - "Type": "AWS::Lambda::Function", - "Properties": { - "Handler": "index.handler", - "Runtime": "nodejs4.3", - "Role": { "Fn::GetAtt" : ["LambdaCreateCognitoExecutionRole", "Arn"] }, - "Code": { - "ZipFile": { - "Fn::Join": ["\n", [ - "var response = require('cfn-response');", - "var AWS = require('aws-sdk');", - "var cognitoidentity = new AWS.CognitoIdentity();", - "var iam = new AWS.IAM();", - "var responseData = {};", - "exports.handler = function(event, context) {", - " console.log('REQUEST RECEIVED:', JSON.stringify(event));", - " if (event.RequestType == 'Delete') {", - " response.send(event, context, response.SUCCESS);", - " return;", - " }", - " var poolName = event.ResourceProperties.StackName.split('-').join('');", - " var params = {", - " AllowUnauthenticatedIdentities: true,", - " IdentityPoolName: poolName,", - " DeveloperProviderName: event.ResourceProperties.CognitoDevId", - " };", - " cognitoidentity.createIdentityPool(params, function(err, data) {", - " if(err) {", - " console.log(JSON.stringify(err));", - " responseData = {Error: JSON.stringify(err)};", - " response.send(event, context, response.FAILED, responseData);", - " return;", - " } else {", - " console.log(JSON.stringify(data));", - " responseData['identityPoolId'] = data.IdentityPoolId;", - " //update lambda function role to be able to get developer identities from cognito", - " var rolename = event.ResourceProperties.LambdaIAMRole.split('role/')[1];", - " var iamParams = {", - " RoleName: rolename,", - " PolicyName: 'getCognitoDevIdForLambda',", - " PolicyDocument: '{\"Version\":\"2012-10-17\",\"Statement\":{\"Effect\":\"Allow\",\"Action\":\"cognito-identity:GetOpenIdTokenForDeveloperIdentity\",\"Resource\":'+", - {"Fn::Join" : [ "", ["'\"arn:aws:cognito-identity:", {"Ref" : "AWS::Region"} , ":", {"Ref" : "AWS::AccountId"} ,":identitypool/' + data.IdentityPoolId + '\"' + "]]}, - " '}}'", - " };", - " console.log(iamParams);", - " iam.putRolePolicy(iamParams, function(err, data) { ", - " if (err) console.log(err, err.stack); // an error occurred", - " else console.log(data); // successful response", - " });", - " response.send(event, context, response.SUCCESS, responseData);", - " return;", - " }", - " });", - "};" - ]] - } - }, - "Timeout": "30" - } - }, - - "UpdateCognitoPoolResource": { - "Type": "Custom::UpdateCognitoPoolResource", - "Properties": { - "ServiceToken": { "Fn::GetAtt" : ["UpdateCognitoIdentityPool", "Arn"] }, - "CognitoPoolId": { - "Fn::GetAtt": ["CreateCognitoPoolResource", "identityPoolId" ] - }, - "AuthRole": { - "Fn::GetAtt" : ["CognitoServerlessBlogAuthenticatedRole", "Arn"] - }, - "UnAuthRole":{ - "Fn::GetAtt" : ["CognitoServerlessBlogUnauthenticatedRole", "Arn"] - } - } - }, - - "UpdateCognitoIdentityPool": { - "DependsOn": "LambdaUpdateCognitoExecutionRole", - "Type": "AWS::Lambda::Function", - "Properties": { - "Handler": "index.handler", - "Runtime": "nodejs4.3", - "Role": { "Fn::GetAtt" : ["LambdaUpdateCognitoExecutionRole", "Arn"] }, - "Code": { - "ZipFile": { - "Fn::Join": ["\n", [ - "var response = require('cfn-response');", - "var AWS = require('aws-sdk');", - "var cognitoidentity = new AWS.CognitoIdentity();", - "var responseData = {};", - "exports.handler = function(event, context) {", - " console.log('REQUEST RECEIVED:', JSON.stringify(event));", - " if (event.RequestType == 'Delete') {", - " response.send(event, context, response.SUCCESS);", - " return;", - " }", - " var params = {", - " IdentityPoolId: event.ResourceProperties.CognitoPoolId,", - " Roles: { ", - " authenticated: event.ResourceProperties.AuthRole,", - " unauthenticated: event.ResourceProperties.UnAuthRole,", - " }", - " };", - " cognitoidentity.setIdentityPoolRoles(params, function(err, data) {", - " if(err) {", - " console.log(JSON.stringify(err));", - " responseData = {Error: JSON.stringify(err)};", - " response.send(event, context, response.FAILED, responseData);", - " } else {", - " var message = \"Identity Pool roles successfully updated.\";", - " console.log(message);", - " response.send(event, context, response.SUCCESS);", - " }", - " });", - "};" - ]] - } - }, - "Timeout": "30" - } - } - }, - - "Outputs": { - - "CognitoPoolId": { - "Value": - { "Fn::GetAtt": [ "CreateCognitoPoolResource", "identityPoolId" ] }, - "Description": "Prod stage deployment URL" - } - } -} diff --git a/cloudformation/config-helper.template b/cloudformation/config-helper.template deleted file mode 100644 index 196bfc60..00000000 --- a/cloudformation/config-helper.template +++ /dev/null @@ -1,115 +0,0 @@ -{ - "AWSTemplateFormatVersion" : "2010-09-09", - - "Description" : "Template that defines a configuration resource for adding items to a DynamoDB config table", - - "Parameters": { - "ConfigTable": { - "Description": "Name of the DynamoDB table where config values should be stored", - "Type": "String" - } - }, - - "Resources" : { - "AddConfigSetting": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Handler": "index.handler", - "Role": { "Fn::GetAtt" : ["AddConfigExecutionRole", "Arn"] }, - "Code": { - "ZipFile": { "Fn::Join": ["", [ - "var response = require('cfn-response');", - "var AWS = require('aws-sdk');", - "var dynamodb = new AWS.DynamoDB();", - - "exports.handler = function(event, context) {", - " console.log('REQUEST RECEIVED:\\n', JSON.stringify(event));", - " var env = event.ResourceProperties.Environment;", - " var configKey = event.ResourceProperties.Key;", - " var configValue = event.ResourceProperties.Value;", - - " if (event.RequestType == 'Delete') {", - " dynamodb.updateItem({", - " TableName: \"", {"Ref": "ConfigTable"} , "\",", - " Key: { environment: { S: env } },", - " UpdateExpression: \"REMOVE \" + configKey", - " },", - " function(err, data){", - " if(err) { console.log(JSON.stringify(err)); }", - " response.send(event, context, response.SUCCESS);", - " });", - " return;", - " } else {", - " var param = {", - " TableName: '", {"Ref": "ConfigTable"} , "',", - " Key: { environment: { S: env } },", - " UpdateExpression: 'SET ' + configKey + ' = :newVal',", - " ExpressionAttributeValues: { ':newVal': { 'S': configValue } }", - " };", - " console.log(JSON.stringify(param));", - " dynamodb.updateItem(param,", - " function(err, data){", - " if(err) { console.log(JSON.stringify(err)); response.send(event, context, response.FAILED, err); }", - " else { response.send(event, context, response.SUCCESS); }", - " });", - " }", - "};" - ]]} - }, - "Runtime": "nodejs4.3", - "Timeout": "30" - } - }, - - "AddConfigExecutionRole": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Version": "2012-10-17", - "Statement": [{ - "Effect": "Allow", - "Principal": {"Service": ["lambda.amazonaws.com"]}, - "Action": ["sts:AssumeRole"] - }] - }, - "Path": "/", - "Policies": [{ - "PolicyName": "root", - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [{ - "Effect": "Allow", - "Action": ["logs:CreateLogGroup","logs:CreateLogStream","logs:PutLogEvents"], - "Resource": "arn:aws:logs:*:*:*" - }, - { - "Sid": "Stmt1453132818000", - "Effect": "Allow", - "Action": [ - "dynamodb:UpdateItem" - ], - "Resource": { - "Fn::Join": ["", ["arn:aws:dynamodb:", { - "Ref": "AWS::Region" - }, - ":", { - "Ref": "AWS::AccountId" - }, - ":table/", { - "Ref": "ConfigTable" - } - ]] - } - }] - } - }] - } - } - }, - - "Outputs" : { - "ServiceToken": { - "Value": { "Fn::GetAtt": ["AddConfigSetting", "Arn"]} - } - } -} diff --git a/cloudformation/lambda-creation-helper.template b/cloudformation/lambda-creation-helper.template deleted file mode 100644 index ffb44c8f..00000000 --- a/cloudformation/lambda-creation-helper.template +++ /dev/null @@ -1,257 +0,0 @@ -{ - "AWSTemplateFormatVersion": "2010-09-09", - "Parameters": { - - "LambdaToDynamoDBCommentTableRole": { - "Type": "String" - }, - "LambdaToDynamoDBPostTableRole": { - "Type": "String" - }, - "LambdaToDynamoDBUserTableRole": { - "Type": "String" - }, - "LambdaToDynamoDBForumTableRole": { - "Type": "String" - }, - "CodeBucket": { - "Description": "S3 Bucket containing Lambda deployment packages and sub-stack templates", - "Type": "String", - "Default": "aws-lambda-serverless-web-refarch" - }, - - "CodeKeyPrefix": { - "Description": "The key prefix for all deployment packages and sub-stack templates within CodeBucket", - "Type": "String", - "Default": "code" - } - }, - - "Resources": - { - - "SaveCommentFunction": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Handler": "blog.comments.handlers.SaveComment", - "Role": { - "Ref": "LambdaToDynamoDBCommentTableRole" - }, - "Description": "function saves a comment to the DDB comment table", - "Code": { - "S3Bucket": { "Ref": "CodeBucket" }, - "S3Key": { - "Fn::Join" : [ "", [{"Ref" : "CodeKeyPrefix"} , "/", "serverless-web-refarch.zip"]] - } - }, - "Runtime": "java8", - "MemorySize" : "512", - "Timeout": "60" - } - }, - - "FindCommentFunction": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Handler": "blog.comments.handlers.FindComment", - "Role": { - "Ref": "LambdaToDynamoDBCommentTableRole" - }, - "Description": "function finds a single comment for a post in the DDB comment table", - "Code": { - "S3Bucket": { "Ref": "CodeBucket" }, - "S3Key": { - "Fn::Join" : [ "", [{"Ref" : "CodeKeyPrefix"} , "/", "serverless-web-refarch.zip"]] - } - }, - "Runtime": "java8", - "MemorySize" : "512", - "Timeout": "60" - } - }, - - - "FindCommentsFunction": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Handler": "blog.comments.handlers.FindComments", - "Role": { - "Ref": "LambdaToDynamoDBCommentTableRole" - }, - "Description": "function finds all comments for a post in the DDB comment table", - "Code": { - "S3Bucket": { "Ref": "CodeBucket" }, - "S3Key": { - "Fn::Join" : [ "", [{"Ref" : "CodeKeyPrefix"} , "/", "serverless-web-refarch.zip"]] - } - }, - "Runtime": "java8", - "MemorySize" : "512", - "Timeout": "60" - } - }, - - "SavePostFunction": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Handler": "blog.posts.handlers.SavePost", - "Role": { - "Ref": "LambdaToDynamoDBPostTableRole" - }, - "Description": "function saves a post in the DDB post table", - "Code": { - "S3Bucket": { "Ref": "CodeBucket" }, - "S3Key": { - "Fn::Join" : [ "", [{"Ref" : "CodeKeyPrefix"} , "/", "serverless-web-refarch.zip"]] - } - }, - "Runtime": "java8", - "MemorySize" : "512", - "Timeout": "60" - } - }, - - "FindPostFunction": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Handler": "blog.posts.handlers.FindPost", - "Role": { - "Ref": "LambdaToDynamoDBPostTableRole" - }, - "Description": "function finds a single post in the DDB post table", - "Code": { - "S3Bucket": { "Ref": "CodeBucket" }, - "S3Key": { - "Fn::Join" : [ "", [{"Ref" : "CodeKeyPrefix"} , "/", "serverless-web-refarch.zip"]] - } - }, - "Runtime": "java8", - "MemorySize" : "512", - "Timeout": "60" - } - }, - - - "FindPostsFunction": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Handler": "blog.posts.handlers.FindPosts", - "Role": { - "Ref": "LambdaToDynamoDBPostTableRole" - }, - "Description": "function finds all the posts in the DDB post table", - "Code": { - "S3Bucket": { "Ref": "CodeBucket" }, - "S3Key": { - "Fn::Join" : [ "", [{"Ref" : "CodeKeyPrefix"} , "/", "serverless-web-refarch.zip"]] - } - }, - "Runtime": "java8", - "MemorySize" : "512", - "Timeout": "60" - } - }, - - "FindForumsFunction": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Handler": "blog.forums.handlers.FindForums", - "Role": { - "Ref": "LambdaToDynamoDBForumTableRole" - }, - "Description": "function finds all the posts in the DDB latest post table", - "Code": { - "S3Bucket": { "Ref": "CodeBucket" }, - "S3Key": { - "Fn::Join" : [ "", [{"Ref" : "CodeKeyPrefix"} , "/", "serverless-web-refarch.zip"]] - } - }, - "Runtime": "java8", - "MemorySize" : "512", - "Timeout": "60" - } - }, - - "SaveUserFunction": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Handler": "blog.users.handlers.SaveUser", - "Role": { - "Ref": "LambdaToDynamoDBUserTableRole" - }, - "Description": "function saves a user in the DDB user table", - "Code": { - "S3Bucket": { "Ref": "CodeBucket" }, - "S3Key": { - "Fn::Join" : [ "", [{"Ref" : "CodeKeyPrefix"} , "/", "serverless-web-refarch.zip"]] - } - }, - "Runtime": "java8", - "MemorySize" : "512", - "Timeout": "60" - } - }, - - "AuthenticateUserFunction": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Handler": "blog.users.handlers.AuthenticateUser", - "Role": { - "Ref": "LambdaToDynamoDBUserTableRole" - }, - "Description": "function authenticates a user in the DDB user table", - "Code": { - "S3Bucket": { "Ref": "CodeBucket" }, - "S3Key": { - "Fn::Join" : [ "", [{"Ref" : "CodeKeyPrefix"} , "/", "serverless-web-refarch.zip"]] - } - }, - "Runtime": "java8", - "MemorySize" : "512", - "Timeout": "60" - } - } - - - }, - - "Outputs": { - - "AuthenticateUserFunction": { - "Value": - { "Fn::GetAtt": ["AuthenticateUserFunction","Arn"] } - }, - "SaveUserFunction": { - "Value": - { "Fn::GetAtt": ["SaveUserFunction","Arn"] } - }, - "FindForumsFunction": { - "Value": - { "Fn::GetAtt": ["FindForumsFunction","Arn"] } - }, - "SavePostFunction": { - "Value": - { "Fn::GetAtt": ["SavePostFunction","Arn"] } - }, - "FindPostsFunction": { - "Value": - { "Fn::GetAtt": ["FindPostsFunction","Arn"] } - }, - "FindPostFunction": { - "Value": - { "Fn::GetAtt": ["FindPostFunction","Arn"] } - }, - "SaveCommentFunction": { - "Value": - { "Fn::GetAtt": ["SaveCommentFunction","Arn"] } - }, - "FindCommentsFunction": { - "Value": - { "Fn::GetAtt": ["FindCommentsFunction","Arn"] } - }, - "FindCommentFunction": { - "Value": - { "Fn::GetAtt": ["FindCommentFunction","Arn"] } - } - } -} diff --git a/cloudformation/serverless-web-master.template b/cloudformation/serverless-web-master.template deleted file mode 100644 index b8a90fe8..00000000 --- a/cloudformation/serverless-web-master.template +++ /dev/null @@ -1,782 +0,0 @@ -{ - "AWSTemplateFormatVersion": "2010-09-09", - - "Parameters": { - "Region": { - "Description": "The AWS region this is running in", - "Type": "String", - "Default": "us-east-1" - }, - - "HostingBucket": { - "Description": "S3 Bucket name for where the website should be hosted", - "Type": "String" - }, - - "CodeBucket": { - "Description": "S3 Bucket containing Lambda deployment packages and sub-stack templates", - "Type": "String", - "Default": "aws-lambda-serverless-web-refarch" - }, - - "CodeKeyPrefix": { - "Description": "The key prefix for all deployment packages and sub-stack templates within CodeBucket", - "Type": "String", - "Default": "code" - }, - - "APIStageName": { - "Description": "The name of the stage for the API deployment", - "Type": "String", - "Default": "prod" - }, - - "DynamoDBForumTableName": { - "Description": "the DynamoDB table name for the forum table", - "Type": "String", - "Default": "BlogForum" - }, - - "DynamoDBLatestPostTableName": { - "Description": "The DynamoDB table name for the Latest Posts table", - "Type": "String", - "Default": "LatestPostsInForum" - }, - - "DynamoDBPostTableName": { - "Description": "The DynamoDB table name for Posts", - "Type": "String", - "Default": "BlogPost" - }, - - "DynamoDBCommentTableName": { - "Description": "The DynamoDB table name for Comments", - "Type": "String", - "Default": "BlogComment" - }, - - "DynamoDBUserTableName": { - "Description": "The DynamoDB table name for Users", - "Type": "String", - "Default": "BlogUser" - }, - - "CognitoPoolDevID": { - "Description": "The name for your developer authenticated identity pool in Cognito", - "Type": "String", - "Default": "login.serverless.blog" - } - }, - - "Resources": - { - "WebsiteBucket" : { - "Type" : "AWS::S3::Bucket", - "Properties" : { - "AccessControl" : "PublicRead", - "BucketName" : {"Ref" : "HostingBucket"}, - "WebsiteConfiguration" : { - "IndexDocument" : "index.html", - "ErrorDocument" : "error.html" - } - }, - "DeletionPolicy" : "Delete" - }, - - "LambdaToDynamoDBUserTableRole": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Version": "2012-10-17", - "Statement": [{ - "Effect": "Allow", - "Principal": { - "Service": [ - "lambda.amazonaws.com" - ] - }, - "Action": [ - "sts:AssumeRole" - ] - }] - }, - "Path": "/", - "Policies": [{ - "PolicyName": "LambdaWriteCWLogs", - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [{ - "Sid": "AllowLogging", - "Effect": "Allow", - "Action": [ - "logs:CreateLogGroup", - "logs:CreateLogStream", - "logs:PutLogEvents" - ], - "Resource": "*" - }] - } - }, { - "PolicyName": "LambdaDDB_UserTable_ReadWrite", - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [{ - "Sid": "AllowDynamoDB", - "Action": [ - "dynamodb:DeleteItem", - "dynamodb:GetItem", - "dynamodb:BatchGetItem", - "dynamodb:PutItem", - "dynamodb:Query", - "dynamodb:Scan", - "dynamodb:UpdateItem" - ], - "Effect": "Allow", - "Resource": [ - {"Fn::Join" : [ "", ["arn:aws:dynamodb:", {"Ref" : "AWS::Region"} , ":", {"Ref" : "AWS::AccountId"} ,":table/", {"Ref": "DynamoDBUserTableName"}]]}, - {"Fn::Join" : [ "", ["arn:aws:dynamodb:", {"Ref" : "AWS::Region"} , ":", {"Ref" : "AWS::AccountId"} ,":table/aws-serverless-config"]]} - ] - }] - } - } - ] - } - }, - - "LambdaToDynamoDBPostTableRole": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Version": "2012-10-17", - "Statement": [{ - "Effect": "Allow", - "Principal": { - "Service": [ - "lambda.amazonaws.com" - ] - }, - "Action": [ - "sts:AssumeRole" - ] - }] - }, - "Path": "/", - "Policies": [{ - "PolicyName": "LambdaWriteCWLogs", - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [{ - "Sid": "AllowLogging", - "Effect": "Allow", - "Action": [ - "logs:CreateLogGroup", - "logs:CreateLogStream", - "logs:PutLogEvents" - ], - "Resource": "*" - }] - } - }, { - "PolicyName": "LambdaDDB_PostTable_ReadWrite", - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [{ - "Sid": "AllowDynamoDB", - "Action": [ - "dynamodb:DeleteItem", - "dynamodb:GetItem", - "dynamodb:BatchGetItem", - "dynamodb:PutItem", - "dynamodb:Query", - "dynamodb:Scan", - "dynamodb:UpdateItem" - ], - "Effect": "Allow", - "Resource": [ - {"Fn::Join" : [ "", ["arn:aws:dynamodb:", {"Ref" : "AWS::Region"} , ":", {"Ref" : "AWS::AccountId"} ,":table/", {"Ref": "DynamoDBPostTableName"}]]}, - {"Fn::Join" : [ "", ["arn:aws:dynamodb:", {"Ref" : "AWS::Region"} , ":", {"Ref" : "AWS::AccountId"} ,":table/", {"Ref": "DynamoDBLatestPostTableName"}]]}, - {"Fn::Join" : [ "", ["arn:aws:dynamodb:", {"Ref" : "AWS::Region"} , ":", {"Ref" : "AWS::AccountId"} ,":table/aws-serverless-config"]]} - ] - }] - } - }] - } - }, - - "LambdaToDynamoDBForumTableRole": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Version": "2012-10-17", - "Statement": [{ - "Effect": "Allow", - "Principal": { - "Service": [ - "lambda.amazonaws.com" - ] - }, - "Action": [ - "sts:AssumeRole" - ] - }] - }, - "Path": "/", - "Policies": [{ - "PolicyName": "LambdaWriteCWLogs", - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [{ - "Sid": "AllowLogging", - "Effect": "Allow", - "Action": [ - "logs:CreateLogGroup", - "logs:CreateLogStream", - "logs:PutLogEvents" - ], - "Resource": "*" - }] - } - }, { - "PolicyName": "LambdaDDB_ForumTable_ReadWrite", - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [{ - "Sid": "AllowDynamoDB", - "Action": [ - "dynamodb:DeleteItem", - "dynamodb:GetItem", - "dynamodb:BatchGetItem", - "dynamodb:PutItem", - "dynamodb:Query", - "dynamodb:Scan", - "dynamodb:UpdateItem" - ], - "Effect": "Allow", - "Resource": [ - {"Fn::Join" : [ "", ["arn:aws:dynamodb:", {"Ref" : "AWS::Region"} , ":", {"Ref" : "AWS::AccountId"} ,":table/", {"Ref": "DynamoDBForumTableName"}]]}, - {"Fn::Join" : [ "", ["arn:aws:dynamodb:", {"Ref" : "AWS::Region"} , ":", {"Ref" : "AWS::AccountId"} ,":table/aws-serverless-config"]]} - ] - }] - } - }] - } - }, - - "LambdaToDynamoDBCommentTableRole": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Version": "2012-10-17", - "Statement": [{ - "Effect": "Allow", - "Principal": { - "Service": [ - "lambda.amazonaws.com" - ] - }, - "Action": [ - "sts:AssumeRole" - ] - }] - }, - "Path": "/", - "Policies": [{ - "PolicyName": "LambdaWriteCWLogs", - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [{ - "Sid": "AllowLogging", - "Effect": "Allow", - "Action": [ - "logs:CreateLogGroup", - "logs:CreateLogStream", - "logs:PutLogEvents" - ], - "Resource": "*" - }] - } - }, { - "PolicyName": "LambdaDDB_CommentTable_ReadWrite", - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [{ - "Sid": "AllowDynamoDB", - "Action": [ - "dynamodb:DeleteItem", - "dynamodb:GetItem", - "dynamodb:BatchGetItem", - "dynamodb:PutItem", - "dynamodb:Query", - "dynamodb:Scan", - "dynamodb:UpdateItem" - ], - "Effect": "Allow", - "Resource": [ - {"Fn::Join" : [ "", ["arn:aws:dynamodb:", {"Ref" : "AWS::Region"} , ":", {"Ref" : "AWS::AccountId"} ,":table/", {"Ref": "DynamoDBCommentTableName"}]]}, - {"Fn::Join" : [ "", ["arn:aws:dynamodb:", {"Ref" : "AWS::Region"} , ":", {"Ref" : "AWS::AccountId"} ,":table/aws-serverless-config"]]} - ] - }] - } - }] - } - }, - - "DDBForumTable": { - "DependsOn" : "LambdaToDynamoDBForumTableRole", - "Type": "AWS::DynamoDB::Table", - "Properties": { - "AttributeDefinitions": [{ - "AttributeName": "id", - "AttributeType": "S" - }], - "KeySchema": [{ - "AttributeName": "id", - "KeyType": "HASH" - }], - "ProvisionedThroughput": { - "ReadCapacityUnits": "5", - "WriteCapacityUnits": "5" - }, - "TableName": { "Ref": "DynamoDBForumTableName" } - } - }, - - "DDBLatestPostTable": { - "DependsOn" : "LambdaToDynamoDBPostTableRole", - "Type": "AWS::DynamoDB::Table", - "Properties": { - "AttributeDefinitions": [{ - "AttributeName": "forumId", - "AttributeType": "S" - }, - { - "AttributeName" : "created_at", - "AttributeType" : "N" - }], - "KeySchema": [{ - "AttributeName": "forumId", - "KeyType": "HASH" - }, - { - "AttributeName": "created_at", - "KeyType": "RANGE" - - }], - "ProvisionedThroughput": { - "ReadCapacityUnits": "5", - "WriteCapacityUnits": "5" - }, - "TableName": { "Ref": "DynamoDBLatestPostTableName" } - } - }, - - "DDBPostTable": { - "DependsOn" : "LambdaToDynamoDBPostTableRole", - "Type": "AWS::DynamoDB::Table", - "Properties": { - "AttributeDefinitions": [{ - "AttributeName": "id", - "AttributeType": "S" - }], - "KeySchema": [{ - "AttributeName": "id", - "KeyType": "HASH" - }], - "ProvisionedThroughput": { - "ReadCapacityUnits": "5", - "WriteCapacityUnits": "5" - }, - "TableName": { "Ref": "DynamoDBPostTableName" } - } - }, - - "DDBCommentTable": { - "DependsOn" : "LambdaToDynamoDBCommentTableRole", - "Type": "AWS::DynamoDB::Table", - "Properties": { - "AttributeDefinitions": [{ - "AttributeName": "postId", - "AttributeType": "S" - }, - { - "AttributeName" : "created_at", - "AttributeType" : "N" - }], - "KeySchema": [{ - "AttributeName": "postId", - "KeyType": "HASH" - }, - { - "AttributeName": "created_at", - "KeyType": "RANGE" - - }], - "ProvisionedThroughput": { - "ReadCapacityUnits": "5", - "WriteCapacityUnits": "5" - }, - "TableName": { "Ref": "DynamoDBCommentTableName" } - } - }, - - "DDBUserTable": { - "DependsOn" : "LambdaToDynamoDBUserTableRole", - "Type": "AWS::DynamoDB::Table", - "Properties": { - "AttributeDefinitions": [{ - "AttributeName": "email", - "AttributeType": "S" - }], - "KeySchema": [{ - "AttributeName": "email", - "KeyType": "HASH" - }], - "ProvisionedThroughput": { - "ReadCapacityUnits": "5", - "WriteCapacityUnits": "5" - }, - "TableName": { "Ref": "DynamoDBUserTableName" } - } - }, - - "LambdaCreationHelperStack": { - "Type" : "AWS::CloudFormation::Stack", - "Properties" : { - "TemplateURL" : {"Fn::Join": ["/", ["https://s3.amazonaws.com", {"Ref": "CodeBucket"}, {"Ref": "CodeKeyPrefix"}, "lambda-creation-helper.template"]]}, - "Parameters" : { - "CodeBucket" : {"Ref": "CodeBucket"}, - "CodeKeyPrefix": { "Ref": "CodeKeyPrefix" }, - "LambdaToDynamoDBCommentTableRole" : {"Fn::GetAtt": ["LambdaToDynamoDBCommentTableRole","Arn"]}, - "LambdaToDynamoDBPostTableRole" : {"Fn::GetAtt": ["LambdaToDynamoDBPostTableRole","Arn"]}, - "LambdaToDynamoDBUserTableRole" : {"Fn::GetAtt": ["LambdaToDynamoDBUserTableRole","Arn"]}, - "LambdaToDynamoDBForumTableRole": {"Fn::GetAtt": ["LambdaToDynamoDBForumTableRole","Arn"]} - }, - "TimeoutInMinutes" : 10 - } - }, - - "ApiCreationHelperStack": { - "Type" : "AWS::CloudFormation::Stack", - "Properties" : { - "TemplateURL" : {"Fn::Join": ["/", ["https://s3.amazonaws.com", {"Ref": "CodeBucket"}, {"Ref": "CodeKeyPrefix"}, "api-creation-helper.template"]]}, - "Parameters" : { - "APIStageName" : {"Ref": "APIStageName"}, - "AuthenticateUserFunction": { "Fn::GetAtt": ["LambdaCreationHelperStack", "Outputs.AuthenticateUserFunction"] }, - "SaveUserFunction" : {"Fn::GetAtt": ["LambdaCreationHelperStack", "Outputs.SaveUserFunction"] }, - "SavePostFunction" : {"Fn::GetAtt": ["LambdaCreationHelperStack", "Outputs.SavePostFunction"] }, - "SaveCommentFunction" : {"Fn::GetAtt": ["LambdaCreationHelperStack", "Outputs.SaveCommentFunction"] }, - "FindPostFunction" : {"Fn::GetAtt": ["LambdaCreationHelperStack", "Outputs.FindPostFunction"] }, - "FindPostsFunction" : {"Fn::GetAtt": ["LambdaCreationHelperStack", "Outputs.FindPostsFunction"] }, - "FindForumsFunction" : {"Fn::GetAtt": ["LambdaCreationHelperStack", "Outputs.FindForumsFunction"] }, - "FindCommentsFunction" : {"Fn::GetAtt": ["LambdaCreationHelperStack", "Outputs.FindCommentsFunction"] }, - "FindCommentFunction" : {"Fn::GetAtt": ["LambdaCreationHelperStack", "Outputs.FindCommentFunction"] } - }, - "TimeoutInMinutes" : 10 - } - }, - - "UnauthenticatedBlogUserPolicy" : { - "DependsOn" : "ApiCreationHelperStack", - "Type" : "AWS::IAM::ManagedPolicy", - "Properties" : { - "Description" : "Policy for users in cognito who are unauthenticated to call API endpoints", - "Path" : "/", - "PolicyDocument" : { - "Version":"2012-10-17", - "Statement" : [{ - "Effect" : "Allow", - "Action" : "execute-api:Invoke", - "Resource" : [ - {"Fn::Join":["",["arn:aws:execute-api:", { "Ref" : "AWS::Region" }, ":", { "Ref" : "AWS::AccountId" }, ":", {"Fn::GetAtt" : ["ApiCreationHelperStack", "Outputs.APIID"]}, "/*/GET/posts/*"]]}, - {"Fn::Join":["",["arn:aws:execute-api:", { "Ref" : "AWS::Region" }, ":", { "Ref" : "AWS::AccountId" }, ":", {"Fn::GetAtt" : ["ApiCreationHelperStack", "Outputs.APIID"]}, "/*/GET/posts/*/comments"]]}, - {"Fn::Join":["",["arn:aws:execute-api:", { "Ref" : "AWS::Region" }, ":", { "Ref" : "AWS::AccountId" }, ":", {"Fn::GetAtt" : ["ApiCreationHelperStack", "Outputs.APIID"]}, "/*/GET/posts/*/comments/*"]]}, - {"Fn::Join":["",["arn:aws:execute-api:", { "Ref" : "AWS::Region" }, ":", { "Ref" : "AWS::AccountId" }, ":", {"Fn::GetAtt" : ["ApiCreationHelperStack", "Outputs.APIID"]}, "/*/GET/forums/*/posts"]]}, - {"Fn::Join":["",["arn:aws:execute-api:", { "Ref" : "AWS::Region" }, ":", { "Ref" : "AWS::AccountId" }, ":", {"Fn::GetAtt" : ["ApiCreationHelperStack", "Outputs.APIID"]}, "/*/GET/forums"]]}, - {"Fn::Join":["",["arn:aws:execute-api:", { "Ref" : "AWS::Region" }, ":", { "Ref" : "AWS::AccountId" }, ":", {"Fn::GetAtt" : ["ApiCreationHelperStack", "Outputs.APIID"]}, "/*/POST/users"]]}, - {"Fn::Join":["",["arn:aws:execute-api:", { "Ref" : "AWS::Region" }, ":", { "Ref" : "AWS::AccountId" }, ":", {"Fn::GetAtt" : ["ApiCreationHelperStack", "Outputs.APIID"]}, "/*/POST/login"]]} - ] - }] - } - } - }, - - "AuthenticatedBlogUserPolicy" : { - "DependsOn" : "ApiCreationHelperStack", - "Type" : "AWS::IAM::ManagedPolicy", - "Properties" : { - "Description" : "Policy for users in cognito who are unauthenticated to call API endpoints", - "Path" : "/", - "PolicyDocument" : { - "Version":"2012-10-17", - "Statement" : [{ - "Effect" : "Allow", - "Action" : "execute-api:Invoke", - "Resource" : [ - {"Fn::Join":["",["arn:aws:execute-api:", { "Ref" : "AWS::Region" }, ":", { "Ref" : "AWS::AccountId" }, ":", {"Fn::GetAtt" : ["ApiCreationHelperStack", "Outputs.APIID"]}, "/*/POST/forums/*/posts"]]}, - {"Fn::Join":["",["arn:aws:execute-api:", { "Ref" : "AWS::Region" }, ":", { "Ref" : "AWS::AccountId" }, ":", {"Fn::GetAtt" : ["ApiCreationHelperStack", "Outputs.APIID"]}, "/*/POST/posts/*/comments"]]} - ] - }] - } - } - }, - - "CognitoCreationHelperStack": { - "Type" : "AWS::CloudFormation::Stack", - "Properties" : { - "TemplateURL" : {"Fn::Join": ["/", ["https://s3.amazonaws.com", {"Ref": "CodeBucket"}, {"Ref": "CodeKeyPrefix"}, "cognito-creation-helper.template"]]}, - "Parameters" : { - "CognitoPoolDevID": { "Ref": "CognitoPoolDevID" }, - "UnauthenticatedBlogUserPolicy" : {"Ref": "UnauthenticatedBlogUserPolicy"}, - "AuthenticatedBlogUserPolicy" : {"Ref": "AuthenticatedBlogUserPolicy"}, - "LambdaUserRole": {"Fn::GetAtt": ["LambdaToDynamoDBUserTableRole","Arn"]} - }, - "TimeoutInMinutes" : 7 - } - }, - - "KmsCMK" : { - "Type" : "AWS::KMS::Key", - "Properties" : { - "Description" : "master key for encrypting data", - "KeyPolicy" : { - "Version": "2012-10-17", - "Id": "serverlessBlogKeyPolicy", - "Statement": [ - { - "Sid": "Allow administration of the key by the root user since do not have knowledge of an IAM admin role", - "Effect": "Allow", - "Principal": { "AWS": {"Fn::Join": ["", ["arn:aws:iam::", { "Ref" : "AWS::AccountId" }, ":root"]]}}, - "Action": [ - "kms:Create*", - "kms:Describe*", - "kms:Enable*", - "kms:List*", - "kms:Put*", - "kms:Update*", - "kms:Revoke*", - "kms:Disable*", - "kms:Get*", - "kms:Delete*", - "kms:ScheduleKeyDeletion", - "kms:CancelKeyDeletion" - ], - "Resource": "*" - }, - { - "Sid": "Allow use of the key", - "Effect": "Allow", - "Principal": { "AWS": {"Fn::GetAtt" : ["LambdaToDynamoDBUserTableRole", "Arn"] }}, - "Action": [ - "kms:Encrypt", - "kms:Decrypt", - "kms:ReEncrypt", - "kms:GenerateDataKey*", - "kms:DescribeKey" - ], - "Resource": "*" - } - ] - } - } - }, - - "DDBConfigTable": { - "Type": "AWS::DynamoDB::Table", - "Properties": { - "AttributeDefinitions": [{ - "AttributeName": "environment", - "AttributeType": "S" - }], - "KeySchema": [{ - "AttributeName": "environment", - "KeyType": "HASH" - }], - "ProvisionedThroughput": { - "ReadCapacityUnits": "5", - "WriteCapacityUnits": "5" - }, - "TableName": "aws-serverless-config" - } - }, - - "ConfigHelperStack": { - "Type" : "AWS::CloudFormation::Stack", - "Properties" : { - "TemplateURL" : {"Fn::Join": ["/", ["https://s3.amazonaws.com", {"Ref": "CodeBucket"}, {"Ref": "CodeKeyPrefix"}, "config-helper.template"]]}, - "Parameters" : { - "ConfigTable": "aws-serverless-config" - }, - "TimeoutInMinutes" : 2 - } - }, - - "DDBForumTableConfig": { - "Type": "Custom::ConfigSetting", - "Properties": { - "ServiceToken": { "Fn::GetAtt" : ["ConfigHelperStack", "Outputs.ServiceToken"] }, - "Environment": {"Ref": "APIStageName"}, - "Key": "forum_ddb_table_name", - "Value": { "Ref": "DynamoDBForumTableName" } - } - }, - "DDBLatestPostTableConfig": { - "Type": "Custom::ConfigSetting", - "Properties": { - "ServiceToken": { "Fn::GetAtt" : ["ConfigHelperStack", "Outputs.ServiceToken"] }, - "Environment": {"Ref": "APIStageName"}, - "Key": "latest_post_ddb_table_name", - "Value": { "Ref": "DynamoDBLatestPostTableName" } - } - }, - "DDBPostTableConfig": { - "Type": "Custom::ConfigSetting", - "Properties": { - "ServiceToken": { "Fn::GetAtt" : ["ConfigHelperStack", "Outputs.ServiceToken"] }, - "Environment": {"Ref": "APIStageName"}, - "Key": "post_ddb_table_name", - "Value": { "Ref": "DynamoDBPostTableName" } - } - }, - "DDBCommentTableConfig": { - "Type": "Custom::ConfigSetting", - "Properties": { - "ServiceToken": { "Fn::GetAtt" : ["ConfigHelperStack", "Outputs.ServiceToken"] }, - "Environment": {"Ref": "APIStageName"}, - "Key": "comment_ddb_table_name", - "Value": { "Ref": "DynamoDBCommentTableName" } - } - }, - "DDBUserTableConfig": { - "Type": "Custom::ConfigSetting", - "Properties": { - "ServiceToken": { "Fn::GetAtt" : ["ConfigHelperStack", "Outputs.ServiceToken"] }, - "Environment": {"Ref": "APIStageName"}, - "Key": "user_ddb_table_name", - "Value": { "Ref": "DynamoDBUserTableName" } - } - }, - "CognitoPoolIdConfig": { - "Type": "Custom::ConfigSetting", - "Properties": { - "ServiceToken": { "Fn::GetAtt" : ["ConfigHelperStack", "Outputs.ServiceToken"] }, - "Environment": {"Ref": "APIStageName"}, - "Key": "cognito_identity_pool_id", - "Value": { "Fn::GetAtt" : ["CognitoCreationHelperStack", "Outputs.CognitoPoolId"] } - } - }, - "CognitoPoolDeveloperIdConfig": { - "Type": "Custom::ConfigSetting", - "Properties": { - "ServiceToken": { "Fn::GetAtt" : ["ConfigHelperStack", "Outputs.ServiceToken"] }, - "Environment": {"Ref": "APIStageName"}, - "Key": "cognito_developer_id", - "Value": { "Ref": "CognitoPoolDevID" } - } - }, - "KMSIdConfig": { - "Type": "Custom::ConfigSetting", - "Properties": { - "ServiceToken": { "Fn::GetAtt" : ["ConfigHelperStack", "Outputs.ServiceToken"] }, - "Environment": {"Ref": "APIStageName"}, - "Key": "encryption_key_id", - "Value": { "Ref": "KmsCMK" } - } - }, - - "PopulateForumsTable": { - "Type": "Custom::PopulateData", - "Properties": { - "ServiceToken": { "Fn::GetAtt" : ["PopulateForumsTableResource", "Arn"] }, - "ForumTableName": { - "Ref": "DynamoDBForumTableName" - } - } - }, - - "PopulateForumsTableResource": { - "DependsOn": ["LambdaToDynamoDBForumTableRole","DDBForumTable"], - "Type": "AWS::Lambda::Function", - "Properties": { - "Handler": "index.handler", - "Runtime": "nodejs4.3", - "Role": { "Fn::GetAtt" : ["LambdaToDynamoDBForumTableRole", "Arn"] }, - "Code": { - "ZipFile": { - "Fn::Join": ["\n", [ - "var response = require('cfn-response');", - "var AWS = require('aws-sdk');", - "var docClient = new AWS.DynamoDB.DocumentClient();", - "exports.handler = function (event, context, callback) {", - " console.log('Received event:', JSON.stringify(event, null, 2))", - " if (event.RequestType == 'Delete') {", - " response.send(event, context, response.SUCCESS);", - " return;", - " }", - " var numForums = 5;", - " var complete = 0;", - " var error = false;", - " for (var i = 1; i <= numForums; i++) {", - " var forumName = 'forum' + i;", - " var params = {", - " TableName: event.ResourceProperties.ForumTableName,", - " Item: {", - " \"id\": i.toString(),", - " \"name\": forumName", - " }}", - " docClient.put(params, function (err, data) {", - " if (err) {", - " console.log(\"Error during put: \" + JSON.stringify(err, null, 2));", - " error = true;", - " } else {", - " console.log(\"Forum added\");", - " }", - " complete = complete + 1;", - " if(complete === numForums) {", - " if(!error) {", - " response.send(event, context, response.SUCCESS, {});", - " } else {", - " response.send(event, context, response.FAILED, {});", - " }", - " }", - " });", - " }", - "};" - ]] - } - }, - "Timeout": "30" - } - } - - }, - - "Outputs": { - "APIEndpoint": { - "Value": - { - "Fn::Join": [ - "", - [ - "https://", - {"Fn::GetAtt" : ["ApiCreationHelperStack", "Outputs.APIID"]}, - ".execute-api.", - {"Ref": "AWS::Region"}, - ".amazonaws.com/", - { "Ref": "APIStageName"} - ]]}, - "Description": "Prod stage deployment URL" - }, - - "WebsiteURL" : { - "Value" : { "Fn::GetAtt" : [ "WebsiteBucket", "WebsiteURL" ] }, - "Description" : "URL for serverless blog hosted on S3" - }, - - "CognitoIdentityPoolId": { - "Value": - { "Fn::GetAtt" : ["CognitoCreationHelperStack", "Outputs.CognitoPoolId"] }, - "Description": "cognito pool id" - } - } - -} diff --git a/deploy.sh b/deploy.sh new file mode 100755 index 00000000..b083e79e --- /dev/null +++ b/deploy.sh @@ -0,0 +1,38 @@ +set -x + +[ -z "$STACK_NAME" ] && echo "Please specify STACK_NAME environment variable" && exit 1; +[ -z "$AWS_DEFAULT_REGION" ] && echo "Please specify AWS_DEFAULT_REGION environment variable" && exit 1; + +sam build --use-container +sam deploy --guided + +export AWS_COGNITO_REGION=$AWS_DEFAULT_REGION +export AWS_USER_POOLS_ID=`aws cloudformation describe-stacks --stack-name $STACK_NAME --query "Stacks[0].Outputs[?OutputKey=='CognitoID'].OutputValue" --output text` +export AWS_USER_POOLS_WEB_CLIENT_ID=`aws cloudformation describe-stacks --stack-name $STACK_NAME --query "Stacks[0].Outputs[?OutputKey=='CognitoClientID'].OutputValue" --output text` +export API_BASE_URL=`aws cloudformation describe-stacks --stack-name $STACK_NAME --query "Stacks[0].Outputs[?OutputKey=='TodoFunctionApi'].OutputValue" --output text` +export STAGE_NAME_PARAM=`aws cloudformation describe-stacks --stack-name $STACK_NAME --query "Stacks[0].Parameters[?ParameterKey=='StageNameParam'].ParameterValue" --output text` +export COGNITO_HOSTED_DOMAIN=`aws cloudformation describe-stacks --stack-name $STACK_NAME --query "Stacks[0].Outputs[?OutputKey=='CognitoDomainName'].OutputValue" --output text` +export REDIRECT_URL=`aws cloudformation describe-stacks --stack-name $STACK_NAME --query "Stacks[0].Outputs[?OutputKey=='AmplifyURL'].OutputValue" --output text` + +cp www/src/config.default.js www/src/config.js +if [[ "$OSTYPE" == "darwin"* ]]; then + sed -i '' -e 's/AWS_COGNITO_REGION/'"$AWS_COGNITO_REGION"'/g' www/src/config.js + sed -i '' -e 's/AWS_USER_POOLS_ID/'"$AWS_USER_POOLS_ID"'/g' www/src/config.js + sed -i '' -e 's/AWS_USER_POOLS_WEB_CLIENT_ID/'"$AWS_USER_POOLS_WEB_CLIENT_ID"'/g' www/src/config.js + sed -i '' -e 's/API_BASE_URL/'"${API_BASE_URL//\//\\/}"'/g' www/src/config.js + sed -i '' -e 's/{StageNameParam}/'"$STAGE_NAME_PARAM"'/g' www/src/config.js + sed -i '' -e 's/COGNITO_HOSTED_DOMAIN/'"$COGNITO_HOSTED_DOMAIN"'/g' www/src/config.js + sed -i '' -e 's/REDIRECT_URL/'"${REDIRECT_URL//\//\\/}"'/g' www/src/config.js +else + sed -i -e 's/AWS_COGNITO_REGION/'"$AWS_COGNITO_REGION"'/g' www/src/config.js + sed -i -e 's/AWS_USER_POOLS_ID/'"$AWS_USER_POOLS_ID"'/g' www/src/config.js + sed -i -e 's/AWS_USER_POOLS_WEB_CLIENT_ID/'"$AWS_USER_POOLS_WEB_CLIENT_ID"'/g' www/src/config.js + sed -i -e 's/API_BASE_URL/'"${API_BASE_URL//\//\\/}"'/g' www/src/config.js + sed -i -e 's/{StageNameParam}/'"$STAGE_NAME_PARAM"'/g' www/src/config.js + sed -i -e 's/COGNITO_HOSTED_DOMAIN/'"$COGNITO_HOSTED_DOMAIN"'/g' www/src/config.js + sed -i -e 's/REDIRECT_URL/'"${REDIRECT_URL//\//\\/}"'/g' www/src/config.js +fi + +git add www/src/config.js +git commit -m 'Frontend config update' +git push diff --git a/env.json b/env.json new file mode 100644 index 00000000..c23e6b13 --- /dev/null +++ b/env.json @@ -0,0 +1,26 @@ +{ + "GetTodoFunction": { + "TABLE_NAME": "todo-table", + "AWS_NODEJS_CONNECTION_REUSE_ENABLED": "1" + }, + "GetAllTodoFunction": { + "TABLE_NAME": "todo-table", + "AWS_NODEJS_CONNECTION_REUSE_ENABLED": "1" + }, + "DeleteTodoFunction": { + "TABLE_NAME": "todo-table", + "AWS_NODEJS_CONNECTION_REUSE_ENABLED": "1" + }, + "CompleteTodoFunction": { + "TABLE_NAME": "todo-table", + "AWS_NODEJS_CONNECTION_REUSE_ENABLED": "1" + }, + "UpdateTodoFunction": { + "TABLE_NAME": "todo-table", + "AWS_NODEJS_CONNECTION_REUSE_ENABLED": "1" + }, + "AddTodoFunction": { + "TABLE_NAME": "todo-table", + "AWS_NODEJS_CONNECTION_REUSE_ENABLED": "1" + } +} \ No newline at end of file diff --git a/img/serverless-refarch-webapp.png b/img/serverless-refarch-webapp.png new file mode 100644 index 0000000000000000000000000000000000000000..2cf7f84d692e9ea179a65798648f360e6aef3284 GIT binary patch literal 100972 zcmZ5{c|26#8$S|5$sS5}Ehw{?8IyhXv5v6}S{P==jBS{)%bK-hYoR2SEK%Ah6;TvY zNl~H@DSOBk!~Cw#=kxu&ey`sj?!9yFx#v0OInVQc-p@Hl2HBnKLi6~hmKfol8h zXg=(qP!5l!3qu3HNkO5E0N@T316Kzp;Nk}SLBO66BTvKu;L$2LIDkQAc-yc;b)k4T z40#BK0E+k95S{I8b)gvGIe_gC$O97?KK`7|PyW{fyfN$$_>d6<3_ApYY<}cKW74?n z|EHPFo-;yezW?@|i6(LE*x@8+pHLXbCBTPjy(wz5PB??hV{?N3tp9u@6P+LRIP3rPC2S zCI+w&v{O{L4R8a#gNFrkBf%t^F_r@hBBDY!C1BZ+s30CZG~70T3H;<5Zw?s61uleO zZwIP1@Fm_3ZiEemM*2DhkUfG(-r@Fe5+{@x4b)*EqX`U_gB8S`WW%PjNlczS7)r8^ z=Hk3*_FMwP#>NprqWDpa?IZ2{ow45jK`eVNl{F!FY&&7&15*D01cy9DICj z0{wUx2ASZ(B1RM3&_2%LTpFA1j{{>pqKH%~!HOH?&!alQ99am!($Fr($N(2>tZjg^ zHJZS5h_(%h#08P4KyySk3kTfX!8TZyy*JD#+$cOSgo@_CtVm8_T!c?3$(c^a*t!$Y z!N#sYMGu&rt6wyY1Hrh0Ll`t$A`&RXxBwSF1Yl@*M`RRmWx+!T9HcQg+9jHZw_*A? z7zaZkeiS+e5{7rQ^D~Zg#%k)XkWI!@HruHW54@4B9aiuy3>#F(Aal%7Y$= zwRfX)J$O7!AO{-Z?N7p5^E{jxOb;$R$_|Qkr@Mp(5S?sjY+oYOiVH=dolx9B0-gt0 z4I0mM_2v?ttPjtauUrE)1NtBPPtpCmi7#Zbe`^hvGR#jA#_b!H5>Yq+2<9vsht)ksKb; z**luSa<>nqVi6uF4+Is8;vvZnWLsw@6wZwFKvI3}z+o&-fKwRHI6TY<%MA*SG)6o5 zIRsLDus+~$?WXpp4hU!YkZ2fJ4}X%i5j8v-Vg!dnVAfVfL>|Nk0k!uh`GrD| zJ`uJQ7dr+UL57gLR$Nhxo);( zYk%ViGQoio79LF@IWnBxUBYSBELVT98^PTv0PE|3BH7qmad-hv#7$Rc*gC+8){!9; zC%4E*GLh`Z#8Yj_I0gq#Ck4;}d1!)0~`>a_O9yGuo(IJ5hp0`UhghWFHM22%wf#HrQ9>fC%gCN4ZL)a(; zoq*-q!K{MaF(Hv51Pa6f!7#=UcuYs856&UnI|48PM?!={6dCRxfg)^T2@>fS9_D5b zJUMJ)r2`?4|Zg^ zhx2S$9$@-r572H%YCu@DcOZcm2IxN`#NQZ6w!xxG!8zPN}yT; zL&Lkc8XFrS{AnzIn~)H?Gb=JG+BU++I}ptcB~y6rgisD3(%8rW9zb?rK||oMNUjHy zWd(Ed4RG~GQLJHfEY*?d>x_<|ao`^Q?ohfvA`Ahh2L?pbgOLz#V;Ibx3Jwe*GegO6 zl(Usf2*u5q9Y%9?fVsF@5sX3u!mun4n@~EH7lOAjg4;R;`5=ruqRAXMHV7HP@?&t| z4mKP=5{zjT;t@{8IT*RykeGffY!DZMX9r>#(RLnOe=^cLgcQ9QEb%;?pHGkx9Y^P4 zqOGuY_6)EwI5NoDIKq~RB!bb-0Wet9X0XLu*`U}F9#J9gP@Xr^2Iou;3MK`y{cvDB z7lny{1O(7RnGR7wJU6Vby|XRV5loFhhuHg}?c51iZ##$3Q2!8Cm@C8wip9FRP~eeB zEIWj3hY2@=K_ZFaM0j*C-WpFP6Ht*bwr>Onj_2C=+i;-vES_C3KG+6pi$U*U9@V<4kKP{wqc3jz&sGYZ8r!#T9@O}vfv z4)A8dq5iJKfMD?E1V@JY`0#8w)>Z*@-%ZEF5d*_>o}Fa0s*xVdG#d?;vlqE!}66#Mr}OMw=}gyLwo2|DifTL?(Tc6`{h3 zz_c2>Ag!F;S#WC?I6N}S4HCk2B>J)3iA;#UHx1$MO7XCU*hTwd=$}!7O)8f7^46@5zKN4@{XYSLbxoJpM(S;!G+bBs?(tHh89P~R}+>)$OeuN~ZxCtJVM0G59{b(u_%5O$kCEQ>cI4doDp-2~^&78c$z6x)X4 zr}XY~6!(3D+9=kM5g zTC8-{ZR$Ox^*=G9T9z%I8f5288M2nh)8nl#vLl*B3!hb777x`M+Fxg?_Y{vb7vw~)%cNUdr*iDY(4iErUxPwIz?YgW>xYdFtsF*%?jO^sJg8?2jQl?Z%;OU% zu|Ler>3cMD7@`|zY?G^%24~wE@w$dDi`Ux(8%J;)7WF-3oYP372Mw~O5=BkJ^%!(2>V43g^6kJI%KchLYWZXNXg=kaxAM+vm$ z5=b>pP4c5)*;@bnKHaTXagNdjl(!Cnf|1!sW!`tx%Hf|$k02SdhYpcq59;cstP}3Y zZ7dg8DJJj4nKH7ss1Sj9Pt~3sbJwu-tWmtsxl1_ckbQ!EqG01=@XYIDE0-^CcX@q2 zIquk{q`>OS2`!DJZ?E5%k)*;-#t+7dp8hnQ^4CII)YH(R?!<_C@55zm&TzMsCJz4LKul1WBApxQu#$p?vTyScs*TE&puT7rYc7(ew_X^tEnTc|Nl4MY{Q* z)$2YfrN@i9L^@lkPnLxienQz z@6@-=p4(vJc+EE^Qo_Rjga;eFsppF%`N%I`o|gvSi>6r~(*X?I>mule?nau!F~*N4 zb{{X1Z(Nl&_;Od>&slG4tO>G<#J7mEO)@X}*}NRvpAexIQD79Y#rcl2yLR}fBddrzvj=(8h_UgsCarb4n&s)%&c82!MR8Vj1-AC zlS)M87mveB*!N7q)p*CE(JQGh(E`kJZT8~xJa+cFaKaq?6#A@p)WXAkts>_pv|AlD z3x6bAdJGHJ&yA8ds;*~_m8ha(rIys5ws2>|aQu7QXG&GRHf%MGRs%_^H6-Jl((XRF zGf%vyz{f-&tp_(QZrn4L7pCD_>Yv=WD9s z?T@^CUHvs0G!UPj@MUD}9>U)9nN)ny`tF~0YWw%OweLh*A~r4a%l54*Xiz#~sO9W3 z`1i1U1LubeW3qVa2&@?&hgw%ZDghcj=1{nJ=dn?ls|3sWt4;lLX0=se%v)2T?)oWN zq76T01FR$#&e;Li&u2dL>3jR4HeM)br3c5?zV1!yy`c+^@Q&}TSrd({Lp8rYe%tL^ zOExLWFx;o@UteeBi>mno=<+Wj?0)R!FSMoZ!zG?m}F0(t-4E zjm2B!385+SN&mrr@6%7x{|ML9laE9evQemH66VRH7+Ja<2_#dAlAC8A9w zv8E;d%^NtmgnMA68j{}{Kh&aF7d>gQllB?MUsi7viP=%?+p>FE%v3sZTO+^wWKZnI z#6i9(N4`R9SxVX zN72d7;(c_(S0IkT&wHacSXgz^?-JYv$^2X!Io_f=71r3|7eRa3+;hr?6 znC4Xt(Rb#ONu$1M%Jh&w_v7A|<4Hwfo^k1^2gEm0>#~cn<7%#rM&0^1qdXHTUON{6 zft#KJxQ_Zx*|*mhWE~-I>`nXRkv&hI9=7V^@w*@u?vE9Cl4S6XxycXH2J3sDo{{RM zKS~V2m^{yaDe1SU@@L~ubPUwHGyiYZlI8m~GOO$Au7JMS%RLU(t1Xlm0Jyg*S}XY$ z#;BXezRi-yJ@3ALQ#*zkDSGo((_SBtFbv=ggSJgh50VqyMBiC#Pj)H)efoBp%d^34DRw#R6yW|c=Ye00=lhe^_$ff;8 zrjp(xavH@BL2XyQ;Y0U9TH^(C9d}t1F-E=cFos! zmq;cjq-a~-G~a37)NR&bH8dQ5hc?}Aa;aPGP#N?6vc&YWM8iKJzVjsm9-e;3=M`n- zwl>Z*h|Fau|5d7vw`}HmME^)P1(-mHbK73D22a$vCBA)uHZED2E=Z_;3&I28R3dNu|5f zyOi+oYURscpGvqC00i$*ZDsVwtts!-`A?K@7f~=6%!|yi<`4b zs4i(?6FPzyuvyeO=h3uqw$MvS?k|SnV4UQYQn~p0fl_yP1*I(A-fgg6+IQ6DhsA>> z6W{Btl-A5u-2b>;+a3VPxayksJ11m7*_nwb{iTrBdCaL*(QW)IXCj_GyEZ5u<}Zq) zWC;8O^0Ryw@Gs>X6TiPyA3CRZ?aOS3dcwCf!{)bZowcv4s>^GA`R@@^y%uu+kw4%D zwg5ts&jv(e8z?apR1b<2pM0nW+XY>ZF-mz6_w`7`y*&ZhhWttP85y4=%;?Ndk8Ks4 zDzj1Ka)iXcMEXY(U?=B0h&g+bg`^YH(^+mc^!zUH__qfr1-+Jrkcf{zwP&G2w_T4E znIMu4pS^FeTK?WF)cbhthDLuJ|CxN5WGTsZ;l( zMeWs|ihRB^0F&JDZLLP65&gA4{86AONZ9aXP2Yhd&va@6oE&`n)0T=-Vj|SWYpbkl z@4vsx4r>^-bq5+b&{g*EKQ{g&RR}QpKx~^DIw7s8tD*e+^XarapAV0JN09G=m&?BG znOtt3%;$Wl?Yb4XW&ir3l1F37LDpIG=h1GSLFk+2+}*PG)Bdq7bW`_c=ycMypOzKT zk}=+BC9Nzk#cTPE8TMU^KhoxDY@uK0`>j}JMayii)xT8lghbSd$A31q6pO$3D5$e* zxY$nhjF&jmkL;#qR?Gj7$|onN$Y>06#NOW|p)I$IlrO2Kr6C2fPP!lO2a1I!oWRvN z#VpC*l7KkX9|5X-M)liSQU6_rwy z_I-Tt#5|{0zy8_o9&yuouHo#5Pwawc-sf#F)9((_EnI6JQ+=(^(`oIfBA@gwh__$<4(y+99bNc_(oc@{Z>i5lmu$!

sx(`XhD19}o~mqy=T|k8v^Lz8;gyxDqCn=ueRaw} zSF{`SwfBkA@#9Z#FAf$y!xoi!CCFSJ(>m_>_im_@tZHE*o+#FVN9vay@X|n23cjbbtV8UW4?W^vo zv4GXC+>f)fZEUNSM$1~s*|5$fN@!x}W^UaA(wPYYwI}|HlKs;y7HXz3o2PfR`nT}) zbCY@|xIX{>?+U#N6$j^7Ifv1f$3ejC*1tM=OH&l99nAR{6vIP zxoKXssCafdtnaIS|255K9jpe8iXna~oH&b6J=7wEnE-Vax~pLNjBe~m2rNIFQv@h~ z54h5hCkg=jiG(&&wB>#g>CbD;Cc-}k-+kO7SsNr0{@AQwLi*8f%n)h4%c^+d{P)vw z-;ytBcW|+M2Fiebt_4uPFKT0)a*Pr$bGfhCDIsj3?A@5gSAKM5?4yZ|;h$auufAn2 zR82}4WUsmxZGZh|aV~H7^kMnA{7AjL!zx-p6oINLKr>{(3W@tBAHG<#`Mmx16`I+d zOD@+U1!{Sd@LII>A_P9%eFY^$?7e8sf7?};)UPvqXXv^1N1P^*V&k6UQY3_M6Np@LZ(Rk)Id<-2UDzzaSx8q9JoPT08j|UwvlD?!mc3GfxS( zIZYh0dPy(Q8o=o$V^GU)*0~Mi&YDhi!G-wn}Xp8yjRhe$$NR$tLr@Tm#4Cv@%HLXKOcRIULHCqjB(BBV?Y_B$KJ@sEg za|17wAt@k#;CJSR^kmr+DH5Iknx;;Cmq%03~hF>E+^ zPqPCgOp*b)KiqY_Q(~X+W68f(1MIU0vlg@quzcN%lpENefQjVm4gXG;6*v#8>kH34 z3R&Dzu9>Zzl#$Q8pMA6?7l=ivNmIab2M{f>!6dk*hLY%u3)RWJ4_1+DqXs|3kHg9m z&XklKwaxF|VZE5EU{BfWB1ALaI;pq^X`8F=UmIH5r==Gw03X=w_N4pv1P99U2>N9&bmk{oMaU zT2Z1KFF-smK4*J8n}GLnr~ z-J;WL|Lt)_Jii^16B&E)OPbiU<}hc8rm-b66QCAwXw!`=GCA5J?VVSeFQRx?j~AQg zG^`@IMqT?6T|bYPG+A3SQZ3)OX0CWDEU8J?m>rooF0|+OdhkTM_QsK2jPV8y_MrX? z*=dg{Q$Yp8bK)U2JJzq?T?g|u#`;75A0^rNi*FfFLNov}HrWT*I-&}RmJb8IXlxaf z)Mx=+!JQ0zc?>sPtPt!dCwe(1_y^yq370<5=O$OAr;DI?01=%z@( zXdOPBA2D1y*P$Ske|*=ly6)CF>Op83Q|ZfvN#U&9t~^J<$&(t6)NapP-^9?9c@saB z-HJA6)CzzO@6=P;-=XX0^2T25MVR-bZrY!Q_M=n-S(mP97Rjz$Z%qSljhX(wc zG+;U(kJ_YGqi0myZ9&AO1h`y05HSAJ+n%U*=*rBG5&lgNk0PI&;pf$#t=O1xM(pmq zTPGZvC|;Ns0YqL3$vJ*kcX*rJoKm(*4_OU}wC9mbIz&@L0(jI-%snR~av!6?JsT=3 z2netC@(ipSFz1e@-s6V+ic6{EJy`hkT`9Q2L_&4Ko^C;6H!5o%4GB9*iEXjOGZfPS z7P&w8eH;C+ri`lU_Kb`S@?9^hv&#AB&WWOUKi^P}d?NA>^z5^7O4ZXBO>DEX(;R=9 z;_P4A+5WldYQwpqzF*4P@01s%*Vgr$9?eUBlTXMx+3Fy$LYOFBC{uVWTe86)Nxi53 z>g`|ZmpeJMy)X&kld@OeHi%&einsS9cH|pkIDcekfWochfLh^w+QkLqO4n8FfH}H zn3xzkQDm!zxw+cZ)D(1268_At=j~_r)xLJFKB^M8zDQ;D-o4iTZmts~?q6)t9gl8O z25GQ2_U~==Tr`*NIbFZqNRrfeq}biE5Zs859zS*wJA0KG5fTd#ey z>OU}SsBloPoDR5|e$+x+_yf(^g>k3-$Lo#XTt>;qt&ZimIUAxpHY`#INOdqhJRF0(svM6S4C5 zSFt7`OZENjBgp;KYE@O!NSQ09Q6d)n*)6sOU|}$BtWmIW@){=X#oI#UMKd>bD)dEj zv-^>;-XHIZcK>{Ham&FY58ud$ho-(?>ZzhOw`Qxm7d~LV6^V(7QAPwPe{6^s#8psA zC6Cre%D%gd+VYq?Dq`uHI0F3m~3Z*^PwZeSe-g zmNGN+fvNZa;`iH&I8;2V-K5sKDle=p*ne%C_48kCLU!?6yE|XAOJ=bA#dinwuAbQB z(DSW1(O!E+tuGj~w;9TLE9jI|f)3~Tym>Ad&G&Tcj-UDZcy=V);{KR`|M$=LS7&y= zFSSv^Xa2oUYZhY-)T53-D%|dA9f4ayvyuiO9)h>XLGW<)0h2MT1_VE!HLalMOtev# z6WnRsxYUD@kd*Y+S0MI6%*MyMN&-^6+w*j10YT_u^H?`Yxxqgga%6h%`G{k& zSg^_6<2Na#SKaP;lgn&X3XDUvR4w9EcN!kwUMF&&(XrDwY(hc%_n3^`6;l;CIXO4K z+(WAGu9$5r!JqLKRa?}YBc%Dh%2Y{ssNL0iUY74%>6|hCYVh9i_0`2MrzFa#5{id= z2>U6{_CcVb*yLrEvgMxJNAgjlDx?)LAb>%m?N8uQ%U4P^5~qT;x_4WITCuNOWDg9K zYWrF|EmZf4J(t|S@g@qa_$OjAMuF?t;u+*rJe8{qq+4=ZtS4@X3+pWJMh4NbiZA>7 z3vUZm{PB}O9i1=l%#c46jx{HxVX#fy!Y;e7SDfm<5iH9+{`3^@+9cH&k zvS})vz8#wqH1xq$elfdov^ceXgt}4YGre@I@3zR=oyXc(3$Jbq{`nd)CV-dfvhiny z?9LP@0`vr;&+o3wlWj`3z!y=gkG4OVo9NNsx!8J6A@lO2_Qoog{}Ip6CHm|-qc@y= z6P1;fbt~{|%W3zlR!1HfIrVb(dvlMzI(v^s zsPe<0Z<4o*ND9q3>HB>#m4j(Z>&51l8<&8e`Q~v}-ZDwL(yQ(=k4Av1erO z{d$?R(zfUsO)Pu;N05Er-SX!NK z@pD`|icqwF3`;ZXPI;*Ps!k*+=^f}HfKt0ItTsoS*sg3|=}iM^s65>Je9cHhBcb%e zwOOjri%;XHS8Pt6J5!|pWA@;d;(>v*AEH6JXkF#Sh`?6dy3{@K$?H4L<@_znSdCfe zG#<`QR+r{jwr(rF$S$t^pkWQ{4}9EQax^k_(%+fd1c3RM3{;)UPVN4J#_S)vd4C;0 z0K<8r{r4qsHehkQ_n!90#BpT&#@~I~O(%x4yJBs1`<}&`EKK)Pf6ZNak}d#dQu!X? zx$24TjK>?w;{^1X-Tn|oP=-a>lGhGFbDH#*`0zRlVJghj%+y!l zvCaSqp^}ZLSEgRgD!i!_!L|RL1&BthfsRAT4k96%@!7(J52Gu8BpR&)dn#S70fFrk zqNk#9XA(es+}bQD$Tug2XB?vpB;7%F3!<|(Wtz7tv6<6wyXg{d8Pt^K|;j^xl#5)B@eM9f;?e`wu+GJhf7C(oZT6 z0k{Nh#kR={+uO&?NiU3fId|M~G`Aq8RF2nT5_*icvNCnub>bQCq{L9Sm&ooN$38E9 z`85zL-W%I<`S#vqzU!{M5$%*`(wT)KT5G?*2tYsdUKGidot_;@^=nb4Qi&kpQmxG- z?l71BC_u(CDVx6Q&oqD#g*T5CRD1jTcbKC^sj{;c8n&Zdx&<>MGc)f%Puv_E9xFM8 z<(%_5Qt&6cu|xxiC?d!qZxz!kXAN1v))#Kyi|5a+VkOqn4G%W2R+@p>fg>XchHwmj zw!q%>HnHbf^~Tjd$vebm9GkQWFC~FR{L=;8*MBk^b<1g9l@iqfL+fcT_VFvbB#-qQ zz=t{YbG*rfo5sg_q;&W(qAwnWclpUUCDy-MZ+`B-|ryH5ODEPvDjPuJA+QUJCUEVM`UhB-v~ zH)XLA+44xPOtsO_fH)>Oy-~B`KuK1qe0=7t&m}KrvZo#Yvr$^huU9dNO8B*g))2OF zyM&{c౽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 JK}T3e^eSCj^Vig{nEf3 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 \ No newline at end of file diff --git a/lambda-functions/build.gradle b/lambda-functions/build.gradle deleted file mode 100644 index 63e69981..00000000 --- a/lambda-functions/build.gradle +++ /dev/null @@ -1,49 +0,0 @@ -apply plugin: 'java' -apply plugin: 'distribution' - -repositories { - jcenter() - maven { - url 'https://oss.sonatype.org/content/repositories/snapshots' - } -} - -dependencies { - compile 'org.slf4j:slf4j-api:1.7.14' - compile 'log4j:log4j:1.2.17' - compile 'com.amazonaws:aws-lambda-java-core:1.1.0' - compile 'com.amazonaws:aws-lambda-java-events:1.3.0' - compile 'com.amazonaws:aws-lambda-java-log4j:1.0.0' - compile 'com.amazonaws:aws-java-sdk-s3:1.10.62' - compile 'com.amazonaws:aws-dynamodb-encryption-java:0.0.5-SNAPSHOT' - compile 'com.fasterxml.jackson.core:jackson-core:2.7.4' - compile 'com.fasterxml.jackson.core:jackson-annotations:2.7.4' - compile 'com.fasterxml.jackson.core:jackson-databind:2.7.4' -} - -sourceSets { - main { - java { - srcDir 'src/java' - } - resources { - srcDir 'src/resources' - } - } -} - -distributions { - main { - contents { - into('/') { - from compileJava - } - into('/') { - from 'src/main/resources' - } - into('/lib') { - from configurations.runtime - } - } - } -} diff --git a/lambda-functions/gradle/wrapper/gradle-wrapper.jar b/lambda-functions/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index 5ccda13e9cb94678ba179b32452cf3d60dc36353..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 53638 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

i1O>I_eGKr^&y;^hm-V94&lC5U~@}s zTa5yXP^(8uV=BE6Dt+3? zLaE_7J031uGhDE7|b&w1(^tMyMTy4Ezl&wqN)pF3smtPe#|`O&uE&FOFx;Lv>2r%dWXcsINN|d3 zzSwY&aG}}M#$$|eJ8DI()+D|nV{jSt3X)A(IZ~?lt!A^rpo2z1YmxIU1%lU1G1LL~ z*NFxe{FkBI2+QE9yNI%iZ4g#m1ZMFxYZ10u>`lpV;=aIeh-*?bt~GKz2fOafq^Y$g zM%cM+$NDj_M%^@usUdiHr=2peP3Y;}A%428!?wZ1t|ZvKyDc#F=ivQGFk$n$Auf_1 z)(P76l5Zj$=3GpYqa$Aox}1I>2ghWjQ1cu{;^*$HRyLhx?bh*pJ)4^KC^Ti5mx3`! zvq54tCQ8wFggOQH(f{j65`l?f+7=HujTDJq%0}^CEPFKPxXlgtM$%#TJB|{_HY7WM zd$9`r31iNC3?q53j|V!gg-2DPTYgyV?leMeIyCC^Np)_#+#JG5N3RiYT!$iODzD5A z6YnL^J_$q67@&_DHO|XTjV7qNFUdl}?SlKC>tG$*#ANykA3n~@zH4~pwrK89kqYs~ zSO`7+bxlDW-G#QxDvwT)N*#jp0`2Cv3kbvct-1AYh_@X*vgtjvB$wGRQ#=vi{<*DF z1Bh84t}k!gqK!CAPYZQId%Xi0FyxOvKCz5?x@0X*=|yS_6KV?iX!k=J;1;lT=_hz} zV=L5=<93TT&R^bhK#YsG)gu;R$yf4TTK0PRo?+jke){Nn3knK58FEQES$;Py4-j^G z(PxSif~@1(9Q{#mzxI-64DH)s7Yo=8Z4%`WBkwkcvPME0UF!K%y(a+o`}UBc@#`*3by8Z}nAp3?M6pH77=*ri%#`G z1QtkLqKP)tWpQF`2<_A6X&3Kj6}edvVlZF^2D?8dS3ObqWNwC zOIenicvYA65wda#o|w6m)lF2wmrdDsn*hSj7*D6e69S?j~z zk(0vUKo34jm&;RR0=U1()qpAHGe6dN0{eTD(xaRBK?0hy1z;><{QXNjG8ek|8)3)_iqnw}LZMK$v> zcP>Dw%~pt!=i@kesT1!3flkCj`90M=pS#q`TcWS1IWH~c(~TT<0|S<;Ugeay#fHqM zJQ3xWaJNGVCn0LC2t=mGq6n`!LbUHW_?@c_v%j(>0%{`@K^ll2$dZF9Bt}qbpR27XI#7 z;U+c(3^sbw9~!92lm}5O2ccjQ zWP+fnk`Y);H#FHdqQ1i4ozrAYV;T`diRyj;tH8UHfkT-Ix!6wNNo0_jkx3`w-eb56PVT}m! z1Lohml~_I04KHA|F$Dcv`{S?O>Ob2`|7Edp^m^(Q(xaRvR35Mtcn^=BVK#j&IC7;6 zwepMCRBECVZ*qEYsG#E^&e1)9)tfu>s&RL^@i2C- zY2oU4^msrZ3KB&g4Pl)`L7(sok*F4A2(ppxtn^vZVxbZ0O!nac=@WIal@ADc03m>q zV4O~~o)#h+8qb)F)FLw;C~+l^8!d^=Yp)_UFQHae#4vcDjb&R+pvpGW+Rr$1Wbs%L zNUlR)6%L3w?Gkri+%?_>exZp=8+NXwGZ^2KHCbclCCRST&H%%^tQ}O}W3I?Vq5S@> zQN2o74BTWy0ks%2111cNdL-vP7AHdR=A{uLeYB*kD;b<@b0U*rw~TdaaXGXEbM0cQ zCpxukm)k;`M*P}PY-{Nb3HH>hI;~c%$-QG&Z1%hL#7I1OEo=f$u9a%aLMb6>Ov9P> zHcM2@&oRJoN5)?^GP=XndpF!Lccdd2{Na2x#+X8|J%iMsr zBE*WMg3U}K_*z&RWY`E(;yLc7IZ3h+#;7TT>kAZAX&28F;W7`=bw*n`uO3woyC?-kF~99-?(=qf@bQg@*mw8t;f@}$ zFUn4>$|}JG@pYra1v+m$O1NV#np*C1*N1RlgXts+LJo3Eh+lI?hx_zS7?5Chqc@L= zee;2E38C=ITE7R8zok9Yy7j%kd(8TLCa8uGTR=SEG|^ZCX`8Zi^05UECSKxjO_luw zuW@tF=a@@l0k*cr5Nw`yI3wG0!j}hHOPr(WV-s#q6V*NyNF#JZ^!rN;Z5c(AuX%5K zM-cSO_ktIMpc`#~USvnFC-;Jvec-|rnjbRj2xRhs6``a89lhK}}SIMA??Kul-qE0}hn{(s5eKb02g3JKp&&xim%gn-Wv834dh_x}$G z07k`MB>;B*7Wf|+1Pv4*Tn+^Vgafef0eJr}>OEj|_;Dctod|yaDl4iaNG~ZT1~{ny z_hiPcx7GrHe^3T|&;Xx5uRP!HZ;StzOjb}%QcP4)>4mJ=Ul9kQyS`Xl-G zTdglXVEy$yuRoao1o%@H>d#2)&vVqf8aRsvnCEQ(CI8<1e_pk|z<|I06X5ez9bkRu z;wtFqU;>DJYpVYkP=lZx|*2EW;l!na}aSiriMT z0C`maq^AEHo-V*v{2M%RJ6!_{LuEktc?oMvLm6vpb4Q!sL*s2~FZBXI?g72@`-bqJ zSFJCg&;AY?5cAi<(H?NFOw826P*7jr(B58F*Gkv;Po;(Dc^`q-5&IrN`wn2w`+MFA z0Gg)1$-C+w${YYwhmonVBOrV>Ae8*?^RD2DZ@L4h5^O-;8UM^U;9l!{kMvvU=j|y0 z5R0(*ZyC>bWo%XfWE==kJKsl)Kd)L}dW_$q%UB!#*IF2}I|@Sw_?cJ%%KZIU)Sp+a zZzn(l@aFu)k;e_Z9J6 za`#W@THgeKhRkm&;zzaqx8dix#Nkgo2f%>-8@#_Y{eKfYd|n&R<@0{Bc&YxD<)25K z=c+kB;b!0b-{kpY0RHU+`q%t@-`skxdh-*|`0an!z@pBQGKad+*WUQO z-kwWv{6s}D{1>SIAjt7N|Ieikelnq%{0pYPItl?wXY*I#gXdJwBhCM$0y6(6s{ayq z{yF^fkm*0+6D|G;{zpsvQvmhnRL^6t{-kPj_!m@vRNDWluIJ$^f08UZ{|l0T4rckh zM9-uB{UpP3|0lA)?nXbyd>(7+C+4ZoKVkl8kUyaR`0*sP<1z(dhrp`TODO zxlip+%+ERh8}m0Fw$C&D+=1~Y!FJxiAo%Mj_4^V$cOUyn&|mlq!Jo6lzcKtiFUfOv zkDo+7rN0pU(p~@QaeKan{u4Q<;uqv!JJTP~zu!=QUWn&gmp`dotN)4Wf6S%NckF(` zF4X)J?2iHCANKE_7vQ;F|0ffE<1b9l-yi-`cmJb6&uz^=X+AgoLh~2c9|ij3_77v< zbED%=eDT(Qf&br4kk9MuxgF&v(F(vY@W-1uU_tnMtI8{BaKKyOcXb&iAS6KOxwoC) GfBiq \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." -fi - -# Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi -fi - -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi - -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi - # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" - fi - i=$((i+1)) - done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac -fi - -# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules -function splitJvmOpts() { - JVM_OPTS=("$@") -} -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" - -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/lambda-functions/gradlew.bat b/lambda-functions/gradlew.bat deleted file mode 100644 index 72d362da..00000000 --- a/lambda-functions/gradlew.bat +++ /dev/null @@ -1,90 +0,0 @@ -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto init - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/lambda-functions/settings.gradle b/lambda-functions/settings.gradle deleted file mode 100644 index 94a93b57..00000000 --- a/lambda-functions/settings.gradle +++ /dev/null @@ -1,18 +0,0 @@ -/* - * This settings file was auto generated by the Gradle buildInit task - * by 'fsd' at '3/25/16 4:40 PM' with Gradle 2.11 - * - * The settings file is used to specify which projects to include in your build. - * In a single project build this file can be empty or even removed. - * - * Detailed information about configuring a multi-project build in Gradle can be found - * in the user guide at https://docs.gradle.org/2.11/userguide/multi_project_builds.html - */ - -/* -// To declare projects as part of a multi-project build use the 'include' method -include 'shared' -include 'api' -include 'services:webservice' -*/ -rootProject.name = 'serverless-web-refarch' diff --git a/lambda-functions/src/main/java/blog/comments/handlers/FindComment.java b/lambda-functions/src/main/java/blog/comments/handlers/FindComment.java deleted file mode 100644 index 6e27eaf6..00000000 --- a/lambda-functions/src/main/java/blog/comments/handlers/FindComment.java +++ /dev/null @@ -1,39 +0,0 @@ -package blog.comments.handlers; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.LambdaLogger; -import com.amazonaws.services.lambda.runtime.RequestStreamHandler; -import com.fasterxml.jackson.databind.ObjectMapper; - -import blog.comments.models.Comment; -import blog.comments.models.FindCommentRequest; -import blog.comments.repositories.DynamoDBCommentRepository; -import blog.configuration.ApplicationConfiguration; -import blog.configuration.ApplicationConfigurationStore; - -public class FindComment implements RequestStreamHandler { - - private ApplicationConfigurationStore applicationConfigurationStore = new ApplicationConfigurationStore(); - - @Override - public void handleRequest(InputStream input, OutputStream output, Context context) throws IOException { - LambdaLogger logger = context.getLogger(); - ObjectMapper mapper = new ObjectMapper(); - FindCommentRequest request = mapper.readValue(input, FindCommentRequest.class); - logger.log("request:\n" + request + "\n"); - - ApplicationConfiguration applicationConfiguration = applicationConfigurationStore - .getApplicationConfiguration(request.getRequestConfiguration()); - logger.log("applicationConfiguration:\n" + applicationConfiguration + "\n"); - - DynamoDBCommentRepository commentRepo = new DynamoDBCommentRepository(applicationConfiguration); - Comment comment = commentRepo.findComment(request.getPostId(), request.getDate()); - logger.log("comment:\n" + comment + "\n"); - - mapper.writeValue(output, comment); - } -} \ No newline at end of file diff --git a/lambda-functions/src/main/java/blog/comments/handlers/FindComments.java b/lambda-functions/src/main/java/blog/comments/handlers/FindComments.java deleted file mode 100644 index 33e69380..00000000 --- a/lambda-functions/src/main/java/blog/comments/handlers/FindComments.java +++ /dev/null @@ -1,40 +0,0 @@ -package blog.comments.handlers; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.List; - -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.LambdaLogger; -import com.amazonaws.services.lambda.runtime.RequestStreamHandler; -import com.fasterxml.jackson.databind.ObjectMapper; - -import blog.comments.models.Comment; -import blog.comments.models.FindCommentRequest; -import blog.comments.repositories.DynamoDBCommentRepository; -import blog.configuration.ApplicationConfiguration; -import blog.configuration.ApplicationConfigurationStore; - -public class FindComments implements RequestStreamHandler { - - private ApplicationConfigurationStore applicationConfigurationStore = new ApplicationConfigurationStore(); - - @Override - public void handleRequest(InputStream input, OutputStream output, Context context) throws IOException { - LambdaLogger logger = context.getLogger(); - ObjectMapper mapper = new ObjectMapper(); - FindCommentRequest request = mapper.readValue(input, FindCommentRequest.class); - logger.log("request:\n" + request + "\n"); - - ApplicationConfiguration applicationConfiguration = applicationConfigurationStore - .getApplicationConfiguration(request.getRequestConfiguration()); - logger.log("applicationConfiguration:\n" + applicationConfiguration + "\n"); - - DynamoDBCommentRepository commentRepo = new DynamoDBCommentRepository(applicationConfiguration); - List comments = commentRepo.findCommentsByPost(request.getPostId()); - logger.log("comments:\n" + comments + "\n"); - - mapper.writeValue(output, comments); - } -} \ No newline at end of file diff --git a/lambda-functions/src/main/java/blog/comments/handlers/SaveComment.java b/lambda-functions/src/main/java/blog/comments/handlers/SaveComment.java deleted file mode 100644 index 0dc1b0cf..00000000 --- a/lambda-functions/src/main/java/blog/comments/handlers/SaveComment.java +++ /dev/null @@ -1,38 +0,0 @@ -package blog.comments.handlers; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.LambdaLogger; -import com.amazonaws.services.lambda.runtime.RequestStreamHandler; -import com.fasterxml.jackson.databind.ObjectMapper; - -import blog.comments.models.Comment; -import blog.comments.repositories.DynamoDBCommentRepository; -import blog.configuration.ApplicationConfiguration; -import blog.configuration.ApplicationConfigurationStore; - -public class SaveComment implements RequestStreamHandler { - - private ApplicationConfigurationStore applicationConfigurationStore = new ApplicationConfigurationStore(); - - @Override - public void handleRequest(InputStream input, OutputStream output, Context context) throws IOException { - LambdaLogger logger = context.getLogger(); - ObjectMapper mapper = new ObjectMapper(); - Comment comment = mapper.readValue(input, Comment.class); - logger.log("comment:\n" + comment + "\n"); - - ApplicationConfiguration applicationConfiguration = applicationConfigurationStore - .getApplicationConfiguration(comment.getRequestConfiguration()); - logger.log("applicationConfiguration:\n" + applicationConfiguration + "\n"); - - DynamoDBCommentRepository commentRepo = new DynamoDBCommentRepository(applicationConfiguration); - Comment savedComment = commentRepo.saveComment(comment); - logger.log("savedComment:\n" + savedComment + "\n"); - - mapper.writeValue(output, savedComment); - } -} \ No newline at end of file diff --git a/lambda-functions/src/main/java/blog/comments/models/Comment.java b/lambda-functions/src/main/java/blog/comments/models/Comment.java deleted file mode 100644 index 61475c00..00000000 --- a/lambda-functions/src/main/java/blog/comments/models/Comment.java +++ /dev/null @@ -1,81 +0,0 @@ -package blog.comments.models; - -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAttribute; -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBHashKey; -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBIgnore; -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBRangeKey; -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTable; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; - -import blog.configuration.RequestConfiguration; - -@DynamoDBTable(tableName = "REPLACED_BY_API") -public class Comment { - - private String postId; - private long createdAt; - private String message; - private String email; - private RequestConfiguration requestConfiguration; - - @DynamoDBHashKey(attributeName = "postId") - public String getPostId() { - return postId; - } - - public void setPostId(String postId) { - this.postId = postId; - } - - @DynamoDBRangeKey(attributeName = "created_at") - public long getCreatedAt() { - return createdAt; - } - - public void setCreatedAt(long createdAt) { - this.createdAt = createdAt; - } - - @DynamoDBAttribute - public String getMessage() { - return message; - } - - public void setMessage(String message) { - this.message = message; - } - - @DynamoDBAttribute - public String getEmail() { - return email; - } - - public void setEmail(String email) { - this.email = email; - } - - @DynamoDBIgnore - @JsonIgnore - public RequestConfiguration getRequestConfiguration() { - return requestConfiguration; - } - - @JsonSetter("config") - public void setRequestConfiguration(RequestConfiguration requestConfiguration) { - this.requestConfiguration = requestConfiguration; - } - - - @Override - public String toString() { - ObjectMapper mapper = new ObjectMapper(); - try { - return mapper.writeValueAsString(this); - } catch (JsonProcessingException e) { - return "error"; - } - } -} \ No newline at end of file diff --git a/lambda-functions/src/main/java/blog/comments/models/FindCommentRequest.java b/lambda-functions/src/main/java/blog/comments/models/FindCommentRequest.java deleted file mode 100644 index 76703c68..00000000 --- a/lambda-functions/src/main/java/blog/comments/models/FindCommentRequest.java +++ /dev/null @@ -1,51 +0,0 @@ -package blog.comments.models; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; - -import blog.configuration.RequestConfiguration; - -public class FindCommentRequest { - - private String postId; - private long date; - private RequestConfiguration requestConfiguration; - - public String getPostId() { - return postId; - } - - public void setPostId(String postId) { - this.postId = postId; - } - - public long getDate() { - return date; - } - - public void setDate(long date) { - this.date = date; - } - - @JsonIgnore - public RequestConfiguration getRequestConfiguration() { - return requestConfiguration; - } - - @JsonSetter("config") - public void setRequestConfiguration(RequestConfiguration requestConfiguration) { - this.requestConfiguration = requestConfiguration; - } - - @Override - public String toString() { - ObjectMapper mapper = new ObjectMapper(); - try { - return mapper.writeValueAsString(this); - } catch (JsonProcessingException e) { - return "error"; - } - } -} \ No newline at end of file diff --git a/lambda-functions/src/main/java/blog/comments/repositories/DynamoDBCommentRepository.java b/lambda-functions/src/main/java/blog/comments/repositories/DynamoDBCommentRepository.java deleted file mode 100644 index e2dbbd1a..00000000 --- a/lambda-functions/src/main/java/blog/comments/repositories/DynamoDBCommentRepository.java +++ /dev/null @@ -1,47 +0,0 @@ -package blog.comments.repositories; - -import java.util.Date; -import java.util.List; - -import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient; -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper; -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapperConfig; -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapperConfig.TableNameOverride; -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBQueryExpression; - -import blog.comments.models.Comment; -import blog.configuration.ApplicationConfiguration; - -public class DynamoDBCommentRepository { - - final private static int DEFAULT_LIMIT = 200; - - private DynamoDBMapper mapper; - - public DynamoDBCommentRepository(ApplicationConfiguration configuration) { - mapper = new DynamoDBMapper(new AmazonDynamoDBClient(), - new DynamoDBMapperConfig(new TableNameOverride(configuration.getCommentDynamoDBTableName()))); - } - - public List findCommentsByPost(String id) { - Comment comment = new Comment(); - comment.setPostId(id); - DynamoDBQueryExpression queryExpression = new DynamoDBQueryExpression() - .withHashKeyValues(comment); - queryExpression.withLimit(DEFAULT_LIMIT); - - List commentList = mapper.query(Comment.class, queryExpression); - return commentList; - } - - public Comment findComment(String id, long date) { - return mapper.load(Comment.class, id, date); - } - - public Comment saveComment(Comment comment) { - comment.setCreatedAt(new Date().getTime()); - mapper.save(comment); - - return mapper.load(comment); - } -} \ No newline at end of file diff --git a/lambda-functions/src/main/java/blog/configuration/ApplicationConfiguration.java b/lambda-functions/src/main/java/blog/configuration/ApplicationConfiguration.java deleted file mode 100644 index 60c959e0..00000000 --- a/lambda-functions/src/main/java/blog/configuration/ApplicationConfiguration.java +++ /dev/null @@ -1,111 +0,0 @@ -package blog.configuration; -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAttribute; -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBHashKey; -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTable; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; - -@DynamoDBTable(tableName = "aws-serverless-config") -public class ApplicationConfiguration { - - private String environment; - private String userDynamoDBTableName; - private String postDynamoDBTableName; - private String latestPostDynamoDBTableName; - private String commentDynamoDBTableName; - private String forumDynamoDBTableName; - private String cognitoIdentityPoolId; - private String cognitoDeveloperId; - private String encryptionKeyId; - - @DynamoDBHashKey(attributeName = "environment") - public String getEnvironment() { - return environment; - } - - public void setEnvironment(String environment) { - this.environment = environment; - } - - @DynamoDBAttribute(attributeName = "user_ddb_table_name") - public String getUserDynamoDBTableName() { - return userDynamoDBTableName; - } - - public void setUserDynamoDBTableName(String userDynamoDBTableName) { - this.userDynamoDBTableName = userDynamoDBTableName; - } - - @DynamoDBAttribute(attributeName = "post_ddb_table_name") - public String getPostDynamoDBTableName() { - return postDynamoDBTableName; - } - - public void setPostDynamoDBTableName(String postDynamoDBTableName) { - this.postDynamoDBTableName = postDynamoDBTableName; - } - - @DynamoDBAttribute(attributeName = "latest_post_ddb_table_name") - public String getLatestPostDynamoDBTableName() { - return latestPostDynamoDBTableName; - } - - public void setLatestPostDynamoDBTableName(String latestPostDynamoDBTableName) { - this.latestPostDynamoDBTableName = latestPostDynamoDBTableName; - } - - @DynamoDBAttribute(attributeName = "comment_ddb_table_name") - public String getCommentDynamoDBTableName() { - return commentDynamoDBTableName; - } - - public void setCommentDynamoDBTableName(String commentDynamoDBTableName) { - this.commentDynamoDBTableName = commentDynamoDBTableName; - } - - @DynamoDBAttribute(attributeName = "cognito_identity_pool_id") - public String getCognitoIdentityPoolId() { - return cognitoIdentityPoolId; - } - - public void setCognitoIdentityPoolId(String cognitoIdentityPoolId) { - this.cognitoIdentityPoolId = cognitoIdentityPoolId; - } - - @DynamoDBAttribute(attributeName = "cognito_developer_id") - public String getCognitoDeveloperId() { - return cognitoDeveloperId; - } - - public void setCognitoDeveloperId(String cognitoDeveloperId) { - this.cognitoDeveloperId = cognitoDeveloperId; - } - - @DynamoDBAttribute(attributeName = "encryption_key_id") - public String getEncryptionKeyId() { - return encryptionKeyId; - } - - public void setEncryptionKeyId(String encryptionKeyId) { - this.encryptionKeyId = encryptionKeyId; - } - - @DynamoDBAttribute(attributeName = "forum_ddb_table_name") - public String getForumDynamoDBTableName() { - return forumDynamoDBTableName; - } - - public void setForumDynamoDBTableName(String forumDynamoDBTableName) { - this.forumDynamoDBTableName = forumDynamoDBTableName; - } - - @Override - public String toString() { - ObjectMapper mapper = new ObjectMapper(); - try { - return mapper.writeValueAsString(this); - } catch (JsonProcessingException e) { - return "error"; - } - } -} \ No newline at end of file diff --git a/lambda-functions/src/main/java/blog/configuration/ApplicationConfigurationStore.java b/lambda-functions/src/main/java/blog/configuration/ApplicationConfigurationStore.java deleted file mode 100644 index c2019490..00000000 --- a/lambda-functions/src/main/java/blog/configuration/ApplicationConfigurationStore.java +++ /dev/null @@ -1,29 +0,0 @@ -package blog.configuration; -import java.util.HashMap; -import java.util.Map; - -import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient; -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper; - -public class ApplicationConfigurationStore { - - private Map configurations; - private static DynamoDBMapper mapper = new DynamoDBMapper(new AmazonDynamoDBClient()); - - public ApplicationConfigurationStore() { - configurations = new HashMap(); - } - - public ApplicationConfiguration getApplicationConfiguration(RequestConfiguration requestConfiguration) { - String stageName = requestConfiguration.getStageName(); - - if (configurations.get(stageName) == null) { - configurations.put(stageName, getApplicationConfigurationFromDynamoDB(stageName)); - } - return configurations.get(stageName); - } - - private ApplicationConfiguration getApplicationConfigurationFromDynamoDB(String stageName) { - return mapper.load(ApplicationConfiguration.class, stageName); - } -} \ No newline at end of file diff --git a/lambda-functions/src/main/java/blog/configuration/RequestConfiguration.java b/lambda-functions/src/main/java/blog/configuration/RequestConfiguration.java deleted file mode 100644 index 125baa4b..00000000 --- a/lambda-functions/src/main/java/blog/configuration/RequestConfiguration.java +++ /dev/null @@ -1,28 +0,0 @@ -package blog.configuration; -import com.fasterxml.jackson.annotation.JsonRootName; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; - -@JsonRootName(value = "config") -public class RequestConfiguration { - - private String stageName; - - public String getStageName() { - return stageName; - } - - public void setStageName(String stageName) { - this.stageName = stageName; - } - - @Override - public String toString() { - ObjectMapper mapper = new ObjectMapper(); - try { - return mapper.writeValueAsString(this); - } catch (JsonProcessingException e) { - return "error"; - } - } -} \ No newline at end of file diff --git a/lambda-functions/src/main/java/blog/forums/handlers/FindForums.java b/lambda-functions/src/main/java/blog/forums/handlers/FindForums.java deleted file mode 100644 index 6d91f9f0..00000000 --- a/lambda-functions/src/main/java/blog/forums/handlers/FindForums.java +++ /dev/null @@ -1,36 +0,0 @@ -package blog.forums.handlers; - -import java.util.List; - -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.LambdaLogger; -import com.amazonaws.services.lambda.runtime.RequestHandler; - -import blog.configuration.ApplicationConfiguration; -import blog.configuration.ApplicationConfigurationStore; -import blog.forums.models.FindForumsRequest; -import blog.forums.models.Forum; -import blog.forums.repositories.DynamoDBForumRepository; - - -public class FindForums implements RequestHandler >{ - - private ApplicationConfigurationStore applicationConfigurationStore = new ApplicationConfigurationStore(); - - @Override - public List handleRequest(FindForumsRequest request, Context context){ - LambdaLogger logger = context.getLogger(); - - logger.log("request:\n" + request + "\n"); - logger.log("requestconfig: " + request); - ApplicationConfiguration applicationConfiguration = applicationConfigurationStore - .getApplicationConfiguration(request.getConfig()); - logger.log("applicationConfiguration:\n" + applicationConfiguration + "\n"); - - DynamoDBForumRepository forumRepo = new DynamoDBForumRepository(applicationConfiguration); - return forumRepo.findForums(); - //logger.log("posts:\n" + posts + "\n"); - - //mapper.writeValue(output, posts); - } -} diff --git a/lambda-functions/src/main/java/blog/forums/models/FindForumsRequest.java b/lambda-functions/src/main/java/blog/forums/models/FindForumsRequest.java deleted file mode 100644 index 15d7b289..00000000 --- a/lambda-functions/src/main/java/blog/forums/models/FindForumsRequest.java +++ /dev/null @@ -1,30 +0,0 @@ -package blog.forums.models; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; - -import blog.configuration.RequestConfiguration; - -public class FindForumsRequest { - - private RequestConfiguration config; - - public RequestConfiguration getConfig() { - return config; - } - - public void setConfig(RequestConfiguration config) { - this.config = config; - } - - @Override - public String toString() { - ObjectMapper mapper = new ObjectMapper(); - try { - return mapper.writeValueAsString(this); - } catch (JsonProcessingException e) { - return "error"; - } - } - -} diff --git a/lambda-functions/src/main/java/blog/forums/models/Forum.java b/lambda-functions/src/main/java/blog/forums/models/Forum.java deleted file mode 100644 index 2f5807ff..00000000 --- a/lambda-functions/src/main/java/blog/forums/models/Forum.java +++ /dev/null @@ -1,59 +0,0 @@ -package blog.forums.models; - -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAttribute; -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBHashKey; -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBIgnore; -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTable; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; - -import blog.configuration.RequestConfiguration; - -@DynamoDBTable(tableName = "REPLACED_BY_API") -public class Forum { - - private String id; - private String name; - private RequestConfiguration requestConfiguration; - - @DynamoDBHashKey - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - @DynamoDBAttribute - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - @DynamoDBIgnore - @JsonIgnore - public RequestConfiguration getRequestConfiguration() { - return requestConfiguration; - } - - @JsonSetter("config") - public void setRequestConfiguration(RequestConfiguration requestConfiguration) { - this.requestConfiguration = requestConfiguration; - } - - @Override - public String toString() { - ObjectMapper mapper = new ObjectMapper(); - try { - return mapper.writeValueAsString(this); - } catch (JsonProcessingException e) { - return "error"; - } - } -} \ No newline at end of file diff --git a/lambda-functions/src/main/java/blog/forums/repositories/DynamoDBForumRepository.java b/lambda-functions/src/main/java/blog/forums/repositories/DynamoDBForumRepository.java deleted file mode 100644 index c2560843..00000000 --- a/lambda-functions/src/main/java/blog/forums/repositories/DynamoDBForumRepository.java +++ /dev/null @@ -1,31 +0,0 @@ -package blog.forums.repositories; - -import java.util.List; - -import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient; -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper; -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapperConfig; -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapperConfig.TableNameOverride; -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBScanExpression; - -import blog.configuration.ApplicationConfiguration; -import blog.forums.models.Forum; - -public class DynamoDBForumRepository { - - - private DynamoDBMapper mapper; - - public DynamoDBForumRepository(ApplicationConfiguration configuration) { - mapper = new DynamoDBMapper(new AmazonDynamoDBClient(), - new DynamoDBMapperConfig(new TableNameOverride(configuration.getForumDynamoDBTableName()))); - } - - public List findForums() { - - DynamoDBScanExpression scanExpression = new DynamoDBScanExpression(); - - List forumList = mapper.scan(Forum.class, scanExpression); - return forumList; - } -} \ No newline at end of file diff --git a/lambda-functions/src/main/java/blog/posts/handlers/FindPost.java b/lambda-functions/src/main/java/blog/posts/handlers/FindPost.java deleted file mode 100644 index 95a36b3c..00000000 --- a/lambda-functions/src/main/java/blog/posts/handlers/FindPost.java +++ /dev/null @@ -1,39 +0,0 @@ -package blog.posts.handlers; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.LambdaLogger; -import com.amazonaws.services.lambda.runtime.RequestStreamHandler; -import com.fasterxml.jackson.databind.ObjectMapper; - -import blog.configuration.ApplicationConfiguration; -import blog.configuration.ApplicationConfigurationStore; -import blog.posts.models.FindPostRequest; -import blog.posts.models.Post; -import blog.posts.repositories.DynamoDBPostRepository; - -public class FindPost implements RequestStreamHandler { - - private ApplicationConfigurationStore applicationConfigurationStore = new ApplicationConfigurationStore(); - - @Override - public void handleRequest(InputStream input, OutputStream output, Context context) throws IOException { - LambdaLogger logger = context.getLogger(); - ObjectMapper mapper = new ObjectMapper(); - FindPostRequest request = mapper.readValue(input, FindPostRequest.class); - logger.log("request:\n" + request + "\n"); - - ApplicationConfiguration applicationConfiguration = applicationConfigurationStore - .getApplicationConfiguration(request.getRequestConfiguration()); - logger.log("applicationConfiguration:\n" + applicationConfiguration + "\n"); - - DynamoDBPostRepository postRepo = new DynamoDBPostRepository(applicationConfiguration); - Post post = postRepo.findOne(request.getForumId(),request.getId()); - logger.log("post:\n" + post + "\n"); - - mapper.writeValue(output, post); - } -} \ No newline at end of file diff --git a/lambda-functions/src/main/java/blog/posts/handlers/FindPosts.java b/lambda-functions/src/main/java/blog/posts/handlers/FindPosts.java deleted file mode 100644 index ebb093c6..00000000 --- a/lambda-functions/src/main/java/blog/posts/handlers/FindPosts.java +++ /dev/null @@ -1,42 +0,0 @@ -package blog.posts.handlers; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.List; - -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.LambdaLogger; -import com.amazonaws.services.lambda.runtime.RequestStreamHandler; -import com.fasterxml.jackson.databind.ObjectMapper; - -import blog.configuration.ApplicationConfiguration; -import blog.configuration.ApplicationConfigurationStore; -import blog.posts.models.FindPostsRequest; -import blog.posts.models.LatestPostByForum; -import blog.posts.models.Post; -import blog.posts.repositories.DynamoDBPostRepository; -import com.amazonaws.services.lambda.runtime.RequestHandler; - -public class FindPosts implements RequestHandler >{ - - private ApplicationConfigurationStore applicationConfigurationStore = new ApplicationConfigurationStore(); - - @Override - public List handleRequest(FindPostsRequest request, Context context){ - LambdaLogger logger = context.getLogger(); - //ObjectMapper mapper = new ObjectMapper(); - // FindPostRequest request = mapper.readValue(input, FindPostRequest.class); - logger.log("request:\n" + request + "\n"); -logger.log("requestconfig: " + request.getConfig()); - ApplicationConfiguration applicationConfiguration = applicationConfigurationStore - .getApplicationConfiguration(request.getConfig()); - logger.log("applicationConfiguration:\n" + applicationConfiguration + "\n"); - - DynamoDBPostRepository postRepo = new DynamoDBPostRepository(applicationConfiguration); - return postRepo.findPosts(request.getForumId()); - //logger.log("posts:\n" + posts + "\n"); - - //mapper.writeValue(output, posts); - } -} diff --git a/lambda-functions/src/main/java/blog/posts/handlers/SavePost.java b/lambda-functions/src/main/java/blog/posts/handlers/SavePost.java deleted file mode 100644 index 299f8f93..00000000 --- a/lambda-functions/src/main/java/blog/posts/handlers/SavePost.java +++ /dev/null @@ -1,42 +0,0 @@ -package blog.posts.handlers; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.LambdaLogger; -import com.amazonaws.services.lambda.runtime.RequestStreamHandler; -import com.fasterxml.jackson.databind.ObjectMapper; - -import blog.configuration.ApplicationConfiguration; -import blog.configuration.ApplicationConfigurationStore; -import blog.configuration.RequestConfiguration; -import blog.posts.models.Post; -import blog.posts.repositories.DynamoDBPostRepository; - -public class SavePost implements RequestStreamHandler { - - private ApplicationConfigurationStore applicationConfigurationStore = new ApplicationConfigurationStore(); - - @Override - public void handleRequest(InputStream input, OutputStream output, Context context) throws IOException { - LambdaLogger logger = context.getLogger(); - ObjectMapper mapper = new ObjectMapper(); - Post post = mapper.readValue(input, Post.class); - logger.log("post:\n" + post + "\n"); - - RequestConfiguration requestConfiguration = post.getRequestConfiguration(); - logger.log("requestConfiguration:\n" + requestConfiguration + "\n"); - - ApplicationConfiguration applicationConfiguration = applicationConfigurationStore - .getApplicationConfiguration(requestConfiguration); - logger.log("applicationConfiguration:\n" + applicationConfiguration + "\n"); - - DynamoDBPostRepository postRepo = new DynamoDBPostRepository(applicationConfiguration); - Post savedPost = postRepo.save(post); - logger.log("savedPost:\n" + savedPost + "\n"); - - mapper.writeValue(output, savedPost); - } -} \ No newline at end of file diff --git a/lambda-functions/src/main/java/blog/posts/models/FindPostRequest.java b/lambda-functions/src/main/java/blog/posts/models/FindPostRequest.java deleted file mode 100644 index 16b58cb0..00000000 --- a/lambda-functions/src/main/java/blog/posts/models/FindPostRequest.java +++ /dev/null @@ -1,51 +0,0 @@ -package blog.posts.models; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; - -import blog.configuration.RequestConfiguration; - -public class FindPostRequest { - - private String forumId; - private String id; - private RequestConfiguration requestConfiguration; - - public String getForumId() { - return forumId; - } - - public void setForumId(String forumId) { - this.forumId = forumId; - } - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - @JsonIgnore - public RequestConfiguration getRequestConfiguration() { - return requestConfiguration; - } - - @JsonSetter("config") - public void setRequestConfiguration(RequestConfiguration requestConfiguration) { - this.requestConfiguration = requestConfiguration; - } - - @Override - public String toString() { - ObjectMapper mapper = new ObjectMapper(); - try { - return mapper.writeValueAsString(this); - } catch (JsonProcessingException e) { - return "error"; - } - } -} \ No newline at end of file diff --git a/lambda-functions/src/main/java/blog/posts/models/FindPostsRequest.java b/lambda-functions/src/main/java/blog/posts/models/FindPostsRequest.java deleted file mode 100644 index 47d9ba5e..00000000 --- a/lambda-functions/src/main/java/blog/posts/models/FindPostsRequest.java +++ /dev/null @@ -1,38 +0,0 @@ -package blog.posts.models; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; - -import blog.configuration.RequestConfiguration; - -public class FindPostsRequest { - - private RequestConfiguration config; - private String forumId; - - public RequestConfiguration getConfig() { - return config; - } - - public void setConfig(RequestConfiguration config) { - this.config = config; - } - - public String getForumId() { - return forumId; - } - - public void setForumId(String forumId) { - this.forumId = forumId; - } - - @Override - public String toString() { - ObjectMapper mapper = new ObjectMapper(); - try { - return mapper.writeValueAsString(this); - } catch (JsonProcessingException e) { - return "error"; - } - } -} diff --git a/lambda-functions/src/main/java/blog/posts/models/LatestPostByForum.java b/lambda-functions/src/main/java/blog/posts/models/LatestPostByForum.java deleted file mode 100644 index d75e9f9b..00000000 --- a/lambda-functions/src/main/java/blog/posts/models/LatestPostByForum.java +++ /dev/null @@ -1,78 +0,0 @@ -package blog.posts.models; - -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAttribute; -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBHashKey; -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBRangeKey; -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTable; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; - - -@DynamoDBTable(tableName="REPLACED_BY_API") -public class LatestPostByForum { - - private String forumId; - private long createdAt; - private String id; - private String title; - - public LatestPostByForum(){ - } - - public LatestPostByForum(Post post){ - this.createdAt = post.getCreatedAt(); - this.forumId = post.getForumId(); - this.id = post.getId(); - this.title = post.getTitle(); - } - - @DynamoDBHashKey - public String getForumId() - { - return forumId; - } - public void setForumId(String forumId) - { - this.forumId = forumId; - } - - @DynamoDBRangeKey(attributeName = "created_at") - public long getCreatedAt() - { - return createdAt; - } - - public void setCreatedAt(long createdAt) - { - this.createdAt = createdAt; - } - - @DynamoDBAttribute - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - @DynamoDBAttribute - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - - @Override - public String toString() { - ObjectMapper mapper = new ObjectMapper(); - try { - return mapper.writeValueAsString(this); - } catch (JsonProcessingException e) { - return "error"; - } - } - -} diff --git a/lambda-functions/src/main/java/blog/posts/models/Post.java b/lambda-functions/src/main/java/blog/posts/models/Post.java deleted file mode 100644 index a587c8de..00000000 --- a/lambda-functions/src/main/java/blog/posts/models/Post.java +++ /dev/null @@ -1,100 +0,0 @@ -package blog.posts.models; - -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAttribute; -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBHashKey; -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBIgnore; -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBRangeKey; -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTable; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; - -import blog.configuration.RequestConfiguration; - -@DynamoDBTable(tableName = "REPLACED_BY_API") -public class Post { - - private String forumId; - private String id; - private long createdAt; - private String email; - private String title; - private String body; - private RequestConfiguration requestConfiguration; - - @DynamoDBHashKey - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - @DynamoDBAttribute - public String getForumId() { - return forumId; - } - - public void setForumId(String forumId) { - this.forumId = forumId; - } - - @DynamoDBAttribute(attributeName = "created_at") - public long getCreatedAt() { - return createdAt; - } - - public void setCreatedAt(long createdAt) { - this.createdAt = createdAt; - } - - @DynamoDBAttribute - public String getEmail() { - return email; - } - - public void setEmail(String email) { - this.email = email; - } - - @DynamoDBAttribute - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - - @DynamoDBAttribute - public String getBody() { - return body; - } - - public void setBody(String body) { - this.body = body; - } - - @DynamoDBIgnore - @JsonIgnore - public RequestConfiguration getRequestConfiguration() { - return requestConfiguration; - } - - @JsonSetter("config") - public void setRequestConfiguration(RequestConfiguration requestConfiguration) { - this.requestConfiguration = requestConfiguration; - } - - @Override - public String toString() { - ObjectMapper mapper = new ObjectMapper(); - try { - return mapper.writeValueAsString(this); - } catch (JsonProcessingException e) { - return "error"; - } - } -} \ No newline at end of file diff --git a/lambda-functions/src/main/java/blog/posts/repositories/DynamoDBPostRepository.java b/lambda-functions/src/main/java/blog/posts/repositories/DynamoDBPostRepository.java deleted file mode 100644 index ef8aba2d..00000000 --- a/lambda-functions/src/main/java/blog/posts/repositories/DynamoDBPostRepository.java +++ /dev/null @@ -1,137 +0,0 @@ -package blog.posts.repositories; - -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; - -import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient; -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper; -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapperConfig; -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapperConfig.TableNameOverride; -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBQueryExpression; -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBSaveExpression; -import com.amazonaws.services.dynamodbv2.datamodeling.QueryResultPage; -import com.amazonaws.services.dynamodbv2.model.AttributeValue; -import com.amazonaws.services.dynamodbv2.model.ConditionalCheckFailedException; -import com.amazonaws.services.dynamodbv2.model.ConditionalOperator; -import com.amazonaws.services.dynamodbv2.model.ExpectedAttributeValue; - -import blog.configuration.ApplicationConfiguration; -import blog.posts.models.LatestPostByForum; -import blog.posts.models.Post; -import org.apache.log4j.Logger; - -public class DynamoDBPostRepository { - - private final static int DEFAULT_LIMIT = 200; - - private static DynamoDBMapper postMapper; - private static DynamoDBMapper latestPostMapper; - private static final Logger log = Logger.getLogger(DynamoDBPostRepository.class); - - public DynamoDBPostRepository(ApplicationConfiguration configuration) { - postMapper = new DynamoDBMapper(new AmazonDynamoDBClient(), - new DynamoDBMapperConfig(new TableNameOverride(configuration.getPostDynamoDBTableName()))); - latestPostMapper = new DynamoDBMapper(new AmazonDynamoDBClient(), - new DynamoDBMapperConfig(new TableNameOverride(configuration.getLatestPostDynamoDBTableName()))); - } - - public Post findOne(String forumId, String id) { - Post post = postMapper.load(Post.class, id); - return post; - } - - public Post findByUser(String userId) { - return null; - } - - public List findPosts(String forumId) { - - HashMap eav = new HashMap(); - eav.put(":v1", new AttributeValue().withS(forumId)); - - DynamoDBQueryExpression queryExpression = - new DynamoDBQueryExpression() - .withKeyConditionExpression("forumId = :v1") - .withExpressionAttributeValues(eav) - .withScanIndexForward(false) - .withLimit(DEFAULT_LIMIT); - - QueryResultPage onePageOfResults = latestPostMapper.queryPage(LatestPostByForum.class, queryExpression); - List resultList = onePageOfResults.getResults(); - log.debug(resultList.size()); - - return resultList; - } - - public Post save(Post post) { - UUID guid = UUID.randomUUID(); - post.setId(guid.toString()); - post.setCreatedAt(new Date().getTime()); - - LatestPostByForum latestPost = new LatestPostByForum(post); - - DynamoDBSaveExpression saveExpression = new DynamoDBSaveExpression(); - - Map expectedAttributes = new - HashMap(); - expectedAttributes.put("forumId", new ExpectedAttributeValue(false)); - expectedAttributes.put("created_at", new ExpectedAttributeValue(false)); - saveExpression.setExpected(expectedAttributes); - saveExpression.setConditionalOperator(ConditionalOperator.AND); - - try { - log.debug("Saving to latestPost" + latestPost); - latestPostMapper.save(latestPost, saveExpression); - - try{ - log.debug("Saving to post table" + post); - postMapper.save(post); - Post savedPost = postMapper.load(Post.class, post.getId()); - log.info("post saved: " + post.getId()); - return savedPost; - } - catch(Exception e) - { - //if the save to the post table fails for any reason, roll back the - //save to the latestPostInForum table - latestPostMapper.delete(latestPost); - return null; - } - - } catch (ConditionalCheckFailedException e) { - //there was a collision on the timestamp, so recreate with a new timestamp and try ONCE more before failing - post.setCreatedAt(new Date().getTime()); - latestPost.setCreatedAt(post.getCreatedAt()); - log.warn("Timestamp Collision occurred when saving Post. Will re-create timestamp and attempt one more time"); - try{ - latestPostMapper.save(latestPost, saveExpression); - - try{ - postMapper.save(post); - Post savedPost = postMapper.load(Post.class, post.getId()); - log.info("post saved: " + post.getId()); - return savedPost; - } - catch(Exception exc) - { - //if the save to the post table fails for any reason, roll back the - //save to the latestPostInForum table - latestPostMapper.delete(latestPost); - return null; - } - - } - catch(ConditionalCheckFailedException failedCheckAgain) - { - //only retry once, then return null to indicate the failure - log.error("Failed to post the message: " + post.toString() + - " because of timestamp collision"); - return null; - } - } - - } -} diff --git a/lambda-functions/src/main/java/blog/users/handlers/AuthenticateUser.java b/lambda-functions/src/main/java/blog/users/handlers/AuthenticateUser.java deleted file mode 100644 index bcbd7693..00000000 --- a/lambda-functions/src/main/java/blog/users/handlers/AuthenticateUser.java +++ /dev/null @@ -1,38 +0,0 @@ -package blog.users.handlers; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.LambdaLogger; -import com.amazonaws.services.lambda.runtime.RequestStreamHandler; -import com.fasterxml.jackson.databind.ObjectMapper; - -import blog.configuration.ApplicationConfiguration; -import blog.configuration.ApplicationConfigurationStore; -import blog.users.models.User; -import blog.users.repositories.DynamoDBUserRepository; - -public class AuthenticateUser implements RequestStreamHandler { - - private ApplicationConfigurationStore applicationConfigurationStore = new ApplicationConfigurationStore(); - - @Override - public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) throws IOException { - LambdaLogger logger = context.getLogger(); - ObjectMapper mapper = new ObjectMapper(); - User user = mapper.readValue(inputStream, User.class); - logger.log("Verifying User:\n" + user + "\n"); - - ApplicationConfiguration applicationConfiguration = applicationConfigurationStore - .getApplicationConfiguration(user.getRequestConfiguration()); - logger.log("applicationConfiguration:\n" + applicationConfiguration + "\n"); - - DynamoDBUserRepository userRepo = new DynamoDBUserRepository(applicationConfiguration); - User verifiedUser = userRepo.verify(user); - logger.log("Verified User:\n" + verifiedUser + "\n"); - - mapper.writeValue(outputStream, verifiedUser); - } -} \ No newline at end of file diff --git a/lambda-functions/src/main/java/blog/users/handlers/SaveUser.java b/lambda-functions/src/main/java/blog/users/handlers/SaveUser.java deleted file mode 100644 index 5f5acc6a..00000000 --- a/lambda-functions/src/main/java/blog/users/handlers/SaveUser.java +++ /dev/null @@ -1,42 +0,0 @@ -package blog.users.handlers; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.LambdaLogger; -import com.amazonaws.services.lambda.runtime.RequestStreamHandler; -import com.fasterxml.jackson.databind.ObjectMapper; - -import blog.configuration.ApplicationConfiguration; -import blog.configuration.ApplicationConfigurationStore; -import blog.users.models.User; -import blog.users.repositories.DynamoDBUserRepository; - -public class SaveUser implements RequestStreamHandler { - - private ApplicationConfigurationStore applicationConfigurationStore = new ApplicationConfigurationStore(); - - @Override - public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) throws IOException { - LambdaLogger logger = context.getLogger(); - ObjectMapper mapper = new ObjectMapper(); - User user = mapper.readValue(inputStream, User.class); - logger.log("Create User:\n" + user + "\n"); - - ApplicationConfiguration applicationConfiguration = applicationConfigurationStore - .getApplicationConfiguration(user.getRequestConfiguration()); - logger.log("applicationConfiguration:\n" + applicationConfiguration + "\n"); - - try { - DynamoDBUserRepository userRepo = new DynamoDBUserRepository(applicationConfiguration); - User savedUser = userRepo.save(user); - logger.log("Saved User:\n" + savedUser + "\n"); - mapper.writeValue(outputStream, savedUser); - } catch (Exception e) { - logger.log(e.getMessage()); - throw new IOException(e); - } - } -} \ No newline at end of file diff --git a/lambda-functions/src/main/java/blog/users/models/User.java b/lambda-functions/src/main/java/blog/users/models/User.java deleted file mode 100644 index 2f8e463f..00000000 --- a/lambda-functions/src/main/java/blog/users/models/User.java +++ /dev/null @@ -1,127 +0,0 @@ -package blog.users.models; - -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAttribute; -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBHashKey; -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBIgnore; -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTable; -import com.amazonaws.services.dynamodbv2.datamodeling.encryption.DoNotTouch; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; - -import blog.configuration.RequestConfiguration; - -@DynamoDBTable(tableName = "REPLACED_BY_API") -public class User { - - private String email; - private String name; - private String openIdToken; - private String identityId; - private String password; - private byte[] passwordHash; - private byte[] salt; - private RequestConfiguration requestConfiguration; - - public User() { - } - - public User(String email) { - this.email = email; - } - - @DynamoDBHashKey(attributeName = "email") - public String getEmail() { - return email; - } - - public void setEmail(String email) { - this.email = email; - } - - @DynamoDBAttribute - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - @DynamoDBAttribute - @JsonIgnore - @DoNotTouch - public byte[] getPasswordHash() { - return passwordHash; - } - - @JsonProperty - public void setPasswordHash(byte[] passwordHash) { - this.passwordHash = passwordHash; - } - - @JsonIgnore - @DynamoDBIgnore - public String getPassword() { - return password; - } - - @JsonProperty - public void setPassword(String password) { - this.password = password; - } - - @DynamoDBAttribute - @DoNotTouch - @DynamoDBIgnore - public String getOpenIdToken() { - return openIdToken; - } - - public void setOpenIdToken(String openIdToken) { - this.openIdToken = openIdToken; - } - - @DynamoDBAttribute - @DoNotTouch - public String getIdentityId() { - return identityId; - } - - public void setIdentityId(String identityId) { - this.identityId = identityId; - } - - @DynamoDBAttribute - @DoNotTouch - public byte[] getSalt() { - return salt; - } - - public void setSalt(byte[] salt) { - this.salt = salt; - } - - @DynamoDBIgnore - @JsonIgnore - public RequestConfiguration getRequestConfiguration() { - return requestConfiguration; - } - - @JsonSetter("config") - public void setRequestConfiguration(RequestConfiguration requestConfiguration) { - this.requestConfiguration = requestConfiguration; - } - - @Override - public String toString() { - ObjectMapper mapper = new ObjectMapper(); - try { - return mapper.writeValueAsString(this); - } catch (JsonProcessingException e) { - return "error"; - } - } -} \ No newline at end of file diff --git a/lambda-functions/src/main/java/blog/users/repositories/DynamoDBUserRepository.java b/lambda-functions/src/main/java/blog/users/repositories/DynamoDBUserRepository.java deleted file mode 100644 index f568389d..00000000 --- a/lambda-functions/src/main/java/blog/users/repositories/DynamoDBUserRepository.java +++ /dev/null @@ -1,115 +0,0 @@ -package blog.users.repositories; - -import java.util.HashMap; - -import com.amazonaws.services.cognitoidentity.AmazonCognitoIdentityClient; -import com.amazonaws.services.cognitoidentity.model.GetOpenIdTokenForDeveloperIdentityRequest; -import com.amazonaws.services.cognitoidentity.model.GetOpenIdTokenForDeveloperIdentityResult; -import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient; -import com.amazonaws.services.dynamodbv2.datamodeling.AttributeEncryptor; -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper; -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapperConfig; -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapperConfig.TableNameOverride; -import com.amazonaws.services.dynamodbv2.datamodeling.encryption.providers.DirectKmsMaterialProvider; -import com.amazonaws.services.kms.AWSKMSClient; - -import blog.configuration.ApplicationConfiguration; -import blog.users.models.User; - -public class DynamoDBUserRepository { - - private AmazonCognitoIdentityClient cognitoClient = new AmazonCognitoIdentityClient(); - private GetOpenIdTokenForDeveloperIdentityRequest tokenRequest = new GetOpenIdTokenForDeveloperIdentityRequest(); - - private DynamoDBMapper mapper; - private String cognitoDeveloperId; - private String cognitoIdentityPoolId; - - public DynamoDBUserRepository(ApplicationConfiguration configuration) { - mapper = new DynamoDBMapper(new AmazonDynamoDBClient(), - new DynamoDBMapperConfig(new TableNameOverride(configuration.getUserDynamoDBTableName())), - new AttributeEncryptor( - new DirectKmsMaterialProvider(new AWSKMSClient(), configuration.getEncryptionKeyId()))); - cognitoDeveloperId = configuration.getCognitoDeveloperId(); - cognitoIdentityPoolId = configuration.getCognitoIdentityPoolId(); - } - - public User findOne(String id) { - return findOne(new User(id)); - } - - public User findOne(User user) { - return mapper.load(User.class, user.getEmail()); - } - - public User save(User newUser) { - User user = null; - - if (newUser.getEmail() != null && newUser.getPassword() != null) { - byte[] salt = PasswordUtil.getNextSalt(); - byte[] passwordHash = PasswordUtil.hash(newUser.getPassword().toCharArray(), salt); - newUser.setPasswordHash(passwordHash); - newUser.setPassword(null); - newUser.setSalt(salt); - - // grab the OpenId token for the user prior to saving to DDB - GetOpenIdTokenForDeveloperIdentityResult result = getOpenIdToken(newUser.getEmail()); - - newUser.setOpenIdToken(result.getToken()); - newUser.setIdentityId(result.getIdentityId()); - - // save the user - mapper.save(newUser); - - // verify the user was saved - user = mapper.load(User.class, newUser.getEmail()); - user.setOpenIdToken(result.getToken()); - user.setIdentityId(result.getIdentityId()); - } - - return user; - } - - public User verify(User userUnverified) { - User user = null; - try { - user = mapper.load(User.class, userUnverified.getEmail()); - - if (user != null) { - - if (userUnverified.getPassword() != null && PasswordUtil.isExpectedPassword( - userUnverified.getPassword().toCharArray(), user.getSalt(), user.getPasswordHash())) { - - GetOpenIdTokenForDeveloperIdentityResult result = getOpenIdToken(user.getEmail()); - user.setOpenIdToken(result.getToken()); - user.setIdentityId(result.getIdentityId()); - } else { - // return a null user since password either wasn't provided - // or didn't match - user = null; - } - } - } catch (Exception e) { - System.out.println(e.toString()); - } - - return user; - } - - private GetOpenIdTokenForDeveloperIdentityResult getOpenIdToken(String userId) { - - tokenRequest.setIdentityPoolId(cognitoIdentityPoolId); - - HashMap map = new HashMap(); - map.put(cognitoDeveloperId, userId); - - tokenRequest.setLogins(map); - tokenRequest.setTokenDuration(new Long(10001)); - - return cognitoClient.getOpenIdTokenForDeveloperIdentity(tokenRequest); - } - - public void delete(User user) { - mapper.delete(user); - } -} \ No newline at end of file diff --git a/lambda-functions/src/main/java/blog/users/repositories/PasswordUtil.java b/lambda-functions/src/main/java/blog/users/repositories/PasswordUtil.java deleted file mode 100644 index c8dd4170..00000000 --- a/lambda-functions/src/main/java/blog/users/repositories/PasswordUtil.java +++ /dev/null @@ -1,66 +0,0 @@ -package blog.users.repositories; - -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; -import java.security.spec.InvalidKeySpecException; -import java.util.Arrays; -import java.util.Random; - -import javax.crypto.SecretKeyFactory; -import javax.crypto.spec.PBEKeySpec; - -public class PasswordUtil { - - private static final Random RANDOM = new SecureRandom(); - private static final int ITERATIONS = 10000; - private static final int KEY_LENGTH = 256; - - private PasswordUtil() { - } - - public static byte[] getNextSalt() { - byte[] salt = new byte[16]; - RANDOM.nextBytes(salt); - return salt; - } - - public static byte[] hash(char[] password, byte[] salt) { - PBEKeySpec spec = new PBEKeySpec(password, salt, ITERATIONS, KEY_LENGTH); - Arrays.fill(password, Character.MIN_VALUE); - try { - SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512"); - return skf.generateSecret(spec).getEncoded(); - } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { - throw new AssertionError("Error while hashing a password: " + e.getMessage(), e); - } finally { - spec.clearPassword(); - } - } - - public static boolean isExpectedPassword(char[] password, byte[] salt, byte[] expectedHash) { - byte[] pwdHash = hash(password, salt); - Arrays.fill(password, Character.MIN_VALUE); - if (pwdHash.length != expectedHash.length) - return false; - for (int i = 0; i < pwdHash.length; i++) { - if (pwdHash[i] != expectedHash[i]) - return false; - } - return true; - } - - public static String generateRandomPassword(int length) { - StringBuilder sb = new StringBuilder(length); - for (int i = 0; i < length; i++) { - int c = RANDOM.nextInt(62); - if (c <= 9) { - sb.append(String.valueOf(c)); - } else if (c < 36) { - sb.append((char) ('a' + c - 10)); - } else { - sb.append((char) ('A' + c - 36)); - } - } - return sb.toString(); - } -} \ No newline at end of file diff --git a/lambda-functions/src/main/resources/log4j.properties b/lambda-functions/src/main/resources/log4j.properties deleted file mode 100644 index 1514474b..00000000 --- a/lambda-functions/src/main/resources/log4j.properties +++ /dev/null @@ -1,7 +0,0 @@ -log = . -log4j.rootLogger = INFO, LAMBDA -log4j.logger.org.apache.http.wire=INFO -#Define the LAMBDA appender -log4j.appender.LAMBDA=com.amazonaws.services.lambda.runtime.log4j.LambdaAppender -log4j.appender.LAMBDA.layout=org.apache.log4j.PatternLayout -log4j.appender.LAMBDA.layout.conversionPattern=%d{yyyy-MM-dd HH:mm:ss} <%X{AWSRequestId}> %-5p %c{1}:%L - %m%n diff --git a/launch.json b/launch.json new file mode 100644 index 00000000..2f0376a0 --- /dev/null +++ b/launch.json @@ -0,0 +1,12 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Chrome", + "type": "chrome", + "request": "launch", + "url": "http://localhost:3000", + "webRoot": "${workspaceRoot}/src" + } + ] +} \ No newline at end of file diff --git a/package.json b/package.json deleted file mode 100644 index 84a086b3..00000000 --- a/package.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "serverless-web-refarch", - "version": "1.0.0", - "description": "AWS Serverless Web Reference Architecture", - "main": "index.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "repository": { - "type": "git", - "url": "ssh://APKAJA754PMPEG76E5EQ@git-codecommit.us-east-1.amazonaws.com/v1/repos/serverless-web-refarch" - }, - "author": "", - "license": "ISC", - "devDependencies": { - "grunt": "^0.4.5", - "grunt-phantomas": "^0.8.0" - } -} diff --git a/template.yaml b/template.yaml new file mode 100644 index 00000000..23c63d79 --- /dev/null +++ b/template.yaml @@ -0,0 +1,379 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: "Sample todo serverless web application" + +# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst +Globals: + Function: + Runtime: nodejs12.x + Timeout: 60 + Tracing: Active + MemorySize: 1024 # From Lambda Power Tuner - https://lambda-power-tuning.show/#gAAAAQACAAQACMAL;Fu+DQkrxg0JplAJBPCnQQBhL0EDm+NlA;m1ZfNJtW3zSbVl81m1bfNZtWXzaaA6Q2 + Tags: + Application: serverless-web-app + Api: + Auth: + UsagePlan: + UsagePlanName: DailyUsagePlan + CreateUsagePlan: PER_API + Description: This will limit 5000 executions per day and throttle executions on 10% of that. + Quota: + Limit: 5000 + Period: DAY + Throttle: + BurstLimit: 100 + RateLimit: 50 + +Parameters: + VersionParam: + Type: String + Default: v1 + StageNameParam: + Type: String + Default: prod + CognitoDomainName: + Type: String + Default: mytodoappdemo + + # For Amplify Console frontend hosting + Repository: + Type: String + Description: GitHub Repository URL + OauthToken: + Type: String + Description: GitHub Repository URL + NoEcho: true + +Resources: + +#cognito declaration + TodoUserPool: + Type: AWS::Cognito::UserPool + Properties: + AdminCreateUserConfig: + AllowAdminCreateUserOnly: false + UserPoolName: TodoUsers + UsernameAttributes: + - email + AutoVerifiedAttributes: + - email + Policies: + PasswordPolicy: + MinimumLength: 6 + RequireLowercase: true + RequireNumbers: false + RequireSymbols: false + RequireUppercase: true + + TodoUserPoolTokenClient: + Type: AWS::Cognito::UserPoolClient + Properties: + UserPoolId: !Ref TodoUserPool + GenerateSecret: false + AllowedOAuthFlowsUserPoolClient: true + AllowedOAuthFlows: ['code', 'implicit'] + CallbackURLs: ['http://localhost:3000', 'http://localhost:8080' ,'https://localhost', !Join ['', ['https://', !GetAtt AmplifyBranch.BranchName, '.', !GetAtt AmplifyApp.DefaultDomain]]] + SupportedIdentityProviders: ['COGNITO'] + AllowedOAuthScopes: ['phone', 'email', 'openid'] + ExplicitAuthFlows: + - USER_PASSWORD_AUTH + + TodoDomain: + Type: AWS::Cognito::UserPoolDomain + Properties: + Domain: !Join ['-', [!Ref CognitoDomainName, !Ref AWS::StackName]] + UserPoolId: !Ref TodoUserPool + + TodoTable: + Type: AWS::Serverless::SimpleTable + Properties: + PrimaryKey: + Name: id + Type: String + TableName: !Join ['-', [todo-table, !Ref AWS::StackName]] + SSESpecification: + SSEEnabled: true + + # API declaration + TodoApi: + Type: AWS::Serverless::Api + MethodSettings: + DataTraceEnabled: true + MetricsEnabled: true + HttpMethod: '*' + ResourcePath: !Sub '${VersionParam}/*' + LoggingLevel: INFO + AccessLogSetting: + DestinationArn: !Sub 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:${ApiAccessLogGroup}' + Format: '$context.identity.sourceIp $context.authorizer.claims.sub [$context.requestTime] "$context.httpMethod $context.resourcePath $context.protocol" $context.status $context.requestId $context.awsEndpointRequestId $context.xrayTraceId $context.responseLatency $context.integrationLatency "$context.error.message"' + Properties: + Name: TodoApi + StageName: !Ref StageNameParam + TracingEnabled: true + Cors: + AllowOrigin: "'*'" + AllowMethods: "'OPTIONS,HEAD,GET,PUT,POST,DELETE'" + AllowHeaders: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'" + Auth: + Authorizers: + CognitoAuthorizer: + UserPoolArn: !GetAtt "TodoUserPool.Arn" + + # API Functions + GetTodoFunction: + Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + Properties: + CodeUri: todo-src/getTodo + Handler: app.getToDoItem + Tracing: Active + Policies: + - DynamoDBReadPolicy: + TableName: !Ref TodoTable + - CloudWatchPutMetricPolicy: {} + Environment: + Variables: + TABLE_NAME: !Ref TodoTable + AWS_NODEJS_CONNECTION_REUSE_ENABLED: "1" + USE_DYNAMODB_LOCAL: "0" + DYNAMODB_LOCAL_URI: "" + + Events: + GetItem: + Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api + Properties: + Path: /item/{id} + Method: get + RestApiId: !Ref TodoApi + Auth: + Authorizer: CognitoAuthorizer + + GetAllTodoFunction: + Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + Properties: + CodeUri: todo-src/getAllTodo + Handler: app.getAllToDoItem + Tracing: Active + Policies: + - DynamoDBReadPolicy: + TableName: !Ref TodoTable + - CloudWatchPutMetricPolicy: {} + Environment: + Variables: + TABLE_NAME: !Ref TodoTable + AWS_NODEJS_CONNECTION_REUSE_ENABLED: "1" + ENDPOINT_OVERRIDE: "" + + Events: + GetItem: + Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api + Properties: + Path: /item + Method: get + RestApiId: !Ref TodoApi + Auth: + Authorizer: CognitoAuthorizer + + CompleteTodoFunction: + Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + Properties: + CodeUri: todo-src/completeToDo/ + Handler: app.completeToDoItem + Tracing: Active + Policies: + - DynamoDBCrudPolicy: + TableName: !Ref TodoTable + - CloudWatchPutMetricPolicy: {} + Environment: + Variables: + TABLE_NAME: !Ref TodoTable + AWS_NODEJS_CONNECTION_REUSE_ENABLED: "1" + ENDPOINT_OVERRIDE: "" + + Events: + CompleteItem: + Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api + Properties: + Path: /item/{id}/done + Method: post + RestApiId: !Ref TodoApi + Auth: + Authorizer: CognitoAuthorizer + + AddTodoFunction: + Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + Properties: + CodeUri: todo-src/addTodo/ + Handler: app.addToDoItem + Tracing: Active + Policies: + - DynamoDBCrudPolicy: + TableName: !Ref TodoTable + - CloudWatchPutMetricPolicy: {} + Environment: + Variables: + TABLE_NAME: !Ref TodoTable + AWS_NODEJS_CONNECTION_REUSE_ENABLED: "1" + ENDPOINT_OVERRIDE: "" + + Events: + PutItem: + Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api + Properties: + Path: /item + Method: POST + RestApiId: !Ref TodoApi + Auth: + Authorizer: CognitoAuthorizer + + UpdateTodoFunction: + Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + Properties: + CodeUri: todo-src/updateTodo/ + Handler: app.updateToDoItem + Tracing: Active + Policies: + - DynamoDBCrudPolicy: + TableName: !Ref TodoTable + - CloudWatchPutMetricPolicy: {} + Environment: + Variables: + TABLE_NAME: !Ref TodoTable + AWS_NODEJS_CONNECTION_REUSE_ENABLED: "1" + Events: + UpdateItem: + Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api + Properties: + Path: /item/{id} + Method: PUT + RestApiId: !Ref TodoApi + Auth: + Authorizer: CognitoAuthorizer + + DeleteTodoFunction: + Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + Properties: + CodeUri: todo-src/deleteTodo + Handler: app.deleteToDoItem + Tracing: Active + Policies: + - DynamoDBCrudPolicy: + TableName: !Ref TodoTable + - CloudWatchPutMetricPolicy: {} + Environment: + Variables: + TABLE_NAME: !Ref TodoTable + AWS_NODEJS_CONNECTION_REUSE_ENABLED: "1" + ENDPOINT_OVERRIDE: "" + + Events: + DeleteItem: + Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api + Properties: + Path: /item/{id} + Method: DELETE + RestApiId: !Ref TodoApi + Auth: + Authorizer: CognitoAuthorizer + + # This role allows API Gateway to push execution and access logs to CloudWatch logs + ApiGatewayPushToCloudWatchRole: + Type: "AWS::IAM::Role" + Properties: + Description: "Push logs to CloudWatch logs from API Gateway" + AssumeRolePolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Principal: + Service: + - "apigateway.amazonaws.com" + Action: "sts:AssumeRole" + ManagedPolicyArns: + - !Sub "arn:${AWS::Partition}:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs" + + + ApiAccessLogGroup: + Type: AWS::Logs::LogGroup + Properties: + LogGroupName: !Sub /aws/apigateway/AccessLog-${TodoApi} + RetentionInDays: 365 + + # Amplify console hosting for static website + # + AmplifyRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Principal: + Service: + - amplify.amazonaws.com + Action: + - sts:AssumeRole + Policies: + - PolicyName: Amplify + PolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Action: "amplify:*" + Resource: "*" + + AmplifyApp: + Type: "AWS::Amplify::App" + Properties: + Name: TodoApp + Repository: !Ref Repository + Description: Todo example app + OauthToken: !Ref OauthToken + BuildSpec: |- + version: 0.1 + frontend: + phases: + build: + commands: + - cd www/src + - npm install + - npm run build + artifacts: + baseDirectory: www/build/ + files: + - '**/*' + Tags: + - Key: Name + Value: Todo + IAMServiceRole: !GetAtt AmplifyRole.Arn + + AmplifyBranch: + Type: AWS::Amplify::Branch + Properties: + BranchName: amplify-console + AppId: !GetAtt AmplifyApp.AppId + Description: Amplify Console Branch + EnableAutoBuild: true + Tags: + - Key: Name + Value: todo-amplify-console + - Key: Branch + Value: amplify-console + +Outputs: + # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function + # Find out more about other implicit resources you can reference within SAM + # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api + TodoFunctionApi: + Description: API Gateway endpoint URL for Prod stage + Value: !Sub "https://${TodoApi}.execute-api.${AWS::Region}.amazonaws.com/{StageNameParam}" + CognitoID: + Description: The Cognito UserPool ID + Value: !Ref TodoUserPool + CognitoClientID: + Description: The Cognito UserPool Client ID + Value: !Ref TodoUserPoolTokenClient + CognitoDomainName: + Description: The Cognito Hosted UI Domain Name + Value: !Join ['', [!Ref CognitoDomainName, '-', !Ref AWS::StackName, '.auth.', !Ref AWS::Region, '.amazoncognito.com']] + AmplifyURL: + Value: !Join ['', ['https://', !GetAtt AmplifyBranch.BranchName, '.', !GetAtt AmplifyApp.DefaultDomain]] diff --git a/todo-src/.npmignore b/todo-src/.npmignore new file mode 100644 index 00000000..e7e1fb04 --- /dev/null +++ b/todo-src/.npmignore @@ -0,0 +1 @@ +tests/* diff --git a/todo-src/addTodo/app.js b/todo-src/addTodo/app.js new file mode 100644 index 00000000..2ba721fd --- /dev/null +++ b/todo-src/addTodo/app.js @@ -0,0 +1,76 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT-0 + +// default imports +const AWSXRay = require('aws-xray-sdk-core') +const AWS = AWSXRay.captureAWS(require('aws-sdk')) +const { metricScope, Unit } = require("aws-embedded-metrics") +const DDB = new AWS.DynamoDB({ apiVersion: "2012-10-08" }) +const { v1: uuidv1 } = require('uuid'); + +// environment variables +const { TABLE_NAME, ENDPOINT_OVERRIDE, REGION } = process.env +const options = { region: REGION } +AWS.config.update({ region: REGION }) + +if (ENDPOINT_OVERRIDE !== "") { + options.endpoint = ENDPOINT_OVERRIDE +} + +const docClient = new AWS.DynamoDB.DocumentClient(options) +// response helper +const response = (statusCode, body, additionalHeaders) => ({ + statusCode, + body: JSON.stringify(body), + headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', ...additionalHeaders }, +}) + +function isValidRequest(context, event) { + return (event.body !== null) +} + +function addRecord(event) { + // auto generated date fields + let d = new Date() + let dISO = d.toISOString() + let auto_fields = { + "id": uuidv1(), + "creation_date": dISO, + "lastupdate_date": dISO + } + + //merge the json objects + let item_body = { ...auto_fields, ...JSON.parse(event.body) } + + //final params to DynamoDB + const params = { + TableName: TABLE_NAME, + Item: item_body + } + + return docClient.put(params) +} + +// Lambda Handler +exports.addToDoItem = + metricScope(metrics => + async (event, context, callback) => { + metrics.setNamespace('TodoApp') + metrics.putDimensions({ Service: "addTodo" }) + metrics.setProperty("RequestId", context.requestId) + + if (!isValidRequest(context, event)) { + metrics.putMetric("Error", 1, Unit.Count) + return response(400, { message: "Error: Invalid request" }) + } + + try { + let data = await addRecord(event).promise() + metrics.putMetric("Success", 1, Unit.Count) + return response(200, data) + } catch (err) { + metrics.putMetric("Error", 1, Unit.Count) + return response(400, { message: err.message }) + } + } + ) \ No newline at end of file diff --git a/todo-src/addTodo/event.json b/todo-src/addTodo/event.json new file mode 100644 index 00000000..170588d8 --- /dev/null +++ b/todo-src/addTodo/event.json @@ -0,0 +1,138 @@ +{ + "resource": "/item/", + "path": "/item/", + "httpMethod": "PUT", + "headers": { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3", + "accept-encoding": "gzip, deflate, br", + "Accept-Language": "en-US,en;q=0.9,pt-BR;q=0.8,pt;q=0.7,es-CO;q=0.6,es;q=0.5,zh-CN;q=0.4,zh;q=0.3", + "cache-control": "max-age=0", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-Country": "US", + "Host": "bsdhc1dx2g.execute-api.us-east-1.amazonaws.com", + "sec-fetch-mode": "navigate", + "sec-fetch-site": "none", + "sec-fetch-user": "?1", + "upgrade-insecure-requests": "1", + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36", + "Via": "2.0 af59cbeda88e3a41b2689a634f61c64d.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "NzOz5TfgbqItgmy_C2Zj4TjAnkMteY-aNMmshY4W9TZ1U-rJr5LHWw==", + "X-Amzn-Trace-Id": "Root=1-5dc86974-035ac025a456001d3ac4b6cb", + "X-Forwarded-For": "72.21.196.66, 54.239.145.80", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "multiValueHeaders": { + "Accept": [ + "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3" + ], + "accept-encoding": [ + "gzip, deflate, br" + ], + "Accept-Language": [ + "en-US,en;q=0.9,pt-BR;q=0.8,pt;q=0.7,es-CO;q=0.6,es;q=0.5,zh-CN;q=0.4,zh;q=0.3" + ], + "cache-control": [ + "max-age=0" + ], + "CloudFront-Forwarded-Proto": [ + "https" + ], + "CloudFront-Is-Desktop-Viewer": [ + "true" + ], + "CloudFront-Is-Mobile-Viewer": [ + "false" + ], + "CloudFront-Is-SmartTV-Viewer": [ + "false" + ], + "CloudFront-Is-Tablet-Viewer": [ + "false" + ], + "CloudFront-Viewer-Country": [ + "US" + ], + "Host": [ + "bsdhc1dx2g.execute-api.us-east-1.amazonaws.com" + ], + "sec-fetch-mode": [ + "navigate" + ], + "sec-fetch-site": [ + "none" + ], + "sec-fetch-user": [ + "?1" + ], + "upgrade-insecure-requests": [ + "1" + ], + "User-Agent": [ + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36" + ], + "Via": [ + "2.0 af59cbeda88e3a41b2689a634f61c64d.cloudfront.net (CloudFront)" + ], + "X-Amz-Cf-Id": [ + "NzOz5TfgbqItgmy_C2Zj4TjAnkMteY-aNMmshY4W9TZ1U-rJr5LHWw==" + ], + "X-Amzn-Trace-Id": [ + "Root=1-5dc86974-035ac025a456001d3ac4b6cb" + ], + "X-Forwarded-For": [ + "72.21.196.66, 54.239.145.80" + ], + "X-Forwarded-Port": [ + "443" + ], + "X-Forwarded-Proto": [ + "https" + ] + }, + "queryStringParameters": null, + "multiValueQueryStringParameters": null, + "pathParameters": { + "id": "1" + }, + "stageVariables": null, + "requestContext": { + "resourceId": "d14k8r", + "resourcePath": "/item/{id}", + "httpMethod": "GET", + "extendedRequestId": "C9VqNFmZIAMFZ6w=", + "requestTime": "10/Nov/2019:19:48:04 +0000", + "path": "/prod/item/1", + "accountId": "407958460921", + "protocol": "HTTP/1.1", + "stage": "prod", + "domainPrefix": "bsdhc1dx2g", + "requestTimeEpoch": 1573415284484, + "requestId": "12cdc30f-1a51-4959-8897-837910884e15", + "identity": { + "cognitoIdentityPoolId": null, + "accountId": null, + "cognitoIdentityId": null, + "caller": null, + "sourceIp": "72.21.196.66", + "principalOrgId": null, + "accessKey": null, + "cognitoAuthenticationType": null, + "cognitoAuthenticationProvider": null, + "userArn": null, + "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36", + "user": null + }, + "domainName": "bsdhc1dx2g.execute-api.us-east-1.amazonaws.com", + "apiId": "bsdhc1dx2g" + }, + "body": { + "completed": false, + "item": "buy apples" + }, + "isBase64Encoded": false +} \ No newline at end of file diff --git a/todo-src/addTodo/package.json b/todo-src/addTodo/package.json new file mode 100644 index 00000000..20dd27ab --- /dev/null +++ b/todo-src/addTodo/package.json @@ -0,0 +1,14 @@ +{ + "name": "CreateTodo-item", + "version": "1.0.0", + "description": "Creates a ToDo item on the DynamoDB Table", + "main": "src/app.js", + "author": "SAM CLI", + "license": "MIT", + "dependencies": { + "aws-sdk": "^2.689.0", + "aws-xray-sdk-core": "^3.0.1", + "aws-embedded-metrics": "^1.1.1", + "uuid": "^8.1.0" + } +} diff --git a/todo-src/completeTodo/app.js b/todo-src/completeTodo/app.js new file mode 100644 index 00000000..d8c81e5b --- /dev/null +++ b/todo-src/completeTodo/app.js @@ -0,0 +1,69 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT-0 + +// default imports +const AWSXRay = require('aws-xray-sdk-core') +const AWS = AWSXRay.captureAWS(require('aws-sdk')) +const { metricScope, Unit } = require("aws-embedded-metrics") +const DDB = new AWS.DynamoDB({ apiVersion: "2012-10-08" }) + +// environment variables +const { TABLE_NAME, ENDPOINT_OVERRIDE, REGION } = process.env +const options = { region: REGION } +AWS.config.update({ region: REGION }) + +if (ENDPOINT_OVERRIDE !== "") { + options.endpoint = ENDPOINT_OVERRIDE +} + +const docClient = new AWS.DynamoDB.DocumentClient(options) +// response helper +const response = (statusCode, body, additionalHeaders) => ({ + statusCode, + body: JSON.stringify(body), + headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', ...additionalHeaders }, +}) + +function isValidRequest(context, event) { + return (event !== null) && + (event.pathParameters !== null) && + (event.pathParameters.id !== null) && + (/^[\w-]+$/.test(event.pathParameters.id)) +} + +function updateRecord(recordId) { + let params = { + TableName: TABLE_NAME, + Key: { "id": recordId }, + UpdateExpression: "set #field = :value", + ExpressionAttributeNames: { '#field': 'completed' }, + ExpressionAttributeValues: { ':value': true } + } + + return docClient.update(params) +} + +// Lambda Handler +exports.completeToDoItem = + metricScope(metrics => + async (event, context, callback) => { + metrics.setNamespace('TodoApp') + metrics.putDimensions({ Service: "completeTodo" }) + metrics.setProperty("RequestId", context.requestId) + + if (!isValidRequest(context, event)) { + metrics.putMetric("Error", 1, Unit.Count) + return response(400, { message: "Error: Invalid request" }) + } + + try { + let data = await updateRecord(event.pathParameters.id).promise() + metrics.putMetric("Success", 1, Unit.Count) + return response(200, data) + } catch (err) { + metrics.putMetric("Error", 1, Unit.Count) + return response(400, { message: err.message }) + } + } + ) + diff --git a/todo-src/completeTodo/event.json b/todo-src/completeTodo/event.json new file mode 100644 index 00000000..a9163a49 --- /dev/null +++ b/todo-src/completeTodo/event.json @@ -0,0 +1,135 @@ +{ + "resource": "done/item/{id}", + "path": "done/item/1", + "httpMethod": "POST", + "headers": { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3", + "accept-encoding": "gzip, deflate, br", + "Accept-Language": "en-US,en;q=0.9,pt-BR;q=0.8,pt;q=0.7,es-CO;q=0.6,es;q=0.5,zh-CN;q=0.4,zh;q=0.3", + "cache-control": "max-age=0", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-Country": "US", + "Host": "bsdhc1dx2g.execute-api.us-east-1.amazonaws.com", + "sec-fetch-mode": "navigate", + "sec-fetch-site": "none", + "sec-fetch-user": "?1", + "upgrade-insecure-requests": "1", + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36", + "Via": "2.0 af59cbeda88e3a41b2689a634f61c64d.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "NzOz5TfgbqItgmy_C2Zj4TjAnkMteY-aNMmshY4W9TZ1U-rJr5LHWw==", + "X-Amzn-Trace-Id": "Root=1-5dc86974-035ac025a456001d3ac4b6cb", + "X-Forwarded-For": "72.21.196.66, 54.239.145.80", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "multiValueHeaders": { + "Accept": [ + "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3" + ], + "accept-encoding": [ + "gzip, deflate, br" + ], + "Accept-Language": [ + "en-US,en;q=0.9,pt-BR;q=0.8,pt;q=0.7,es-CO;q=0.6,es;q=0.5,zh-CN;q=0.4,zh;q=0.3" + ], + "cache-control": [ + "max-age=0" + ], + "CloudFront-Forwarded-Proto": [ + "https" + ], + "CloudFront-Is-Desktop-Viewer": [ + "true" + ], + "CloudFront-Is-Mobile-Viewer": [ + "false" + ], + "CloudFront-Is-SmartTV-Viewer": [ + "false" + ], + "CloudFront-Is-Tablet-Viewer": [ + "false" + ], + "CloudFront-Viewer-Country": [ + "US" + ], + "Host": [ + "bsdhc1dx2g.execute-api.us-east-1.amazonaws.com" + ], + "sec-fetch-mode": [ + "navigate" + ], + "sec-fetch-site": [ + "none" + ], + "sec-fetch-user": [ + "?1" + ], + "upgrade-insecure-requests": [ + "1" + ], + "User-Agent": [ + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36" + ], + "Via": [ + "2.0 af59cbeda88e3a41b2689a634f61c64d.cloudfront.net (CloudFront)" + ], + "X-Amz-Cf-Id": [ + "NzOz5TfgbqItgmy_C2Zj4TjAnkMteY-aNMmshY4W9TZ1U-rJr5LHWw==" + ], + "X-Amzn-Trace-Id": [ + "Root=1-5dc86974-035ac025a456001d3ac4b6cb" + ], + "X-Forwarded-For": [ + "72.21.196.66, 54.239.145.80" + ], + "X-Forwarded-Port": [ + "443" + ], + "X-Forwarded-Proto": [ + "https" + ] + }, + "queryStringParameters": null, + "multiValueQueryStringParameters": null, + "pathParameters": { + "id": "1" + }, + "stageVariables": null, + "requestContext": { + "resourceId": "d14k8r", + "resourcePath": "/item/{id}", + "httpMethod": "GET", + "extendedRequestId": "C9VqNFmZIAMFZ6w=", + "requestTime": "10/Nov/2019:19:48:04 +0000", + "path": "/prod/item/1", + "accountId": "407958460921", + "protocol": "HTTP/1.1", + "stage": "prod", + "domainPrefix": "bsdhc1dx2g", + "requestTimeEpoch": 1573415284484, + "requestId": "12cdc30f-1a51-4959-8897-837910884e15", + "identity": { + "cognitoIdentityPoolId": null, + "accountId": null, + "cognitoIdentityId": null, + "caller": null, + "sourceIp": "72.21.196.66", + "principalOrgId": null, + "accessKey": null, + "cognitoAuthenticationType": null, + "cognitoAuthenticationProvider": null, + "userArn": null, + "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36", + "user": null + }, + "domainName": "bsdhc1dx2g.execute-api.us-east-1.amazonaws.com", + "apiId": "bsdhc1dx2g" + }, + "body": null, + "isBase64Encoded": false +} \ No newline at end of file diff --git a/todo-src/completeTodo/package.json b/todo-src/completeTodo/package.json new file mode 100644 index 00000000..8098270d --- /dev/null +++ b/todo-src/completeTodo/package.json @@ -0,0 +1,13 @@ +{ + "name": "CompleteTodo-item", + "version": "1.0.0", + "description": "Add a new ToDo item to the DynamoDB Table", + "main": "src/app.js", + "author": "SAM CLI", + "license": "MIT", + "dependencies": { + "aws-sdk": "^2.689.0", + "aws-embedded-metrics": "^1.1.1", + "aws-xray-sdk-core": "^3.0.1" + } +} diff --git a/todo-src/deleteTodo/app.js b/todo-src/deleteTodo/app.js new file mode 100644 index 00000000..4de8cfd0 --- /dev/null +++ b/todo-src/deleteTodo/app.js @@ -0,0 +1,66 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT-0 + +// default imports +const AWSXRay = require('aws-xray-sdk-core') +const AWS = AWSXRay.captureAWS(require('aws-sdk')) +const { metricScope, Unit } = require("aws-embedded-metrics") +const DDB = new AWS.DynamoDB({ apiVersion: "2012-10-08" }) + +// environment variables +const { TABLE_NAME, ENDPOINT_OVERRIDE, REGION } = process.env +const options = { region: REGION } +AWS.config.update({ region: REGION }) + +if (ENDPOINT_OVERRIDE !== "") { + options.endpoint = ENDPOINT_OVERRIDE +} + +const docClient = new AWS.DynamoDB.DocumentClient(options) +// response helper +const response = (statusCode, body, additionalHeaders) => ({ + statusCode, + body: JSON.stringify(body), + headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', ...additionalHeaders }, +}) + +function isValidRequest(context, event) { + return (event !== null) && + (event.pathParameters !== null) && + (event.pathParameters.id !== null) && + (/^[\w-]+$/.test(event.pathParameters.id)) +} + +function deleteRecordById(recordId) { + let params = { + TableName: TABLE_NAME, + Key: { + "id": recordId + } + } + + return docClient.delete(params) +} + +// Lambda Handler +exports.deleteToDoItem = + metricScope(metrics => + async (event, context, callback) => { + metrics.setNamespace('TodoApp') + metrics.putDimensions({ Service: "deleteTodo" }) + metrics.setProperty("RequestId", context.requestId) + + if (!isValidRequest(context, event)) { + metrics.putMetric("Error", 1, Unit.Count) + return response(400, { message: "Error: Invalid request" }) + } + + try { + let data = await deleteRecordById(event.pathParameters.id).promise() + metrics.putMetric("Success", 1, Unit.Count) + return response(200, data) + } catch (err) { + metrics.putMetric("Error", 1, Unit.Count) + return response(400, { message: err.message }) + } + }) diff --git a/todo-src/deleteTodo/event.json b/todo-src/deleteTodo/event.json new file mode 100644 index 00000000..72a66f44 --- /dev/null +++ b/todo-src/deleteTodo/event.json @@ -0,0 +1,135 @@ +{ + "resource": "item/{id}", + "path": "item/bad9af20-0429-11ea-a27a-a18cf3b2c720", + "httpMethod": "POST", + "headers": { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3", + "accept-encoding": "gzip, deflate, br", + "Accept-Language": "en-US,en;q=0.9,pt-BR;q=0.8,pt;q=0.7,es-CO;q=0.6,es;q=0.5,zh-CN;q=0.4,zh;q=0.3", + "cache-control": "max-age=0", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-Country": "US", + "Host": "bsdhc1dx2g.execute-api.us-east-1.amazonaws.com", + "sec-fetch-mode": "navigate", + "sec-fetch-site": "none", + "sec-fetch-user": "?1", + "upgrade-insecure-requests": "1", + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36", + "Via": "2.0 af59cbeda88e3a41b2689a634f61c64d.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "NzOz5TfgbqItgmy_C2Zj4TjAnkMteY-aNMmshY4W9TZ1U-rJr5LHWw==", + "X-Amzn-Trace-Id": "Root=1-5dc86974-035ac025a456001d3ac4b6cb", + "X-Forwarded-For": "72.21.196.66, 54.239.145.80", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "multiValueHeaders": { + "Accept": [ + "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3" + ], + "accept-encoding": [ + "gzip, deflate, br" + ], + "Accept-Language": [ + "en-US,en;q=0.9,pt-BR;q=0.8,pt;q=0.7,es-CO;q=0.6,es;q=0.5,zh-CN;q=0.4,zh;q=0.3" + ], + "cache-control": [ + "max-age=0" + ], + "CloudFront-Forwarded-Proto": [ + "https" + ], + "CloudFront-Is-Desktop-Viewer": [ + "true" + ], + "CloudFront-Is-Mobile-Viewer": [ + "false" + ], + "CloudFront-Is-SmartTV-Viewer": [ + "false" + ], + "CloudFront-Is-Tablet-Viewer": [ + "false" + ], + "CloudFront-Viewer-Country": [ + "US" + ], + "Host": [ + "bsdhc1dx2g.execute-api.us-east-1.amazonaws.com" + ], + "sec-fetch-mode": [ + "navigate" + ], + "sec-fetch-site": [ + "none" + ], + "sec-fetch-user": [ + "?1" + ], + "upgrade-insecure-requests": [ + "1" + ], + "User-Agent": [ + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36" + ], + "Via": [ + "2.0 af59cbeda88e3a41b2689a634f61c64d.cloudfront.net (CloudFront)" + ], + "X-Amz-Cf-Id": [ + "NzOz5TfgbqItgmy_C2Zj4TjAnkMteY-aNMmshY4W9TZ1U-rJr5LHWw==" + ], + "X-Amzn-Trace-Id": [ + "Root=1-5dc86974-035ac025a456001d3ac4b6cb" + ], + "X-Forwarded-For": [ + "72.21.196.66, 54.239.145.80" + ], + "X-Forwarded-Port": [ + "443" + ], + "X-Forwarded-Proto": [ + "https" + ] + }, + "queryStringParameters": null, + "multiValueQueryStringParameters": null, + "pathParameters": { + "id": "b2ff3a30-042a-11ea-be16-59b2de0241df" + }, + "stageVariables": null, + "requestContext": { + "resourceId": "d14k8r", + "resourcePath": "/item/{id}", + "httpMethod": "GET", + "extendedRequestId": "C9VqNFmZIAMFZ6w=", + "requestTime": "10/Nov/2019:19:48:04 +0000", + "path": "/prod/item/1", + "accountId": "407958460921", + "protocol": "HTTP/1.1", + "stage": "prod", + "domainPrefix": "bsdhc1dx2g", + "requestTimeEpoch": 1573415284484, + "requestId": "12cdc30f-1a51-4959-8897-837910884e15", + "identity": { + "cognitoIdentityPoolId": null, + "accountId": null, + "cognitoIdentityId": null, + "caller": null, + "sourceIp": "72.21.196.66", + "principalOrgId": null, + "accessKey": null, + "cognitoAuthenticationType": null, + "cognitoAuthenticationProvider": null, + "userArn": null, + "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36", + "user": null + }, + "domainName": "bsdhc1dx2g.execute-api.us-east-1.amazonaws.com", + "apiId": "bsdhc1dx2g" + }, + "body": null, + "isBase64Encoded": false +} \ No newline at end of file diff --git a/todo-src/deleteTodo/package.json b/todo-src/deleteTodo/package.json new file mode 100644 index 00000000..f522c7a1 --- /dev/null +++ b/todo-src/deleteTodo/package.json @@ -0,0 +1,13 @@ +{ + "name": "DeleteTodo-item", + "version": "1.0.0", + "description": "Delete a ToDo item from the DynamoDB Table", + "main": "src/app.js", + "author": "SAM CLI", + "license": "MIT", + "dependencies": { + "aws-sdk": "^2.689.0", + "aws-embedded-metrics": "^1.1.1", + "aws-xray-sdk-core": "^3.0.1" + } +} diff --git a/todo-src/getAllTodo/app.js b/todo-src/getAllTodo/app.js new file mode 100644 index 00000000..121aed4d --- /dev/null +++ b/todo-src/getAllTodo/app.js @@ -0,0 +1,53 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT-0 + +// default imports +const AWSXRay = require('aws-xray-sdk-core') +const AWS = AWSXRay.captureAWS(require('aws-sdk')) +const {metricScope, Unit} = require("aws-embedded-metrics") +const DDB = new AWS.DynamoDB({apiVersion: "2012-10-08"}) + +// environment variables +const {TABLE_NAME, ENDPOINT_OVERRIDE, REGION} = process.env +const options = {region: REGION} +AWS.config.update({region: REGION}) + +if (ENDPOINT_OVERRIDE !== "") { + options.endpoint = ENDPOINT_OVERRIDE +} + +const docClient = new AWS.DynamoDB.DocumentClient(options) +// response helper +const response = (statusCode, body, additionalHeaders) => ({ + statusCode, + body: JSON.stringify(body), + headers: {'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', ...additionalHeaders}, +}) + +function getRecords() { + let params = { + TableName: TABLE_NAME, + } + + return docClient.scan(params) +} + +// Lambda Handler +exports.getAllToDoItem = + metricScope(metrics => + async (event, context, callback) => { + metrics.setNamespace('TodoApp') + metrics.putDimensions({Service: "getAllTodo"}) + metrics.setProperty("RequestId", context.requestId) + + try { + let data = await getRecords().promise() + metrics.putMetric("Success", 1, Unit.Count) + return response(200, data) + + } catch (err) { + metrics.putMetric("Error", 1, Unit.Count) + return response(400, {message: err.message}) + } + } + ) diff --git a/todo-src/getAllTodo/event.json b/todo-src/getAllTodo/event.json new file mode 100644 index 00000000..70d4cec1 --- /dev/null +++ b/todo-src/getAllTodo/event.json @@ -0,0 +1,135 @@ +{ + "resource": "/item/{id}", + "path": "/item/1", + "httpMethod": "GET", + "headers": { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3", + "accept-encoding": "gzip, deflate, br", + "Accept-Language": "en-US,en;q=0.9,pt-BR;q=0.8,pt;q=0.7,es-CO;q=0.6,es;q=0.5,zh-CN;q=0.4,zh;q=0.3", + "cache-control": "max-age=0", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-Country": "US", + "Host": "bsdhc1dx2g.execute-api.us-east-1.amazonaws.com", + "sec-fetch-mode": "navigate", + "sec-fetch-site": "none", + "sec-fetch-user": "?1", + "upgrade-insecure-requests": "1", + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36", + "Via": "2.0 af59cbeda88e3a41b2689a634f61c64d.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "NzOz5TfgbqItgmy_C2Zj4TjAnkMteY-aNMmshY4W9TZ1U-rJr5LHWw==", + "X-Amzn-Trace-Id": "Root=1-5dc86974-035ac025a456001d3ac4b6cb", + "X-Forwarded-For": "72.21.196.66, 54.239.145.80", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "multiValueHeaders": { + "Accept": [ + "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3" + ], + "accept-encoding": [ + "gzip, deflate, br" + ], + "Accept-Language": [ + "en-US,en;q=0.9,pt-BR;q=0.8,pt;q=0.7,es-CO;q=0.6,es;q=0.5,zh-CN;q=0.4,zh;q=0.3" + ], + "cache-control": [ + "max-age=0" + ], + "CloudFront-Forwarded-Proto": [ + "https" + ], + "CloudFront-Is-Desktop-Viewer": [ + "true" + ], + "CloudFront-Is-Mobile-Viewer": [ + "false" + ], + "CloudFront-Is-SmartTV-Viewer": [ + "false" + ], + "CloudFront-Is-Tablet-Viewer": [ + "false" + ], + "CloudFront-Viewer-Country": [ + "US" + ], + "Host": [ + "bsdhc1dx2g.execute-api.us-east-1.amazonaws.com" + ], + "sec-fetch-mode": [ + "navigate" + ], + "sec-fetch-site": [ + "none" + ], + "sec-fetch-user": [ + "?1" + ], + "upgrade-insecure-requests": [ + "1" + ], + "User-Agent": [ + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36" + ], + "Via": [ + "2.0 af59cbeda88e3a41b2689a634f61c64d.cloudfront.net (CloudFront)" + ], + "X-Amz-Cf-Id": [ + "NzOz5TfgbqItgmy_C2Zj4TjAnkMteY-aNMmshY4W9TZ1U-rJr5LHWw==" + ], + "X-Amzn-Trace-Id": [ + "Root=1-5dc86974-035ac025a456001d3ac4b6cb" + ], + "X-Forwarded-For": [ + "72.21.196.66, 54.239.145.80" + ], + "X-Forwarded-Port": [ + "443" + ], + "X-Forwarded-Proto": [ + "https" + ] + }, + "queryStringParameters": null, + "multiValueQueryStringParameters": null, + "pathParameters": { + "id": "1" + }, + "stageVariables": null, + "requestContext": { + "resourceId": "d14k8r", + "resourcePath": "/item/{id}", + "httpMethod": "GET", + "extendedRequestId": "C9VqNFmZIAMFZ6w=", + "requestTime": "10/Nov/2019:19:48:04 +0000", + "path": "/prod/item/1", + "accountId": "407958460921", + "protocol": "HTTP/1.1", + "stage": "prod", + "domainPrefix": "bsdhc1dx2g", + "requestTimeEpoch": 1573415284484, + "requestId": "12cdc30f-1a51-4959-8897-837910884e15", + "identity": { + "cognitoIdentityPoolId": null, + "accountId": null, + "cognitoIdentityId": null, + "caller": null, + "sourceIp": "72.21.196.66", + "principalOrgId": null, + "accessKey": null, + "cognitoAuthenticationType": null, + "cognitoAuthenticationProvider": null, + "userArn": null, + "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36", + "user": null + }, + "domainName": "bsdhc1dx2g.execute-api.us-east-1.amazonaws.com", + "apiId": "bsdhc1dx2g" + }, + "body": null, + "isBase64Encoded": false +} \ No newline at end of file diff --git a/todo-src/getAllTodo/package.json b/todo-src/getAllTodo/package.json new file mode 100644 index 00000000..5c4273c8 --- /dev/null +++ b/todo-src/getAllTodo/package.json @@ -0,0 +1,13 @@ +{ + "name": "GetAllTodo-item", + "version": "1.0.0", + "description": "Get all ToDo item from the DynamoDB Table", + "main": "src/app.js", + "author": "SAM CLI", + "license": "MIT", + "dependencies": { + "aws-sdk": "^2.689.0", + "aws-embedded-metrics": "^1.1.1", + "aws-xray-sdk-core": "^3.0.1" + } +} diff --git a/todo-src/getTodo/app.js b/todo-src/getTodo/app.js new file mode 100644 index 00000000..636fe9b9 --- /dev/null +++ b/todo-src/getTodo/app.js @@ -0,0 +1,68 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT-0 + +// default imports +const AWSXRay = require('aws-xray-sdk-core') +const AWS = AWSXRay.captureAWS(require('aws-sdk')) +const { metricScope, Unit } = require("aws-embedded-metrics") +const DDB = new AWS.DynamoDB({ apiVersion: "2012-10-08" }) + + +// environment variables +const { TABLE_NAME, ENDPOINT_OVERRIDE, REGION } = process.env +const options = { region: REGION } +AWS.config.update({ region: REGION }) + +if (ENDPOINT_OVERRIDE !== "") { + options.endpoint = ENDPOINT_OVERRIDE +} + +const docClient = new AWS.DynamoDB.DocumentClient(options) +// response helper +const response = (statusCode, body, additionalHeaders) => ({ + statusCode, + body: JSON.stringify(body), + headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', ...additionalHeaders }, +}) + +function isValidRequest(context, event) { + return (event !== null) && + (event.pathParameters !== null) && + (event.pathParameters.id !== null) && + (/^[\w-]+$/.test(event.pathParameters.id)) +} + +function getRecordById(recordId) { + let params = { + TableName: TABLE_NAME, + Key: { + "id": recordId + } + } + + return docClient.get(params) +} + +// Lambda Handler +exports.getToDoItem = + metricScope(metrics => + async (event, context, callback) => { + metrics.setNamespace('TodoApp') + metrics.putDimensions({ Service: "getTodo" }) + metrics.setProperty("RequestId", context.requestId) + if (!isValidRequest(context, event)) { + metrics.putMetric("Error", 1, Unit.Count) + return response(400, { message: "Error: Invalid request" }) + } + + try { + let data = await getRecordById(event.pathParameters.id).promise() + metrics.putMetric("Success", 1, Unit.Count) + return response(200, data) + + } catch (err) { + metrics.putMetric("Error", 1, Unit.Count) + return response(400, { message: err.message }) + } + } + ) diff --git a/todo-src/getTodo/event.json b/todo-src/getTodo/event.json new file mode 100644 index 00000000..70d4cec1 --- /dev/null +++ b/todo-src/getTodo/event.json @@ -0,0 +1,135 @@ +{ + "resource": "/item/{id}", + "path": "/item/1", + "httpMethod": "GET", + "headers": { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3", + "accept-encoding": "gzip, deflate, br", + "Accept-Language": "en-US,en;q=0.9,pt-BR;q=0.8,pt;q=0.7,es-CO;q=0.6,es;q=0.5,zh-CN;q=0.4,zh;q=0.3", + "cache-control": "max-age=0", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-Country": "US", + "Host": "bsdhc1dx2g.execute-api.us-east-1.amazonaws.com", + "sec-fetch-mode": "navigate", + "sec-fetch-site": "none", + "sec-fetch-user": "?1", + "upgrade-insecure-requests": "1", + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36", + "Via": "2.0 af59cbeda88e3a41b2689a634f61c64d.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "NzOz5TfgbqItgmy_C2Zj4TjAnkMteY-aNMmshY4W9TZ1U-rJr5LHWw==", + "X-Amzn-Trace-Id": "Root=1-5dc86974-035ac025a456001d3ac4b6cb", + "X-Forwarded-For": "72.21.196.66, 54.239.145.80", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "multiValueHeaders": { + "Accept": [ + "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3" + ], + "accept-encoding": [ + "gzip, deflate, br" + ], + "Accept-Language": [ + "en-US,en;q=0.9,pt-BR;q=0.8,pt;q=0.7,es-CO;q=0.6,es;q=0.5,zh-CN;q=0.4,zh;q=0.3" + ], + "cache-control": [ + "max-age=0" + ], + "CloudFront-Forwarded-Proto": [ + "https" + ], + "CloudFront-Is-Desktop-Viewer": [ + "true" + ], + "CloudFront-Is-Mobile-Viewer": [ + "false" + ], + "CloudFront-Is-SmartTV-Viewer": [ + "false" + ], + "CloudFront-Is-Tablet-Viewer": [ + "false" + ], + "CloudFront-Viewer-Country": [ + "US" + ], + "Host": [ + "bsdhc1dx2g.execute-api.us-east-1.amazonaws.com" + ], + "sec-fetch-mode": [ + "navigate" + ], + "sec-fetch-site": [ + "none" + ], + "sec-fetch-user": [ + "?1" + ], + "upgrade-insecure-requests": [ + "1" + ], + "User-Agent": [ + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36" + ], + "Via": [ + "2.0 af59cbeda88e3a41b2689a634f61c64d.cloudfront.net (CloudFront)" + ], + "X-Amz-Cf-Id": [ + "NzOz5TfgbqItgmy_C2Zj4TjAnkMteY-aNMmshY4W9TZ1U-rJr5LHWw==" + ], + "X-Amzn-Trace-Id": [ + "Root=1-5dc86974-035ac025a456001d3ac4b6cb" + ], + "X-Forwarded-For": [ + "72.21.196.66, 54.239.145.80" + ], + "X-Forwarded-Port": [ + "443" + ], + "X-Forwarded-Proto": [ + "https" + ] + }, + "queryStringParameters": null, + "multiValueQueryStringParameters": null, + "pathParameters": { + "id": "1" + }, + "stageVariables": null, + "requestContext": { + "resourceId": "d14k8r", + "resourcePath": "/item/{id}", + "httpMethod": "GET", + "extendedRequestId": "C9VqNFmZIAMFZ6w=", + "requestTime": "10/Nov/2019:19:48:04 +0000", + "path": "/prod/item/1", + "accountId": "407958460921", + "protocol": "HTTP/1.1", + "stage": "prod", + "domainPrefix": "bsdhc1dx2g", + "requestTimeEpoch": 1573415284484, + "requestId": "12cdc30f-1a51-4959-8897-837910884e15", + "identity": { + "cognitoIdentityPoolId": null, + "accountId": null, + "cognitoIdentityId": null, + "caller": null, + "sourceIp": "72.21.196.66", + "principalOrgId": null, + "accessKey": null, + "cognitoAuthenticationType": null, + "cognitoAuthenticationProvider": null, + "userArn": null, + "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36", + "user": null + }, + "domainName": "bsdhc1dx2g.execute-api.us-east-1.amazonaws.com", + "apiId": "bsdhc1dx2g" + }, + "body": null, + "isBase64Encoded": false +} \ No newline at end of file diff --git a/todo-src/getTodo/package.json b/todo-src/getTodo/package.json new file mode 100644 index 00000000..7c8d77e0 --- /dev/null +++ b/todo-src/getTodo/package.json @@ -0,0 +1,13 @@ +{ + "name": "GetTodo-item", + "version": "1.0.0", + "description": "Add a new ToDo item to the DynamoDB Table", + "main": "src/app.js", + "author": "SAM CLI", + "license": "MIT", + "dependencies": { + "aws-sdk": "^2.689.0", + "aws-embedded-metrics": "^1.1.1", + "aws-xray-sdk-core": "^3.0.1" + } +} diff --git a/todo-src/package.json b/todo-src/package.json new file mode 100644 index 00000000..8f876665 --- /dev/null +++ b/todo-src/package.json @@ -0,0 +1,19 @@ +{ + "name": "hello_world", + "version": "1.0.0", + "description": "hello world sample for NodeJS", + "main": "app.js", + "repository": "https://github.com/awslabs/aws-sam-cli/tree/develop/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs", + "author": "SAM CLI", + "license": "MIT", + "dependencies": { + "axios": "^0.18.0" + }, + "scripts": { + "test": "mocha tests/unit/" + }, + "devDependencies": { + "chai": "^4.2.0", + "mocha": "^6.1.4" + } +} diff --git a/todo-src/test/environment/mac.json b/todo-src/test/environment/mac.json new file mode 100644 index 00000000..254161cb --- /dev/null +++ b/todo-src/test/environment/mac.json @@ -0,0 +1,28 @@ +{ + "GetTodoFunction": { + "ENDPOINT_OVERRIDE": "http://docker.for.mac.localhost:8000", + "TABLE_NAME": "TodoTable" + }, + "GetAllTodoFunction": { + "ENDPOINT_OVERRIDE": "http://docker.for.mac.localhost:8000", + "TABLE_NAME": "TodoTable" + }, + "CompleteTodoFunction": { + "ENDPOINT_OVERRIDE": "http://docker.for.mac.localhost:8000", + "TABLE_NAME": "TodoTable" + }, + "AddTodoFunction": { + "ENDPOINT_OVERRIDE": "http://docker.for.mac.localhost:8000", + "TABLE_NAME": "TodoTable" + }, + "UpdateTodoFunction": { + "ENDPOINT_OVERRIDE": "http://docker.for.mac.localhost:8000", + "TABLE_NAME": "TodoTable" + }, + "DeleteTodoFunction": { + "ENDPOINT_OVERRIDE": "http://docker.for.mac.localhost:8000", + "TABLE_NAME": "TodoTable" + } + + +} \ No newline at end of file diff --git a/todo-src/test/unit/test-handler.js b/todo-src/test/unit/test-handler.js new file mode 100644 index 00000000..bd75a0ba --- /dev/null +++ b/todo-src/test/unit/test-handler.js @@ -0,0 +1,22 @@ +'use strict'; + +const app = require('../../app.js'); +const chai = require('chai'); +const expect = chai.expect; +var event, context; + +describe('Tests index', function () { + it('verifies successful response', async () => { + const result = await app.getToDoItems(event, context) + + expect(result).to.be.an('object'); + expect(result.statusCode).to.equal(200); + expect(result.body).to.be.an('string'); + + let response = JSON.parse(result.body); + + expect(response).to.be.an('object'); + expect(response.message).to.be.equal("List of all the items"); + // expect(response.location).to.be.an("string"); + }); +}); diff --git a/todo-src/updateTodo/app.js b/todo-src/updateTodo/app.js new file mode 100644 index 00000000..dd9b1377 --- /dev/null +++ b/todo-src/updateTodo/app.js @@ -0,0 +1,88 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT-0 + +// default imports +const AWSXRay = require('aws-xray-sdk-core') +const AWS = AWSXRay.captureAWS(require('aws-sdk')) +const { metricScope, Unit } = require("aws-embedded-metrics") +const DDB = new AWS.DynamoDB({ apiVersion: "2012-10-08" }) + + +// environment variables +const { TABLE_NAME, ENDPOINT_OVERRIDE, REGION } = process.env +const options = { region: REGION } +AWS.config.update({ region: REGION }) + +if (ENDPOINT_OVERRIDE !== "") { + options.endpoint = ENDPOINT_OVERRIDE +} + +const docClient = new AWS.DynamoDB.DocumentClient(options) +// response helper +const response = (statusCode, body, additionalHeaders) => ({ + statusCode, + body: JSON.stringify(body), + headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', ...additionalHeaders }, +}) + +function isValidRequest(context, event) { + let isIdValid = (event !== null) && + (event.pathParameters !== null) && + (event.pathParameters.id !== null) && + (/^[\w-]+$/.test(event.pathParameters.id)) + + let body = event.body + let isBodyValid = (body !== null) && + (body.completed !== null) && + (body.item !== null); + + return isIdValid && isBodyValid; +} + +function updateRecord(recordId, eventBody) { + let d = new Date() + const params = { + TableName: TABLE_NAME, + Key: { + "id": recordId + }, + UpdateExpression: "set completed = :c, lastupdate_date = :lud, #i = :i", + ExpressionAttributeNames: { + // using ExpressionAttributeNames to show how to + // overcome reserved names, in this case + '#i': 'item' + }, + ExpressionAttributeValues: { + ':c': eventBody.completed, + ':lud': d.toISOString(), + ':i': eventBody.item + }, + ReturnValues: "ALL_NEW" + } + + return docClient.update(params) +} + +// Lambda Handler +exports.updateToDoItem = + metricScope(metrics => + async (event, context, callback) => { + metrics.setNamespace('TodoApp') + metrics.putDimensions({ Service: "updateTodo" }) + metrics.setProperty("RequestId", context.requestId) + + if (!isValidRequest(context, event)) { + metrics.putMetric("Error", 1, Unit.Count) + return response(400, { message: "Error: Invalid request" }) + } + + try { + let data = await updateRecord(event.pathParameters.id, event.body).promise() + metrics.putMetric("Success", 1, Unit.Count) + return response(200, data) + } catch (err) { + metrics.putMetric("Error", 1, Unit.Count) + return response(400, { message: err.message }) + } + } + ) diff --git a/todo-src/updateTodo/event.json b/todo-src/updateTodo/event.json new file mode 100644 index 00000000..0871c47c --- /dev/null +++ b/todo-src/updateTodo/event.json @@ -0,0 +1,138 @@ +{ + "resource": "/item/{id}", + "path": "/item/1", + "httpMethod": "POST", + "headers": { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3", + "accept-encoding": "gzip, deflate, br", + "Accept-Language": "en-US,en;q=0.9,pt-BR;q=0.8,pt;q=0.7,es-CO;q=0.6,es;q=0.5,zh-CN;q=0.4,zh;q=0.3", + "cache-control": "max-age=0", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-Country": "US", + "Host": "bsdhc1dx2g.execute-api.us-east-1.amazonaws.com", + "sec-fetch-mode": "navigate", + "sec-fetch-site": "none", + "sec-fetch-user": "?1", + "upgrade-insecure-requests": "1", + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36", + "Via": "2.0 af59cbeda88e3a41b2689a634f61c64d.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "NzOz5TfgbqItgmy_C2Zj4TjAnkMteY-aNMmshY4W9TZ1U-rJr5LHWw==", + "X-Amzn-Trace-Id": "Root=1-5dc86974-035ac025a456001d3ac4b6cb", + "X-Forwarded-For": "72.21.196.66, 54.239.145.80", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "multiValueHeaders": { + "Accept": [ + "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3" + ], + "accept-encoding": [ + "gzip, deflate, br" + ], + "Accept-Language": [ + "en-US,en;q=0.9,pt-BR;q=0.8,pt;q=0.7,es-CO;q=0.6,es;q=0.5,zh-CN;q=0.4,zh;q=0.3" + ], + "cache-control": [ + "max-age=0" + ], + "CloudFront-Forwarded-Proto": [ + "https" + ], + "CloudFront-Is-Desktop-Viewer": [ + "true" + ], + "CloudFront-Is-Mobile-Viewer": [ + "false" + ], + "CloudFront-Is-SmartTV-Viewer": [ + "false" + ], + "CloudFront-Is-Tablet-Viewer": [ + "false" + ], + "CloudFront-Viewer-Country": [ + "US" + ], + "Host": [ + "bsdhc1dx2g.execute-api.us-east-1.amazonaws.com" + ], + "sec-fetch-mode": [ + "navigate" + ], + "sec-fetch-site": [ + "none" + ], + "sec-fetch-user": [ + "?1" + ], + "upgrade-insecure-requests": [ + "1" + ], + "User-Agent": [ + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36" + ], + "Via": [ + "2.0 af59cbeda88e3a41b2689a634f61c64d.cloudfront.net (CloudFront)" + ], + "X-Amz-Cf-Id": [ + "NzOz5TfgbqItgmy_C2Zj4TjAnkMteY-aNMmshY4W9TZ1U-rJr5LHWw==" + ], + "X-Amzn-Trace-Id": [ + "Root=1-5dc86974-035ac025a456001d3ac4b6cb" + ], + "X-Forwarded-For": [ + "72.21.196.66, 54.239.145.80" + ], + "X-Forwarded-Port": [ + "443" + ], + "X-Forwarded-Proto": [ + "https" + ] + }, + "queryStringParameters": null, + "multiValueQueryStringParameters": null, + "pathParameters": { + "id": "1" + }, + "stageVariables": null, + "requestContext": { + "resourceId": "d14k8r", + "resourcePath": "/item/{id}", + "httpMethod": "GET", + "extendedRequestId": "C9VqNFmZIAMFZ6w=", + "requestTime": "10/Nov/2019:19:48:04 +0000", + "path": "/prod/item/1", + "accountId": "407958460921", + "protocol": "HTTP/1.1", + "stage": "prod", + "domainPrefix": "bsdhc1dx2g", + "requestTimeEpoch": 1573415284484, + "requestId": "12cdc30f-1a51-4959-8897-837910884e15", + "identity": { + "cognitoIdentityPoolId": null, + "accountId": null, + "cognitoIdentityId": null, + "caller": null, + "sourceIp": "72.21.196.66", + "principalOrgId": null, + "accessKey": null, + "cognitoAuthenticationType": null, + "cognitoAuthenticationProvider": null, + "userArn": null, + "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36", + "user": null + }, + "domainName": "bsdhc1dx2g.execute-api.us-east-1.amazonaws.com", + "apiId": "bsdhc1dx2g" + }, + "body": { + "completed":false, + "item": "buy even more milk" + }, + "isBase64Encoded": false +} \ No newline at end of file diff --git a/todo-src/updateTodo/package.json b/todo-src/updateTodo/package.json new file mode 100644 index 00000000..fef48644 --- /dev/null +++ b/todo-src/updateTodo/package.json @@ -0,0 +1,13 @@ +{ + "name": "UpdateTodo-item", + "version": "1.0.0", + "description": "Updates a ToDo item on the DynamoDB Table", + "main": "src/app.js", + "author": "SAM CLI", + "license": "MIT", + "dependencies": { + "aws-sdk": "^2.689.0", + "aws-embedded-metrics": "^1.1.1", + "aws-xray-sdk-core": "^3.0.1" + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..c5b06ab7 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "outDir": "./dist", + "esModuleInterop": true, + "experimentalDecorators": true, + "emitDecoratorMetadata": true + } +} \ No newline at end of file diff --git a/website/.bowerrc b/website/.bowerrc deleted file mode 100644 index 69fad358..00000000 --- a/website/.bowerrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "directory": "bower_components" -} diff --git a/website/.editorconfig b/website/.editorconfig deleted file mode 100644 index e717f5eb..00000000 --- a/website/.editorconfig +++ /dev/null @@ -1,13 +0,0 @@ -# http://editorconfig.org -root = true - -[*] -indent_style = space -indent_size = 2 -end_of_line = lf -charset = utf-8 -trim_trailing_whitespace = true -insert_final_newline = true - -[*.md] -trim_trailing_whitespace = false diff --git a/website/.eslintrc b/website/.eslintrc deleted file mode 100644 index 5a33f9f6..00000000 --- a/website/.eslintrc +++ /dev/null @@ -1,13 +0,0 @@ -{ - "extends": "eslint:recommended", - "plugins": ["angular"], - "env": { - "browser": true, - "jasmine": true - }, - "globals": { - "angular": true, - "module": true, - "inject": true - } -} diff --git a/website/.gitignore b/website/.gitignore deleted file mode 100644 index 8fdca78e..00000000 --- a/website/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -node_modules/ -bower_components/ -coverage/ -.sass-cache/ -.idea/ -.tmp/ -./dist/ diff --git a/website/.yo-rc.json b/website/.yo-rc.json deleted file mode 100644 index 53584fc4..00000000 --- a/website/.yo-rc.json +++ /dev/null @@ -1,80 +0,0 @@ -{ - "generator-gulp-angular": { - "version": "1.1.0", - "props": { - "angularVersion": "~1.5.3", - "angularModules": [ - { - "key": "animate", - "module": "ngAnimate" - }, - { - "key": "cookies", - "module": "ngCookies" - }, - { - "key": "touch", - "module": "ngTouch" - }, - { - "key": "sanitize", - "module": "ngSanitize" - }, - { - "key": "messages", - "module": "ngMessages" - }, - { - "key": "aria", - "module": "ngAria" - } - ], - "jQuery": { - "key": "jqLite" - }, - "resource": { - "key": "$http", - "module": null - }, - "router": { - "key": "ui-router", - "module": "ui.router" - }, - "ui": { - "key": "angular-material", - "module": "ngMaterial" - }, - "cssPreprocessor": { - "key": "noCssPrepro", - "extension": "css" - }, - "jsPreprocessor": { - "key": "noJsPrepro", - "extension": "js", - "srcExtension": "js" - }, - "htmlPreprocessor": { - "key": "noHtmlPrepro", - "extension": "html" - }, - "bootstrapComponents": { - "name": null, - "version": null, - "key": null, - "module": null - }, - "foundationComponents": { - "name": null, - "version": null, - "key": null, - "module": null - }, - "paths": { - "src": "src", - "dist": "dist", - "e2e": "e2e", - "tmp": ".tmp" - } - } - } -} \ No newline at end of file diff --git a/website/Readme.md b/website/Readme.md deleted file mode 100644 index 8ee03e22..00000000 --- a/website/Readme.md +++ /dev/null @@ -1,114 +0,0 @@ -## Serverless Blog Engine Front End -The front-end website is an Angular 1.5.5 application that uses the following: - -- [Angular 1.5.5](https://angularjs.org) -- [Angular Material UI](https://material.angularjs.org) -- [AWS SDK for JavaScript in the Browser](https://aws.amazon.com/sdk-for-browser/) -- [Amazon Cognito Sync Manager](https://github.com/aws/amazon-cognito-js) - -The project uses 1 module (index) and components/directives for UI views. - - - website - - **dist** - production build is generated here, this is what you upload to your S3 bucket - - **e2e** - end-to-end tests that you can run with gulp test - - **gulp** - gulp tasks - - **src** - AngularJS application - - **app** - - components - directives and UI components - - **aws** - main AWS, Amazon API Gateway, and Amazon Cognito services - - **aws.config.js** - update the Amazon Cognito identity ID and region here - - **aws.service.js** - AWS services - - **facebook** - login with Facebook directive - - **jumbotron** - the main header - - **navbar** - UI navbar - - **post** - directive that renders a post - - **sidenav** - the side nav bar - - **lib** - vendor / 3rd party libraries including API Gateway SDK - - **aws-api-client** - the API Gateway SDK as generated from the console - - **aws-cognito** - the Amazon Cognito Sync Manager - - **aws-mobile-analytics** - the AWS Mobile Analytics JavaScript SDK - - **main** - main view - - **post** - post view/s - - **index.*** - default application module - - **assets** - - **index.html** - -After running the CloudFormation template within your account, there are a few steps needed when running the client app: - -1. Update the aws-api-client SDK with the endpoint for your deployed API. -2. Update the Amazon Cognito identity ID and region. -3. Build and upload to the S3 bucket or (optional) run locally. - -These steps are outlined below. Note that these steps assume you already have npm installed. If you do not have npm installed, go to the NodeJS site and get the latest install. (https://nodejs.org/en/download/) - -## Updating the API Gateway SDK -The API Gateway SDK is located at `website/src/lib/aws-api-client`. You need to update the endpoint for the API created by the CloudFormation template. - -1. After you run the CloudFormation stack, pull the output parameter from the stack labeled: -'APIEndpoint'. -2. Open the file located at `website/src/lib/aws-api-client/apigClient.js` for editing. -3. Replace the value for 'invokeUrl' variable in the file at line 56 with the APIEndpoint value from the CloudFormation stack output parameters. - -## Updating the Amazon Cognito identity ID and AWS region -To update the Amazon Cognito identity ID and AWS region, modify `website/src/app/components/aws/aws.config.js`: - -The code will look like: - angular - .module('ServerlessBlog') - .constant('awsRegion','') - .constant('awsCognitoIdentityPoolId', '') - .config(config); - -After you run the CloudFormation stack, replace the value in the code above with the output parameter from the stack labeled: **CognitoIdentityPoolId**. - -Update the 'awsRegion' value with the region that you are running in. This will be the first portion of the value of the **CognitoIdentityPoolId** up to the ':'. - -## Building - -To start, run the following from the website directory: - - $ cd website - $ sudo npm install -g gulp - $ sudo npm install -g bower - $ npm install - $ bower install - -### Production build -To create a production build, run the following from the site directory: - - $ gulp build - -This outputs minified scripts that can be uploaded to your S3 static site; **everything within dist/ should be uploaded**. Go to `Upload build to S3` for instructions for uploading to S3. - -### Upload build to S3 -1. Sign into the AWS management console. -2. Select S3 from the list of services. -3. Select the S3 bucket created by the CloudFormation stack for your website. -4. From the 'Actions' menu, select 'Upload'. -5. Upload the contents of the `website/dist` directory to the S3 bucket. -6. Click the 'Select Details' button and leave the defaults. -7. Click the 'Set Permissions' button. -8. Select 'Make everything public'. -9. Click 'Start Upload'. -10. Once the upload is complete, your blog is now up and running. You can find the endpoint for your serverless blog in the CloudFormation stack's output parameter labeled: **WebsiteURL** - -### Running locally -To run the website locally instead of on S3, perform the following command: - $ gulp serve - -This should open your browser window to localhost:3000. - -(Option) You can also serve the production build locally with the following: - - $ gulp serve:dist - -### Tests -You can run protractor tests; make sure you have the Java 7+ runtime installed and run the following from the site directory: - - $ gulp protractor - -This should start the protractor tests; the tests can be created and edited in the e2e/ directory. Unit tests can be run with Karma as follows: - - $ gulp test - -This runs the unit spec files located in src/app/*.spec.js. diff --git a/website/bower.json b/website/bower.json deleted file mode 100644 index b807cb8f..00000000 --- a/website/bower.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "name": "aws-serverless-ref-arch", - "version": "0.0.0", - "dependencies": { - "angular-animate": "~1.5.3", - "angular-cookies": "~1.5.3", - "angular-touch": "~1.5.3", - "angular-sanitize": "~1.5.3", - "angular-messages": "~1.5.3", - "angular-aria": "~1.5.3", - "angular-ui-router": "~0.2.15", - "angular-material": "~1.0.0", - "material-design-iconfont": "~0.0.2", - "malarkey": "yuanqing/malarkey#~1.3.1", - "angular-toastr": "~1.5.0", - "moment": "~2.10.6", - "animate.css": "~3.4.0", - "angular": "~1.5.3", - "aws-sdk": "^2.3.7" - }, - "devDependencies": { - "angular-mocks": "~1.5.3" - }, - "resolutions": { - "angular": "~1.5.3" - } -} diff --git a/website/e2e/.eslintrc b/website/e2e/.eslintrc deleted file mode 100644 index d2c6f97f..00000000 --- a/website/e2e/.eslintrc +++ /dev/null @@ -1,9 +0,0 @@ -{ - "globals": { - "browser": false, - "element": false, - "by": false, - "$": false, - "$$": false - } -} diff --git a/website/e2e/main.po.js b/website/e2e/main.po.js deleted file mode 100644 index 3faf1837..00000000 --- a/website/e2e/main.po.js +++ /dev/null @@ -1,15 +0,0 @@ -/** - * This file uses the Page Object pattern to define the main page for tests - * https://docs.google.com/presentation/d/1B6manhG0zEXkC-H-tPo2vwU06JhL8w9-XCF9oehXzAQ - */ - -'use strict'; - -var MainPage = function() { - this.jumbEl = element(by.css('.jumbotron')); - this.h1El = this.jumbEl.element(by.css('h1')); - this.imgEl = this.jumbEl.element(by.css('img')); - this.thumbnailEls = element(by.css('body')).all(by.repeater('post in main.posts')); -}; - -module.exports = new MainPage(); diff --git a/website/e2e/main.spec.js b/website/e2e/main.spec.js deleted file mode 100644 index e059631c..00000000 --- a/website/e2e/main.spec.js +++ /dev/null @@ -1,23 +0,0 @@ -'use strict'; - -describe('The main view', function () { - var page; - - beforeEach(function () { - browser.get('/index.html'); - page = require('./main.po'); - }); - - it('should include jumbotron with correct data', function() { - expect(page.h1El.getText()).toBe('Serverless Blog Engine'); - expect(page.imgEl.getAttribute('src')).toMatch(/assets\/images\/aws-cloud.png$/); - expect(page.imgEl.getAttribute('alt')).toBe('Amazon Web Services'); - }); - - /* - it('should list more than 5 posts', function () { - expect(page.thumbnailEls.count()).toBeGreaterThan(5); - }); - */ - -}); diff --git a/website/gulp/.eslintrc b/website/gulp/.eslintrc deleted file mode 100644 index 10d22388..00000000 --- a/website/gulp/.eslintrc +++ /dev/null @@ -1,5 +0,0 @@ -{ - "env": { - "node": true - } -} diff --git a/website/gulp/build.js b/website/gulp/build.js deleted file mode 100644 index f46db832..00000000 --- a/website/gulp/build.js +++ /dev/null @@ -1,97 +0,0 @@ -'use strict'; - -var path = require('path'); -var gulp = require('gulp'); -var conf = require('./conf'); - -var $ = require('gulp-load-plugins')({ - pattern: ['gulp-*', 'main-bower-files', 'uglify-save-license', 'del'] -}); - -gulp.task('partials', function () { - return gulp.src([ - path.join(conf.paths.src, '/app/**/*.html'), - path.join(conf.paths.tmp, '/serve/app/**/*.html') - ]) - .pipe($.htmlmin({ - removeEmptyAttributes: true, - removeAttributeQuotes: true, - collapseBooleanAttributes: true, - collapseWhitespace: true - })) - .pipe($.angularTemplatecache('templateCacheHtml.js', { - module: 'ServerlessBlog', - root: 'app' - })) - .pipe(gulp.dest(conf.paths.tmp + '/partials/')); -}); - -gulp.task('html', ['inject', 'partials'], function () { - var partialsInjectFile = gulp.src(path.join(conf.paths.tmp, '/partials/templateCacheHtml.js'), { read: false }); - var partialsInjectOptions = { - starttag: '', - ignorePath: path.join(conf.paths.tmp, '/partials'), - addRootSlash: false - }; - - var htmlFilter = $.filter('*.html', { restore: true }); - var jsFilter = $.filter('**/*.js', { restore: true }); - var cssFilter = $.filter('**/*.css', { restore: true }); - - return gulp.src(path.join(conf.paths.tmp, '/serve/*.html')) - .pipe($.inject(partialsInjectFile, partialsInjectOptions)) - .pipe($.useref()) - .pipe(jsFilter) - .pipe($.sourcemaps.init()) - .pipe($.ngAnnotate()) - //.pipe($.uglify({ preserveComments: $.uglifySaveLicense })).on('error', conf.errorHandler('Uglify')) - .pipe($.rev()) - .pipe($.sourcemaps.write('maps')) - .pipe(jsFilter.restore) - .pipe(cssFilter) - // .pipe($.sourcemaps.init()) - .pipe($.replace('../../bower_components/material-design-iconfont/iconfont/', '../fonts/')) - .pipe($.cssnano()) - .pipe($.rev()) - // .pipe($.sourcemaps.write('maps')) - .pipe(cssFilter.restore) - .pipe($.revReplace()) - .pipe(htmlFilter) - .pipe($.htmlmin({ - removeEmptyAttributes: true, - removeAttributeQuotes: true, - collapseBooleanAttributes: true, - collapseWhitespace: true - })) - .pipe(htmlFilter.restore) - .pipe(gulp.dest(path.join(conf.paths.dist, '/'))) - .pipe($.size({ title: path.join(conf.paths.dist, '/'), showFiles: true })); - }); - -// Only applies for fonts from bower dependencies -// Custom fonts are handled by the "other" task -gulp.task('fonts', function () { - return gulp.src($.mainBowerFiles().concat('bower_components/material-design-iconfont/iconfont/*')) - .pipe($.filter('**/*.{eot,otf,svg,ttf,woff,woff2}')) - .pipe($.flatten()) - .pipe(gulp.dest(path.join(conf.paths.dist, '/fonts/'))); -}); - -gulp.task('other', function () { - var fileFilter = $.filter(function (file) { - return file.stat.isFile(); - }); - - return gulp.src([ - path.join(conf.paths.src, '/**/*'), - path.join('!' + conf.paths.src, '/**/*.{html,css,js}') - ]) - .pipe(fileFilter) - .pipe(gulp.dest(path.join(conf.paths.dist, '/'))); -}); - -gulp.task('clean', function () { - return $.del([path.join(conf.paths.dist, '/'), path.join(conf.paths.tmp, '/')]); -}); - -gulp.task('build', ['html', 'fonts', 'other']); diff --git a/website/gulp/conf.js b/website/gulp/conf.js deleted file mode 100644 index 74db2bb6..00000000 --- a/website/gulp/conf.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * This file contains the variables used in other gulp files - * which defines tasks - * By design, we only put there very generic config values - * which are used in several places to keep good readability - * of the tasks - */ - -var gutil = require('gulp-util'); - -/** - * The main paths of your project handle these with care - */ -exports.paths = { - src: 'src', - dist: 'dist', - tmp: '.tmp', - e2e: 'e2e' -}; - -/** - * Wiredep is the lib which inject bower dependencies in your project - * Mainly used to inject script tags in the index.html but also used - * to inject css preprocessor deps and js files in karma - */ -exports.wiredep = { - exclude: [/jquery/], - directory: 'bower_components' -}; - -/** - * Common implementation for an error handler of a Gulp plugin - */ -exports.errorHandler = function(title) { - 'use strict'; - - return function(err) { - gutil.log(gutil.colors.red('[' + title + ']'), err.toString()); - this.emit('end'); - }; -}; diff --git a/website/gulp/e2e-tests.js b/website/gulp/e2e-tests.js deleted file mode 100644 index 3a66702a..00000000 --- a/website/gulp/e2e-tests.js +++ /dev/null @@ -1,38 +0,0 @@ -'use strict'; - -var path = require('path'); -var gulp = require('gulp'); -var conf = require('./conf'); - -var browserSync = require('browser-sync'); - -var $ = require('gulp-load-plugins')(); - -// Downloads the selenium webdriver -gulp.task('webdriver-update', $.protractor.webdriver_update); - -gulp.task('webdriver-standalone', $.protractor.webdriver_standalone); - -function runProtractor (done) { - var params = process.argv; - var args = params.length > 3 ? [params[3], params[4]] : []; - - gulp.src(path.join(conf.paths.e2e, '/**/*.js')) - .pipe($.protractor.protractor({ - configFile: 'protractor.conf.js', - args: args - })) - .on('error', function (err) { - // Make sure failed tests cause gulp to exit non-zero - throw err; - }) - .on('end', function () { - // Close browser sync server - browserSync.exit(); - done(); - }); -} - -gulp.task('protractor', ['protractor:src']); -gulp.task('protractor:src', ['serve:e2e', 'webdriver-update'], runProtractor); -gulp.task('protractor:dist', ['serve:e2e-dist', 'webdriver-update'], runProtractor); diff --git a/website/gulp/inject.js b/website/gulp/inject.js deleted file mode 100644 index 9441465f..00000000 --- a/website/gulp/inject.js +++ /dev/null @@ -1,53 +0,0 @@ -'use strict'; - -var path = require('path'); -var gulp = require('gulp'); -var conf = require('./conf'); - -var $ = require('gulp-load-plugins')(); - -var wiredep = require('wiredep').stream; -var _ = require('lodash'); - -var browserSync = require('browser-sync'); - -gulp.task('inject-reload', ['inject'], function() { - browserSync.reload(); -}); - -gulp.task('inject', ['scripts'], function () { - var injectStyles = gulp.src([ - path.join(conf.paths.src, '/app/**/*.css') - ], { read: false }); - - var injectScripts = gulp.src([ - path.join(conf.paths.src, '/lib/aws-api-client/lib/axios/dist/axios.standalone.js'), - path.join(conf.paths.src, '/lib/aws-api-client/lib/CryptoJS/components/hmac.js'), - path.join(conf.paths.src, '/lib/aws-api-client/lib/CryptoJS/components/enc-base64.js'), - path.join(conf.paths.src, '/lib/aws-api-client/lib/CryptoJS/rollups/hmac-sha256.js'), - path.join(conf.paths.src, '/lib/aws-api-client/lib/CryptoJS/rollups/sha256.js'), - path.join(conf.paths.src, '/lib/aws-api-client/lib/url-template/url-template.js'), - path.join(conf.paths.src, '/lib/aws-api-client/lib/apiGatewayCore/sigV4Client.js'), - path.join(conf.paths.src, '/lib/aws-api-client/lib/apiGatewayCore/apiGatewayClient.js'), - path.join(conf.paths.src, '/lib/aws-api-client/lib/apiGatewayCore/simpleHttpClient.js'), - path.join(conf.paths.src, '/lib/aws-api-client/lib/apiGatewayCore/utils.js'), - path.join(conf.paths.src, '/lib/aws-api-client/apigClient.js'), - path.join(conf.paths.src, '/lib/aws-cognito/*.js'), - path.join(conf.paths.src, '/app/**/*.module.js'), - path.join(conf.paths.src, '/app/**/*.js'), - path.join('!' + conf.paths.src, '/app/**/*.spec.js'), - path.join('!' + conf.paths.src, '/app/**/*.mock.js'), - ]) - .pipe($.angularFilesort()).on('error', conf.errorHandler('AngularFilesort')); - - var injectOptions = { - ignorePath: [conf.paths.src, path.join(conf.paths.tmp, '/serve')], - addRootSlash: false - }; - - return gulp.src(path.join(conf.paths.src, '/*.html')) - .pipe($.inject(injectStyles, injectOptions)) - .pipe($.inject(injectScripts, injectOptions)) - .pipe(wiredep(_.extend({}, conf.wiredep))) - .pipe(gulp.dest(path.join(conf.paths.tmp, '/serve'))); -}); diff --git a/website/gulp/scripts.js b/website/gulp/scripts.js deleted file mode 100644 index cab12e76..00000000 --- a/website/gulp/scripts.js +++ /dev/null @@ -1,29 +0,0 @@ -'use strict'; - -var path = require('path'); -var gulp = require('gulp'); -var conf = require('./conf'); - -var browserSync = require('browser-sync'); - -var $ = require('gulp-load-plugins')(); - - -gulp.task('scripts-reload', function() { - return buildScripts() - .pipe(browserSync.stream()); -}); - -gulp.task('scripts', function() { - return buildScripts(); -}); - -function buildScripts() { - return gulp.src([ - path.join(conf.paths.src, '/lib/**/*.js'), - path.join(conf.paths.src, '/app/**/*.js') - ]) - //.pipe($.eslint()) - //.pipe($.eslint.format()) - .pipe($.size()) -}; diff --git a/website/gulp/server.js b/website/gulp/server.js deleted file mode 100644 index 500b0c0d..00000000 --- a/website/gulp/server.js +++ /dev/null @@ -1,63 +0,0 @@ -'use strict'; - -var path = require('path'); -var gulp = require('gulp'); -var conf = require('./conf'); - -var browserSync = require('browser-sync'); -var browserSyncSpa = require('browser-sync-spa'); - -var util = require('util'); - -var proxyMiddleware = require('http-proxy-middleware'); - -function browserSyncInit(baseDir, browser) { - browser = browser === undefined ? 'default' : browser; - - var routes = null; - if(baseDir === conf.paths.src || (util.isArray(baseDir) && baseDir.indexOf(conf.paths.src) !== -1)) { - routes = { - '/bower_components': 'bower_components' - }; - } - - var server = { - baseDir: baseDir, - routes: routes - }; - - /* - * You can add a proxy to your backend by uncommenting the line below. - * You just have to configure a context which will we redirected and the target url. - * Example: $http.get('/users') requests will be automatically proxified. - * - * For more details and option, https://github.com/chimurai/http-proxy-middleware/blob/v0.9.0/README.md - */ - // server.middleware = proxyMiddleware('/users', {target: 'http://jsonplaceholder.typicode.com', changeOrigin: true}); - - browserSync.instance = browserSync.init({ - startPath: '/', - server: server, - browser: browser - }); -} - -browserSync.use(browserSyncSpa({ - selector: '[ng-app]'// Only needed for angular apps -})); - -gulp.task('serve', ['watch'], function () { - browserSyncInit([path.join(conf.paths.tmp, '/serve'), conf.paths.src]); -}); - -gulp.task('serve:dist', ['build'], function () { - browserSyncInit(conf.paths.dist); -}); - -gulp.task('serve:e2e', ['inject'], function () { - browserSyncInit([conf.paths.tmp + '/serve', conf.paths.src], []); -}); - -gulp.task('serve:e2e-dist', ['build'], function () { - browserSyncInit(conf.paths.dist, []); -}); diff --git a/website/gulp/unit-tests.js b/website/gulp/unit-tests.js deleted file mode 100644 index 576887ae..00000000 --- a/website/gulp/unit-tests.js +++ /dev/null @@ -1,52 +0,0 @@ -'use strict'; - -var path = require('path'); -var gulp = require('gulp'); -var conf = require('./conf'); - -var karma = require('karma'); - -var pathSrcHtml = [ - path.join(conf.paths.src, '/**/*.html') -]; - -var pathSrcJs = [ - path.join(conf.paths.src, '/**/!(*.spec).js') -]; - -function runTests (singleRun, done) { - var reporters = ['progress']; - var preprocessors = {}; - - pathSrcHtml.forEach(function(path) { - preprocessors[path] = ['ng-html2js']; - }); - - if (singleRun) { - pathSrcJs.forEach(function(path) { - preprocessors[path] = ['coverage']; - }); - reporters.push('coverage') - } - - var localConfig = { - configFile: path.join(__dirname, '/../karma.conf.js'), - singleRun: singleRun, - autoWatch: !singleRun, - reporters: reporters, - preprocessors: preprocessors - }; - - var server = new karma.Server(localConfig, function(failCount) { - done(failCount ? new Error("Failed " + failCount + " tests.") : null); - }) - server.start(); -} - -gulp.task('test', ['scripts'], function(done) { - runTests(true, done); -}); - -gulp.task('test:auto', ['watch'], function(done) { - runTests(false, done); -}); diff --git a/website/gulp/watch.js b/website/gulp/watch.js deleted file mode 100644 index fba2f86f..00000000 --- a/website/gulp/watch.js +++ /dev/null @@ -1,36 +0,0 @@ -'use strict'; - -var path = require('path'); -var gulp = require('gulp'); -var conf = require('./conf'); - -var browserSync = require('browser-sync'); - -function isOnlyChange(event) { - return event.type === 'changed'; -} - -gulp.task('watch', ['inject'], function () { - - gulp.watch([path.join(conf.paths.src, '/*.html'), 'bower.json'], ['inject-reload']); - - gulp.watch(path.join(conf.paths.src, '/app/**/*.css'), function(event) { - if(isOnlyChange(event)) { - browserSync.reload(event.path); - } else { - gulp.start('inject-reload'); - } - }); - - gulp.watch(path.join(conf.paths.src, '/app/**/*.js'), function(event) { - if(isOnlyChange(event)) { - gulp.start('scripts-reload'); - } else { - gulp.start('inject-reload'); - } - }); - - gulp.watch(path.join(conf.paths.src, '/app/**/*.html'), function(event) { - browserSync.reload(event.path); - }); -}); diff --git a/website/gulpfile.js b/website/gulpfile.js deleted file mode 100644 index 1f26ad8f..00000000 --- a/website/gulpfile.js +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Welcome to your gulpfile! - * The gulp tasks are split into several files in the gulp directory - * because putting it all here was too long - */ - -'use strict'; - -var gulp = require('gulp'); -var wrench = require('wrench'); - -/** - * This will load all js or coffee files in the gulp directory - * in order to load all gulp tasks - */ -wrench.readdirSyncRecursive('./gulp').filter(function(file) { - return (/\.(js|coffee)$/i).test(file); -}).map(function(file) { - require('./gulp/' + file); -}); - - -/** - * Default task clean temporaries directories and launch the - * main optimization build task - */ -gulp.task('default', ['clean'], function () { - gulp.start('build'); -}); diff --git a/website/karma.conf.js b/website/karma.conf.js deleted file mode 100644 index 79166955..00000000 --- a/website/karma.conf.js +++ /dev/null @@ -1,111 +0,0 @@ -'use strict'; - -var path = require('path'); -var conf = require('./gulp/conf'); - -var _ = require('lodash'); -var wiredep = require('wiredep'); - -var pathSrcHtml = [ - path.join(conf.paths.src, '/**/*.html') -]; - -function listFiles() { - var wiredepOptions = _.extend({}, conf.wiredep, { - dependencies: true, - devDependencies: true - }); - - var patterns = wiredep(wiredepOptions).js - .concat([ - path.join(conf.paths.src, '/app/**/*.module.js'), - path.join(conf.paths.src, '/app/**/*.js'), - path.join(conf.paths.src, '/**/*.spec.js'), - path.join(conf.paths.src, '/**/*.mock.js'), - ]) - .concat(pathSrcHtml); - - var files = patterns.map(function(pattern) { - return { - pattern: pattern - }; - }); - files.push({ - pattern: path.join(conf.paths.src, '/assets/**/*'), - included: false, - served: true, - watched: false - }); - return files; -} - -module.exports = function(config) { - - var configuration = { - files: listFiles(), - - singleRun: true, - - autoWatch: false, - - ngHtml2JsPreprocessor: { - stripPrefix: conf.paths.src + '/', - moduleName: 'ServerlessBlog' - }, - - logLevel: 'WARN', - - frameworks: ['phantomjs-shim', 'jasmine', 'angular-filesort'], - - angularFilesort: { - whitelist: [path.join(conf.paths.src, '/**/!(*.html|*.spec|*.mock).js')] - }, - - browsers : ['PhantomJS'], - - plugins : [ - 'karma-phantomjs-launcher', - 'karma-angular-filesort', - 'karma-phantomjs-shim', - 'karma-coverage', - 'karma-jasmine', - 'karma-ng-html2js-preprocessor' - ], - - coverageReporter: { - type : 'html', - dir : 'coverage/' - }, - - reporters: ['progress'], - - proxies: { - '/assets/': path.join('/base/', conf.paths.src, '/assets/') - } - }; - - // This is the default preprocessors configuration for a usage with Karma cli - // The coverage preprocessor is added in gulp/unit-test.js only for single tests - // It was not possible to do it there because karma doesn't let us now if we are - // running a single test or not - configuration.preprocessors = {}; - pathSrcHtml.forEach(function(path) { - configuration.preprocessors[path] = ['ng-html2js']; - }); - - // This block is needed to execute Chrome on Travis - // If you ever plan to use Chrome and Travis, you can keep it - // If not, you can safely remove it - // https://github.com/karma-runner/karma/issues/1144#issuecomment-53633076 - if(configuration.browsers[0] === 'Chrome' && process.env.TRAVIS) { - configuration.customLaunchers = { - 'chrome-travis-ci': { - base: 'Chrome', - flags: ['--no-sandbox'] - } - }; - configuration.browsers = ['chrome-travis-ci']; - } - - config.set(configuration); -}; diff --git a/website/package.json b/website/package.json deleted file mode 100644 index 752cdf93..00000000 --- a/website/package.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "name": "blogAngular", - "version": "0.0.0", - "dependencies": {}, - "scripts": { - "test": "gulp test" - }, - "devDependencies": { - "estraverse": "~4.1.0", - "gulp": "~3.9.0", - "gulp-autoprefixer": "~3.0.2", - "gulp-angular-templatecache": "~1.8.0", - "del": "~2.0.2", - "lodash": ">=4.17.12", - "gulp-cssnano": "~2.1.1", - "gulp-filter": "~3.0.1", - "gulp-flatten": "~0.2.0", - "gulp-eslint": "~1.0.0", - "eslint-plugin-angular": "~0.12.0", - "gulp-load-plugins": "~0.10.0", - "gulp-size": "~2.0.0", - "gulp-uglify": "~1.4.1", - "gulp-useref": "~3.0.3", - "gulp-util": "~3.0.6", - "gulp-ng-annotate": "~1.1.0", - "gulp-replace": "~0.5.4", - "gulp-rename": "~1.2.2", - "gulp-rev": "~6.0.1", - "gulp-rev-replace": "~0.4.2", - "gulp-htmlmin": "~1.3.0", - "gulp-inject": "~3.0.0", - "gulp-protractor": "~2.1.0", - "gulp-sourcemaps": "~1.6.0", - "gulp-angular-filesort": "~1.1.1", - "main-bower-files": "~2.9.0", - "wiredep": "~2.2.2", - "karma": "~0.13.10", - "karma-jasmine": "~0.3.6", - "karma-phantomjs-launcher": "~0.2.1", - "phantomjs": "~1.9.18", - "karma-angular-filesort": "~1.0.0", - "karma-phantomjs-shim": "~1.2.0", - "karma-coverage": "~0.5.2", - "karma-ng-html2js-preprocessor": "~0.2.0", - "browser-sync": "~2.9.11", - "browser-sync-spa": "~1.0.3", - "http-proxy-middleware": "~0.9.0", - "chalk": "~1.1.1", - "uglify-save-license": "~0.4.1", - "wrench": "~1.5.8" - }, - "engines": { - "node": ">=0.10.0" - } -} diff --git a/website/protractor.conf.js b/website/protractor.conf.js deleted file mode 100644 index 862d068e..00000000 --- a/website/protractor.conf.js +++ /dev/null @@ -1,27 +0,0 @@ -'use strict'; - -var paths = require('./.yo-rc.json')['generator-gulp-angular'].props.paths; - -// An example configuration file. -exports.config = { - // The address of a running selenium server. - //seleniumAddress: 'http://localhost:4444/wd/hub', - //seleniumServerJar: deprecated, this should be set on node_modules/protractor/config.json - - // Capabilities to be passed to the webdriver instance. - capabilities: { - 'browserName': 'chrome' - }, - - baseUrl: 'http://localhost:3000', - - // Spec patterns are relative to the current working directory when - // protractor is called. - specs: [paths.e2e + '/**/*.js'], - - // Options to be passed to Jasmine-node. - jasmineNodeOpts: { - showColors: true, - defaultTimeoutInterval: 30000 - } -}; diff --git a/website/src/app/components/aws/aws.api.js b/website/src/app/components/aws/aws.api.js deleted file mode 100644 index 3fd21b78..00000000 --- a/website/src/app/components/aws/aws.api.js +++ /dev/null @@ -1,332 +0,0 @@ -(function() { - 'use strict' - - angular - .module('ServerlessBlog') - .factory('apigService', apigService); - - /** - * - * API Gateway service wrapper. Handles making calls - * to the API Gateway and retrieve the Cognito Identitiy. - * Most methods will return a $q promise and provide the results - * property returned from the API result. Data is cached using - * the Angular cacheFactory for less hits to the API, the cache - * is cleared on page refresh. - * - */ - function apigService( - $q, - $log, - $cacheFactory, - $cookies, - authService ) { - - var svc = {}, - client = undefined, - sync = undefined, - cache = $cacheFactory('api'); - - /** - * Returns the Cognito Sync Manager - */ - svc.getSyncClient = function() { - return sync; - }; - - /** - * Gets the API Gateway client if defined. If not defined - * creates a new API Client based on users cognito identity. - * - * @param {boolean} refresh the api credentials on login/register - */ - svc.getApigClient = function(refresh) { - var then = $q.defer(); - if (typeof client === 'undefined' || refresh) { - authService.getIdentity() - .then(function(cognitoSync) { - client = apigClientFactory.newClient({ - accessKey: AWS.config.credentials.accessKeyId, - secretKey: AWS.config.credentials.secretAccessKey, - sessionToken: AWS.config.credentials.sessionToken - }); - sync = cognitoSync; - then.resolve(client); - - }, function(err) { - $log.error(err); - then.reject(err); - }); - } else { - then.resolve(client); - } - return then.promise; - }; - - /** - * Refresh the api gateway credentials - * @return {promise} - **/ - svc.refresh = function() { - return svc.getApigClient(true); - }, - - /** - * Get posts for a particular forum - * @param {string} - * @returns {promise} - */ - svc.getPosts = function(forumId) { - var then = $q.defer(); - svc.getApigClient() - .then(function() { - if (cache.get('posts-'+forumId)) { - $log.debug('retrieved posts from cache'); - then.resolve(cache.get('posts-'+forumId)); - } - client.forumsIdPostsGet({'id':forumId}) - .then(function(result) { - $log.info('forumsIdPostsGet: ', result); - cache.put('posts-'+forumId,result.data); - then.resolve(result.data); - }) - .catch(function(result) { - cache.remove('posts-'+forumId); - then.reject(result.data); - }); - }, function(error) { - cache.remove('posts'); - $log.error(error); - then.reject(error); - }); - return then.promise; - }; - - /** - * Get all forums - * @return {promose} - */ - svc.getForums = function() { - var then = $q.defer(); - svc.getApigClient() - .then(function() { - client.forumsGet() - .then(function(result) { - cache.put('forums',result.data); - then.resolve(result.data); - }).catch(function(result) { - then.reject(result.data); - }); - }, function(error) { - $log.error(error); - then.reject(error); - }); - return then.promise; - }; - - /** - * create a post within a forum - * @param {object} post body - * @param {string} forum ID - * @return {promise} - */ - svc.createPost = function(post,forumId) { - var then = $q.defer(); - post.email = authService.me().email; - svc.getApigClient() - .then(function() { - var params = (forumId)?{'id':forumId}:{}; - console.log('params: ', params); - client.forumsIdPostsPost(params,post) - .then(function(result) { - // for now clear the posts cache so we get the updated - // posts when the user returns to the posts section - cache.remove('posts'); - then.resolve(result.data); - }) - .catch(function(error) { - $log.error(error); - then.reject(error); - }) - }, function(error) { - then.reject(error); - }); - return then.promise; - }; - - /** - * create a comment on a post - * @param {string} - * @param {string} - * @return {promise} - */ - svc.createComment = function(postId,message) { - var then = $q.defer(); - svc.getApigClient() - .then(function() { - var body = { - 'email': authService.me().email, - 'message': message - }; - var params = { - 'id': postId - }; - client.postsIdCommentsPost(params,body) - .then(function(result) { - then.resolve(result.data); - }) - .catch(function(error) { - then.reject(error); - }); - }, function(error) { - then.reject(error); - }); - return then.promise; - } - - /** - * Get a post - * @param {string} - * @return {promise} - */ - svc.getPost = function(id) { - var then = $q.defer(); - svc.getApigClient() - .then(function() { - if (cache.get(id)) { - $log.debug('retrieved post '+id+' from cache'); - then.resolve(cache.get(id)); - } - client.postsIdGet({id:id}) - .then(function(result) { - cache.put(id,result.data); - then.resolve(result.data); - }) - .catch(function(result) { - cache.remove(id); - then.reject(result); - }); - }, function(error) { - cache.remove(id); - $log.error(error); - then.reject(error); - }); - return then.promise; - }; - - /** - * Get comments for a post - * @param {string} - * @return {promise} - */ - svc.getComments = function(postId) { - var then = $q.defer(); - var cacheId = 'comments:'+postId; - if (cache.get(cacheId)) { - $log.debug('retrieved comments for post ' + postId + ' from cache'); - then.resolve(cache.get(cacheId)); - } - svc.getApigClient() - .then(function() { - client.postsIdCommentsGet({'id':postId}) - .then(function(result) { - cache.put(cacheId,result.data); - then.resolve(result.data); - },function(error) { - $log.error(error); - cache.remove(cacheId); - then.reject(error); - }); - }) - .catch(function(error) { - $log.error(error); - cache.remove(cacheId); - then.reject(error); - }); - return then.promise; - }; - - /** - * Logout a user - * @return {promise} - */ - svc.logout = function() { - var then = $q.defer(); - AWS.config.credentials.clearCachedId(); - authService.logout('cognito-identity.amazonaws.com') - .then(function(result) { - then.resolve(result); - },function(error) { - then.reject(error); - }); - return then.promise; - }; - - /** - * user: { email: '', password: '' } - */ - svc.login = function(user) { - var then = $q.defer(); - svc.getApigClient() - .then(function() { - client.loginPost({},user) - .then(function(result) { - //$log.debug('Login Result: ', result); - if (result.data) { - authService.login('cognito-identity.amazonaws.com',result.data) - .then(function() { - then.resolve(result.data); - },function(error) { - then.reject(error); - }); - } else { - then.reject('Login Failed'); - } - }) - .catch(function(error) { - $log.error(error); - then.reject(error); - }); - }, function(error) { - $log.error(error); - then.reject(error); - }); - return then.promise; - }; - - /** - * user: { email: '', password: '' } - */ - svc.register = function(user) { - var then = $q.defer(); - svc.getApigClient() - .then(function() { - client.usersPost({},user) - .then(function(result) { - if (!result.data || !result.data.openIdToken) { - $log.error(result); - then.reject('Registration Failed :('); - } else { - svc.login(user) - .then(function(result) { - then.resolve(result); - },function(error) { - then.reject(error); - }) - } - }) - .catch(function(error) { - $log.error(error); - then.reject(error); - }); - }) - .catch(function(error) { - $log.error(error); - then.reject(error); - }); - return then.promise; - }; - - return svc; - } -})(); \ No newline at end of file diff --git a/website/src/app/components/aws/aws.auth.js b/website/src/app/components/aws/aws.auth.js deleted file mode 100644 index 8526d112..00000000 --- a/website/src/app/components/aws/aws.auth.js +++ /dev/null @@ -1,152 +0,0 @@ -(function() { - 'use strict' - - angular - .module('ServerlessBlog') - .factory('authService', authService); - - /** - * - * Authentication service to handle Cognito authentication and identity. - * Most services will return a $q promise. sessionStatus variable is used - * to track logged in status for views. Views can call sessionStatus() or - * $watch sessionStatus() for changes to authentication. - * - * $cookies are used to store authentication identity tokens etc. from - * cognito to persist sessions. - * - */ - function authService( - $q, - $log, - $cookies, - $window, - awsRegion, - awsCognitoIdentityPoolId ) { - - var sessionStatus = undefined, - user = {}, - syncClient = undefined; - - var svc = { - me: function() { - return user; - }, - setSessionStatus: function(status) { - sessionStatus = status; - }, - logout: function(service) { - var then = $q.defer(); - AWS.config.credentials.clearCachedId(); - delete AWS.config.credentials.params.Logins; - var creds = AWS.config.credentials = new AWS.CognitoIdentityCredentials({ - IdentityPoolId: awsCognitoIdentityPoolId - }); - AWS.config.update({ - region: awsRegion, - credentials: creds - }); - - $cookies.remove('awsOpenIdToken'); - $cookies.remove('awsIdentityId'); - $cookies.remove('awsEmail'); - - creds.get(function(err) { - if (err) { - $log.error(err); - $q.reject(err); - } else { - sessionStatus = false; - then.resolve(); - } - }); - - if (typeof FB !== 'undefined') { - FB.logout(function(response) { - sessionStatus = false; - }); - } - - return then.promise; - }, - sessionStatus: function() { - if (typeof sessionStatus === 'undefined') { - svc.getIdentity(); - }; - return sessionStatus; - }, - login: function(service,data) { - var then = $q.defer(); - AWS.config.credentials.params.Logins = AWS.config.credentials.params.Logins || {}; - AWS.config.credentials.params.Logins[service] = data.openIdToken; - - if (data) { - $cookies.put('awsOpenIdToken',data.openIdToken); - $cookies.put('awsIdentityId',data.identityId); - $cookies.put('awsEmail',data.email); - } - - svc.getIdentity(data) - .then(function(client) { - svc.setSessionStatus(true); - then.resolve(client); - }, function(err) { - $log.error(err); - svc.setSessionStatus(false); - then.reject(); - }); - return then.promise; - }, - getIdentity: function(data) { - var then = $q.defer(); - - if ($cookies.get('awsOpenIdToken') && - $cookies.get('awsEmail') && - $cookies.get('awsIdentityId')) { - - AWS.config.credentials = new AWS.CognitoIdentityCredentials({ - IdentityPoolId: awsCognitoIdentityPoolId, - IdentityId: $cookies.get('awsIdentityId'), - Logins: { - 'cognito-identity.amazonaws.com': $cookies.get('awsOpenIdToken') - } - }); - user.email = $cookies.get('awsEmail'); - this.setSessionStatus(true); - } - AWS.config.credentials.get(function(err,data) { - if (!err) { - var id = (data)?data.identityId:AWS.config.credentials.identityId; - user.id = id; - if (sessionStatus && !syncClient) { - syncClient = new AWS.CognitoSyncManager(); - syncClient.openOrCreateDataset('blogCreds', function(err, dataset) { - dataset.put('email',$cookies.get('awsEmail'), function(err, record) { - if (err) $log.error(err); - }); - dataset.synchronize({ - onSuccess: function(dataset, newRecords) { - //$log.debug('dataset: ', newRecords); - }, - onConflict: function(dataset, conflicts, callback) { - $log.error('CONFLICT:', conflicts); - } - }); - }); - } - then.resolve(syncClient); - } else { - $log.error(err); - svc.logout(); - } - }); - return then.promise; - }, - getSyncClient: function() { - return syncClient; - } - }; - return svc; - } - -})(); \ No newline at end of file diff --git a/website/src/app/components/aws/aws.config.js b/website/src/app/components/aws/aws.config.js deleted file mode 100644 index 01857140..00000000 --- a/website/src/app/components/aws/aws.config.js +++ /dev/null @@ -1,19 +0,0 @@ -(function() { - 'use strict'; - angular - .module('ServerlessBlog') - .constant('awsRegion','') - .constant('awsCognitoIdentityPoolId', 'UPDATE_WITH_COGNITO_IDENTITY_ID') - .config(config); - - function config(awsRegion,awsCognitoIdentityPoolId) { - var creds = AWS.config.credentials = new AWS.CognitoIdentityCredentials({ - IdentityPoolId: awsCognitoIdentityPoolId - }); - AWS.config.update({ - region: awsRegion, - credentials: creds - }); - }; - -})(); diff --git a/website/src/app/components/facebook/facebook-login.directive.js b/website/src/app/components/facebook/facebook-login.directive.js deleted file mode 100644 index fff4aa48..00000000 --- a/website/src/app/components/facebook/facebook-login.directive.js +++ /dev/null @@ -1,71 +0,0 @@ -(function() { - 'use strict' - - angular - .module('ServerlessBlog') - .directive('facebookLogin', facebookLogin); - - /** @ngInject **/ - function facebookLogin(authService,$timeout,$log) { - var directive = { - restrict:'E', - replace:true, - template:'Login with Facebook', - link: link - }; - - return directive; - - function link(scope,element,attrs) { - scope.onFbLogin = function() { - $ionicLoading.show({ - template: 'Loading...' - }); - FB.login(function(response) { - if (response.authResponse) { - getLoginStatus(); - } else { - //user hit cancel button - console.log('User cancelled login or did not fully authorize.'); - } - }, { - scope: 'public_profile,email' - }); - }; - function getLoginStatus() { - FB.getLoginStatus(function(response) { - if (response.status === 'connected') { - // the user is logged in and has authenticated your - // app, and response.authResponse supplies - // the user's ID, a valid access token, a signed - // request, and the time the access token - // and signed request each expire - var uid = response.authResponse.userID; - var accessToken = response.authResponse.accessToken; - authService.login('graph.facebook.com', accessToken); - } else if (response.status === 'not_authorized') { - // the user is logged in to Facebook, - // but has not authenticated your app - $log.debug(response); - } else { - // the user isn't logged in to Facebook. - } - }); - } - // init facebook sdk - var d = document, s = 'script', id = 'facebook-jssdk'; - var js, fjs = d.getElementsByTagName(s)[0]; - if (d.getElementById(id)) return; - js = d.createElement(s); - js.id = id; - js.type = 'text/javascript'; - js.src = "//connect.facebook.net/en_US/sdk.js#xfbml=1&version=v2.6&appId=581233125359779"; - fjs.parentNode.insertBefore(js, fjs); - - $timeout(function() { - getLoginStatus(); - },500); - } - } -})(); \ No newline at end of file diff --git a/website/src/app/components/forums/forum-select.js b/website/src/app/components/forums/forum-select.js deleted file mode 100644 index af987ab8..00000000 --- a/website/src/app/components/forums/forum-select.js +++ /dev/null @@ -1,33 +0,0 @@ -(function() { - 'use strict' - - angular - .module('ServerlessBlog') - .directive('forumSelect',forumSelect); - - /** @ngInject **/ - function forumSelect($log,apigService) { - var directive = { - restrict: 'E', - replace:true, - scope: { - 'selectedForum': '=forum' - }, - template: ' ' + - ' {{forum.name}} ' + - '', - link: link - }; - - return directive; - - function link(scope, el, attr) { - apigService.getForums() - .then(function(result) { - scope.forums = result; - },function(result) { - $log.error('getForums error: ', result); - }); - }; - } -})(); \ No newline at end of file diff --git a/website/src/app/components/jumbotron/jumbotron.css b/website/src/app/components/jumbotron/jumbotron.css deleted file mode 100644 index 981bdd7f..00000000 --- a/website/src/app/components/jumbotron/jumbotron.css +++ /dev/null @@ -1,10 +0,0 @@ -.jumbotron-img { - max-width:256px; -} -.jumbotron-href { - color:white; - text-decoration: none; -} -.jumbotron-href:hover { - text-decoration: underline; -} \ No newline at end of file diff --git a/website/src/app/components/jumbotron/jumbotron.directive.js b/website/src/app/components/jumbotron/jumbotron.directive.js deleted file mode 100644 index 3e6caa7c..00000000 --- a/website/src/app/components/jumbotron/jumbotron.directive.js +++ /dev/null @@ -1,30 +0,0 @@ -(function() { - 'use strict' - - angular - .module('ServerlessBlog') - .directive('jumbotron', jumbotron); - - /** @ngInject **/ - function jumbotron() { - var directive = { - restrict:'E', - replace:true, - scope: { - loading:'@' - }, - templateUrl:'app/components/jumbotron/jumbotron.html', - link:link - }; - - return directive; - - function link(scope,el,attrs) { - scope.img = attrs.img || 'assets/images/aws-cloud.png'; - scope.alt = attrs.apt || 'Amazon Web Services'; - scope.title = attrs.title || 'Serverless Blog Engine'; - scope.description = attrs.description || 'A serverless blog engine using Amazon Web Services'; - } - } - -})(); \ No newline at end of file diff --git a/website/src/app/components/jumbotron/jumbotron.html b/website/src/app/components/jumbotron/jumbotron.html deleted file mode 100644 index e5f518e7..00000000 --- a/website/src/app/components/jumbotron/jumbotron.html +++ /dev/null @@ -1,16 +0,0 @@ -

- - - -

{{title}}

-

- {{alt}} -
- {{description}} -

- -
-
-
- -
\ No newline at end of file diff --git a/website/src/app/components/navbar/navbar.css b/website/src/app/components/navbar/navbar.css deleted file mode 100644 index 6037e411..00000000 --- a/website/src/app/components/navbar/navbar.css +++ /dev/null @@ -1,3 +0,0 @@ -.acme-navbar-text { - color: white; -} diff --git a/website/src/app/components/navbar/navbar.directive.js b/website/src/app/components/navbar/navbar.directive.js deleted file mode 100644 index 6aab16ad..00000000 --- a/website/src/app/components/navbar/navbar.directive.js +++ /dev/null @@ -1,32 +0,0 @@ -(function() { - 'use strict'; - - angular - .module('ServerlessBlog') - .directive('acmeNavbar', acmeNavbar); - - /** @ngInject */ - function acmeNavbar() { - var directive = { - restrict: 'E', - templateUrl: 'app/components/navbar/navbar.html', - scope: { - creationDate: '=' - }, - controller: NavbarController, - controllerAs: 'vm', - bindToController: true - }; - - return directive; - - /** @ngInject */ - function NavbarController(moment) { - var vm = this; - - // "vm.creationDate" is available by directive option "bindToController: true" - vm.relativeDate = moment(vm.creationDate).fromNow(); - } - } - -})(); diff --git a/website/src/app/components/navbar/navbar.html b/website/src/app/components/navbar/navbar.html deleted file mode 100644 index 2cb2f1a0..00000000 --- a/website/src/app/components/navbar/navbar.html +++ /dev/null @@ -1,10 +0,0 @@ - - -
- Amazon Web Services - Home - Login - Register -
- -
diff --git a/website/src/app/components/post/post.directive.js b/website/src/app/components/post/post.directive.js deleted file mode 100644 index 8876ce98..00000000 --- a/website/src/app/components/post/post.directive.js +++ /dev/null @@ -1,40 +0,0 @@ -(function() { - 'use strict' - - angular - .module('ServerlessBlog') - .directive('post',post); - - /** @ngInject **/ - function post($log) { - var directive = { - restrict: 'E', - replace:true, - template: ' ' + - ' ' + - '' + - ' ' + - '{{ post.title || "Untitled" }} ' + - ' ' + - '{{ post.createdAt | date:\'fullDate\' }} ' + - ' ' + - ' ' + - ' ' + - '

{{ post.body }}

' + - '
' + - ' ' + - ' ' + - 'Read More ' + - ' ' + - ' ' + - '
', - link: link - }; - - return directive; - - function link(scope, el, attr) { - - }; - } -})(); \ No newline at end of file diff --git a/website/src/app/components/sidenav/menu-button.directive.js b/website/src/app/components/sidenav/menu-button.directive.js deleted file mode 100644 index e45cb30c..00000000 --- a/website/src/app/components/sidenav/menu-button.directive.js +++ /dev/null @@ -1,40 +0,0 @@ -(function() { - 'use strict' - - angular - .module('ServerlessBlog') - .directive('menuButton',menuButton); - - /** @ngInject **/ - function menuButton() { - var directive = { - restrict:'E', - replace:true, - template:'
' + - ' ' + - 'menu ' + - ' ' + - '
', - controller:MenuButtonController, - controllerAs: 'vm', - bindToController:true - }; - - return directive; - } - - function MenuButtonController($log,$mdSidenav) { - var vm = this; - vm.toggleRight = buildToggler('right'); - function buildToggler(navID) { - return function() { - $mdSidenav(navID).toggle(); - } - } - - } - -})(); \ No newline at end of file diff --git a/website/src/app/components/sidenav/sidenav.directive.js b/website/src/app/components/sidenav/sidenav.directive.js deleted file mode 100644 index cef812f7..00000000 --- a/website/src/app/components/sidenav/sidenav.directive.js +++ /dev/null @@ -1,106 +0,0 @@ -(function() { - 'use strict' - - angular.module('ServerlessBlog') - .directive('sidenav', sidenav); - - /** @ngInject **/ - function sidenav() { - - var directive = { - restrict:'E', - replace:true, - templateUrl:'app/components/sidenav/sidenav.html', - controller:SideNavController, - controllerAs: 'vm', - bindToController: true - }; - - return directive; - - function SideNavController( - $scope, - $log, - $window, - $mdSidenav, - apigService, - authService, - toastr ) { - - var vm = this; - vm.user = {}; - vm.loading = false; - - $scope.$watch( - function() { - return authService.sessionStatus(); - }, - function(authenticated) { - vm.authenticated = authenticated; - if (authenticated && !vm.user.email) { - vm.user.email = authService.me().email; - } - }); - - vm.close = function() { - $mdSidenav('right').close(); - }; - vm.login = function() { - vm.loading = true; - if (vm.register) { - var params = { - 'email': vm.user.email, - 'password': vm.user.password - }; - if (params.password !== vm.user.password2) { - vm.loading = false; - toastr.error('Passwords do not match'); - } else { - apigService.register(params) - .then(function(result) { - $log.debug(result); - apigService.refresh().then(function() { - vm.authenticated = true; - vm.loading = false; - },function(error) { - vm.loading = false; - $log.error(error); - toastr.error('failed to login'); - }); - },function(error) { - $log.error(error); - vm.loading = false; - vm.authenticated = false; - toastr.error('A login error occurred, please try logging in with your credentials.'); - }); - } - } else { - apigService.login(vm.user) - .then(function(result) { - apigService.refresh().then(function() { - vm.authenticated = true; - vm.loading = false; - },function(error) { - vm.loading = false; - $log.error(error); - toastr.error('failed to login'); - }); - },function(error) { - vm.loading = false; - vm.authenticated = false; - $log.error(error); - toastr.error('A login error occurred, please try again.'); - }); - } - }; - vm.logout = function() { - authService.logout() - .then(function() { - vm.authenticated = false; - },function(error) { - $log.error(error); - }); - }; - } - }; -})(); \ No newline at end of file diff --git a/website/src/app/components/sidenav/sidenav.html b/website/src/app/components/sidenav/sidenav.html deleted file mode 100644 index 3869771e..00000000 --- a/website/src/app/components/sidenav/sidenav.html +++ /dev/null @@ -1,66 +0,0 @@ - - -

-
- - close - -
- User Menu -

-
- - -
- - - - - - - - - - - - - - - Register - - - -
-
-
-
- {{ (vm.register)?'Register':'Login' }} -
-
-
- -
- Logged in as {{vm.user.email}} - -
-
-
-
- Logout -
-
-
- -
- Create Post -
- -
- -
\ No newline at end of file diff --git a/website/src/app/index.config.js b/website/src/app/index.config.js deleted file mode 100644 index 79866bfe..00000000 --- a/website/src/app/index.config.js +++ /dev/null @@ -1,24 +0,0 @@ -(function() { - 'use strict'; - - angular - .module('ServerlessBlog') - .config(config); - - /** @ngInject */ - function config($logProvider, $locationProvider, toastrConfig) { - // Enable log - $logProvider.debugEnabled(true); - - // HTML5Mode - // $locationProvider.html5Mode(true); - - // Set options third-party lib - toastrConfig.allowHtml = true; - toastrConfig.timeOut = 3000; - toastrConfig.positionClass = 'toast-top-right'; - toastrConfig.preventDuplicates = true; - toastrConfig.progressBar = true; - } - -})(); diff --git a/website/src/app/index.constants.js b/website/src/app/index.constants.js deleted file mode 100644 index b989f28c..00000000 --- a/website/src/app/index.constants.js +++ /dev/null @@ -1,9 +0,0 @@ -/* global malarkey:false, moment:false */ -(function() { - 'use strict'; - - angular - .module('ServerlessBlog') - .constant('malarkey', malarkey) - .constant('moment', moment); -})(); diff --git a/website/src/app/index.css b/website/src/app/index.css deleted file mode 100644 index 1e518242..00000000 --- a/website/src/app/index.css +++ /dev/null @@ -1,2803 +0,0 @@ -@import url(//fonts.googleapis.com/css?family=Roboto+Slab:400,700|Roboto:400,700,700italic,400italic); - -@font-face { - font-family: 'Material Icons'; - font-style: normal; - font-weight: 400; - src: url(../../bower_components/material-design-iconfont/iconfont/MaterialIcons-Regular.eot); /* For IE6-8 */ - src: local('Material Icons'), - local('MaterialIcons-Regular'), - url(../../bower_components/material-design-iconfont/iconfont/MaterialIcons-Regular.woff2) format('woff2'), - url(../../bower_components/material-design-iconfont/iconfont/MaterialIcons-Regular.woff) format('woff'), - url(../../bower_components/material-design-iconfont/iconfont/MaterialIcons-Regular.ttf) format('truetype'); -} - -.material-icons { - font-family: 'Material Icons'; - font-weight: normal; - font-style: normal; - font-size: 24px; /* Preferred icon size */ - display: inline-block; - width: 1em; - height: 1em; - line-height: 1; - text-transform: none; - letter-spacing: normal; - word-wrap: normal; - - /* Support for all WebKit browsers. */ - -webkit-font-smoothing: antialiased; - /* Support for Safari and Chrome. */ - text-rendering: optimizeLegibility; - - /* Support for Firefox. */ - -moz-osx-font-smoothing: grayscale; - - /* Support for IE. */ - font-feature-settings: 'liga'; -} - -html { - font-family: 'Roboto Slab', serif; -} - -[layout=row] { - flex-direction: row; -} - -.browsehappy { - margin: 0.2em 0; - background: #ccc; - color: #000; - padding: 0.2em 0; -} - -md-toolbar.md-default-theme { - background-color: black; -} - -section.jumbotron { - margin-bottom: 30px; - padding: 1px 30px; - background-color: #F8981D; - text-align: center; - - color: white; -} - -section.jumbotron h1 { - font-size: 3em; -} - -.techs { - display: flex; - flex-flow: row wrap; -} - -.techs md-card { - width: 30%; -} - -.techs md-card img.pull-right { - float: right; - width: 100px; -} - -md-toolbar.md-default-theme:not(.md-menu-toolbar), md-toolbar:not(.md-menu-toolbar) { - background-color: #363636; -} - - -/** - * Loaders.css - * - * Copyright (c) 2016 Jovey Zheng - * - * All animations must live in their own file - * in the animations directory and be included - * here. - * - */ -[data-loader] -{ - margin: 8px; -} -[data-loader='circle'] -{ - width: 25px; - height: 25px; - - -webkit-animation: circle infinite .75s linear; - -moz-animation: circle infinite .75s linear; - -o-animation: circle infinite .75s linear; - animation: circle infinite .75s linear; - - border: 2px solid #fff; - border-top-color: transparent; - border-radius: 100%; -} -[data-loader='circle-side'] -{ - position: relative; - - width: 25px; - height: 25px; - - -webkit-animation: circle infinite .75s linear; - -moz-animation: circle infinite .75s linear; - -o-animation: circle infinite .75s linear; - animation: circle infinite .75s linear; - - border: 2px solid #fff; - border-top-color: rgba(0, 0, 0, .2); - border-right-color: rgba(0, 0, 0, .2); - border-bottom-color: rgba(0, 0, 0, .2); - border-radius: 100%; -} - -@-webkit-keyframes circle -{ - 0% - { - -webkit-transform: rotate(0); - -ms-transform: rotate(0); - -o-transform: rotate(0); - transform: rotate(0); - } - 100% - { - -webkit-transform: rotate(360deg); - -ms-transform: rotate(360deg); - -o-transform: rotate(360deg); - transform: rotate(360deg); - } -} -@-moz-keyframes circle -{ - 0% - { - -webkit-transform: rotate(0); - -ms-transform: rotate(0); - -o-transform: rotate(0); - transform: rotate(0); - } - 100% - { - -webkit-transform: rotate(360deg); - -ms-transform: rotate(360deg); - -o-transform: rotate(360deg); - transform: rotate(360deg); - } -} -@-o-keyframes circle -{ - 0% - { - -webkit-transform: rotate(0); - -ms-transform: rotate(0); - -o-transform: rotate(0); - transform: rotate(0); - } - 100% - { - -webkit-transform: rotate(360deg); - -ms-transform: rotate(360deg); - -o-transform: rotate(360deg); - transform: rotate(360deg); - } -} -@keyframes circle -{ - 0% - { - -webkit-transform: rotate(0); - -ms-transform: rotate(0); - -o-transform: rotate(0); - transform: rotate(0); - } - 100% - { - -webkit-transform: rotate(360deg); - -ms-transform: rotate(360deg); - -o-transform: rotate(360deg); - transform: rotate(360deg); - } -} - - -[data-loader='arrow-circle'] -{ - position: relative; - - width: 25px; - height: 25px; - - -webkit-animation: arrow-circle infinite .75s linear; - -moz-animation: arrow-circle infinite .75s linear; - -o-animation: arrow-circle infinite .75s linear; - animation: arrow-circle infinite .75s linear; - - border: 2px solid #fff; - border-top-color: transparent; - border-bottom-color: transparent; - border-radius: 100%; -} -[data-loader='arrow-circle']:before, -[data-loader='arrow-circle']:after -{ - position: absolute; - top: 19px; - left: -3px; - - content: ''; - -webkit-transform: rotate(-30deg); - -ms-transform: rotate(-30deg); - -o-transform: rotate(-30deg); - transform: rotate(-30deg); - - border-top: 5px solid #fff; - border-right: 5px solid transparent; - border-left: 5px solid transparent; -} -[data-loader='arrow-circle']:after -{ - top: 0; - left: 17px; - - -webkit-transform: rotate(150deg); - -ms-transform: rotate(150deg); - -o-transform: rotate(150deg); - transform: rotate(150deg); -} - -@-webkit-keyframes arrow-circle -{ - 0% - { - -webkit-transform: rotate(360deg); - -ms-transform: rotate(360deg); - -o-transform: rotate(360deg); - transform: rotate(360deg); - } - 100% - { - -webkit-transform: rotate(0); - -ms-transform: rotate(0); - -o-transform: rotate(0); - transform: rotate(0); - } -} - -@-moz-keyframes arrow-circle -{ - 0% - { - -webkit-transform: rotate(360deg); - -ms-transform: rotate(360deg); - -o-transform: rotate(360deg); - transform: rotate(360deg); - } - 100% - { - -webkit-transform: rotate(0); - -ms-transform: rotate(0); - -o-transform: rotate(0); - transform: rotate(0); - } -} - -@-o-keyframes arrow-circle -{ - 0% - { - -webkit-transform: rotate(360deg); - -ms-transform: rotate(360deg); - -o-transform: rotate(360deg); - transform: rotate(360deg); - } - 100% - { - -webkit-transform: rotate(0); - -ms-transform: rotate(0); - -o-transform: rotate(0); - transform: rotate(0); - } -} - -@keyframes arrow-circle -{ - 0% - { - -webkit-transform: rotate(360deg); - -ms-transform: rotate(360deg); - -o-transform: rotate(360deg); - transform: rotate(360deg); - } - 100% - { - -webkit-transform: rotate(0); - -ms-transform: rotate(0); - -o-transform: rotate(0); - transform: rotate(0); - } -} - -[data-loader='ball-scale'] -{ - width: 50px; - height: 50px; - - -webkit-animation: ball-scale infinite linear .75s; - -moz-animation: ball-scale infinite linear .75s; - -o-animation: ball-scale infinite linear .75s; - animation: ball-scale infinite linear .75s; - - border-radius: 100%; - background-color: #fff; -} -@-webkit-keyframes ball-scale -{ - 0% - { - -webkit-transform: scale(.1); - -ms-transform: scale(.1); - -o-transform: scale(.1); - transform: scale(.1); - - opacity: 1; - } - 100% - { - -webkit-transform: scale(1); - -ms-transform: scale(1); - -o-transform: scale(1); - transform: scale(1); - - opacity: 0; - } -} - -@-moz-keyframes ball-scale -{ - 0% - { - -webkit-transform: scale(.1); - -ms-transform: scale(.1); - -o-transform: scale(.1); - transform: scale(.1); - - opacity: 1; - } - 100% - { - -webkit-transform: scale(1); - -ms-transform: scale(1); - -o-transform: scale(1); - transform: scale(1); - - opacity: 0; - } -} - -@-o-keyframes ball-scale -{ - 0% - { - -webkit-transform: scale(.1); - -ms-transform: scale(.1); - -o-transform: scale(.1); - transform: scale(.1); - - opacity: 1; - } - 100% - { - -webkit-transform: scale(1); - -ms-transform: scale(1); - -o-transform: scale(1); - transform: scale(1); - - opacity: 0; - } -} - -@keyframes ball-scale -{ - 0% - { - -webkit-transform: scale(.1); - -ms-transform: scale(.1); - -o-transform: scale(.1); - transform: scale(.1); - - opacity: 1; - } - 100% - { - -webkit-transform: scale(1); - -ms-transform: scale(1); - -o-transform: scale(1); - transform: scale(1); - - opacity: 0; - } -} - -[data-loader='ball-rotate'] -{ - position: relative; - - width: 15px; - height: 15px; - - -webkit-animation: ball-rotate 1s 0s cubic-bezier(.7, -.13, .22, .86) infinite; - -moz-animation: ball-rotate 1s 0s cubic-bezier(.7, -.13, .22, .86) infinite; - -o-animation: ball-rotate 1s 0s cubic-bezier(.7, -.13, .22, .86) infinite; - animation: ball-rotate 1s 0s cubic-bezier(.7, -.13, .22, .86) infinite; - - border-radius: 100%; - background-color: #fff; - - -webkit-animation-fill-mode: both; - animation-fill-mode: both; -} -[data-loader='ball-rotate']:before, -[data-loader='ball-rotate']:after -{ - position: absolute; - - width: 15px; - height: 15px; - margin: 2px; - - content: ''; - - opacity: .8; - border-radius: 100%; - background-color: #fff; -} -[data-loader='ball-rotate']:before -{ - top: 0; - left: -28px; -} -[data-loader='ball-rotate']:after -{ - top: 0; - left: 25px; -} -@-webkit-keyframes ball-rotate -{ - 0% - { - -webkit-transform: rotate(0deg) scale(1); - -ms-transform: rotate(0deg) scale(1); - -o-transform: rotate(0deg) scale(1); - transform: rotate(0deg) scale(1); - } - 50% - { - -webkit-transform: rotate(180deg) scale(.6); - -ms-transform: rotate(180deg) scale(.6); - -o-transform: rotate(180deg) scale(.6); - transform: rotate(180deg) scale(.6); - } - 100% - { - -webkit-transform: rotate(360deg) scale(1); - -ms-transform: rotate(360deg) scale(1); - -o-transform: rotate(360deg) scale(1); - transform: rotate(360deg) scale(1); - } -} -@-moz-keyframes ball-rotate -{ - 0% - { - -webkit-transform: rotate(0deg) scale(1); - -ms-transform: rotate(0deg) scale(1); - -o-transform: rotate(0deg) scale(1); - transform: rotate(0deg) scale(1); - } - 50% - { - -webkit-transform: rotate(180deg) scale(.6); - -ms-transform: rotate(180deg) scale(.6); - -o-transform: rotate(180deg) scale(.6); - transform: rotate(180deg) scale(.6); - } - 100% - { - -webkit-transform: rotate(360deg) scale(1); - -ms-transform: rotate(360deg) scale(1); - -o-transform: rotate(360deg) scale(1); - transform: rotate(360deg) scale(1); - } -} -@-o-keyframes ball-rotate -{ - 0% - { - -webkit-transform: rotate(0deg) scale(1); - -ms-transform: rotate(0deg) scale(1); - -o-transform: rotate(0deg) scale(1); - transform: rotate(0deg) scale(1); - } - 50% - { - -webkit-transform: rotate(180deg) scale(.6); - -ms-transform: rotate(180deg) scale(.6); - -o-transform: rotate(180deg) scale(.6); - transform: rotate(180deg) scale(.6); - } - 100% - { - -webkit-transform: rotate(360deg) scale(1); - -ms-transform: rotate(360deg) scale(1); - -o-transform: rotate(360deg) scale(1); - transform: rotate(360deg) scale(1); - } -} -@keyframes ball-rotate -{ - 0% - { - -webkit-transform: rotate(0deg) scale(1); - -ms-transform: rotate(0deg) scale(1); - -o-transform: rotate(0deg) scale(1); - transform: rotate(0deg) scale(1); - } - 50% - { - -webkit-transform: rotate(180deg) scale(.6); - -ms-transform: rotate(180deg) scale(.6); - -o-transform: rotate(180deg) scale(.6); - transform: rotate(180deg) scale(.6); - } - 100% - { - -webkit-transform: rotate(360deg) scale(1); - -ms-transform: rotate(360deg) scale(1); - -o-transform: rotate(360deg) scale(1); - transform: rotate(360deg) scale(1); - } -} - -[data-loader='ball-pulse'] -{ - position: relative; - - width: 1px; - height: 1px; -} -[data-loader='ball-pulse']:before, -[data-loader='ball-pulse']:after -{ - position: absolute; - - display: inline-block; - - width: 15px; - height: 15px; - - content: ''; - - border-radius: 100%; - background-color: #fff; -} -[data-loader='ball-pulse']:before -{ - left: -15px; - - -webkit-animation: ball-pulse infinite .75s -.4s cubic-bezier(.2, .68, .18, 1.08); - -moz-animation: ball-pulse infinite .75s -.4s cubic-bezier(.2, .68, .18, 1.08); - -o-animation: ball-pulse infinite .75s -.4s cubic-bezier(.2, .68, .18, 1.08); - animation: ball-pulse infinite .75s -.4s cubic-bezier(.2, .68, .18, 1.08); -} -[data-loader='ball-pulse']:after -{ - right: -15px; - - -webkit-animation: ball-pulse infinite .75s cubic-bezier(.2, .68, .18, 1.08); - -moz-animation: ball-pulse infinite .75s cubic-bezier(.2, .68, .18, 1.08); - -o-animation: ball-pulse infinite .75s cubic-bezier(.2, .68, .18, 1.08); - animation: ball-pulse infinite .75s cubic-bezier(.2, .68, .18, 1.08); -} -@-webkit-keyframes ball-pulse -{ - 0% - { - transform: scale(1); - - opacity: 1; - } - 50% - { - transform: scale(.1); - - opacity: .6; - } - 100% - { - transform: scale(1); - - opacity: 1; - } -} -@-moz-keyframes ball-pulse -{ - 0% - { - transform: scale(1); - - opacity: 1; - } - 50% - { - transform: scale(.1); - - opacity: .6; - } - 100% - { - transform: scale(1); - - opacity: 1; - } -} -@-o-keyframes ball-pulse -{ - 0% - { - transform: scale(1); - - opacity: 1; - } - 50% - { - transform: scale(.1); - - opacity: .6; - } - 100% - { - transform: scale(1); - - opacity: 1; - } -} -@keyframes ball-pulse -{ - 0% - { - transform: scale(1); - - opacity: 1; - } - 50% - { - transform: scale(.1); - - opacity: .6; - } - 100% - { - transform: scale(1); - - opacity: 1; - } -} - - -[data-loader='ball-circle'] -{ - position: relative; - - width: 40px; - height: 40px; -} -[data-loader='ball-circle']:before, -[data-loader='ball-circle']:after -{ - position: absolute; - - width: 10px; - height: 10px; - - content: ''; - - border-radius: 100%; - background-color: #fff; -} -[data-loader='ball-circle']:before -{ - transform: translate(0, 0); - -webkit-animation: ball-circle-before infinite 1.5s linear; - -moz-animation: ball-circle-before infinite 1.5s linear; - -o-animation: ball-circle-before infinite 1.5s linear; - animation: ball-circle-before infinite 1.5s linear; -} -[data-loader='ball-circle']:after -{ - transform: translate(30px, 30px); - -webkit-animation: ball-circle-after infinite 1.5s linear; - -moz-animation: ball-circle-after infinite 1.5s linear; - -o-animation: ball-circle-after infinite 1.5s linear; - animation: ball-circle-after infinite 1.5s linear; -} - -@-webkit-keyframes ball-circle-after -{ - 0% - { - -webkit-transform: translate(30px, 30px); - -ms-transform: translate(30px, 30px); - -o-transform: translate(30px, 30px); - transform: translate(30px, 30px); - } - 25% - { - -webkit-transform: translate(0, 30px); - -ms-transform: translate(0, 30px); - -o-transform: translate(0, 30px); - transform: translate(0, 30px); - } - 50% - { - -webkit-transform: translate(0, 0); - -ms-transform: translate(0, 0); - -o-transform: translate(0, 0); - transform: translate(0, 0); - } - 75% - { - -webkit-transform: translate(30px, 0); - -ms-transform: translate(30px, 0); - -o-transform: translate(30px, 0); - transform: translate(30px, 0); - } - 100% - { - -webkit-transform: translate(30px, 30px); - -ms-transform: translate(30px, 30px); - -o-transform: translate(30px, 30px); - transform: translate(30px, 30px); - } -} -@-moz-keyframes ball-circle-after -{ - 0% - { - -webkit-transform: translate(30px, 30px); - -ms-transform: translate(30px, 30px); - -o-transform: translate(30px, 30px); - transform: translate(30px, 30px); - } - 25% - { - -webkit-transform: translate(0, 30px); - -ms-transform: translate(0, 30px); - -o-transform: translate(0, 30px); - transform: translate(0, 30px); - } - 50% - { - -webkit-transform: translate(0, 0); - -ms-transform: translate(0, 0); - -o-transform: translate(0, 0); - transform: translate(0, 0); - } - 75% - { - -webkit-transform: translate(30px, 0); - -ms-transform: translate(30px, 0); - -o-transform: translate(30px, 0); - transform: translate(30px, 0); - } - 100% - { - -webkit-transform: translate(30px, 30px); - -ms-transform: translate(30px, 30px); - -o-transform: translate(30px, 30px); - transform: translate(30px, 30px); - } -} -@-o-keyframes ball-circle-after -{ - 0% - { - -webkit-transform: translate(30px, 30px); - -ms-transform: translate(30px, 30px); - -o-transform: translate(30px, 30px); - transform: translate(30px, 30px); - } - 25% - { - -webkit-transform: translate(0, 30px); - -ms-transform: translate(0, 30px); - -o-transform: translate(0, 30px); - transform: translate(0, 30px); - } - 50% - { - -webkit-transform: translate(0, 0); - -ms-transform: translate(0, 0); - -o-transform: translate(0, 0); - transform: translate(0, 0); - } - 75% - { - -webkit-transform: translate(30px, 0); - -ms-transform: translate(30px, 0); - -o-transform: translate(30px, 0); - transform: translate(30px, 0); - } - 100% - { - -webkit-transform: translate(30px, 30px); - -ms-transform: translate(30px, 30px); - -o-transform: translate(30px, 30px); - transform: translate(30px, 30px); - } -} -@keyframes ball-circle-after -{ - 0% - { - -webkit-transform: translate(30px, 30px); - -ms-transform: translate(30px, 30px); - -o-transform: translate(30px, 30px); - transform: translate(30px, 30px); - } - 25% - { - -webkit-transform: translate(0, 30px); - -ms-transform: translate(0, 30px); - -o-transform: translate(0, 30px); - transform: translate(0, 30px); - } - 50% - { - -webkit-transform: translate(0, 0); - -ms-transform: translate(0, 0); - -o-transform: translate(0, 0); - transform: translate(0, 0); - } - 75% - { - -webkit-transform: translate(30px, 0); - -ms-transform: translate(30px, 0); - -o-transform: translate(30px, 0); - transform: translate(30px, 0); - } - 100% - { - -webkit-transform: translate(30px, 30px); - -ms-transform: translate(30px, 30px); - -o-transform: translate(30px, 30px); - transform: translate(30px, 30px); - } -} - -@-webkit-keyframes ball-circle-before -{ - 0% - { - -webkit-transform: translate(0, 0); - -ms-transform: translate(0, 0); - -o-transform: translate(0, 0); - transform: translate(0, 0); - } - 25% - { - -webkit-transform: translate(30px, 0); - -ms-transform: translate(30px, 0); - -o-transform: translate(30px, 0); - transform: translate(30px, 0); - } - 50% - { - -webkit-transform: translate(30px, 30px); - -ms-transform: translate(30px, 30px); - -o-transform: translate(30px, 30px); - transform: translate(30px, 30px); - } - 75% - { - -webkit-transform: translate(0, 30px); - -ms-transform: translate(0, 30px); - -o-transform: translate(0, 30px); - transform: translate(0, 30px); - } - 100% - { - -webkit-transform: translate(0, 0); - -ms-transform: translate(0, 0); - -o-transform: translate(0, 0); - transform: translate(0, 0); - } -} -@-moz-keyframes ball-circle-before -{ - 0% - { - -webkit-transform: translate(0, 0); - -ms-transform: translate(0, 0); - -o-transform: translate(0, 0); - transform: translate(0, 0); - } - 25% - { - -webkit-transform: translate(30px, 0); - -ms-transform: translate(30px, 0); - -o-transform: translate(30px, 0); - transform: translate(30px, 0); - } - 50% - { - -webkit-transform: translate(30px, 30px); - -ms-transform: translate(30px, 30px); - -o-transform: translate(30px, 30px); - transform: translate(30px, 30px); - } - 75% - { - -webkit-transform: translate(0, 30px); - -ms-transform: translate(0, 30px); - -o-transform: translate(0, 30px); - transform: translate(0, 30px); - } - 100% - { - -webkit-transform: translate(0, 0); - -ms-transform: translate(0, 0); - -o-transform: translate(0, 0); - transform: translate(0, 0); - } -} -@-o-keyframes ball-circle-before -{ - 0% - { - -webkit-transform: translate(0, 0); - -ms-transform: translate(0, 0); - -o-transform: translate(0, 0); - transform: translate(0, 0); - } - 25% - { - -webkit-transform: translate(30px, 0); - -ms-transform: translate(30px, 0); - -o-transform: translate(30px, 0); - transform: translate(30px, 0); - } - 50% - { - -webkit-transform: translate(30px, 30px); - -ms-transform: translate(30px, 30px); - -o-transform: translate(30px, 30px); - transform: translate(30px, 30px); - } - 75% - { - -webkit-transform: translate(0, 30px); - -ms-transform: translate(0, 30px); - -o-transform: translate(0, 30px); - transform: translate(0, 30px); - } - 100% - { - -webkit-transform: translate(0, 0); - -ms-transform: translate(0, 0); - -o-transform: translate(0, 0); - transform: translate(0, 0); - } -} -@keyframes ball-circle-before -{ - 0% - { - -webkit-transform: translate(0, 0); - -ms-transform: translate(0, 0); - -o-transform: translate(0, 0); - transform: translate(0, 0); - } - 25% - { - -webkit-transform: translate(30px, 0); - -ms-transform: translate(30px, 0); - -o-transform: translate(30px, 0); - transform: translate(30px, 0); - } - 50% - { - -webkit-transform: translate(30px, 30px); - -ms-transform: translate(30px, 30px); - -o-transform: translate(30px, 30px); - transform: translate(30px, 30px); - } - 75% - { - -webkit-transform: translate(0, 30px); - -ms-transform: translate(0, 30px); - -o-transform: translate(0, 30px); - transform: translate(0, 30px); - } - 100% - { - -webkit-transform: translate(0, 0); - -ms-transform: translate(0, 0); - -o-transform: translate(0, 0); - transform: translate(0, 0); - } -} - -[data-loader='rectangle'] -{ - position: relative; - - width: 10px; - height: 30px; - - -webkit-animation: rectangle infinite 1s ease-in-out -.2s; - -moz-animation: rectangle infinite 1s ease-in-out -.2s; - -o-animation: rectangle infinite 1s ease-in-out -.2s; - animation: rectangle infinite 1s ease-in-out -.2s; - - background-color: #fff; -} -[data-loader='rectangle']:before, -[data-loader='rectangle']:after -{ - position: absolute; - - width: 10px; - height: 30px; - - content: ''; - - background-color: #fff; -} -[data-loader='rectangle']:before -{ - left: -20px; - - -webkit-animation: rectangle infinite 1s ease-in-out -.4s; - -moz-animation: rectangle infinite 1s ease-in-out -.4s; - -o-animation: rectangle infinite 1s ease-in-out -.4s; - animation: rectangle infinite 1s ease-in-out -.4s; -} -[data-loader='rectangle']:after -{ - right: -20px; - - -webkit-animation: rectangle infinite 1s ease-in-out; - -moz-animation: rectangle infinite 1s ease-in-out; - -o-animation: rectangle infinite 1s ease-in-out; - animation: rectangle infinite 1s ease-in-out; -} -@-webkit-keyframes rectangle -{ - 0%, - 80%, - 100% - { - height: 35px; - - -webkit-box-shadow: 0 0 #fff; - box-shadow: 0 0 #fff; - } - 40% - { - height: 45px; - - -webkit-box-shadow: 0 -20px #fff; - box-shadow: 0 -20px #fff; - } -} -@-moz-keyframes rectangle -{ - 0%, - 80%, - 100% - { - height: 35px; - - -webkit-box-shadow: 0 0 #fff; - box-shadow: 0 0 #fff; - } - 40% - { - height: 45px; - - -webkit-box-shadow: 0 -20px #fff; - box-shadow: 0 -20px #fff; - } -} -@-o-keyframes rectangle -{ - 0%, - 80%, - 100% - { - height: 35px; - - -webkit-box-shadow: 0 0 #fff; - box-shadow: 0 0 #fff; - } - 40% - { - height: 45px; - - -webkit-box-shadow: 0 -20px #fff; - box-shadow: 0 -20px #fff; - } -} -@keyframes rectangle -{ - 0%, - 80%, - 100% - { - height: 35px; - - -webkit-box-shadow: 0 0 #fff; - box-shadow: 0 0 #fff; - } - 40% - { - height: 45px; - - -webkit-box-shadow: 0 -20px #fff; - box-shadow: 0 -20px #fff; - } -} -[data-loader='heart'] -{ - position: relative; - - width: 100px; - height: 90px; - - -webkit-animation: heart infinite .85s linear; - -moz-animation: heart infinite .85s linear; - -o-animation: heart infinite .85s linear; - animation: heart infinite .85s linear; -} -[data-loader='heart']:before, -[data-loader='heart']:after -{ - position: absolute; - top: 0; - left: 30px; - - width: 30px; - height: 50px; - - content: ''; - -webkit-transform: rotate(-45deg); - -moz-transform: rotate(-45deg); - -ms-transform: rotate(-45deg); - -o-transform: rotate(-45deg); - transform: rotate(-45deg); - -webkit-transform-origin: 0 100%; - -moz-transform-origin: 0 100%; - -ms-transform-origin: 0 100%; - -o-transform-origin: 0 100%; - transform-origin: 0 100%; - - -moz-border-radius: 30px 30px 0 0; - border-radius: 30px 30px 0 0; - background: #fff; -} -[data-loader='heart']:after -{ - left: 0; - - -webkit-transform: rotate(45deg); - -moz-transform: rotate(45deg); - -ms-transform: rotate(45deg); - -o-transform: rotate(45deg); - transform: rotate(45deg); - -webkit-transform-origin: 100% 100%; - -moz-transform-origin: 100% 100%; - -ms-transform-origin: 100% 100%; - -o-transform-origin: 100% 100%; - transform-origin: 100% 100%; -} -@-webkit-keyframes heart -{ - 0% - { - -webkit-transform: scale(.8); - -ms-transform: scale(.8); - -o-transform: scale(.8); - transform: scale(.8); - } - 50% - { - -webkit-transform: scale(1); - -ms-transform: scale(1); - -o-transform: scale(1); - transform: scale(1); - } - 100% - { - -webkit-transform: scale(.8); - -ms-transform: scale(.8); - -o-transform: scale(.8); - transform: scale(.8); - } -} -@-moz-keyframes heart -{ - 0% - { - -webkit-transform: scale(.8); - -ms-transform: scale(.8); - -o-transform: scale(.8); - transform: scale(.8); - } - 50% - { - -webkit-transform: scale(1); - -ms-transform: scale(1); - -o-transform: scale(1); - transform: scale(1); - } - 100% - { - -webkit-transform: scale(.8); - -ms-transform: scale(.8); - -o-transform: scale(.8); - transform: scale(.8); - } -} -@-o-keyframes heart -{ - 0% - { - -webkit-transform: scale(.8); - -ms-transform: scale(.8); - -o-transform: scale(.8); - transform: scale(.8); - } - 50% - { - -webkit-transform: scale(1); - -ms-transform: scale(1); - -o-transform: scale(1); - transform: scale(1); - } - 100% - { - -webkit-transform: scale(.8); - -ms-transform: scale(.8); - -o-transform: scale(.8); - transform: scale(.8); - } -} -@keyframes heart -{ - 0% - { - -webkit-transform: scale(.8); - -ms-transform: scale(.8); - -o-transform: scale(.8); - transform: scale(.8); - } - 50% - { - -webkit-transform: scale(1); - -ms-transform: scale(1); - -o-transform: scale(1); - transform: scale(1); - } - 100% - { - -webkit-transform: scale(.8); - -ms-transform: scale(.8); - -o-transform: scale(.8); - transform: scale(.8); - } -} - -[data-loader='jumping'] -{ - position: relative; - - width: 50px; - - -webkit-perspective: 200px; - -moz-perspective: 200px; - -ms-perspective: 200px; - perspective: 200px; -} -[data-loader='jumping']:before, -[data-loader='jumping']:after -{ - position: absolute; - - width: 20px; - height: 20px; - - content: ''; - animation: jumping .5s infinite alternate; - - background: rgba(0,0,0,0); -} -[data-loader='jumping']:before -{ - left: 0; -} -[data-loader='jumping']:after -{ - right: 0; - - animation-delay: .15s; -} -@-webkit-keyframes jumping -{ - 0% - { - -webkit-transform: scale(1.0) translateY(0px) rotateX(0deg); - -ms-transform: scale(1.0) translateY(0px) rotateX(0deg); - -o-transform: scale(1.0) translateY(0px) rotateX(0deg); - transform: scale(1.0) translateY(0px) rotateX(0deg); - - -webkit-box-shadow: 0 0 0 rgba(0,0,0,0); - box-shadow: 0 0 0 rgba(0,0,0,0); - } - 100% - { - -webkit-transform: scale(1.2) translateY(-25px) rotateX(45deg); - -ms-transform: scale(1.2) translateY(-25px) rotateX(45deg); - -o-transform: scale(1.2) translateY(-25px) rotateX(45deg); - transform: scale(1.2) translateY(-25px) rotateX(45deg); - - background: rgb(255,255,255); - -webkit-box-shadow: 0 25px 40px rgb(255,255,255); - box-shadow: 0 25px 40px rgb(255,255,255); - } -} -@-moz-keyframes jumping -{ - 0% - { - -webkit-transform: scale(1.0) translateY(0px) rotateX(0deg); - -ms-transform: scale(1.0) translateY(0px) rotateX(0deg); - -o-transform: scale(1.0) translateY(0px) rotateX(0deg); - transform: scale(1.0) translateY(0px) rotateX(0deg); - - -webkit-box-shadow: 0 0 0 rgba(0,0,0,0); - box-shadow: 0 0 0 rgba(0,0,0,0); - } - 100% - { - -webkit-transform: scale(1.2) translateY(-25px) rotateX(45deg); - -ms-transform: scale(1.2) translateY(-25px) rotateX(45deg); - -o-transform: scale(1.2) translateY(-25px) rotateX(45deg); - transform: scale(1.2) translateY(-25px) rotateX(45deg); - - background: rgb(255,255,255); - -webkit-box-shadow: 0 25px 40px rgb(255,255,255); - box-shadow: 0 25px 40px rgb(255,255,255); - } -} -@-o-keyframes jumping -{ - 0% - { - -webkit-transform: scale(1.0) translateY(0px) rotateX(0deg); - -ms-transform: scale(1.0) translateY(0px) rotateX(0deg); - -o-transform: scale(1.0) translateY(0px) rotateX(0deg); - transform: scale(1.0) translateY(0px) rotateX(0deg); - - -webkit-box-shadow: 0 0 0 rgba(0,0,0,0); - box-shadow: 0 0 0 rgba(0,0,0,0); - } - 100% - { - -webkit-transform: scale(1.2) translateY(-25px) rotateX(45deg); - -ms-transform: scale(1.2) translateY(-25px) rotateX(45deg); - -o-transform: scale(1.2) translateY(-25px) rotateX(45deg); - transform: scale(1.2) translateY(-25px) rotateX(45deg); - - background: rgb(255,255,255); - -webkit-box-shadow: 0 25px 40px rgb(255,255,255); - box-shadow: 0 25px 40px rgb(255,255,255); - } -} -@keyframes jumping -{ - 0% - { - -webkit-transform: scale(1.0) translateY(0px) rotateX(0deg); - -ms-transform: scale(1.0) translateY(0px) rotateX(0deg); - -o-transform: scale(1.0) translateY(0px) rotateX(0deg); - transform: scale(1.0) translateY(0px) rotateX(0deg); - - -webkit-box-shadow: 0 0 0 rgba(0,0,0,0); - box-shadow: 0 0 0 rgba(0,0,0,0); - } - 100% - { - -webkit-transform: scale(1.2) translateY(-25px) rotateX(45deg); - -ms-transform: scale(1.2) translateY(-25px) rotateX(45deg); - -o-transform: scale(1.2) translateY(-25px) rotateX(45deg); - transform: scale(1.2) translateY(-25px) rotateX(45deg); - - background: rgb(255,255,255); - -webkit-box-shadow: 0 25px 40px rgb(255,255,255); - box-shadow: 0 25px 40px rgb(255,255,255); - } -} - -[data-loader='satellite'] -{ - position: relative; - - width: 48px; - height: 48px; - - animation: satellite 3s infinite linear; - - border: 1px solid #fff; - border-radius: 100%; -} -[data-loader='satellite']:before, -[data-loader='satellite']:after -{ - position: absolute; - left: 0; - - width: 15px; - height: 15px; - - content: ''; - - border-radius: 100%; - background-color: #fff; - -webkit-box-shadow: 0 0 10px #fff; - box-shadow: 0 0 10px #fff; -} -[data-loader='satellite']:after -{ - right: 0; - - width: 24px; - height: 24px; - margin: 12px; -} - -@-webkit-keyframes satellite -{ - from - { - -webkit-transform: rotate(0) translateZ(0); - -ms-transform: rotate(0) translateZ(0); - -o-transform: rotate(0) translateZ(0); - transform: rotate(0) translateZ(0); - } - to - { - -webkit-transform: rotate(360deg) translateZ(0); - -ms-transform: rotate(360deg) translateZ(0); - -o-transform: rotate(360deg) translateZ(0); - transform: rotate(360deg) translateZ(0); - } -} -@-moz-keyframes satellite -{ - from - { - -webkit-transform: rotate(0) translateZ(0); - -ms-transform: rotate(0) translateZ(0); - -o-transform: rotate(0) translateZ(0); - transform: rotate(0) translateZ(0); - } - to - { - -webkit-transform: rotate(360deg) translateZ(0); - -ms-transform: rotate(360deg) translateZ(0); - -o-transform: rotate(360deg) translateZ(0); - transform: rotate(360deg) translateZ(0); - } -} -@-o-keyframes satellite -{ - from - { - -webkit-transform: rotate(0) translateZ(0); - -ms-transform: rotate(0) translateZ(0); - -o-transform: rotate(0) translateZ(0); - transform: rotate(0) translateZ(0); - } - to - { - -webkit-transform: rotate(360deg) translateZ(0); - -ms-transform: rotate(360deg) translateZ(0); - -o-transform: rotate(360deg) translateZ(0); - transform: rotate(360deg) translateZ(0); - } -} -@keyframes satellite -{ - from - { - -webkit-transform: rotate(0) translateZ(0); - -ms-transform: rotate(0) translateZ(0); - -o-transform: rotate(0) translateZ(0); - transform: rotate(0) translateZ(0); - } - to - { - -webkit-transform: rotate(360deg) translateZ(0); - -ms-transform: rotate(360deg) translateZ(0); - -o-transform: rotate(360deg) translateZ(0); - transform: rotate(360deg) translateZ(0); - } -} - -[data-loader='circle-scale'] -{ - position: relative; - - width: 36px; -} -[data-loader='circle-scale']:before, -[data-loader='circle-scale']:after -{ - position: absolute; - left: 0; - - width: 32px; - height: 32px; - - content: ''; - -webkit-animation: .75s circle-scale infinite linear alternate; - -o-animation: .75s circle-scale infinite linear alternate; - animation: .75s circle-scale infinite linear alternate; - - border: 3px solid #fff; - border-radius: 100%; -} -[data-loader='circle-scale']:before -{ - margin: 2px; - - -webkit-animation-delay: .35s; - -o-animation-delay: .35s; - animation-delay: .35s; -} -[data-loader='circle-scale']:after -{ - width: 36px; - height: 36px; -} -@-webkit-keyframes circle-scale -{ - 0% - { - -webkit-transform: scale(.2); - -ms-transform: scale(.2); - -o-transform: scale(.2); - transform: scale(.2); - } - 100% - { - -webkit-transform: scale(1.2); - -ms-transform: scale(1.2); - -o-transform: scale(1.2); - transform: scale(1.2); - } -} -@-moz-keyframes circle-scale -{ - 0% - { - -webkit-transform: scale(.2); - -ms-transform: scale(.2); - -o-transform: scale(.2); - transform: scale(.2); - } - 100% - { - -webkit-transform: scale(1.2); - -ms-transform: scale(1.2); - -o-transform: scale(1.2); - transform: scale(1.2); - } -} -@-o-keyframes circle-scale -{ - 0% - { - -webkit-transform: scale(.2); - -ms-transform: scale(.2); - -o-transform: scale(.2); - transform: scale(.2); - } - 100% - { - -webkit-transform: scale(1.2); - -ms-transform: scale(1.2); - -o-transform: scale(1.2); - transform: scale(1.2); - } -} -@keyframes circle-scale -{ - 0% - { - -webkit-transform: scale(.2); - -ms-transform: scale(.2); - -o-transform: scale(.2); - transform: scale(.2); - } - 100% - { - -webkit-transform: scale(1.2); - -ms-transform: scale(1.2); - -o-transform: scale(1.2); - transform: scale(1.2); - } -} - -[data-loader='ball-fade'] -{ - position: relative; - - width: 15px; - height: 15px; - - -webkit-animation: 1.2s ball-fade infinite cubic-bezier(.78, .14, .15, .86) .2s; - -o-animation: 1.2s ball-fade infinite cubic-bezier(.78, .14, .15, .86) .2s; - animation: 1.2s ball-fade infinite cubic-bezier(.78, .14, .15, .86) .2s; - - border-radius: 100%; - background-color: rgba(255, 255, 255, .0); -} -[data-loader='ball-fade']:before, -[data-loader='ball-fade']:after -{ - position: absolute; - - width: 15px; - height: 15px; - - content: ''; - -webkit-animation: 1.2s ball-fade infinite cubic-bezier(.78, .14, .15, .86); - -o-animation: 1.2s ball-fade infinite cubic-bezier(.78, .14, .15, .86); - animation: 1.2s ball-fade infinite cubic-bezier(.78, .14, .15, .86); - - border-radius: 100%; - background-color: rgba(255, 255, 255, .0); -} -[data-loader='ball-fade']:before -{ - left: -20px; -} -[data-loader='ball-fade']:after -{ - right: -20px; - - -webkit-animation-delay: .4s; - -o-animation-delay: .4s; - animation-delay: .4s; -} -@-webkit-keyframes ball-fade -{ - 0% - { - background-color: rgba(255, 255, 255, 1); - } - 100% - { - background-color: rgba(255, 255, 255, 0); - } -} -@-moz-keyframes ball-fade -{ - 0% - { - background-color: rgba(255, 255, 255, 1); - } - 100% - { - background-color: rgba(255, 255, 255, 0); - } -} -@-o-keyframes ball-fade -{ - 0% - { - background-color: rgba(255, 255, 255, 1); - } - 100% - { - background-color: rgba(255, 255, 255, 0); - } -} -@keyframes ball-fade -{ - 0% - { - background-color: rgba(255, 255, 255, 1); - } - 100% - { - background-color: rgba(255, 255, 255, 0); - } -} - -[data-loader='spinner'] -{ - width: 25px; - height: 25px; - - -webkit-animation: spinner 1.2s infinite ease-in-out; - -o-animation: spinner 1.2s infinite ease-in-out; - animation: spinner 1.2s infinite ease-in-out; - - background-color: #fff; - box-shadow: 0 0 10px #fff; -} -@-webkit-keyframes spinner -{ - 0% - { - -webkit-transform: perspective(120px) rotateX(0) rotateY(0); - -ms-transform: perspective(120px) rotateX(0) rotateY(0); - -o-transform: perspective(120px) rotateX(0) rotateY(0); - transform: perspective(120px) rotateX(0) rotateY(0); - } - 50% - { - -webkit-transform: perspective(120px) rotateX(-180deg) rotateY(0); - -ms-transform: perspective(120px) rotateX(-180deg) rotateY(0); - -o-transform: perspective(120px) rotateX(-180deg) rotateY(0); - transform: perspective(120px) rotateX(-180deg) rotateY(0); - } - 100% - { - -webkit-transform: perspective(120px) rotateX(-180deg) rotateY(-180deg); - -ms-transform: perspective(120px) rotateX(-180deg) rotateY(-180deg); - -o-transform: perspective(120px) rotateX(-180deg) rotateY(-180deg); - transform: perspective(120px) rotateX(-180deg) rotateY(-180deg); - } -} -@-moz-keyframes spinner -{ - 0% - { - -webkit-transform: perspective(120px) rotateX(0) rotateY(0); - -ms-transform: perspective(120px) rotateX(0) rotateY(0); - -o-transform: perspective(120px) rotateX(0) rotateY(0); - transform: perspective(120px) rotateX(0) rotateY(0); - } - 50% - { - -webkit-transform: perspective(120px) rotateX(-180deg) rotateY(0); - -ms-transform: perspective(120px) rotateX(-180deg) rotateY(0); - -o-transform: perspective(120px) rotateX(-180deg) rotateY(0); - transform: perspective(120px) rotateX(-180deg) rotateY(0); - } - 100% - { - -webkit-transform: perspective(120px) rotateX(-180deg) rotateY(-180deg); - -ms-transform: perspective(120px) rotateX(-180deg) rotateY(-180deg); - -o-transform: perspective(120px) rotateX(-180deg) rotateY(-180deg); - transform: perspective(120px) rotateX(-180deg) rotateY(-180deg); - } -} -@-o-keyframes spinner -{ - 0% - { - -webkit-transform: perspective(120px) rotateX(0) rotateY(0); - -ms-transform: perspective(120px) rotateX(0) rotateY(0); - -o-transform: perspective(120px) rotateX(0) rotateY(0); - transform: perspective(120px) rotateX(0) rotateY(0); - } - 50% - { - -webkit-transform: perspective(120px) rotateX(-180deg) rotateY(0); - -ms-transform: perspective(120px) rotateX(-180deg) rotateY(0); - -o-transform: perspective(120px) rotateX(-180deg) rotateY(0); - transform: perspective(120px) rotateX(-180deg) rotateY(0); - } - 100% - { - -webkit-transform: perspective(120px) rotateX(-180deg) rotateY(-180deg); - -ms-transform: perspective(120px) rotateX(-180deg) rotateY(-180deg); - -o-transform: perspective(120px) rotateX(-180deg) rotateY(-180deg); - transform: perspective(120px) rotateX(-180deg) rotateY(-180deg); - } -} -@keyframes spinner -{ - 0% - { - -webkit-transform: perspective(120px) rotateX(0) rotateY(0); - -ms-transform: perspective(120px) rotateX(0) rotateY(0); - -o-transform: perspective(120px) rotateX(0) rotateY(0); - transform: perspective(120px) rotateX(0) rotateY(0); - } - 50% - { - -webkit-transform: perspective(120px) rotateX(-180deg) rotateY(0); - -ms-transform: perspective(120px) rotateX(-180deg) rotateY(0); - -o-transform: perspective(120px) rotateX(-180deg) rotateY(0); - transform: perspective(120px) rotateX(-180deg) rotateY(0); - } - 100% - { - -webkit-transform: perspective(120px) rotateX(-180deg) rotateY(-180deg); - -ms-transform: perspective(120px) rotateX(-180deg) rotateY(-180deg); - -o-transform: perspective(120px) rotateX(-180deg) rotateY(-180deg); - transform: perspective(120px) rotateX(-180deg) rotateY(-180deg); - } -} - -[data-loader='ball-roll'] -{ - position: relative; - left: -20px; - - width: 15px; - height: 15px; - - -webkit-animation: ball-roll 1s linear infinite alternate; - -moz-animation: ball-roll 1s linear infinite alternate; - animation: ball-roll 1s linear infinite alternate; - - border-radius: 50%; -} -@-webkit-keyframes ball-roll -{ - 0% - { - background-color: rgba(255,255,255, 1); - box-shadow: 22px 0 0 0 rgba(255,255,255,.2), - 44px 0 0 0 rgba(255,255,255,.2); - } - 25% - { - background-color: rgba(255,255,255, .4); - box-shadow: 22px 0 0 0 rgba(255,255,255,2), - 44px 0 0 0 rgba(255,255,255,.2); - } - 75% - { - background-color: rgba(255,255,255, .4); - box-shadow: 22px 0 0 0 rgba(255,255,255,.2), - 44px 0 0 0 rgba(255,255,255,1); - } -} -@-moz-keyframes ball-roll -{ - 0% - { - background-color: rgba(255,255,255, 1); - box-shadow: 22px 0 0 0 rgba(255,255,255,.2), - 44px 0 0 0 rgba(255,255,255,.2); - } - 25% - { - background-color: rgba(255,255,255, .4); - box-shadow: 22px 0 0 0 rgba(255,255,255,2), - 44px 0 0 0 rgba(255,255,255,.2); - } - 75% - { - background-color: rgba(255,255,255, .4); - box-shadow: 22px 0 0 0 rgba(255,255,255,.2), - 44px 0 0 0 rgba(255,255,255,1); - } -} -@-o-keyframes ball-roll -{ - 0% - { - background-color: rgba(255,255,255, 1); - box-shadow: 22px 0 0 0 rgba(255,255,255,.2), - 44px 0 0 0 rgba(255,255,255,.2); - } - 25% - { - background-color: rgba(255,255,255, .4); - box-shadow: 22px 0 0 0 rgba(255,255,255,2), - 44px 0 0 0 rgba(255,255,255,.2); - } - 75% - { - background-color: rgba(255,255,255, .4); - box-shadow: 22px 0 0 0 rgba(255,255,255,.2), - 44px 0 0 0 rgba(255,255,255,1); - } -} -@keyframes ball-roll -{ - 0% - { - background-color: rgba(255,255,255, 1); - box-shadow: 22px 0 0 0 rgba(255,255,255,.2), - 44px 0 0 0 rgba(255,255,255,.2); - } - 25% - { - background-color: rgba(255,255,255, .4); - box-shadow: 22px 0 0 0 rgba(255,255,255,2), - 44px 0 0 0 rgba(255,255,255,.2); - } - 75% - { - background-color: rgba(255,255,255, .4); - box-shadow: 22px 0 0 0 rgba(255,255,255,.2), - 44px 0 0 0 rgba(255,255,255,1); - } -} - -[data-loader='ball-auto'] -{ - width: 8px; - height: 8px; - - animation: ball-auto 2.5s infinite linear; - - box-shadow: 0 -20px 0 -3px #de5454, - 20px 0 0 -3px #1ecaba, - 0 20px 0 -3px #ffeb02, - -20px 0 0 -3px #2c89e8; -} -@-webkit-keyframes ball-auto -{ - 0% - { - transform: rotate(0); - } - 45% - { - transform: rotate(360deg); - - border-radius: 100%; - box-shadow: 0 -20px 0 10px #de5454, - 20px 0 0 10px #1ecaba, - 0 20px 0 10px #decf20, - -20px 0 0 10px #2c89e8; - } - 100% - { - transform: rotate(720deg); - } -} -@-moz-keyframes ball-auto -{ - 0% - { - transform: rotate(0); - } - 45% - { - transform: rotate(360deg); - - border-radius: 100%; - box-shadow: 0 -20px 0 10px #de5454, - 20px 0 0 10px #1ecaba, - 0 20px 0 10px #decf20, - -20px 0 0 10px #2c89e8; - } - 100% - { - transform: rotate(720deg); - } -} -@-o-keyframes ball-auto -{ - 0% - { - transform: rotate(0); - } - 45% - { - transform: rotate(360deg); - - border-radius: 100%; - box-shadow: 0 -20px 0 10px #de5454, - 20px 0 0 10px #1ecaba, - 0 20px 0 10px #decf20, - -20px 0 0 10px #2c89e8; - } - 100% - { - transform: rotate(720deg); - } -} -@keyframes ball-auto -{ - 0% - { - transform: rotate(0); - } - 45% - { - transform: rotate(360deg); - - border-radius: 100%; - box-shadow: 0 -20px 0 10px #de5454, - 20px 0 0 10px #1ecaba, - 0 20px 0 10px #decf20, - -20px 0 0 10px #2c89e8; - } - 100% - { - transform: rotate(720deg); - } -} - -[data-loader='wave'] -{ - width: 3em; - height: 2em; - - animation: wave 1.5s linear infinite; - - background: linear-gradient(#9b59b6, #9b59b6) 0 50%, linear-gradient(#9b59b6, #9b59b6) .625em 50%, linear-gradient(#9b59b6, #9b59b6) 1.25em 50%, linear-gradient(#9b59b6, #9b59b6) 1.875em 50%, linear-gradient(#9b59b6, #9b59b6) 2.5em 50%; - background-repeat: no-repeat; - background-size: .5em .25em, .5em .25em, .5em .25em, .5em .25em, .5em .25em; -} -@-webkit-keyframes wave -{ - 25% - { - background: linear-gradient(#3498db, #3498db) 0 50%, linear-gradient(#9b59b6, #9b59b6) .625em 50%, linear-gradient(#9b59b6, #9b59b6) 1.25em 50%, linear-gradient(#9b59b6, #9b59b6) 1.875em 50%, linear-gradient(#9b59b6, #9b59b6) 2.5em 50%; - background-repeat: no-repeat; - background-size: .5em 2em, .5em .25em, .5em .25em, .5em .25em, .5em .25em; - } - 37.5% - { - background: linear-gradient(#9b59b6, #9b59b6) 0 50%, linear-gradient(#3498db, #3498db) .625em 50%, linear-gradient(#9b59b6, #9b59b6) 1.25em 50%, linear-gradient(#9b59b6, #9b59b6) 1.875em 50%, linear-gradient(#9b59b6, #9b59b6) 2.5em 50%; - background-repeat: no-repeat; - background-size: .5em .25em, .5em 2em, .5em .25em, .5em .25em, .5em .25em; - } - 50% - { - background: linear-gradient(#9b59b6, #9b59b6) 0 50%, linear-gradient(#9b59b6, #9b59b6) .625em 50%, linear-gradient(#3498db, #3498db) 1.25em 50%, linear-gradient(#9b59b6, #9b59b6) 1.875em 50%, linear-gradient(#9b59b6, #9b59b6) 2.5em 50%; - background-repeat: no-repeat; - background-size: .5em .25em, .5em .25em, .5em 2em, .5em .25em, .5em .25em; - } - 62.5% - { - background: linear-gradient(#9b59b6, #9b59b6) 0 50%, linear-gradient(#9b59b6, #9b59b6) .625em 50%, linear-gradient(#9b59b6, #9b59b6) 1.25em 50%, linear-gradient(#3498db, #3498db) 1.875em 50%, linear-gradient(#9b59b6, #9b59b6) 2.5em 50%; - background-repeat: no-repeat; - background-size: .5em .25em, .5em .25em, .5em .25em, .5em 2em, .5em .25em; - } - 75% - { - background: linear-gradient(#9b59b6, #9b59b6) 0 50%, linear-gradient(#9b59b6, #9b59b6) .625em 50%, linear-gradient(#9b59b6, #9b59b6) 1.25em 50%, linear-gradient(#9b59b6, #9b59b6) 1.875em 50%, linear-gradient(#3498db, #3498db) 2.5em 50%; - background-repeat: no-repeat; - background-size: .5em .25em, .5em .25em, .5em .25em, .5em .25em, .5em 2em; - } -} -@-moz-keyframes wave -{ - 25% - { - background: linear-gradient(#3498db, #3498db) 0 50%, linear-gradient(#9b59b6, #9b59b6) .625em 50%, linear-gradient(#9b59b6, #9b59b6) 1.25em 50%, linear-gradient(#9b59b6, #9b59b6) 1.875em 50%, linear-gradient(#9b59b6, #9b59b6) 2.5em 50%; - background-repeat: no-repeat; - background-size: .5em 2em, .5em .25em, .5em .25em, .5em .25em, .5em .25em; - } - 37.5% - { - background: linear-gradient(#9b59b6, #9b59b6) 0 50%, linear-gradient(#3498db, #3498db) .625em 50%, linear-gradient(#9b59b6, #9b59b6) 1.25em 50%, linear-gradient(#9b59b6, #9b59b6) 1.875em 50%, linear-gradient(#9b59b6, #9b59b6) 2.5em 50%; - background-repeat: no-repeat; - background-size: .5em .25em, .5em 2em, .5em .25em, .5em .25em, .5em .25em; - } - 50% - { - background: linear-gradient(#9b59b6, #9b59b6) 0 50%, linear-gradient(#9b59b6, #9b59b6) .625em 50%, linear-gradient(#3498db, #3498db) 1.25em 50%, linear-gradient(#9b59b6, #9b59b6) 1.875em 50%, linear-gradient(#9b59b6, #9b59b6) 2.5em 50%; - background-repeat: no-repeat; - background-size: .5em .25em, .5em .25em, .5em 2em, .5em .25em, .5em .25em; - } - 62.5% - { - background: linear-gradient(#9b59b6, #9b59b6) 0 50%, linear-gradient(#9b59b6, #9b59b6) .625em 50%, linear-gradient(#9b59b6, #9b59b6) 1.25em 50%, linear-gradient(#3498db, #3498db) 1.875em 50%, linear-gradient(#9b59b6, #9b59b6) 2.5em 50%; - background-repeat: no-repeat; - background-size: .5em .25em, .5em .25em, .5em .25em, .5em 2em, .5em .25em; - } - 75% - { - background: linear-gradient(#9b59b6, #9b59b6) 0 50%, linear-gradient(#9b59b6, #9b59b6) .625em 50%, linear-gradient(#9b59b6, #9b59b6) 1.25em 50%, linear-gradient(#9b59b6, #9b59b6) 1.875em 50%, linear-gradient(#3498db, #3498db) 2.5em 50%; - background-repeat: no-repeat; - background-size: .5em .25em, .5em .25em, .5em .25em, .5em .25em, .5em 2em; - } -} -@-o-keyframes wave -{ - 25% - { - background: linear-gradient(#3498db, #3498db) 0 50%, linear-gradient(#9b59b6, #9b59b6) .625em 50%, linear-gradient(#9b59b6, #9b59b6) 1.25em 50%, linear-gradient(#9b59b6, #9b59b6) 1.875em 50%, linear-gradient(#9b59b6, #9b59b6) 2.5em 50%; - background-repeat: no-repeat; - background-size: .5em 2em, .5em .25em, .5em .25em, .5em .25em, .5em .25em; - } - 37.5% - { - background: linear-gradient(#9b59b6, #9b59b6) 0 50%, linear-gradient(#3498db, #3498db) .625em 50%, linear-gradient(#9b59b6, #9b59b6) 1.25em 50%, linear-gradient(#9b59b6, #9b59b6) 1.875em 50%, linear-gradient(#9b59b6, #9b59b6) 2.5em 50%; - background-repeat: no-repeat; - background-size: .5em .25em, .5em 2em, .5em .25em, .5em .25em, .5em .25em; - } - 50% - { - background: linear-gradient(#9b59b6, #9b59b6) 0 50%, linear-gradient(#9b59b6, #9b59b6) .625em 50%, linear-gradient(#3498db, #3498db) 1.25em 50%, linear-gradient(#9b59b6, #9b59b6) 1.875em 50%, linear-gradient(#9b59b6, #9b59b6) 2.5em 50%; - background-repeat: no-repeat; - background-size: .5em .25em, .5em .25em, .5em 2em, .5em .25em, .5em .25em; - } - 62.5% - { - background: linear-gradient(#9b59b6, #9b59b6) 0 50%, linear-gradient(#9b59b6, #9b59b6) .625em 50%, linear-gradient(#9b59b6, #9b59b6) 1.25em 50%, linear-gradient(#3498db, #3498db) 1.875em 50%, linear-gradient(#9b59b6, #9b59b6) 2.5em 50%; - background-repeat: no-repeat; - background-size: .5em .25em, .5em .25em, .5em .25em, .5em 2em, .5em .25em; - } - 75% - { - background: linear-gradient(#9b59b6, #9b59b6) 0 50%, linear-gradient(#9b59b6, #9b59b6) .625em 50%, linear-gradient(#9b59b6, #9b59b6) 1.25em 50%, linear-gradient(#9b59b6, #9b59b6) 1.875em 50%, linear-gradient(#3498db, #3498db) 2.5em 50%; - background-repeat: no-repeat; - background-size: .5em .25em, .5em .25em, .5em .25em, .5em .25em, .5em 2em; - } -} -@keyframes wave -{ - 25% - { - background: linear-gradient(#3498db, #3498db) 0 50%, linear-gradient(#9b59b6, #9b59b6) .625em 50%, linear-gradient(#9b59b6, #9b59b6) 1.25em 50%, linear-gradient(#9b59b6, #9b59b6) 1.875em 50%, linear-gradient(#9b59b6, #9b59b6) 2.5em 50%; - background-repeat: no-repeat; - background-size: .5em 2em, .5em .25em, .5em .25em, .5em .25em, .5em .25em; - } - 37.5% - { - background: linear-gradient(#9b59b6, #9b59b6) 0 50%, linear-gradient(#3498db, #3498db) .625em 50%, linear-gradient(#9b59b6, #9b59b6) 1.25em 50%, linear-gradient(#9b59b6, #9b59b6) 1.875em 50%, linear-gradient(#9b59b6, #9b59b6) 2.5em 50%; - background-repeat: no-repeat; - background-size: .5em .25em, .5em 2em, .5em .25em, .5em .25em, .5em .25em; - } - 50% - { - background: linear-gradient(#9b59b6, #9b59b6) 0 50%, linear-gradient(#9b59b6, #9b59b6) .625em 50%, linear-gradient(#3498db, #3498db) 1.25em 50%, linear-gradient(#9b59b6, #9b59b6) 1.875em 50%, linear-gradient(#9b59b6, #9b59b6) 2.5em 50%; - background-repeat: no-repeat; - background-size: .5em .25em, .5em .25em, .5em 2em, .5em .25em, .5em .25em; - } - 62.5% - { - background: linear-gradient(#9b59b6, #9b59b6) 0 50%, linear-gradient(#9b59b6, #9b59b6) .625em 50%, linear-gradient(#9b59b6, #9b59b6) 1.25em 50%, linear-gradient(#3498db, #3498db) 1.875em 50%, linear-gradient(#9b59b6, #9b59b6) 2.5em 50%; - background-repeat: no-repeat; - background-size: .5em .25em, .5em .25em, .5em .25em, .5em 2em, .5em .25em; - } - 75% - { - background: linear-gradient(#9b59b6, #9b59b6) 0 50%, linear-gradient(#9b59b6, #9b59b6) .625em 50%, linear-gradient(#9b59b6, #9b59b6) 1.25em 50%, linear-gradient(#9b59b6, #9b59b6) 1.875em 50%, linear-gradient(#3498db, #3498db) 2.5em 50%; - background-repeat: no-repeat; - background-size: .5em .25em, .5em .25em, .5em .25em, .5em .25em, .5em 2em; - } -} - -[data-loader='spinner-circle'] -{ - width: 25px; - height: 25px; - - animation: .4s infinite linear spinner-circle; - - border-radius: 50%; - box-shadow: 30px 0 0 -6px rgba(255, 255, 255, .2), - 0 30px 0 -6px rgba(255, 255, 255, .2), - -30px 0 0 -6px rgba(255, 255, 255, .2), - 0 -30px 0 -6px rgba(255, 255, 255, .2), - 21px 21px 0 -6px rgba(255, 255, 255, .2), - -21px 21px 0 -6px rgba(255, 255, 255, .2), - -21px -21px 0 -6px rgba(255, 255, 255, .2), - 21px -21px 0 -6px rgba(255, 255, 255, .2); -} -@-webkit-keyframes spinner-circle -{ - 12.5% - { - box-shadow: 30px 0 0 -6px rgba(255, 255, 255, .2), - 0 30px 0 -6px rgba(255, 255, 255, .2), - -30px 0 0 -6px rgba(255, 255, 255, .2), - 0 -30px 0 -6px rgba(255, 255, 255, 1), - 21px 21px 0 -6px rgba(255, 255, 255, .2), - -21px 21px 0 -6px rgba(255, 255, 255, .2), - -21px -21px 0 -6px rgba(255, 255, 255, .2), - 21px -21px 0 -6px rgba(255, 255, 255, .2); - } - 25% - { - box-shadow: 30px 0 0 -6px rgba(255, 255, 255, .2), - 0 30px 0 -6px rgba(255, 255, 255, .2), - -30px 0 0 -6px rgba(255, 255, 255, .2), - 0 -30px 0 -6px rgba(255, 255, 255, .2), - 21px 21px 0 -6px rgba(255, 255, 255, .2), - -21px 21px 0 -6px rgba(255, 255, 255, .2), - -21px -21px 0 -6px rgba(255, 255, 255, .2), - 21px -21px 0 -6px rgba(255, 255, 255, 1); - } - 37.5% - { - box-shadow: 30px 0 0 -6px rgba(255, 255, 255, 1), - 0 30px 0 -6px rgba(255, 255, 255, .2), - -30px 0 0 -6px rgba(255, 255, 255, .2), - 0 -30px 0 -6px rgba(255, 255, 255, .2), - 21px 21px 0 -6px rgba(255, 255, 255, .2), - -21px 21px 0 -6px rgba(255, 255, 255, .2), - -21px -21px 0 -6px rgba(255, 255, 255, .2), - 21px -21px 0 -6px rgba(255, 255, 255, .2); - } - 50% - { - box-shadow: 30px 0 0 -6px rgba(255, 255, 255, .2), - 0 30px 0 -6px rgba(255, 255, 255, .2), - -30px 0 0 -6px rgba(255, 255, 255, .2), - 0 -30px 0 -6px rgba(255, 255, 255, .2), - 21px 21px 0 -6px rgba(255, 255, 255, 1), - -21px 21px 0 -6px rgba(255, 255, 255, .2), - -21px -21px 0 -6px rgba(255, 255, 255, .2), - 21px -21px 0 -6px rgba(255, 255, 255, .2); - } - 62.5% - { - box-shadow: 30px 0 0 -6px rgba(255, 255, 255, .2), - 0 30px 0 -6px rgba(255, 255, 255, 1), - -30px 0 0 -6px rgba(255, 255, 255, .2), - 0 -30px 0 -6px rgba(255, 255, 255, .2), - 21px 21px 0 -6px rgba(255, 255, 255, .2), - -21px 21px 0 -6px rgba(255, 255, 255, .2), - -21px -21px 0 -6px rgba(255, 255, 255, .2), - 21px -21px 0 -6px rgba(255, 255, 255, .2); - } - 75% - { - box-shadow: 30px 0 0 -6px rgba(255, 255, 255, .2), - 0 30px 0 -6px rgba(255, 255, 255, .2), - -30px 0 0 -6px rgba(255, 255, 255, .2), - 0 -30px 0 -6px rgba(255, 255, 255, .2), - 21px 21px 0 -6px rgba(255, 255, 255, .2), - -21px 21px 0 -6px rgba(255, 255, 255, 1), - -21px -21px 0 -6px rgba(255, 255, 255, .2), - 21px -21px 0 -6px rgba(255, 255, 255, .2); - } - 87.5% - { - box-shadow: 30px 0 0 -6px rgba(255, 255, 255, .2), - 0 30px 0 -6px rgba(255, 255, 255, .2), - -30px 0 0 -6px rgba(255, 255, 255, 1), - 0 -30px 0 -6px rgba(255, 255, 255, .2), - 21px 21px 0 -6px rgba(255, 255, 255, .2), - -21px 21px 0 -6px rgba(255, 255, 255, .2), - -21px -21px 0 -6px rgba(255, 255, 255, .2), - 21px -21px 0 -6px rgba(255, 255, 255, .2); - } - 100% - { - box-shadow: 30px 0 0 -6px rgba(255, 255, 255, .2), - 0 30px 0 -6px rgba(255, 255, 255, .2), - -30px 0 0 -6px rgba(255, 255, 255, .2), - 0 -30px 0 -6px rgba(255, 255, 255, .2), - 21px 21px 0 -6px rgba(255, 255, 255, .2), - -21px 21px 0 -6px rgba(255, 255, 255, .2), - -21px -21px 0 -6px rgba(255, 255, 255, 1), - 21px -21px 0 -6px rgba(255, 255, 255, .2); - } -} -@-moz-keyframes spinner-circle -{ - 12.5% - { - box-shadow: 30px 0 0 -6px rgba(255, 255, 255, .2), - 0 30px 0 -6px rgba(255, 255, 255, .2), - -30px 0 0 -6px rgba(255, 255, 255, .2), - 0 -30px 0 -6px rgba(255, 255, 255, 1), - 21px 21px 0 -6px rgba(255, 255, 255, .2), - -21px 21px 0 -6px rgba(255, 255, 255, .2), - -21px -21px 0 -6px rgba(255, 255, 255, .2), - 21px -21px 0 -6px rgba(255, 255, 255, .2); - } - 25% - { - box-shadow: 30px 0 0 -6px rgba(255, 255, 255, .2), - 0 30px 0 -6px rgba(255, 255, 255, .2), - -30px 0 0 -6px rgba(255, 255, 255, .2), - 0 -30px 0 -6px rgba(255, 255, 255, .2), - 21px 21px 0 -6px rgba(255, 255, 255, .2), - -21px 21px 0 -6px rgba(255, 255, 255, .2), - -21px -21px 0 -6px rgba(255, 255, 255, .2), - 21px -21px 0 -6px rgba(255, 255, 255, 1); - } - 37.5% - { - box-shadow: 30px 0 0 -6px rgba(255, 255, 255, 1), - 0 30px 0 -6px rgba(255, 255, 255, .2), - -30px 0 0 -6px rgba(255, 255, 255, .2), - 0 -30px 0 -6px rgba(255, 255, 255, .2), - 21px 21px 0 -6px rgba(255, 255, 255, .2), - -21px 21px 0 -6px rgba(255, 255, 255, .2), - -21px -21px 0 -6px rgba(255, 255, 255, .2), - 21px -21px 0 -6px rgba(255, 255, 255, .2); - } - 50% - { - box-shadow: 30px 0 0 -6px rgba(255, 255, 255, .2), - 0 30px 0 -6px rgba(255, 255, 255, .2), - -30px 0 0 -6px rgba(255, 255, 255, .2), - 0 -30px 0 -6px rgba(255, 255, 255, .2), - 21px 21px 0 -6px rgba(255, 255, 255, 1), - -21px 21px 0 -6px rgba(255, 255, 255, .2), - -21px -21px 0 -6px rgba(255, 255, 255, .2), - 21px -21px 0 -6px rgba(255, 255, 255, .2); - } - 62.5% - { - box-shadow: 30px 0 0 -6px rgba(255, 255, 255, .2), - 0 30px 0 -6px rgba(255, 255, 255, 1), - -30px 0 0 -6px rgba(255, 255, 255, .2), - 0 -30px 0 -6px rgba(255, 255, 255, .2), - 21px 21px 0 -6px rgba(255, 255, 255, .2), - -21px 21px 0 -6px rgba(255, 255, 255, .2), - -21px -21px 0 -6px rgba(255, 255, 255, .2), - 21px -21px 0 -6px rgba(255, 255, 255, .2); - } - 75% - { - box-shadow: 30px 0 0 -6px rgba(255, 255, 255, .2), - 0 30px 0 -6px rgba(255, 255, 255, .2), - -30px 0 0 -6px rgba(255, 255, 255, .2), - 0 -30px 0 -6px rgba(255, 255, 255, .2), - 21px 21px 0 -6px rgba(255, 255, 255, .2), - -21px 21px 0 -6px rgba(255, 255, 255, 1), - -21px -21px 0 -6px rgba(255, 255, 255, .2), - 21px -21px 0 -6px rgba(255, 255, 255, .2); - } - 87.5% - { - box-shadow: 30px 0 0 -6px rgba(255, 255, 255, .2), - 0 30px 0 -6px rgba(255, 255, 255, .2), - -30px 0 0 -6px rgba(255, 255, 255, 1), - 0 -30px 0 -6px rgba(255, 255, 255, .2), - 21px 21px 0 -6px rgba(255, 255, 255, .2), - -21px 21px 0 -6px rgba(255, 255, 255, .2), - -21px -21px 0 -6px rgba(255, 255, 255, .2), - 21px -21px 0 -6px rgba(255, 255, 255, .2); - } - 100% - { - box-shadow: 30px 0 0 -6px rgba(255, 255, 255, .2), - 0 30px 0 -6px rgba(255, 255, 255, .2), - -30px 0 0 -6px rgba(255, 255, 255, .2), - 0 -30px 0 -6px rgba(255, 255, 255, .2), - 21px 21px 0 -6px rgba(255, 255, 255, .2), - -21px 21px 0 -6px rgba(255, 255, 255, .2), - -21px -21px 0 -6px rgba(255, 255, 255, 1), - 21px -21px 0 -6px rgba(255, 255, 255, .2); - } -} -@-o-keyframes spinner-circle -{ - 12.5% - { - box-shadow: 30px 0 0 -6px rgba(255, 255, 255, .2), - 0 30px 0 -6px rgba(255, 255, 255, .2), - -30px 0 0 -6px rgba(255, 255, 255, .2), - 0 -30px 0 -6px rgba(255, 255, 255, 1), - 21px 21px 0 -6px rgba(255, 255, 255, .2), - -21px 21px 0 -6px rgba(255, 255, 255, .2), - -21px -21px 0 -6px rgba(255, 255, 255, .2), - 21px -21px 0 -6px rgba(255, 255, 255, .2); - } - 25% - { - box-shadow: 30px 0 0 -6px rgba(255, 255, 255, .2), - 0 30px 0 -6px rgba(255, 255, 255, .2), - -30px 0 0 -6px rgba(255, 255, 255, .2), - 0 -30px 0 -6px rgba(255, 255, 255, .2), - 21px 21px 0 -6px rgba(255, 255, 255, .2), - -21px 21px 0 -6px rgba(255, 255, 255, .2), - -21px -21px 0 -6px rgba(255, 255, 255, .2), - 21px -21px 0 -6px rgba(255, 255, 255, 1); - } - 37.5% - { - box-shadow: 30px 0 0 -6px rgba(255, 255, 255, 1), - 0 30px 0 -6px rgba(255, 255, 255, .2), - -30px 0 0 -6px rgba(255, 255, 255, .2), - 0 -30px 0 -6px rgba(255, 255, 255, .2), - 21px 21px 0 -6px rgba(255, 255, 255, .2), - -21px 21px 0 -6px rgba(255, 255, 255, .2), - -21px -21px 0 -6px rgba(255, 255, 255, .2), - 21px -21px 0 -6px rgba(255, 255, 255, .2); - } - 50% - { - box-shadow: 30px 0 0 -6px rgba(255, 255, 255, .2), - 0 30px 0 -6px rgba(255, 255, 255, .2), - -30px 0 0 -6px rgba(255, 255, 255, .2), - 0 -30px 0 -6px rgba(255, 255, 255, .2), - 21px 21px 0 -6px rgba(255, 255, 255, 1), - -21px 21px 0 -6px rgba(255, 255, 255, .2), - -21px -21px 0 -6px rgba(255, 255, 255, .2), - 21px -21px 0 -6px rgba(255, 255, 255, .2); - } - 62.5% - { - box-shadow: 30px 0 0 -6px rgba(255, 255, 255, .2), - 0 30px 0 -6px rgba(255, 255, 255, 1), - -30px 0 0 -6px rgba(255, 255, 255, .2), - 0 -30px 0 -6px rgba(255, 255, 255, .2), - 21px 21px 0 -6px rgba(255, 255, 255, .2), - -21px 21px 0 -6px rgba(255, 255, 255, .2), - -21px -21px 0 -6px rgba(255, 255, 255, .2), - 21px -21px 0 -6px rgba(255, 255, 255, .2); - } - 75% - { - box-shadow: 30px 0 0 -6px rgba(255, 255, 255, .2), - 0 30px 0 -6px rgba(255, 255, 255, .2), - -30px 0 0 -6px rgba(255, 255, 255, .2), - 0 -30px 0 -6px rgba(255, 255, 255, .2), - 21px 21px 0 -6px rgba(255, 255, 255, .2), - -21px 21px 0 -6px rgba(255, 255, 255, 1), - -21px -21px 0 -6px rgba(255, 255, 255, .2), - 21px -21px 0 -6px rgba(255, 255, 255, .2); - } - 87.5% - { - box-shadow: 30px 0 0 -6px rgba(255, 255, 255, .2), - 0 30px 0 -6px rgba(255, 255, 255, .2), - -30px 0 0 -6px rgba(255, 255, 255, 1), - 0 -30px 0 -6px rgba(255, 255, 255, .2), - 21px 21px 0 -6px rgba(255, 255, 255, .2), - -21px 21px 0 -6px rgba(255, 255, 255, .2), - -21px -21px 0 -6px rgba(255, 255, 255, .2), - 21px -21px 0 -6px rgba(255, 255, 255, .2); - } - 100% - { - box-shadow: 30px 0 0 -6px rgba(255, 255, 255, .2), - 0 30px 0 -6px rgba(255, 255, 255, .2), - -30px 0 0 -6px rgba(255, 255, 255, .2), - 0 -30px 0 -6px rgba(255, 255, 255, .2), - 21px 21px 0 -6px rgba(255, 255, 255, .2), - -21px 21px 0 -6px rgba(255, 255, 255, .2), - -21px -21px 0 -6px rgba(255, 255, 255, 1), - 21px -21px 0 -6px rgba(255, 255, 255, .2); - } -} -@keyframes spinner-circle -{ - 12.5% - { - box-shadow: 30px 0 0 -6px rgba(255, 255, 255, .2), - 0 30px 0 -6px rgba(255, 255, 255, .2), - -30px 0 0 -6px rgba(255, 255, 255, .2), - 0 -30px 0 -6px rgba(255, 255, 255, 1), - 21px 21px 0 -6px rgba(255, 255, 255, .2), - -21px 21px 0 -6px rgba(255, 255, 255, .2), - -21px -21px 0 -6px rgba(255, 255, 255, .2), - 21px -21px 0 -6px rgba(255, 255, 255, .2); - } - 25% - { - box-shadow: 30px 0 0 -6px rgba(255, 255, 255, .2), - 0 30px 0 -6px rgba(255, 255, 255, .2), - -30px 0 0 -6px rgba(255, 255, 255, .2), - 0 -30px 0 -6px rgba(255, 255, 255, .2), - 21px 21px 0 -6px rgba(255, 255, 255, .2), - -21px 21px 0 -6px rgba(255, 255, 255, .2), - -21px -21px 0 -6px rgba(255, 255, 255, .2), - 21px -21px 0 -6px rgba(255, 255, 255, 1); - } - 37.5% - { - box-shadow: 30px 0 0 -6px rgba(255, 255, 255, 1), - 0 30px 0 -6px rgba(255, 255, 255, .2), - -30px 0 0 -6px rgba(255, 255, 255, .2), - 0 -30px 0 -6px rgba(255, 255, 255, .2), - 21px 21px 0 -6px rgba(255, 255, 255, .2), - -21px 21px 0 -6px rgba(255, 255, 255, .2), - -21px -21px 0 -6px rgba(255, 255, 255, .2), - 21px -21px 0 -6px rgba(255, 255, 255, .2); - } - 50% - { - box-shadow: 30px 0 0 -6px rgba(255, 255, 255, .2), - 0 30px 0 -6px rgba(255, 255, 255, .2), - -30px 0 0 -6px rgba(255, 255, 255, .2), - 0 -30px 0 -6px rgba(255, 255, 255, .2), - 21px 21px 0 -6px rgba(255, 255, 255, 1), - -21px 21px 0 -6px rgba(255, 255, 255, .2), - -21px -21px 0 -6px rgba(255, 255, 255, .2), - 21px -21px 0 -6px rgba(255, 255, 255, .2); - } - 62.5% - { - box-shadow: 30px 0 0 -6px rgba(255, 255, 255, .2), - 0 30px 0 -6px rgba(255, 255, 255, 1), - -30px 0 0 -6px rgba(255, 255, 255, .2), - 0 -30px 0 -6px rgba(255, 255, 255, .2), - 21px 21px 0 -6px rgba(255, 255, 255, .2), - -21px 21px 0 -6px rgba(255, 255, 255, .2), - -21px -21px 0 -6px rgba(255, 255, 255, .2), - 21px -21px 0 -6px rgba(255, 255, 255, .2); - } - 75% - { - box-shadow: 30px 0 0 -6px rgba(255, 255, 255, .2), - 0 30px 0 -6px rgba(255, 255, 255, .2), - -30px 0 0 -6px rgba(255, 255, 255, .2), - 0 -30px 0 -6px rgba(255, 255, 255, .2), - 21px 21px 0 -6px rgba(255, 255, 255, .2), - -21px 21px 0 -6px rgba(255, 255, 255, 1), - -21px -21px 0 -6px rgba(255, 255, 255, .2), - 21px -21px 0 -6px rgba(255, 255, 255, .2); - } - 87.5% - { - box-shadow: 30px 0 0 -6px rgba(255, 255, 255, .2), - 0 30px 0 -6px rgba(255, 255, 255, .2), - -30px 0 0 -6px rgba(255, 255, 255, 1), - 0 -30px 0 -6px rgba(255, 255, 255, .2), - 21px 21px 0 -6px rgba(255, 255, 255, .2), - -21px 21px 0 -6px rgba(255, 255, 255, .2), - -21px -21px 0 -6px rgba(255, 255, 255, .2), - 21px -21px 0 -6px rgba(255, 255, 255, .2); - } - 100% - { - box-shadow: 30px 0 0 -6px rgba(255, 255, 255, .2), - 0 30px 0 -6px rgba(255, 255, 255, .2), - -30px 0 0 -6px rgba(255, 255, 255, .2), - 0 -30px 0 -6px rgba(255, 255, 255, .2), - 21px 21px 0 -6px rgba(255, 255, 255, .2), - -21px 21px 0 -6px rgba(255, 255, 255, .2), - -21px -21px 0 -6px rgba(255, 255, 255, 1), - 21px -21px 0 -6px rgba(255, 255, 255, .2); - } -} - -[data-loader='circle-clock'] -{ - width: 1em; - height: 1em; - - -webkit-animation: circle-clock .75s infinite alternate ease-in-out; - -o-animation: circle-clock .75s infinite alternate ease-in-out; - animation: circle-clock .75s infinite alternate ease-in-out; - - border-radius: 50%; - background: #fff; - box-shadow: 1.5em 0 0 0 #fff; -} -@-webkit-keyframes circle-clock -{ - 0% - { - -webkit-transform: rotate(-150deg) scale(1); - -ms-transform: rotate(-150deg) scale(1); - -o-transform: rotate(-150deg) scale(1); - transform: rotate(-150deg) scale(1); - } - 100% - { - -webkit-transform: rotate(210deg) scale(.35); - -ms-transform: rotate(210deg) scale(.35); - -o-transform: rotate(210deg) scale(.35); - transform: rotate(210deg) scale(.35); - - background: rgba(255, 255, 255, .3); - } -} -@-moz-keyframes circle-clock -{ - 0% - { - -webkit-transform: rotate(-150deg) scale(1); - -ms-transform: rotate(-150deg) scale(1); - -o-transform: rotate(-150deg) scale(1); - transform: rotate(-150deg) scale(1); - } - 100% - { - -webkit-transform: rotate(210deg) scale(.35); - -ms-transform: rotate(210deg) scale(.35); - -o-transform: rotate(210deg) scale(.35); - transform: rotate(210deg) scale(.35); - - background: rgba(255, 255, 255, .3); - } -} -@-o-keyframes circle-clock -{ - 0% - { - -webkit-transform: rotate(-150deg) scale(1); - -ms-transform: rotate(-150deg) scale(1); - -o-transform: rotate(-150deg) scale(1); - transform: rotate(-150deg) scale(1); - } - 100% - { - -webkit-transform: rotate(210deg) scale(.35); - -ms-transform: rotate(210deg) scale(.35); - -o-transform: rotate(210deg) scale(.35); - transform: rotate(210deg) scale(.35); - - background: rgba(255, 255, 255, .3); - } -} -@keyframes circle-clock -{ - 0% - { - -webkit-transform: rotate(-150deg) scale(1); - -ms-transform: rotate(-150deg) scale(1); - -o-transform: rotate(-150deg) scale(1); - transform: rotate(-150deg) scale(1); - } - 100% - { - -webkit-transform: rotate(210deg) scale(.35); - -ms-transform: rotate(210deg) scale(.35); - -o-transform: rotate(210deg) scale(.35); - transform: rotate(210deg) scale(.35); - - background: rgba(255, 255, 255, .3); - } -} - -[data-loader='500px-spinner'] -{ - width: 32px; - height: 16px; - - -webkit-animation: infinity_spinner 2s steps(60) infinite; - -o-animation: infinity_spinner 2s steps(60) infinite; - animation: infinity_spinner 2s steps(60) infinite; - - background: transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAPACAYAAABU1nvrAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2hpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw%2FeHBhY2tldCBiZWdpbj0i77u%2FIiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8%2BIDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDpCNjI2N0ZFOThFMjE2ODExODIyQTk2NDZFMjJDMzI5NCIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpDNkRDMjIwNEU3RDIxMUUyQUQ0NkM0Mjk2RTM0NjAxRiIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpDNkRDMjIwM0U3RDIxMUUyQUQ0NkM0Mjk2RTM0NjAxRiIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M2IChNYWNpbnRvc2gpIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6QjYyNjdGRTk4RTIxNjgxMTgyMkE5NjQ2RTIyQzMyOTQiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6QjYyNjdGRTk4RTIxNjgxMTgyMkE5NjQ2RTIyQzMyOTQiLz4gPC9yZGY6RGVzY3JpcHRpb24%2BIDwvcmRmOlJERj4gPC94OnhtcG1ldGE%2BIDw%2FeHBhY2tldCBlbmQ9InIiPz6doOLOAAAjTElEQVR42uydCXwV1b3Hb%2B5NgORmNwuBhDWIIAQChDVAAigioBaxrUutvvfUVv1Un7UVfa64a7W2lVrt9qpWqyyK%2BqBhMQECgZCARgiILGYhZA9kM5jt%2Ff7hDD05zF2SO3PGwpnPZz6EuTPnf%2Bac%2F%2FKd%2F5k5x6%2Bzs9Oms4Vgd2IPwN6BvQn7Key6J%2BtsduyhrAz6u5WV0SCe6CdUIAx7Ava%2BOoW2YT%2BBvdKD8Bjscdj9dX47jb2E3cw5FYjFPpCOeRDQiP0ouyt%2Bo9Yahj3Yw%2FUk8Dj2Cr4C1FyJXgjXNhL%2BFfZv2P%2BD2PUBXl5PQg9jr9cqMBp7IHcC9XstE9APeyR2h1BIOyvExoTr%2FV7Dmj2QlWHnfqeyC6kCVPtRQu0Osabmm3cw0xGbUFGbULCN9XGR0E3UNRcLrXyAKkB9H88dpDs%2F5qLp4pmu2Hbu3Dmvtrb2XhIeGxv71MSJE3ewc6hvS11cP5S1hLaV2nX6rclN31HBpdu3b19aXV39bEdHR3%2FsMRUVFQ%2Fxv7u5Xiw7wN%2FWw23jxo13nT59epmO3fdq82f2zW%2BBrk7etGnTCgi%2FU7QIdMEzXBcFuGkFp3itXVA22iKY5ndrquzs7HtaWlpE4d%2FExcXdi%2F7P5o7FujDJYFY2vzX4s375FnsfdpDM6RJmQi2aCbW2to7r5kL9%2FJr69u17LYRv1zFDspZLdczQTzDDZjszuzKhZg7mUgdhj6b%2FDxgwYL3WWna7vRLC52HLYMcO6XhGrQxy7VGCnnRq3cS74gR2gcutsrIyoqioKD4gIODD8ePHnzTSFWsb1XSAC5dqejA6e4wLx%2F5mh2O7iybq7IEwnzbFA4oHFA8oHvCaBxD9%2FDIyMlZ8%2B%2B23PzWSB0gHSDlGCtp7kIXiri0rKyseLPBWW1tbmg4P3I%2F%2B3%2BWLEtKBMRwP8CbUkpeXNw19%2FBbOC9XjAYTk7b6YoT%2FHA0N0YrkNmn6fjvBqCF8M4TvZoUM6jsjhJryf5QGtRjWufDzg41Q3ynA49gQGBk7ihNPWTM2pg3fuXHG9VzyA5g8vKCh4sL29vb%2B%2Fv%2F%2FbdXV1L1533XXtigcUDygeUDygeEA6DzzxxBP2adOmvQYeuN0qHngHPDBTGg%2Bcwpafn59GEIK7Xsps3XAe0PzARQIP2IBfv25tbZ3pAs8qEJKvmjNnTq6vjkiXBxoaGvq5Eo6QvAd3PpkT7hMP8E1SovVbSEhIC8DjS%2BGuT0Lwo6jcZDR7sQv3%2FCUrp82FcOqOI1occMsDBw8eHFxaWnorfg8AFe0KCgpaDSv4xux0vRovUDygeEDxgOIBXR7YunVrQnNz89%2Bl8AAYYEZNTc0i%2FEkB5wh%2BH4XIeK0UHjh27NiA%2Ffv3r3HhRt3lB4zhgZMnTw70JJx4wMj8QDceiI6O%2FhgcUOjirs%2FyAGCkyDQegA4E1NfXL0a%2FT6PhGghuRKV24a5XKR5QPKB4QPGA4gFTeSA7O%2Fv7cMG34DGsGD7%2FlvT09P3SeABh%2BDKE4X9qP1KsR9RbjX8Pms0DXeMFVVVVtXzNINQJoTf3gAd8Gy%2BYPHlyXp8%2Bfd7wSmkM5oFuwWjz5s0%2Fa2lpeRjHovV4AJV8GdDyjKnjBevXr%2B%2BLu7wSAqe1t7dHQymbiAeghKsXLVrUrHhA8YDigfOTB7755ps%2BBQUF4%2BFkgqXzQHFxcdi%2BffveR0HxJSUl9TExMQtSUlIqZfGA%2FciRI7NJOAtCoWjWe1zxwIYNG16H8Ec88EC8G3m67xN%2B3c3btLVdhZiQwh8jHpgyZUqWDoz4%2FD6hX2Zmpj%2F8%2FHHqS75gBJ7ViAMHEQ9Gmp4f2LRp092Igr%2Fztt%2BMzA%2BcdcUZGRl%2FcwchPA%2BgdZboPKL7%2Fj4h%2Bv4%2BxgMRlvIAtqvQ91PwW5SWH1A8oHjAVB7AQ8nimpqaOZGRkXvGjBlTKJUH4GaT8Ei2CxUhQe0JCQm3jRs3bqUsHvCHf7%2BaCe96msFTUjL%2B%2FatOkx%2F2kQfohuoEHgixw94Hdruljo4aqTyAu3cIHq9Van6ABqKFFkiSmR%2Bww81mdzvS2ko8kCSNB%2FLy8gLQh8XUn7zCBAQErETrfCVlvAB3fBfA9FVLeQC8979o4h9%2FF3jgIRy7yDIeyMrK6odjV9J4AfGAlh9Q4wWm8wDI%2BL%2FR7LdByY4lJye%2FEBUV1SiNByB8CZRvlRapgoKC3oaWvyKDB%2Bz0yjbM7wU%2BTMIpheOfatZcVcypiHc7kt2tFmYDdBxRJSujmgvdNiar6xHOH8FlNu5%2BOF87aP1zLJ5r2wkdHiDPN6IHPFAj8AB5xyCKhumCo1mfnp6%2BXRoPoLkHdLMfuz3H0vECqESH4gHFA4oHLigeOKeWeDJq7d%2B%2F%2F9ejR4%2FeC%2FNq43igxWbCJnbBD%2BFkXoBFJKDJay6%2B%2BOLbR4wYUWQmD5w1Cyjhk7CEd0k4S1peVFJScq3QWgksCuppuxYhE2yuX4Qh0BmuxZOzFcCdL4UFPCyeDaU7oVNIMMuu8%2Bl9LeMe7E2rs0RI16dDXTyAZn9RdCKw8z9NmDDhZbN5wA93P6e5uXkzz34gInppeb2U%2FAAez9MER7NBEG46DwwUKpArmwe6xX9ov8oPKB5QPHBh8cA59rtlyxYKuy2443atvy%2B99NIMCJfLA%2FxJ%2Ffr1WwOFW242DzwOS3hHFE4blHOsqTxA%2BQEIedTVeIHT6dxoFg%2FQf%2Fw%2B%2BeSTQ%2BiKRN4hQuPX0Ce%2BCM3H0P9fmTZekJmZmdrY2LiN5wH0%2BSI2yYmU8YJ53ToyIOCfgnBzecB25kNn3uHskj1e4KfyA4oHFA8oHnDHA6gE9LOjHXfeYRkPsC6oRSs8gL7%2FkynjBcQD7kKyhgbjx4%2BfGR8fv9eo8QKveIBPGTB7NjY%2FAIV7XhBOPPAemvwh%2FPsmU55OsMFbuPsqQ%2FMDuPuZcEJb3fFAaWlpIOo5cuDAgQ4z8gNzPfEA7prM7zPFA4oHFA8oHrggeYBqd5pyh7j7Nst5AIr3XF1d3cumdIGXPNCVykVUXDpr1qwSw5RQfH%2FAYwfb7SfggCYlJyeX%2BWqGDjQtRbO1tjPfHvM8sAr7OxBWii4ZYeMmSkGlQ%2FBbRUJCQgE7PkKnydsZB9QyJezH3aAfq3SVf2pq6gw4oUSBBxbzIRmu%2BJGmpqa3EbpnaaAaFRV11GbA%2BwN2b3iA%2Bjs3Nzfd6XT%2BEpX7EHxwd01NTeK6M9s%2F8%2FPzp3OnV7DmbXXBA%2FwW4u8tDzz22GMUpmloJxY8cC%2Bs4QHtbhgPLGJRsMIDD0QqHlA8oHhA8UBP8wN%2BUNAA2L7DUh4gy0AXvJKTk%2FMEvGKbZTyALvkcPHBNWlra15bxALqnir5DMcIM3fIAnNHfccdFjAf41zydbW1tPywuLt4zbNiwCl94wKv8wKeffjoQ3UOffs8WvWFSUtISgElNb%2FMDXvEAnM7xXbt2zUGrvN6tHTs7nQUFBW%2F7wgNe5weIBy6%2F%2FPKfoO%2BfFCoR7Ut%2BwN5THnDjeBQPKB5QPHCe8oDZW0%2FyA%2BZ2QQ%2FyA8YrYS94wDgztLl5fwCCCtAdl5jqiLzhAVdTwaASLf379%2F%2B5L1PBeJ0fAA%2BkoVX%2BKITifidOnHgF7nyWLB64XeQBqjNiyQOW8gCNO8nigT%2FAVG8TY3pMTMyzsnjgNj0emDRp0jbFA4oHFA8oHvj35oF169YdQlRUPOAND7yhxwM7d%2B6cL4sH7tDjgerq6qeys7OvszI%2F4AfzfACVmGVFfoAP5TRqkiM1P8D2roGssLCwHdJ5ADu9bX1jRETEPjaFiOIBxQOKBxQPKB6QzwP47XJLeaCqquppesfQUh6oq6tbRu8W9HigoSc84GJ%2Bwn8ZOX7DOSvcyDOWB0gJaeeP4Zw7QUZ3ecsD%2FuHh4ZtR83KOBwJhFdszMjK84gF256tpAOusGbW10XoIXq13bAgP4PppcGQf0Iyf%2BK1u9OjRtwwdOvS4VB7Iz8%2Bnb5AWDh8%2B%2FDhatVHxgOIBxQOKBxQPXNA88Hs3Axjnrl8AJbwCSsjPOfEN3O7UuXPnFnijhJouCL4iC%2FpyE7174kkJfc4P6PEA%2B%2F1UbGzsDZMmTcozPT%2Fw6aefTsf1ayl48eehFTZCcR9054i6aoTmXoGT%2F%2BZVUkln%2FQI09Q46Rh9K8%2Bfi%2F7VuXHH39YxQ01tQyM9pEhwXd%2B12PSM61tjYOBlO7EFUsgDnroODek0nGLlfz0jxgOIBxQOKBxQPGMADr9O5VvPANijyDWlpaaWW8QA9%2B6MMejU4c8aMGVtxbphpPJCZmTkFLbSWrQhyzgbr2QQFXmYaD6Snp%2B%2BiNZARNXe70I05aEW7qTxAayBfeeWVU3DO%2F9C5gm%2FZh71DGg%2Fk5%2BcHIWougV5NIUUdMmTIXxMTE0ts3sxPSDwAz9fFAy7mMva4wTWTdXxC6sEBieIBxQOKBxQPXGA8EBQUdKPwbbI1PIBY8iXKprGDQZGRkR%2BmpKRkScsP6GxtSUlJ3xs0aFCZKfkB%2Bs3T5YglsablB%2Bg3OkfkAa7yu4cOHfqh6TwAS3BC2BJaEwn970SFytHCiPRbNrDvl83lAbQmVfRjUg8%2BQQFK7lA8oHhA8YDiAct5gFaTtIQH0DpzYU1vUdh3Op3v4IH219J4AIqejJ0W7zrrJ4YPH75w1KhR62TzwFkfERUVNXbq1KkHZPNAV4%2FBzzxOwqXzAH3FDd34ASr%2FlEweCEIZ5Sgja9u2bZss5QG0kuIBxQOKB6znga%2B%2B%2BmpwTk7OQlSc3HbP5yPywAOtl1122aOuToLg5JqamncpfVxbW3sqMTFxnlQeqK%2Bvp7WU%2B7DuCystLZ1uxvuEt9O5dA1%2FHBFzMcq%2BTNChAhk8cBhlT2BfX%2FhzgenowoULE63igU5Q0VJQ0RrD3idEJQbj%2BjXwFRM8lYHKL4eyPmYFD5RCxg814VJ4AGUEY68gUM3Ozl5nKQ8A%2FRQPfPd4oGs%2Bori4uLXCm7Rd8xHhbt%2BFEpax%2BYj4woOgYNcXFxfn93Q%2Boj179kxCQFoIi7HFxMQckMoDEH5jWVnZa6ylvw0ODp4ilQcQMcdy3dwHOnetVB7AzVYJhwZIzQ%2FolN0ulQdQ9mThUJk0HoCyz0fZV%2FDHaFRdSn4AvyWhYkt4U8b1hxctWnSxZTwAU1%2BK6y3hgU5cvxwW9bgVPFACGTdowqWNF6AM4gHo5dYN4sx%2BUnkADNh24fIATD38888%2FfwjdkoDr%2FwA6elkqD6B1l8FUl4pmqK1vqLt%2BAb0RA2fyFjkVIQpG4ZpN5N1sXq5fgDvnW6ZrjWWS7RMP2M5MBXO%2FNzwA5Z4JM93C3yjkpPvEA5T58JYHZs%2BevY3WUhZaMl0qD4hrKdNay7J5QJTVqXhA8YDigQuDB0pKSvrgOTJi9OjRndJ54NChQ4l4GH2d8oLUBeiiXyIU%2F%2BNst3DN5nJ9Q%2Ba3w1G55SEhIbmkcDqn6K5viLu%2FjoQzR5RAayrDYy7vVoEerGdE3xRPQCF5uGYq%2F7huc7GeEfq%2BTM9j0hrL2tCty%2FWMjOCBsWPHvoCy%2FmIT1kymNZal8kBmZuaVTU1Na%2FnuCQoKmiuNB0DE62gtZeFxPU0qD9CLTEJ0HCiVByCrXZCl8gOKBxQPWM8DpnxvSDwwZsyYDRBc61GfzOIBmN3TeOJeKHaBNB6AQibpOCLigXdIluk8gEi3wVWrkyySaWMCTXt%2FYMeOHZcgBA9G6yW7dESyxgvIFUMnPuF5IDg4eKa08QIqk8oW4sY82fmBXEvHC3QrpXhA8YDigfOSB9DE7bRGMs3cKZ0H8Ax4O%2BLGsyg3Uq8LRB4wdLygqKgo5YsvvtjiYrDCtRIaxQP4LcaD8HN4wND8wPDhw0nLV7Mua8C1b7I1k98TomPXGIXP%2BQFXPFBSUkJF72fLFMvngYSEhDxeuOIBxQOKBxQPXHg8AC%2FYSmsk284M13dK44GVK1c6IiIi7oMuLEMZcnmA7B5PwqvR7Ck9UUJD3h%2FYvXt3QmVl5W5X04C4M0Nv3ycUxwO6vU8IEroDfuAq0VGhjHexv48y6P2BkVwOwQ%2F%2Fnzhy5MhXDeEBWMipo0ePvq0dh9VsdTqdN%2FGfehnFA7rrGcH%2BB8XExPwcFf%2B%2FoKCgB2ltZHFNbFc80NP1jm9HyC0XImIXD0D7n4eQ%2FyQggYK644FFigcUDygeUDxgOg%2FADdvRFX1EH2M6D8D%2B%2FdFKFNbvsemMpJvKA%2Bi6IWQJaPYk6TzApg3%2FqDdm6PP3BQjDiRBO74uEC5VsQCXfIoeGMmpM4wGWiAoSumkLbuBGfqpY0%2FIDonBaE5nWRhbmqTWMB%2B5AyK1wFREpd0Cf%2B0HBpPNArzfFA4oHFA%2BcnzzQowcKo%2FMD4eHhD0EX7qPhfqk80FslNIQHfDJDm4%2FvD%2FjsiHzlAS4s8910zlQwpvGAKJzOoXPNyg8oHnjDah64TfGA4gHFA4oHFA8oHlA84E3uz9T8AJrtYSN5wKv5CWFmAyGYlHC2qOmUZfNFCd3yADOhETDD7%2BPfEEF4I9myVTxQBYW7yhIeQGQsQBddnZaW9rVUHqAmx4VP5%2BbmThSFCzzwmKE8gMKacWE%2BFOt9CG6UzgNd3wbb7a24M1P4QPGA4gHFA%2F9%2BPAABKyIiIp6zeXg%2FyBQegLYvQ1%2F%2BGU1ZFRUVRVzQ2hMeIM%2FYKx5g7wi9jQJmaceGDRt2E64LKy8vf8k0HkDrHMLfXfkB0cwiIyMfnj59%2Bm9Q8AwreKAcXZAyZcqUUl8dUY%2FnK6Y1i0JCQiZrwlko7tF8xeSKSXhP8wO1qPUvGxsbp4k2bnZ%2BoBb7bkS51RMnTmy1Ij%2FQF38HQIn8bSZsigcUDygeUDzgNQ%2BwLZ72GTNmrOoND%2FR6vKC0tDSwpKRkEv48%2B9ru1KlTM%2BLi4u41e7xA44HvUX6APuGBF3tWAxzsh6XzwJgxY64ZMmTIMfz5le3M3ONSeaCNkhP494AmXCoPOJ3OuwYPHrzDprNan2H5gZycnEB4rAVcfqABF%2B7E%2F1cvWLDgtOk8gItbhfwArrdTmLUrHlA8oHhA8cCFywNMexOl8wAqdwABqHTatGkU%2FfSWCfHIA7CIg1D0cT7xAMzlz2lpaa%2FxDSWVB%2BADeLVulM4DuIDyRRRGyba%2F9MADxuUH8N9o6EHx2LFjV8TExFR6a3qG5Qc66b3tjo7Iffv2XQ5Q6ad4QPGA4oHzlgfEIOOQygO06prQrKOl8oA4jTM0fjHMaoI0HsjMzPRvamoqw51H84MW6M83cKfPQ5tJeKSpPIBC7oaJ%2FE5sstDQ0N9Cm9%2FUaU1jeQB%2FvEqTlohnodZDdC42hwfmz5%2F%2FYzQ7zwM0Ceoa3kFJ4YH169f3RfOkJiQklCcmJtKqzGT%2FtD7pKTN44JwxgNzc3Hb0e99jx4796MiRI7G9eaFR8YDiAcUD%2F148ID4bqPwA5QfweHaJ1PxAeXl5MZSpP9eXH6OpaToHKjzQTB6w04A0zO5JUbttZ6Z8HUQPqDrCW1lUbCTtRpy4nJyTcE4Q7vhmmPgzEP4DW%2Ffvljtxkw%2FQs6eWH%2Fg9DqygFqQlgYcOHfq6Gz0wlAfEcEx3G2fTXxKGwugJ25nVWWyGByPeErHT%2B6NO5rXk8gBMyAEvlgq3OwsXKx5QPKB44ALgAQj0tzQ%2FgL1cOBYrlQegMBlCZpyW%2FegnjQdoyAZKc9%2BpU6eugLv9bObMmf%2Bw%2FWvJcNN5QPOENAfJkB50nWH5AT4WJDAI8bQRDxwV%2B98nV8xtUcwsL2we8EtNTZ3F3G6MbB74ATT6V6h1vHQegIt9AsIf9VRjU3ggMzNzSVNTkyU80DUfUUxMzMfCFM9tcCIfYX%2BHVur2Yj6iT3SanBzRu9jfhw65np8QTZ%2BGAjJ54Yjl1MQfS%2BEBbJMEHtjECzebB%2BzR0dG5XEBpdzqdK1zxgIv5irvxAJ0jrm0u8AC%2FDfAfO3bsUdT65oqKihkwob1JSUm5nnhg%2B%2FbtAXV1dQ94q7Se%2FAA9FQ%2FkjtFDZpEbHugKyTt37pxfXV39uOi2qQtycnJ%2BqtcKGRkZHyMkL%2BLOfYIqQBOYjRTC7EEWinkeoLVLwvgC8%2FPzp%2Fj8fQF7hWMMVUiohOIBxQPW8QDAdCqtukYvtuDiZpk8sJTxwGDpPABzehh2utyS%2FEB2dvY4uNY9FJpl80CXDoSEhFyCprVzAlrR72ux79XLD3DrF%2FQ2P6CtX%2FABtUDX6gvggqdxcD7dHWp3Pfppkwwe8GPTPHdBaGVlZURYWNjXaN7DOtZhhxW8Js5PJ269CUbxwhNxiTtnQwtkesoPuLn2EVqYi6vAH3v8rrAZ%2BQFxMfRAmfkBxQOKBxQP%2BOvchR0mNg0eK42QXWp%2BAA8nMXv37t2AfhwnnQfIHefl5V0Pu%2F61TB7QPFYoPR3BMVR46me99YyYGeZ5Ibzrprutb8haYLTmgnfs2LG4oaFhAf48gibbZ%2Fp4gcYDfBNhP8Ts3fz8ADM5fqvjhZudH1A8oHhA8YDige8eDzAhnYWFhbHHjx%2B%2F9UzF7bug1atN5wFmZqQLfdevX78KdzBEKg%2BwhKUfwCRs9%2B7dm13xAPbvQYOLDecBLTkRGxt7Cja8yxUPwO5p0eXJpvCA5jXr6%2Bsb9%2BzZMwsn036d6KK95QFvHJHiAcUDigcUDygesJQH7K54wCg77zUPaAdKS0uj9%2B%2Ff%2Fz%2B4myj6LrWuru5F03lAaM5nUeBl2v8dDsceFGJOfkBvsAJ92C1YoSXMzQ9ocrHXkp3n5OTMqKmp%2BZsYNaXmB7KysuLhRt%2FCHaZZwgNpaWmlCxYsmIO%2Be81oHvDXcTpNuppzpq%2FuREyvNvr7AsUDigcUDyge%2BG7zgMwuiHUVkjk4iTl%2B%2FHhCa2vrytTU1DojlZB4INGd8MLCwlFHjx6lxXb74U4qUcjVhr0%2FwFysnx4P2M68ZR0Jj7eA%2FU0uNcbw9wfc8QBdmJ2d%2FbOTJ0%2F%2ByhIeIKeDPn%2FJDB6we8sDtEFx7jT6fULFA4oHFA8oHlA80CMeMFMJPfKAWWboNQ%2FwjojGERobG2%2BDM%2FkrnFC0FB7QXHFubu68ysrK56TzALld7BUNDQ0plvEAcanD4fjQUh5IS0vbCFN73lIeAJ6tBA88onhA8YDiAcUDigcs4wFmJSNtvZ84vfc8YLQr7jEPaAcAJnNOnDjxirTxAo0HWGXiq6qqlonXShkvELrKGh6gP6Kjo59RPKB4QPGA4gHFA4oHLOWBvLy8mZbyQGVl5YNW80CHpTwQGxv7DFvXRuSBP0jjgaKiopgvvviCpg8JEpRwS0hIyA0zZswok8IDbE6z4G4u1s%2BvAQr5Hv49gjsfbyoPbN68eRoqsVaYdFkuD2RlZQ0hb4gImNQTV8wrC9ltkU1nLmIuGLmcr5jWQKa1kGlNZB0%2FcjYYeZyv2AgegCMKPXXqFL0OPoE8rXQegPB2CPyW1kpWPKB4QPHAd54HfFm7lOeBzoiIiOcR%2FVZI5QF6m4bWP4iKinrQ4XA0ggXeRmB6SBoPaK1QUFAwobi4%2BE%2FaQVRmq9PpvMkbJfRnff4txwNkbpd4ywP0R0NDwzXdfmhvn4VjB9Aiq1CZL7n8gD%2FPA%2Bnp6dv9mdmVCTzg8BCauzkiFPYX7HewGT3OKAX8AoT%2B2JUj6jZfMbvbSi%2F15pz5iqdPn16EJp9EayF74wXdzVfsc34A1nAf7nwZrQJkWX6gsLCwD5zUElQiBXsEWYtUHigtLQ2gNZJZWPdTPKB4QPGA4gEpPEDvGPK%2FS%2BUB0aMWFRVBFfp8HhcX1yyFB5gPcbA88VNQrisoPwBr%2BEAYLzCHB5jZJh46dGgUCWc8EELrF2B3ywP%2BHA8E%2BZAfoKY%2BADMLZy3jsIQHmFv%2BCdzy08LMr3J5gNZIRFAiHphKXSE9PwA9cNAayd5Mtqd4QPGA4gHFA%2BcnDzC33S3vbNp4gcgD%2FO%2Fbt2%2B%2FtLm5OR4ykl2NFxjKA7yAzMzM%2B5uamm6Ukh%2FQeIA3X3jGy7zhAcPGC2z%2FWuWJzmmDMyqwbLyAZAFMY4Fod0PzB%2FRqPiJfeABgGgiB83SsRvGA4gHFAxc4D6DPuwUsWmtZKg%2FARFOEFjkujQcQHa9EdFzLKyEUda6pPEBllJWV2fbv30%2BLtNzMn4O7%2F3rhwoXDTOMBrYzDhw%2F%2FF4TfKhYAhf2FqTzAmVq8jvAnEQ9WmcoD2sG4uLiVWsUYD1zPTx1mGg%2Bw%2BEJl2I8ePRoIXYiA48oTZ%2FIxkwcWiy80FhYW%2BikeUDygeODC5oFOweYpEErlAQgUeaBMJg%2FMAA9k8UoIU0w3fLyA86iXsjJOUxmtra2P8cIdDsdh6MsW03mAa24%2BfHfCIrryA4a9T8iED7MJb1aejfUlJVEHDhx4BK46HtbyO8SDl0wZL2A3Eeci0p5mvHBKKg%2Bw1qIyGjyFY23TKhBgM%2FkDB8UDigcUD1xw%2BQExajpk80B%2Fy8YLSOegtLdCb36rOTYo6hTT8wMaD7Ay7KhEan19fTI8ZkZycvK7hnxfYDsz9J%2FoIoraXDx%2FUuXrpfGAIPy4ZtKKB0znAbIy7MQD27Kzs9eJk6TI5oFSeMv7wQPvWckD5LKXw2U%2FZikPOJ3Openp6VJ44DC4cAJb1U%2BT54cKvyiVB9BKi5ubm9eIj2bSeIDWUEaLbOwWCh2OZKk8AAJ6ta6ujkZU%2FWnKaUTRnVJ5AL%2Fn4fHsR7W1teMGDBiQM3To0JJer3eM%2Fp9eUVFBcxR2REZGvjJ16lRtTcwK1lJ621Cm0GdbVCoPYL9YsLYD0nmA%2B51MvFDxgOIBGfkB%2BuCxHF4va9u2bZus5oEixgOrLM0PIAg9imfFpyzjAZzXcdFFF42bPn36Pin5AehCMvarcU6A9owYGho6An92VSBIeB7sZHfHh%2BQTOq7YgXhwM4R7xQMbN26cCyh5hyZbDgwM%2FGDMmDG0JFVQt%2FWO2UZ3fsxTMCIeAIy8IPoMd%2Bsdk6Oqrq5OQBhu5INRj9YzYufGr8NGd6PHAx6cVAL3%2F4oL631C5ty6WZPiAcUDigekjBfA95fDQ26pqqraKL7AIJUHACV7x48ff3lcXFy1ZTwA4XdNnDhxtaaEHtcz0nvEY0%2FTgZxwr9YzIg5AGK5gskL5FnC5npELHjjriLzhAegBObohYWFhn%2BARLkN0RFLyA65csbT8ACuzTrQ2xQOKBxQPKB6wnAdMeZ8QLrrfyZMnF8FRTQELtA4cOPAvo0aN0k2ASuEBmoljwYIFN3rqAp94AFFxEMLxB65C8uzZs1NDQkJaRCU0hAcyMzOn0BrIroQjQG1lwjUdk8cD%2Ffr1ywKGbQULhLpyRKbwAATfQMsSWsYDgnDFA4oHFA8oHvju8sD5N18xHNIvEBVvAgOUJyUlPQt%2FcdLbLvA5P4CouIaWI9aOISB9NG%2FevOXulNDQ%2FAAvvMtLdXToffLZjQfsbsYLqllzVTGnYtMZLwim1%2FQgfIM4WAEWrEd3vMTKqBZih5%2Fmss3igSyE5ZvmzJlz3BtXbPR4wWvz58%2B%2Fq1M%2F%2FXbO4HWP1zNiu83V%2BwO0BqIL4XplBygeUDygeEDxwPnFA752gU888Nlnn4W3trZeM3jw4NKYmJi6niqhT%2B8P0LThgJEP2bNi44gRI%2F5j5MiRR70xQ0N4oKWlZSP3oBpcVla2gF1T6YkHTHl%2FICAg4HP8s1%2FophrBFZPMIMPzA%2BDA36empv5Gx1Hp5gcM5wFA6F1urlc8oHhA8YDiAcUD5vGAr0roEw8YYYa9Hi%2FIyspK9PPzuxmxoDApKWlvbxyRL%2FmByXhEf1kLyYgP96akpGT3xBX3mgfoky3wwG94Hqivr5%2FKzjlsOg9UVFQ8JF7r7%2B%2F%2Fkc31m%2FXm8kB4ePgLIKFPLeEBYPiDtAay4gHFA4oHFA8oHrCEB4xwRL3mAaNcca%2FGC9jHbhSS7QhMT02cOHEHO6dHH7v5wgMPd3R09KfUDKtIt9%2B95QGj1jfsdTmG8AB1gfi74gHFA4oHFA8oHlA8YDoP5Obmzquurr7XEh7Izs6%2BrrKy8lkjeKDH%2BYEtW7bMaWhoeMALf2IOD7S1tV0lWgSxQW95wK6TD4pgmi%2FaOGl6bGho6E5ek%2BPi4u5B%2F2dzx2JdmGQwK5vfGnrFAwUFBcl1dXWjce2baWlphxUPKB7whQf%2BX4ABAGSSMYsxT5XaAAAAAElFTkSuQmCC) center top no-repeat; -} - -@-webkit-keyframes infinity_spinner -{ - from - { - background-position: center 0; - }to - { - background-position: center -960px; - } -} -@-moz-keyframes infinity_spinner -{ - from - { - background-position: center 0; - }to - { - background-position: center -960px; - } -} -@-o-keyframes infinity_spinner -{ - from - { - background-position: center 0; - }to - { - background-position: center -960px; - } -} -@keyframes infinity_spinner -{ - from - { - background-position: center 0; - }to - { - background-position: center -960px; - } -} - -[data-loader='timer'] -{ - position: relative; - - width: 24px; - height: 24px; - - border: 2px solid #fff; - border-radius: 50%; - background-color: transparent; -} -[data-loader='timer']:after, -[data-loader='timer']:before -{ - position: absolute; - - content: ''; - - background-color: #fff; -} -[data-loader='timer']:after -{ - top: 11px; - left: 11px; - - width: 10px; - height: 2px; - - -webkit-transform-origin: 1px 1px; - -moz-transform-origin: 1px 1px; - transform-origin: 1px 1px; - -webkit-animation: timerhand 2s linear infinite; - -moz-animation: timerhand 2s linear infinite; - animation: timerhand 2s linear infinite; -} - -[data-loader='timer']:before -{ - top: 11px; - left: 11px; - - width: 8px; - height: 2px; - - -webkit-transform-origin: 1px 1px; - -moz-transform-origin: 1px 1px; - transform-origin: 1px 1px; - -webkit-animation: timerhand 8s linear infinite; - -moz-animation: timerhand 8s linear infinite; - animation: timerhand 8s linear infinite; -} - -@-webkit-keyframes timerhand -{ - 0% - { - -webkit-transform: rotate(0deg); - } - 100% - { - -webkit-transform: rotate(360deg); - } -} -@-moz-keyframes timerhand -{ - 0% - { - -webkit-transform: rotate(0deg); - } - 100% - { - -webkit-transform: rotate(360deg); - } -} -@-o-keyframes timerhand -{ - 0% - { - -webkit-transform: rotate(0deg); - } - 100% - { - -webkit-transform: rotate(360deg); - } -} -@keyframes timerhand -{ - 0% - { - -webkit-transform: rotate(0deg); - } - 100% - { - -webkit-transform: rotate(360deg); - } -} diff --git a/website/src/app/index.module.js b/website/src/app/index.module.js deleted file mode 100644 index 660bb11a..00000000 --- a/website/src/app/index.module.js +++ /dev/null @@ -1,17 +0,0 @@ -(function() { - 'use strict'; - - angular - .module('ServerlessBlog', [ - 'ngAnimate', - 'ngCookies', - 'ngSanitize', - 'ngMessages', - 'ngAria', - 'ui.router', - 'ngMaterial', - 'toastr' - ] - ); - -})(); diff --git a/website/src/app/index.route.js b/website/src/app/index.route.js deleted file mode 100644 index ebe0ae16..00000000 --- a/website/src/app/index.route.js +++ /dev/null @@ -1,44 +0,0 @@ -(function() { - 'use strict'; - - angular - .module('ServerlessBlog') - .config(routerConfig); - - /** @ngInject */ - function routerConfig($stateProvider, $urlRouterProvider) { - $stateProvider - .state('home', { - url: '/', - templateUrl: 'app/main/main.html', - controller: 'MainController', - controllerAs: 'main' - }) - .state('post', { - url: '/post/:id', - templateUrl: 'app/post/post.html', - controller: 'PostController', - controllerAs: 'post' - }) - .state('create', { - url: '/create', - templateUrl:'app/post/create.html', - controller: 'CreateController', - controllerAs: 'post', - onEnter: function(authService,$state,$rootScope) { - $rootScope.$watch( - function() { - return authService.sessionStatus(); - }, - function(authenticated) { - if (!authenticated) { - $state.go('home'); - } - }); - } - }); - - $urlRouterProvider.otherwise('/'); - } - -})(); diff --git a/website/src/app/index.run.js b/website/src/app/index.run.js deleted file mode 100644 index 4e2140f3..00000000 --- a/website/src/app/index.run.js +++ /dev/null @@ -1,13 +0,0 @@ -(function() { - 'use strict'; - - angular - .module('ServerlessBlog') - .run(runBlock); - - /** @ngInject */ - function runBlock($log) { - //$log.debug('runBlock end'); - } - -})(); diff --git a/website/src/app/main/main.controller.js b/website/src/app/main/main.controller.js deleted file mode 100644 index dd6ebb85..00000000 --- a/website/src/app/main/main.controller.js +++ /dev/null @@ -1,84 +0,0 @@ -(function() { - 'use strict'; - - angular - .module('ServerlessBlog') - .controller('MainController', MainController); - - /** @ngInject */ - function MainController( - $scope, - $timeout, - $log, - toastr, - apigService, - $mdSidenav, - authService ) { - - var vm = this; - vm.posts = []; - vm.loading = true; - vm.toggleRight = buildToggler('right'); - vm.authenticated = false; - vm.selectedForum = undefined; - - $scope.$watch( - function() { - return authService.sessionStatus(); - }, - function(authenticated) { - vm.authenticated = authenticated; - }); - - $scope.$watch( - function() { - return vm.selectedForum - }, - function(forum) { - if (forum) { - getPosts(forum); - } - }); - - activate(); - - function buildToggler(navID) { - return function() { - $mdSidenav(navID) - .toggle() - .then(function () { - $log.debug("toggle " + navID + " is done"); - }); - } - } - - function activate() { - vm.loading = true; - apigService.getForums() - .then(function(result) { - vm.loading = false; - vm.forums = result; - },function(result) { - vm.loading = false; - $log.error(result); - toastr.error(result); - }); - } - - function getPosts(forumId) { - $log.info('selected forum: ', forumId); - apigService.getPosts(forumId) - .then(function(result) { - vm.posts = result; - vm.loading = false; - }) - .catch(function(result) { - $log.error(result); - vm.loading = false; - // @TODO: - // Not sure how a readable error message is returned here... - toastr.error(result); - }); - } - } -})(); diff --git a/website/src/app/main/main.controller.spec.js b/website/src/app/main/main.controller.spec.js deleted file mode 100644 index 85593e13..00000000 --- a/website/src/app/main/main.controller.spec.js +++ /dev/null @@ -1,29 +0,0 @@ -(function() { - 'use strict'; - - describe('controllers', function(){ - var vm; - var $timeout; - var toastr; - - beforeEach(module('ServerlessBlog')); - beforeEach(inject(function(_$controller_, _$timeout, _toastr_) { - spyOn(_toastr_, 'info').and.callThrough(); - - vm = _$controller_('MainController'); - $timeout = _$timeout_; - toastr = _toastr_; - })); - - it('should show a Toastr info and stop animation when invoke showToastr()', function() { - vm.showToastr(); - expect(toastr.info).toHaveBeenCalled(); - expect(vm.classAnimation).toEqual(''); - }); - - it('should define posts', function() { - expect(angular.isArray(vm.posts)).toBeTruthy(); - expect(vm.posts.length === 0).toBeTruthy(); - }); - }); -})(); diff --git a/website/src/app/main/main.css b/website/src/app/main/main.css deleted file mode 100644 index 6c2e26bb..00000000 --- a/website/src/app/main/main.css +++ /dev/null @@ -1,22 +0,0 @@ -.post-title { - width: 200px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.post-title a { - color:#333; - text-decoration: none; -} - -.post-title a:hover { - text-decoration: underline; -} - -.post-body { - width: 200px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} \ No newline at end of file diff --git a/website/src/app/main/main.html b/website/src/app/main/main.html deleted file mode 100644 index 681b64f5..00000000 --- a/website/src/app/main/main.html +++ /dev/null @@ -1,39 +0,0 @@ -
- - - - - -
- -
- - - search - - - - - -
- -
- - - - - -
- -
-
- -
diff --git a/website/src/app/post/create.controller.js b/website/src/app/post/create.controller.js deleted file mode 100644 index c2877884..00000000 --- a/website/src/app/post/create.controller.js +++ /dev/null @@ -1,64 +0,0 @@ -(function() { - 'use strict' - - angular - .module('ServerlessBlog') - .controller('CreateController', CreateController); - - /** @ngInject **/ - function CreateController( - $scope, - $timeout, - $log, - $state, - $location, - toastr, - apigService, - authService ) { - - var vm = this; - vm.content = {}; - vm.title = "Create a New Blog Post"; - vm.loading = false; - vm.submitting = false; - vm.authenticated = false; - vm.create = create; - vm.selectedForum = undefined; - - $scope.$watch( - function() { - return authService.sessionStatus(); - }, - function(authenticated) { - vm.authenticated = authenticated; - }); - - $scope.$watch( - function() { - return vm.selectedForum - }, - function(forum) { - $log.debug('selected forum: ', forum); - }); - - function create(forum) { - vm.submitting = true; - $log.debug('creating post in forum ', forum); - apigService.createPost(vm.content,forum) - .then(function(result) { - if (result.errorMessage) { - return toastr.error(result.errorMessage); - } - toastr.info('Post Created'); - $log.debug(result); - vm.submitting = false; - $state.go('post',{id:result.id}); - },function(error) { - $log.error(error); - vm.submitting = false; - toastr.error('Error creating Post'); - }); - } - } - -})(); \ No newline at end of file diff --git a/website/src/app/post/create.html b/website/src/app/post/create.html deleted file mode 100644 index 65d5703c..00000000 --- a/website/src/app/post/create.html +++ /dev/null @@ -1,43 +0,0 @@ -
- - - - - -
-

{{post.title}}

- - - -
- - - - - - - - - - - - - - -
-
-
- Post -
- -
-
-
- -
- - -
- -
diff --git a/website/src/app/post/post.controller.js b/website/src/app/post/post.controller.js deleted file mode 100644 index 5a9a8fcb..00000000 --- a/website/src/app/post/post.controller.js +++ /dev/null @@ -1,92 +0,0 @@ -(function() { - 'use strict' - - angular - .module('ServerlessBlog') - .controller('PostController', PostController); - - /** @ngInject **/ - function PostController( - $scope, - $timeout, - $log, - $stateParams, - $mdSidenav, - toastr, - apigService, - authService ) { - - var vm = this; - vm.content = "Loading..."; - vm.title = ""; - vm.comments = []; - vm.loading = true; - vm.submitting = false; - vm.comment = ""; - vm.submitComment = submitComment; - vm.login = login; - vm.authenticated = authService.sessionStatus(); - - $scope.$watch( - function() { - return authService.sessionStatus(); - }, - function(status) { - vm.authenticated = status; - } - ); - - activate(); - - function activate() { - getPost(); - } - - function login() { - $mdSidenav('right').toggle(); - } - - function submitComment() { - vm.submitting = true; - //$log.info(vm.comment); - apigService.createComment($stateParams.id,vm.comment) - .then(function(result) { - //$log.info(result); - vm.comment = ""; - vm.comments.unshift(result); - vm.submitting = false; - }, function(error) { - $log.error(error); - toastr.error(error.errorMessage || "Error Creating Comment"); - vm.submitting = false; - }); - } - - function getPost() { - vm.loading = true; - apigService.getPost($stateParams.id) - .then(function(result) { - vm.content = result.body; - vm.title = result.title; - vm.created = result.createdAt; - getComments(result.id); - },function(error) { - $log.error(error); - vm.loading = false; - }); - } - - function getComments(postId) { - vm.loading = true; - apigService.getComments(postId) - .then(function(result) { - vm.comments = result; - vm.loading = false; - },function(error) { - $log.error(error); - vm.loading = false; - }); - } - } - -})(); \ No newline at end of file diff --git a/website/src/app/post/post.controller.spec.js b/website/src/app/post/post.controller.spec.js deleted file mode 100644 index e69de29b..00000000 diff --git a/website/src/app/post/post.css b/website/src/app/post/post.css deleted file mode 100644 index 6ebb6f4b..00000000 --- a/website/src/app/post/post.css +++ /dev/null @@ -1,35 +0,0 @@ -.post-content { - text-align: center; -} - -.jumbotron h1 a { - color: white; - text-decoration: none; -} - -.jumbotron h1 a:hover { - text-decoration: underline; -} - -.published-date { - color:silver; -} - -.comment-body { - padding-top:5px; - padding-bottom:10px; -} - -.comment-date { - padding-bottom:15px; -} - -.comment-who { - padding-top:10px; - color: blue; -} - -.comment-list-item { - padding: 10px; - min-height:100px; -} \ No newline at end of file diff --git a/website/src/app/post/post.html b/website/src/app/post/post.html deleted file mode 100644 index 97511a95..00000000 --- a/website/src/app/post/post.html +++ /dev/null @@ -1,75 +0,0 @@ -
- - - - - -
-

{{ post.title }}

- - Published on {{ post.created | date:'short' }} -  |  - {{ post.comments.length }} Comments - -

{{ post.content }}

-
- -
- -
- - - - -
- - - face - - - - Post Comment -
-
-
-
-
-
-
- -
- - - - {{ (post.comments.length === 0)?'No':post.comments.length }} Comments - - - Login to Post a Comment - - - - - chat -
-

{{ comment.email || "Anonymous" }}

-
-

{{ comment.message || "*crickets*"}}

-
-

{{ comment.createdAt | date:'short' }}

-
- -
-
-
-
-
- -
-
- -
diff --git a/website/src/assets/images/angular-material.png b/website/src/assets/images/angular-material.png deleted file mode 100644 index 62fba49ee905b4f420abed856b83a87d81679343..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2119 zcmV-N2)Or&P)SK zK~#9!?VQg~TUQpxzvtX*qqG4W2#p6KF_ric0+dQ4b!L%OR#UM>v#HuYpzBKW7tC(f z*^boF?AS(?y2vVvs8S_RpbkHz4%3i2VW1{Nim=~3XBN*tz;=N6J>$#hK6z!?KIeUW z?mg$8ACLa>*9hUd*u?=sfE8c`SOHdm6<`Gf0akz&U{6!ijDZ0c)UJlDd@uZ@Pl26L0L zw#5mz2u^AOCwckjEtngm#3eOYPGQN^cp3J~zL;P*HB#w>4x$jj`WDYki7_B(ofrW` z@Bd2h#fDs{F_@c@^(_`jffN9n(CO56FEC-Hz;`DkiYRh0jJIV}BB%oKZ743HFafc{ zIy}1Q=Rr^a3L<1Iu4NH9L=oMckd*?v(>7&IFJL)`KQ6eFPKTf|sn0$URV>{%)8m)D zjcx46@*_ut<1(zl!X2j#gM&5t!>Tcz@}eZY4o&eC#R1N|+p>tzm(iICdGV4DN+>(J(T~M@^yMNySdL0Z`omMU zrrA7uV_9tl8-@a3FWJBUD_VU<#^6Rj2y|S=e|F8EK6R@F1H#$720jFUa`_xmP%O;I z(+%SU*FS&)#^C!W`mz*#^M(D}KPr70AXSHx@-lf_#?pQB?L%38Ml%x$G`i#DPC`WDo~(GUP+jZ(Y#-dc!DM}Gpu!P+)4IssXub!Gg8 z6{|OttC~+!d_SDi(h*S_GKo<-qT!sj#^?$R<+{Ntb<0u!7`-Kit5JA8zMuM7@+11Z zNG6S&Fofqt%8%&O$Bh_04&n`C^p@0>@fTLCUSXT(r`?Ug>OLicf$P;j-5O9Du!vwy zRo=jL6{+rDHwH03?F!r61y;%8tan|<6c%&DLOcQr5N@$&R)4 z{M7muM-I31I{J~+1+5m$aiuHAgRa1e-O$bp`nOQ*k%(cak^f4K`d7DN!g|1ZkJ<>C&q^NqDuAy6gnm<>%x&G*P#tB1=KrI@T4*1)Op)fOEtzbQr*fr zP^Y}&dRjJvY^GMl>WEawYIN(kBZ~Y@J$WbfD%Qxb4t)6j=U4?1UhUIhzXEu*Pq_i@ zAc1#=8cZnumpZNlhyJWfu`DLM+NY+p$>9+I?Cn#z{N87@_=q<*CGpRGG^}fz%_Y(c zW^c|=BT0inSMpif5&AgmvbM?P3d$9%ZSriV=k1QwxB|)(a_AZmKvXsA~{ymq}8FcE)!jnNKYt_d=r?@tAcR~pGah>}zosZC6aL}m{*tLw%9ar#& zSyAApZ9hinP6}p*Va!d*N`V*dIBNjHwLW~%*GXh}kwK?-xfrb}X05J1GoQ}L+9o?m z^n`*mH~4yt9+&wP4U0H;Ua8|jr+0m^As6Iat)7O_q0Ne4&$yxkh#_Y78R zjPBdj8sGN}mWN$IXXguz=@BezF+c5E^HbJn3QIfjk~yhHy`O1*+Svpyt@F@91lcTF zpH|`=68?cP`h>jNs$nh4<<1}0k)Vr)(e}A62}vc#bcPyHl<2W0p+76Y3a|pK04u-> xSAZ2@1y})AfE8ecE5Hh{0;~WlzzVR!^*_-IePnphNfiJ9002ovPDHLkV1mL6{E7eo diff --git a/website/src/assets/images/angular.png b/website/src/assets/images/angular.png deleted file mode 100644 index 59f36fa8374f04f95f2620a7ec5990d0acc0302d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13522 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 diff --git a/website/src/assets/images/aws-cloud.png b/website/src/assets/images/aws-cloud.png deleted file mode 100644 index 161dcc32b013e15c7b3efc7aa5b31d40c92ad0fe..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28328 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 WYhU*tHvj$!qN1p&P%UR3_J05_mnh-@ diff --git a/website/src/assets/images/aws.png b/website/src/assets/images/aws.png deleted file mode 100644 index efc295d1bb5dba8e1e4019ed9275e6aa5bf275ca..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 55178 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 diff --git a/website/src/assets/images/gulp.png b/website/src/assets/images/gulp.png deleted file mode 100644 index 5da1891e043759e8a05cdee7a21d52e64b4e4901..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10678 zcmZ{}1yo$yvbNnoS z{qO(WHI`PrPfe*g$FgV@C24dN5)=RcfG#T|srLJ5{%1o%_`P@DOxFMaP{eE`BvfQ2 zBtR;zPL?+I765<@dR~U7hWZjgB(#&K^BWE|i0+!i2N^UiAcGH7jg}2kfsF{G_mG5t zi-*mqWvQ$AL+F6aWLq4nJx?0P59@br7+WWGwWOe4h z&<0R>3IX=H>%atY7V6-g9`zdF;EoO4!4hG?VCcZ~Y*<x8=J`?IT&4^xK6bx|75mocVF>Dr-4(8^v+JLW3;2A6CVFh!&k1vN?dAyH_(a zKi?|=*zmHA$M@(9?W5VH@Q6>NFJS>lX#;#6t{AcReAf8@uQIFc7+|z4qzro@%VuZ) ztL4K*llnd64O3db;zpF00`$5WErE?NsNN|<3WiSCA=^6nq?YXDM0bAI`o4QyR-Czprz)br*j|Cx4qumjFciCg7k6Hp{gd)K-viKYpQafekW%82a z<7gO}Q1jvzTg|8PE)7FoovPC@_)8aP@<5g%CpBYmtlC||oetZS3&-4fY-e(gO_q8h|F zZYYRRtMA^TC7Qd^6)_VRuZP+9!r~+$x_?073l5N+Kx1%od-=BGp6kmJn0n#e*A^h)%*v5xxILUqSRbw5m`-Md+Hwoum!PF!bRA z;x`Z{!ZVD27CnPpQ`sd`m%vs^L(PSxNFWq0ZbEbjpW2;;|uMX?T9GJ2&{ zJlP0ZLN!{(?B-w%SzBsDdLjBws?-sFb7@y1T~Q^bGe>s0v=~Rx)tIVyD+SKY~yY!<>UpnQqg-p*( zV{ie3F;$@4ud=5WSk+$9UiIMO2Cc%k8_j(};j&UWMd^?zNDd?#61vGXs7lw75}fh{ zN4Zk;pkbP>l!`=rrqp}7rCG6=f7xQ0ja-_ifW056v^?eA#}AJZxx-_Gx*rz3D#`I!qwV<}3q+7aMKXA0?8)(@i zRxG!-qA;eg+ce*_c*BgQD!F>kf?xfLy>suGP=#W3V)F=A35=OfnAKU1m??D` zb>Vd!nhzTu`j-cC>uVtS$;IJ-0kEVaj7BOUDkUm8b5J zKv9jNGV8UIwd=p6+(FK4&&q}Qg`kz)x~lzIf0{nSK6^Z@UM-yOJuEyb19^cp z$onWIz_&mxL}65RWE}W+!2VvC-u4hm)@fr}gINMt@h1t7a6x1)G(-FcHzF`IXAS+a zcZ;T1fR`tb1tKLWG0-lP_;vcLbZk6aCUty3_EK}OT@vg@av$}wnNl5wxD%WDc!O7eBAmuTF+ z${mHeYvWeW9pFN0d*oxv-VoDX{Dt99i>G8@W>{m`lw{_BVYHcSoh-iW7Ob+^ryuVc zjW2_@E7OwFiWxa7rz$6G!kT|J&xq5R#xyV)atJYWvi5Tpvq`j#+RUA5;ueWcB`dsg z)3t2RO8BL4BNIoPOBbq(+GN|fW+iP=FseF^y?ec#v+X!WK5{kk7{F!8<@wI4%i~wX zQ!m%49Y$^Ve4W8ctCFW-Z`;OA*HNMa$U$c5eC+L5yanmHW6avD>Ic<^E)(mfv)v)_ zR&mvhPL@f14EsTQeEXAC%cfe}l}u`*sT;`HX3VBC-S?EEbzOh$`_=K=u#?Wx+tNqR zA09}a-&YUXu5_{?x<*Ui>V6p3?u=g+U4CiTKJL;o)ps$$v`I8iY-X=$?3Amwm7Xg< zEIAA&NzB>pJoNo?lD9m(ve3Qm5I6%94>yP?Oo2z%5%~2^#jaM&VbJvZMomBTsOYe* z+bYmFQ1V3PA{i?)ANrT318gm0~LTDj8s z_@tRxZB@r-DBz#x-+2=896wLdo}XcS9AICB*Eg#CeWrRtHg*F3D%eN`+DVmcxWY*>234dL+M&+Xxx3=s?W#k!Sk`W%$cq5ZpL4q z@6xBL3j#bJJFOri1Lt;AN7*8BYZYC55A(MUd*=JwGoChQW#utz)mNCE&U+#N0Pd@e zhK{?Aq5{8}lLOe)+zDa<_HuCktp)%Dz4(8hI#{@yg1j8;9o_i7gsA^g@c%ylV`ilW z{Uvd?6Qb5pQ~^mixmtj@z?@(>|0V@l|BUs&js1U3tiP&#^{@bw$X7_h{tsA$vLlx%s?KLp9r zjJU$Z%e?_NQ~jg`zJg!Aeis89D9T9Y^Ffi1DiSF3p%M^!!w%-O?JK5pkPxj zc`%Qp+{~X&Du}GNb3j;_X;!9>DI%*maP?jP44AD>Upgy&uId%?Cg7paGspFZ7D zINH5HW|y+ljWf4eM!s;K(NB!OW@JS=%RW67wIH!wxgGIYZ&zYf5ATC3v|F3{zVdkT z)ddZ>nUT*V&m>dtGYmZ-V$)l3(oManC&fgeGTA24u5Fu@oJycA6P3-MX%X21Fv4#+ zuYAU=8L@Qa&4Ef^hl)$kr;_JqpW0Ma-n+>^)z!`V%2e%_PxUFKNzcyhi-G~d8VC6h>}RMVDE5z@~qqJ?KX70UZ9K`Htm;>9Rxxg$YCLhTv=H++VY{4@2P-8 z^BEEI8Pf6@qS*mT^t``dmlSVpZ<-km*|@#sxlg#)p^CAG>g;&WrHZi~knE8JUx*U> zzMYnz+^2wWl}svWAD*b_uV8Ftwfil++b4I^tkWMPSLA7HtL*O9HpAiIwT71yfA9Jo zx<~=(wYK?a7GHRK>(SFfj>SwwY*tmGMaq|Sjj{{1+K050!OE8#o@!rpB zw-cs$$-gIEQ95xkZR>?)${Q@$vQ5PGu?3Q zjemd78IvNM{{myZFYQ2k`xtnDaQ-n4qN|lO+{fo!BrjBb{1$$lh*Dq<%`Cnfw)WNX zCnh&3cxw&R-fxe@@KH2k7`+9@#|DG`@?X8+d>w+<3fqJmKG&VT5#s%_riV?E&N3Df zXhi^W*{Lck18-kYH^093+BB_Dy|WR7_+r;Ts6?N}0mBlykyB+9^Mfhz$06meg2S!a z05n_sOD0jRE*8CAJY`;(zA>UF#u>6*Y{dB+v$s2#qs66_l@Q;^QP16LtRdX0Z{KcD zst*8}J4uu(Yw4B1ySyeugox`O?w;D?S^XJSz1=Y?zH@DRWB9o9T_R8C9WS6md(`|; zgEm(8L`;k-a8tO!DLnB)drgb z9^(zpSjZkd1mc9gFN-;!ZJvl#HbXyuQk4uj@9W zuHH29csn5)B&Dmv_x7?928U0ffPe5n9fsrj1hL@AvhND31zL!;m;LoU>bu+@WL7X4 zbzFiAVuxtIrsLQR>af0o2sg#n1?!~D$r$pFWQv~L3U0{rh}Qg~g|bM?#Pn9hJfyN%KftWsTpa0bt=uAM>Sxux(^ zxMax2=-UHV60dq>a;dnWz*)nzIl9aa!u1%V5)1P|bWWDo%-0BGjQ~c&Xb%bDOTsL0 z=4sotp=c4kfSW0=Vip6^Pd1^`(;K)>V^GW`{k9+W_9tpEd7&`xE1xhi`YSTM?RFCF zxt;z7YERmsr+nOheM7bA4vU`-H2MwF;LQpU7WnM|gU~C_P?al)&xnOH8b@!f)>| zp0Fm4@8RGeZljPvL;j%a>k9aYA@najb-#i~Z)ae;G2pw!mA@+Bhun-i6okI;CB37e z)ssFU1sSo>IKIRCh&pEJzMy%?Ohkl5zTglIVh;J<*!y~^p+~(2Y42e2%Lgo=v=#Yw zBwSE}H%{5RSM0mxw1SS}!?6Z?_9VkMj^+u%^>wF4QuASiISWOjQGLvj@`y=u)ec8R z@RXxIr^|EM<4QI40;Y^+Slh!I+mS)%xS~FIbhg0De$yf}b9V-m`@?T8N4=>q1^TE# zbIH+F2c{kRNdxoFQI^9pPhJKvBm;|*Dp@p7Kop{D&$l*!R2kf8#%v8(pBY<#UC3S@ zI+nh7pzy+d^?|hJQVoY7S101*LY&%}f{kc42IymJ-&LMz*shnOW1W$t1dD@7zHie} z)&v!qDM@x8FdSj2_&SuT!WXDV=A!5hgb~2#9Hv?ni{c9&I-(*?5dW%+XGC~w<`O)L z?icwyC0k^T&NlZAHwWae zz3D$P&!G?^^hTL|H%LFC8izxqB6Akof|ODb4?cm4BmsIwLmI|=*sweUbf zf=sEg##J3UbXs2T({ao{2yWZof%3M&xAfdY<+L`~B6j`KL$D#b>)+Iw*VRdw4yF&N z2sv=h=Ci&NmoJ+&q`vJ`vH6(kE!Xpf#t@AFXY*B-fjD&A@%dUU4J?A0|D{=oKH&0A zD^r~!7G^H6KE)+kotMVDzaLs-C5W00)EQ~l>mO~e1Lq>*N;YY@R#dE3biRb(25Zn` zS6rtQ)P)7LF?GSS!KiuOvm#{0L*?wii-j-BWWW5H8Rf6-UY7#zj~&rE7j^RugFbY6C+JuxHvTIHAIGBWB}k z9R<^I?!?Z9x|nrclxXnce$1%YOZTV*6v^q?z5WCfzCmrp2-`f3FSv9)CkGLId(~vt zAG2N*>>vlm7fq;?#}i_?!O09*8^$xgApEHD{rsV~SC%x!0WCJtXpQv%n=aoD5W7Ky zHTNKiuUVbOpQCrAAY-?|rlh)~D5&06jwoY5h1M9!iSS|i^BU?=bUA$R$EdNMsZjrl zxYDh#aMg^L&P9ouQ9G7721nkpoWR2%$zWd~4F#zo3k9mLOwiz**?oEJGUL&SSP!b# zf?pIhGg`L>gtpUx@gkNEH7``oFK`Ou%dDT0fWbo$@4bs$Jc1iDr~{q|N2sQjX7R&X zB;@I+=@VD?kX-)y`>%wj(ENPrYpAOcl%alwO!7f}*Ty*_n+pIR|K4 za>1Z#5u3xab_zU-88kY_H&VR`)Ax0)9;h3V@R+?yjpk{A4ffo4H9Xmfwi_V>3AJM*qo}c9TQ#r-_;I z`a(kUr_QJknFC|;gh!^T<8sod_fJuZ{NTIICFZltS)Gh|Q92B*U(K8>->XLagwMQA z^>ZJ(ea>a1hj*@HRu)aR5(qE(RM8t6oh!Npzt7)7jEpBGGl=HZMjhrjn2lbF;-YS} zwq;IqgjwS!JaF$b`7fCgrfH<I1s^o$~`z+ z6uly(sja;!d=qNNzN8ogVr%4P8!}G3(HzWE9?yIq^pc6kp9Yf7B}Ml;quw>v!Pyp3 zNL=di8!igM62Y|2p7L`v@ed5lon~V3lzm{j%_13VOgFoUu0?)bTBe_24V9#4&xyyuiERJX^Zryt)y9aqo`{yJ5Y> z*q|sM_M@JN7^~`l4Rxw)n*|hFsy4E-u_f<7XP+}3^7A)BYin7zkK(O)WOq*FaFEnQ z7uQjJNv8)ipH$ZgGIDiSix?5mm4K62n35CZF%oM6Qo5E{@Sp2wX&4dThN|4lPGB*W z5)okeZ^_NE)GUr#QYc@mjPYeG3c`;yjuMhAfOQ7-0J4j+I@{YjlPdDi`H=PQA4Fg7O9e{=tIiN@D69~)+Y>viidN&Y$vSv!UZ_o=1 z@e+33pdfHIgn4YkOo~H@13XGnxqs$p>&EPRC<3|dc`^xXLqGkDuF^YVx_QV&d!n#4 zcWcfsN8O(S$IzxWhzGESf(E9o~A+v5q2Y&sjXhZ=PZG zBgUyPVP(fycqd$)c?`B~$-vLUhLPa~D62c2fj}P{2iWdwN9y)(%YxS^`X~+PmeN?` zB=hf<%D*@{?V3gUAc_lf5F#jZOKnt}D8BsufZ@r|dFHA`zt&k-f2Ke2>|Zgz8zAq7 zOV2#kRd&y+DcbyABmHr>t83VV;qhkt=|MANuBQ%EVW0{kZBpJ!Q5_DX3m}Z4>s`C! zzPT3s+OmBU*Q0N~nT3LKuYkA8*Wo~F*Y>_vE(rX&-Ugaqu0Yk1ks5HAjLNr|ruUK; zA?0-bMTj|}JAdj$K(+i4{=Jag*c6S^&T~t?FmGIWWN;{Y z^ssfl0H*#Z#mG)XW7nNnII_Y|Un`47@D?I}q+9h9l3!|1lj~sCT10R#WfRfO@~5Ta z1p1b?p#El+o{WX*k(f8kFaILIwn6|%;!6)FTZ(qU40zN zStjpK#YMzenC-CLpGz^Dl%9jXZ+Do`?$$e|Vq|${gzpjz8lV$poetrnq((UZ`5K!- z(1Yvl#$Q%UXZQw3fetE#8Zv7s8i3WRzVg0QR%`%MbUPAUS!;o11)h~AS~=zYjxi3_ zy5i4)`d7GoI3vFl`QCPt6QOWBv~h4?GkwyM6;62E^sua(J${({{d+vwTeqe@Rj^#K zep_8fP)nf#o~qfqPmvj1=}cjHNPEf@jt;A|>|V*<;d(sBa=yRsJ)D~?x=(Mf!dK=e z6y7nC6~{1<93AQq@bO>-75Eo*<~`HxzUz?Y5WA+2@=FaNUut`YzPvCoJ&wiMzUcWA z>FxN7XoMlqS{riJX35GdV`)i%Bmc4J-SMEP z$qNTlkvUQrH8x4I{{!Q=Y)&@tmPKMleS!PtuFImNt`07BthLUCk|O{8lN=r%H_!DA zx#-uKtD?m70I@64GpmH=&%ZuzMl2oM0batd!&m`Nh7H{(?x70%zEGou^%p0y=ez6% ztY1{#MHlao(`F6%E(O5?QAu$9T%VR>U3Sd5n?9O?1-nf`5V@Qsu82nv?-losSMJQk zsg6yZ5XVIZzuoAQ5}%9(mUKRQqU7Htg*C59*LSrQvSq%w6J-nJ+})m*@qEVwWnw@l zR{*# zpM2%WeQCfZ$T8>kE3;dWdv>`ieyV*@vvAo>m-gfZ_lmZ_x!;+*dZ~Vd#IyZvQpM-9 z?XaoG?38V_qqBup9h{oUn&ZyIeD^$?)a7#Gl_4mX_)W~LWe<~hl zV<&l;{-&vI7$rD8X^=|+Cud4kgAR=;uf^8=ycqvzkGioc>kBsY9xcU?X=G0I_meIL z&7$CWtm)N8V8De_Uk&jstm^Ff%*FLxp0#MKF3_qg%H&5bHfkCxhjSh zR2FL^BcYabY>jmtQMzD|LP__M`L-^}dOtQmJ~K1jvt}1##1WU`LuKGCVzW=+NkG&C zCtiguS-onXnt6*ZFAOTXee$5X9U_%gRVgVSE=KJjx8?Xa+^pW2^yY{KL&U5KjI~tJ@ZR#jL4_;32pDC#&_FHED8Ffpfp#3Jj5jKAwQdbH z;q9WYgxiRV580n2Y>igFkR6|TC% zU|9oQ5VmNSrfU3;eAM|e`t&xl#ysI-%u!c>^knUR?iC7VSaVg(L9}MrmV6P50q<+d z4TWD9NXps5rQ(BPtJ;dANU`rwQ(>wjwmq@?9j6<+^p)p=%=$5hpgxz8=%l&~aY z7-X?WW=ZkY1so)}4JUB+nz8dLjHcbL-l`m@d!UlmLg==aQjRnTL~$}yaz_;UnCMTc zQa7vm=m`CY;UYe&d?~)(c0aaIcqvJFjS51RH}Ocs^?6TG+YfPYrw7OBWYTtmg;*Jw zm=tqM-gKMokdM6aCTr=OFCKpxg^l7QC1M&ZD4rFwrQ?BcNg@k|6;%}2H@H=-AAEI33%fqZ)U%5i}PJ1*k zPH$qyI9%$uCw$ZIi5qm>b1fWpM)KtDt_BBRjfkhsjLv~nKb+rN5kfe;(N|Q#vK0AK zm)L|INIa<_**Smc{j{hy43mW>chA|+FgYF5yu6LL!yC@Y=;W%MJ+_Mj8~6EBp0@Ki z4@iRs-Y4x5Z>Ut)65s8b&Oxz4Y~Pi?GU1e?x}}P$=-7E$`$z-kmEvraS47FV#VHCV zxkvHU7uO#nQwg^Q(wkEDE^Pdn7dU-uldne&xzPpFqw>dgzIJygiD-Ht|7dYdr|&kE*T0sazzMi`R-s7PGCL$Pq<$J-O-mtX?btrFsB z^reVNUvXdmCMEwZFYTwLw?CGKit8&GE}@c#vbw3hQh*e^q2mQ+ zYFNXP;&j$8vFgoDq1IOXmCZGlDO+>(MF2(uHb~tn_YS_QCm=jjA^8nP`TI>r@GVG4 zE36t#R89xMw)#^`j5CuYRT?Lgow>AIv$oW;OZX-KfjJXg(ljLZ)e>_PB&Z_tn-nvs zR9CytZ4hvI<$a64s#KP5Vu93uw;9sHZhtI@eU1+kWkGz!Fj?z|iT(Q3xRj`pWFEU; zHIk^@(L~T`#Sj*6y2Fxp`_B*L3T>trf>JvgVs#L}RAN9hc+qJ1Q;7zRL&=FN9E72m z$Rxu4u$NZd`?udF+GfjVD(jYPIfR9WY#UoU%K@?Lc`j5gv;+cRq>3Bg;{W{ta133x z@dISu+@iTgP}?P_N0HaXF<_&=QJopVC&6D}F!`Zz&)ElAiwtD1J0lpuRp4xVOMz4G zoLx8L&4j=I8ZpVsWx4qb%AT~F>6t-@J_e9wSMn?aBLxgLDlLMx{Ar+Nk!;BLhbDlf z8LEGSl=>f^n!zlIBJ%3PWu`o8WUV z4gqKA!lb@dlr@Brs&Nz?L@+P*gD&!M8E&+>_z+2)?4>=c=s}bta;}0i9U)@0xq8xqyn^@EO6wdebsMV-)sI$ZC>>ta^RTt2OFui6{b9DjhXp7yi|hjgS%29AO*;X zmFf52_ToIe&z#TGE$Qb5$w9P|GyBgmy<+X;askV0WF&#HR_`a6eHOzOu3B?iQ+$4$ zoVS0*;7FVby$*WR3A-!cUr1*AEX~WpV#KFpt@2CWX+uL$Nkc;* zP;zf(X>4Tx07wm;mUmQB*%pV-y*Itk5+Wca^cs2zAksTX6$DXM^`x7XQc?|s+0 z08spb1j2M!0f022SQPH-!CVp(%f$Br7!UytSOLJ{W@ZFO_(THK{JlMynW#v{v-a*T zfMmPdEWc1DbJqWVks>!kBnAKqMb$PuekK>?0+ds;#ThdH1j_W4DKdsJG8Ul;qO2n0 z#IJ1jr{*iW$(WZWsE0n`c;fQ!l&-AnmjxZO1uWyz`0VP>&nP`#itsL#`S=Q!g`M=rU9)45( zJ;-|dRq-b5&z?byo>|{)?5r=n76A4nTALlSzLiw~v~31J<>9PP?;rs31pu_(obw)r zY+jPY;tVGXi|p)da{-@gE-UCa`=5eu%D;v=_nFJ?`&K)q7e9d`Nfk3?MdhZarb|T3 z%nS~f&t(1g5dY)AIcd$w!z`Siz!&j_=v7hZlnI21XuE|xfmo0(WD10T)!}~_HYW!e zew}L+XmwuzeT6wtxJd`dZ#@7*BLgIEKY9Xv>st^p3dp{^Xswa2bB{85{^$B13tWnB z;Y>jyQ|9&zk7RNsqAVGs--K+z0uqo1bf5|}fi5rtEMN^BfHQCd-XH*kfJhJnmIE$G z0%<@5vOzxB0181d*a3EfYH$G5fqKvcPJ%XY23!PJzzuK<41h;K3WmW;Fah3yX$XSw z5EY_9s*o0>51B&N5F1(uc|$=^I1~fLLy3?Ol0f;;Ca4%HgQ}rJP(Ab`bQ-z{U4#0d z2hboi2K@njgb|nm(_szR0JebHusa+GN5aeCM0gdP2N%HG;Yzp`J`T6S7vUT504#-H z!jlL<$Or?`Mpy_N@kBz9SR?@vA#0H$qyni$nvf2p8@Y{0k#Xb$28W?xm>3qu8RLgp zjNxKdVb)?wFx8l2m{v>|<~C*!GlBVnrDD~wrdTJeKXwT=5u1%I#8zOBU|X=4u>;s) z>^mF|$G{ol9B_WP7+f-LHLe7=57&&lfa}8z;U@8Tyei%l?}87(bMRt(A-)QK9Dg3) zj~~XrCy)tR1Z#p1A(kK{Y$Q|=8VKhI{e%(1G*N-5Pjn)N5P8I0VkxnX*g?EW941ba z6iJ387g8iCnY4jaNopcpCOsy-A(P2EWJhusSwLP-t|XrzUnLKcKTwn?CKOLf97RIe zPB}`sKzTrUL#0v;sBY9)s+hW+T2H-1eM)^VN0T#`^Oxhvt&^*fYnAJldnHel*Ozyf zUoM{~Um<@={-*r60#U(0!Bc^wuvVc);k3d%g-J!4qLpHZVwz%!VuRu}#Ze`^l7W)9 z5>Kf>>9Eozr6C$Z)1`URxU@~QI@)F0FdauXr2Es8>BaOP=)Lp_WhG@>R;lZ?BJkMlIuMhw8ApiF&yDYW2hFJ?fJhni{?u z85&g@mo&yT8JcdI$(rSw=QPK(Xj%)k1X|@<=e1rim6`6$RAwc!i#egKuI;BS(LSWz zt39n_sIypSqfWEV6J3%nTQ@-4i zi$R;gsG*9XzhRzXqv2yCs*$VFDx+GXJH|L;wsDH_KI2;^u!)^Xl1YupO;gy^-c(?^ z&$Q1BYvyPsG^;hc$D**@Sy`+`)}T4VJji^bd7Jqw3q6Zii=7tT7GEswEK@D(EFW1Z zSp`^awCb?>!`j4}Yh7b~$A)U-W3$et-R8BesV(1jzwLcHnq9En7Q0Tn&-M=XBKs!$ zF$X<|c!#|X_tWYh)GZit z(Q)Cp9CDE^WG;+fcyOWARoj*0TI>4EP1lX*cEoMO-Pk?Z{kZ!p4@(b`M~lalr<3Oz z&kJ6Nm#vN_+kA5{dW4@^Vjg_`q%qU1ULk& z3Fr!>1V#i_2R;ij2@(Z$1jE4r!MlPVFVbHmT+|iPIq0wy5aS{>yK?9ZAjVh%SOwMWgFjair&;wpi!{CU}&@N=Eg#~ zLQ&zpEzVmGY{hI9Z0+4-0xS$$Xe-OToc?Y*V;rTcf_ zb_jRe-RZjXSeas3UfIyD;9afd%<`i0x4T#DzE)vdabOQ=k7SRuGN`h>O0Q~1)u-yD z>VX=Mn&!Rgd$;YK+Q-}1zu#?t(*cbG#Ronf6db&N$oEidtwC+YVcg-Y!_VuY>bk#Y ze_ww@?MU&F&qswvrN_dLb=5o6*Egs)ls3YRlE$&)amR1{;Ppd$6RYV^Go!iq1UMl% z@#4q$AMc(FJlT1QeX8jv{h#)>&{~RGq1N2iiMFIRX?sk2-|2wUogK~{EkB$8eDsX= znVPf8XG_nK&J~=SIiGia@9y}|z3FhX{g&gcj=lwb=lWgyFW&aLedUh- zof`v-2Kw$UzI*>(+&$@i-u=-BsSjR1%z8NeX#HdC`Hh-Z(6xI-`hmHDqv!v)W&&nrf>M(RhcN6(D;jNN*%^u_SYjF;2ng}*8Ow)d6M ztDk;%`@Lsk$;9w$(d(H%O5UixIr`T2ZRcd@o7Y)Y&xA5Qa1{*)a6)d%L)kzDLWVWA6FuMYzWfJ{lo0Bon!qt zvOIer?4NQ2E9KihRX7>P^)rEGuk~zet)2HjXlirIPV z7rCvSt^ab#`Iix7QERKayc6!U(vk)%-I`7{yzLBk6V@n9#V8QDuWP$IC!B-@&Q0xr`~&$uK(zT52jyJWlAE< z^5M%RH3qKm=|}hdkrPB6E>%hMN6g&Kcdc*OmOs|EoP$zs3IfObvh360^k#BIBu!MJix^%Z%fLz`g*Ta&*dVFhl?u zaG~K~!&Pyx&-bi%Ec+G|-w!7&zVr`*CEc7(v~{)ND;R=qG4Qq| zL2S6oiH!B!k+VkFfqw~|9)oSLa$XJ=MMf-3MA(vW)Q>#$CfdzMWhNSsnx1^72MNB( z3xWkxy3#9DQ9;tTm>Sz6h>%o75;1GUtWPz#K^uKJGvy|&jF*KYZR|p@S)`$qVW{HI^0?fFL<5BBc^|2s)4kHWCcP}3l|$0n*~$CRP>E2RD{ZC@^nz2$LGzm1PX|Tw z1|%A{?asq&mK8}vZ-F|lgmjRtocfABk}C(Ns;mzM+aM2#=lXBFF6Al9$9+9XZTWa> zt|X>ApLq%!bN+;-)1Oq~QpT&NvU>7t43Jgpm7Yy^rSo$}&Dkd5d)GF&$&>SFCC~Tr z;*Z0O4BxjZq?Xg%GTe3t9D7HZMB#WK{!RhGNVhm&-hh73cw+X=9C1Gc^B znmoH zT4{Q~+97DL52Uk)LW3hA)*%q5QJEMh>PN8F>s%PgZA>(}*p@$0=;Vu+PJdx@5oMDI zQmRAewxnk?IH~Cc6;ZFPFoa+7(%krrFfD!IzklEvS^>xQ_j+A{s6-liLZb@@Jc*(XfTPEFnx*zs*Z>CG#pz^DKX}^v|o!iaxQM7@(Q5-RFvT|5kmT&a(@Q} zKeb%@e)9wdgaOuY={;ljJJV|4n|$xEl-TEL57+)y|TSQ|xWS47v1!yuP3aVW<^D{ZKL* zk&OO{p)!>9G1M;hL6MRldilhxOCGsorCO18J&u@kDTe@kH!1CM})zD^(WN72i_))2L(fN4DSAlo)Wz`dqiMMU}Zj z@nH%P&j}=+i6s7a!TXWwsW%yCB60vaT4v0_qncs{q2c(&sHZBGdE|BJ^Pdta|L}|M zOTS-gbE}joKCXq#VkUJ+z=>l5FZjP24byS2KVqPw5&-sBxMhThA*9L%MIBWGy4R=J zSU(r&Xj6)khFvLJ_RmPHM%FxK5Es&?pE?V#1w#Z!AQ;tey_>YO?Gn%PzDt#w^wP6h z#1%2}j8pdXGS1B^4?zg`7wgues;OUs2wBl24O6N&($EfhLLqdx7MB1}Ln7;CUZ4X< zNpvL@@@gW7KoF_Jy4R;(q6o=ku9{5!yjK=OyQ(>RUliIlwPMSPI8nd#P8>d=YuZiJ zcM?+4gR9;jQ4%;hUPH3+M7+?+YN-?Fp|{2ASN&#^3##JtTV_sZN;V#?hPbUY0?$h_ z2*kZ$CH-T@FPZw+I29EUnsonAbA% zXH}TPs4zp6huR)M8ICpf#3tNg)MG5n5@j)`Yy19Hl04zEl-O~lY7(Rn22hZvqcPdg zgqM_urK*+j#Wm>9RoMD}pSooB8*#*t_=^}Ch;i{0F{R;2H}P%7!Sq~Z;sD(e&)`_~ zCBTJ0oBo|XAlY~-%<=e~Bj;>uU@1{wYF~E65Cus=b)KyS7~rEgE{lN?=FEE3{l$UJ z&iQ&jqj^S6DXE6saC|^>3z}zs!*vqJ6W)-Ht%59LJe|UNkkck~rT-X53;_f|jILh0 zweL02I*F1Q6$|FhVih7Ltp#Y) z;1-oJh%7DKdmZ=+&>^C242bToMvOv!>0~F}nak)NV+!R=3AnQ$$_K98;p+AT1CRZ+?C*56%-mCwDW;68KklKCj`WG?0W|Of z69_2rI?5^m0Quz@@elL9Z*-&ph5T`xRaR6$DF`=l>9l)jV~&W5+E9H{7>$2fUWp{F ztk7n-G13dN35|T!wG$VrO*q_PvTF(q^^o8fZ2dK5kP3V(es~2|o#=$|#cIW5;KjHD zLD_}Mq-Csenfnj~fXuUjYwT~PoYdY(hd8BAK2sTJN8F{*+Arx!zpT#I)0qemEqc~9 zh`1D@E}F~O9-{2fpT3RisrNS;;9kzc3%utaNhDB}4$^(sq^`CO)dT!hC8M$( z*%LjRxMcc`sNQ=Bu?SDqi0PmbY0*A)-iVodXpAG(Sy^Z01GC0fh$T!k&bSbWXJw)A z-u6sqyq@R(vicv_=$Y0n#pjKGS|a+E=lW;m{QP@xk_9L2!d&c+R0`2n`?1^K^=}}wvV(GBnBPq1T%$G%Bl9Z#jjcCQT|nqhLoMa-zGP40>M(9Fb1s9hooA{x zt!b{TwTvJDWbT^+yT!HKgR@>E}>KH^WTI5iezM8TJb2$NuJJM_l#^m6ZckT4fnQG%ui+wrhV2?&qpx zNk?fZ#1am(;pnHHE6D6g^nZus)yCKktd<0!`Yvoc-|2cpr%kYAPIC4 zdvj@0-u^El85!ACp7&)=#&E0`@RM$G7dx3dR%N3|NK{XeAkt^@Eao3Ge>xfOYxb4d3G%Hc8>+y!LdkHxZ&vaY9aYKi*3cs?xh?ztz^-WBne#B{k$R21t zFeERln-S?9#G#7d!{;M-D{Z#y>)Ob*y{U4Fe>Tt;f{S?R86J*8fih@j<6 ztyE=q5~u164fNH=Ufqz%t~vnb_=@I46#Z2eK1D)C_=8|KU`tNpT{Vez3n8c$IT!ZB z_TM9%QN}6~L^n#fIVXyXXiAie>wci{yA@>r_xys-Zh$wX3x*29qRtK*lpJ^lgQ7Ef zg3LP9#WQr_w=mBA6PC37zR+O{V2Z?PO}83pgD8)8G|wDAxMARe)!8*K5)ZzuQ2j>z z$mb8btb5nx){h>L957^EcKrpDx-NZQY6uQBHlUOUvQ}3gn;z1gwGVWB`-GmJ503%S z!w2EDuFbApHEZOoYo{)m`iAP2RuNE9m}%{j{G$U~?8mVshbwHtt4hMK*UM)rJGFJj z$&BFrEfFzQk0FSmP-oh^?1;&q;Mt)HA&~kIqadzYkK{Q{Vv#b5%S-KTbnjcvNm^M5 zP+4^9K-9!hto%QUva{y5%zC=Rnt2cRcQ-Z+UIqa&szby{uS^@R);d~iyR|td>HoYm zNXa~^<`()*CQr#gjl~Byb~MlWVZj}hULwwKs zT6d2%l077!OD2*u81Bk~7&#C*)bjH}X_|i zTeL=tlz<}w45_&R0a^@q7)IzYM!4-gJs&(rVDWy`>K*Gm|M%!>vj|vv-&$r}ZQ6j7 zhi$toY==FCdR8-$lXfKKCa>noB@L6hrv8p{`Uq|VkCweAgWL$T+X!o=9$gX+mtg&2KB+$K4BWB z{3`{ILI{odXpVvoBjtFDuc?B{^;nAL&Ko(S6*YQO!q2~uuv5QkN;G{ZX(#tUszn43 zVni;fJgd;VLU$TOTJ{kOn$w>&IKUY)K=Q@C!A18{k0r0clQ6H4BN)pP$Z!;JB<&TX zGP~E(~6z;V8nAB`8cqhORbiAD(cNKXqX z`iKA}wH7XMa`Hs4@c(uu`Za>5zp|Ri-&I7qnLZE$b=Wf=!J8tFwqKMd>K4$1Y#JOU z^7iHW1=%o(g&NlsWr2$J=jcyt5`c2o_<(qn5Y zeP{-rhG-zgXMr{a16HsnhJ)CN>}0xcz`O3VbB;)VPNnGVTHkig#umH^a#qmJYkXcW zd|riv-&mJh_n?(+FjJ@U?BbvY1OTxGrc=;eTv#=z2R4!?2h()_{neyr3!~ z@vhh*&i3MHV`NmRDvV-agWTkiNVY_uCM(!i8EYpY#B=~;`=|~=1%Wpp*|bkG>E2XO zk=CRoQ=eKN_y;-3;Lv=&`>=dI!EfSFC-4u;IqreHJ8{xulh&vyO@sx$y-;2aFrg|l z9=5HN2<0MH8$P{dv`ybE`)GM$V&($Jo07$GMc_RL?5hhZJ|t2}-C}S_H!p^p$La88 zBJva(ll~6aknOcv4~7BAzI8}KY%BMIiiWD7?+#Tz$d74EG-=mSoEUU@3N$}x{8$wO=bn;{FZ%R#=T`e zNpKtN6*FK;43JVb+UGpBa9K4}gi{*4v zNDWErlaybDNbdwkC*|oBTxc0gA+(?6m{Cn-zZxqj)WKle|Bc=V3uAi`UY0q!iALab zW)bUdkE%pRl&_9o6*UGQpO+W~J0`&e#K1rVNuw#!CJZkLFwO9cSx=S_1=im}oW{0Z zAc*w|_B3ow**7#mgaV<<3;%7S`YPngz-#19`Yy1#6TZzXpGt-0bRBW-lt-jnq+S|z{F#HDTDDhUS_>khRCrEU97 z+7(R2ce)8vsIs^o$VhFlp0)BXVXQl&L}XxV5MDKbbZW(q4Q!*kBUibyfJUl3dqy9) zbk5)2dHDc3aXcz%|iN zUD&|~)orp-#Q{-uJ0D~Rsw$a}UZWe>O;zjHGG5Ta`&=?`)|!0j>bZ^Q)@5n2g1GEy zebDLc1Dlh>)_a)~&|3-_90qaB;7?-#xKD%>&P90<$VeX2>0pqhaqrf%C^Vz%eo~Qm zjKc4w&ZEd*$#M_@6-}FAbU6W&1e%X|w>$4WL?H1u3<)2a{9yWTrEM8+ud9v~`(Cil zvxS0i0A5;0cx@wruIuNW;vhI-rNdXCL3*Kjf*j&#&&`>*N}JUt!y1?0tn}lBI8I{| z8-wR)u*9#zE&9yu7%_A2ZYy&%)t&^F4rxj>3=%RFN7KGtAqrynxV*{rxt>3CzimBF zIZM&uD(&iZS&E26&^j?JXk48*R1oi`%Pr#lT9gFeo?xrs{M;rD_BgZ&36{xWUJ)YzRt zBDJ)1XRpQspV)o0FUf+Ke(1OKIxjWaiG2l2v1%h>Mj=(DgOZNpXQB#QQL?!-1s&iSNJjzMFnFy}qEYe@qsAib>uK zNOGbHt>TLs7s{{bbChNL;!Boutxqv2k3l|9#<4cQdUm0;$kMcP`7u((0vm7v-w(h> zP@))~sVJ@KE(V8eK8ju>de;Zpt7>@ zF$5C-&wwG0=H}b9nov@s!Jno^mGiRE>D`k#XPfcq>2eOtgjQ)iVQuB#W;CDHveZ_0 z#XI{Ro@n%!Y4eP7MIjj3f~}u?FFCJe#(X4l9sR$3-BJG2W7ECsvQ%CUDeZ}{bupl} zY^xnq)=KHk*W73joQ&nFfd?JFCAt}=Ritx^68hORL-hVTcI4+-c-sl+F z{sbI5Q%5d|@ajjVdxt$7CpZr`*J8iqMFSspmUWDraSx8y1H{B0Q$5jGP4nx*m*zca zrzm-zjyKC^q)hxR6}Sm&FT>dNx!W5BWIn+IRHPx zi2s|$JPh!5W=+&Lm{UK1NvT7|Egtut>cnw8VNh}~2g`I@xW0HS!Tu>bu|KZwlmU*T zDS&&)cE>BL|MAqq&V{*Xz$vCFcwK!k9t9Ykz}ZENY&u!aH#{;YS0E=|Px^lT)A3eO%~7&!vxL6SCc_4@N_N1uhu{5%vKwsc>=! zQATenP|2X{1ByHe$amt7Jkq#s$X`{5I2&Z%Na$T-f*jCe>oh*d`Gn6~K=9M^N6!3# zy~O^B@{?>h)|OQLgz*16w!>_lbrGxfHHEB|B8;+qi#hciMA@Bz-gT5w1P0;rf6gKb zsq15b<4_LF*vu?$j-~z?D`1fpPlDXIO zc~<+Lg?Lp3F3AOQ6g_8N^8L(U%S1nneH2Fvi8S^ZJmn`q)V5|EP_g2KMmG-JlUP~Dk)#J1G<^MHLjzxPjEGVRr}3b=tQe*xmY z9?p$eMaQF%kDadUDFfLo-~6fPCNGFQrMaWq_hlURQ^qd)i{nXsbhn-{Aa%0&%zutw zI^C>}EYxdVF>=<S?h_$?9AjFSF3;`m;sZ&W+ zc;2X)|A{n-80ugpF_eX)EPNm73Yazg4?CYfK@XK2&z>~2afpbK0eJ!-g2}u$UqEak z1D199<)hp8*E6*(I4)u(3COt__)cPO77pO)~{LqDP)dIkITWCuM-UWd<^58N_9jl zY3HUj)-Zmbp`1qIA?CXbP}tt!Rt9sYQQ#dqb3fs^yWp|KR#{KE$;w`k!a49TZ}~Tp&-?^i%~Fl1!y6Y3 zPk#}^twPci9AiF=!Qk?s0PiB6FVZ5rEYq>5%;Nw`M-=Xg6Y`#)TYr}-*P3P|M3>fZ zrX^%&z}3M`$tD{ZH8*cueUqX1h0fyI3G6@UQFQ9I;lN(k*Ln(yoy7aHj;er=$d8uP zC+YN&TtB0b2)~9(AXFZYCLf;2T3#y~J|Ca-K>AHRQ&RcDM2=Q*XwYShFC8&q6?r`$ zdjHhSUIW~(x?(zEstUiTP{+Lz5-Ca%9c+|}Ayp@3SXx`nnDlBoV8!;`^H|;T1C7eZ zrh6yc1eeuJ|GL;e5Ke~OLD4(X%n}aBWXBMX+j`QYb7ccTR#BrAw|wK-9Z}803PI39 z2bssfgoFyuEYq3D^`(zbhDN9_L81cYO%Swje`SFPB9stdkWc{r7s?-Xs(6}bVOCA= zq9jDfD_S-9?RnUZgEYm;yV}Hn3GnYVKl2{k{#aBOj>}zi@|)Zn$uBTb^t$X9Bx-#b z9*lbdC#5bO+u8P;f-9|9mtfbVQ_(IMW@;v&m`<W-4RoZCY=BYb-efy)uVkg=%I3><<+=QE zLGjc)I9%mum zn)9jfRY1D+hi0K71rH;Cimr7X(_bI+R0u>_$LIPOe04a!-0 zskXR14g})j7bPoyl+2icPF^|svfXiwuP;0=TQCsiE&TkoyJ59 z4_s)ZonxvmVrk7YAVl{@jh2?LxR#@$t;;*GKjlWf(?GUBtctARtr$Z6i=qf6nG`fN zOpJN^dO>x@FtsV6$u2JJ2X$w(gn3*bNa;fkO89M3p z1;sLz7b+cfJeSYiNa!8c(1gWnN(c35laPxS^*dIsPf)$%o*kDH_v%;oT=%g4?~I(6 zT?`&J=>W5jdBPmYWCiPxiQo!X^)jQTxjh%%;116QG3x6 zAYB&`5b7{`>;(7W8>=&V7uOkdcTS7N4mo(`=Z{t;h;XqU-S?NG4&&9$z!PafRY`9f z4zF4kwiAMJS=RvLnIeMn&TF1=0rlL(0Wmku9nr2uXV!$SOaJKE!9^N;f|a;FC|hZA zGCIcAvC<4UM@j;u21_&acO7|2L_E&m0MHZ$Jyz0SMzdoX)IM66g0LWF5ym1SQCoUw zPhlq$nsF&2FBqzbd=TnDD>zq~qP>E;?k`1lLmdaf(_~mlb#-0{#)4dU$>Q{Kh04RW z>{Ev}ZuOm^4O^YbflQy%v-I$EYVwk{8yNfC!fAKvqaj|HKlWS3z$0|RAds!uW71no z@55b*T)G&L@>>WYjw-qpyc+@I`lDJdv3xw}W5Z-CZGX%hKZqtfyQY=8U z#k49S>DVXEJG%WS>D78TN*xquYdaSstm}mUIHn$zmv3^Lv>ohL9ReejclNUj4U*}} z`-DC$wk-$)hPq~y1n`NJcRmBm*I1%jk3BJ`_$ah8Am{v-IbtXsnPIGna4JDMNVlvaw;v-PwHZb<~m>fT>_<5<~ms4O^mJq6^ zusleA&{wdikzr=Vp4)QmruV=!=9J}1d2==lUa`j%%xSA5hUXD^Y1->HP<&OmM<1AEl_f;aX`Sya%1K;x1XZt zAfYO90o-sJ8#%v^WSpjsQ>3=tpmpGI6}Dhr{45=mDMZJ3I3-|(my$$DExVl-6KQTE zZlo90yq>c1j1NFsu%M$keNca@D;iX@2x}T`$T^9XjsoH(9W0@@7OFwBQKzis7?l)_ zN!Yn~Glf@5#3)mZC^&D#B5Z4x_Jjxt;+TpyRMBr?#WImMEA<{@V5gU>R)sgHnTr;tic%tNw^Nj6LL!4agpX z_&)tt9pEW6=C*bBgoma-AuSWfSbYj3MrK=?wrl~~kI}!XuNr4*w5h1&XB;nQHr_`Z zqe4-VdJzB@J#i5W)|}%BgWpXoNuy~=O%=z%GDjQH$=^;`I&G%viVz&t+uyIaG8*U+ z~_D~D1>Dvz}r>wE#s7N{#XQS@< z%2*sk>ZT<1A&A~ZQer2qYdIC}Ejpo9*0G%sN3i(@H`pDA3wpAsF3u@w!(0j|5k&`@ zacIj4J^9>Z!fSYI$vCdnPE}52%e=*;)>*0`Ze(29M%;Y_Px$vgf^+DHQd!7@dm&t7 z5mt5P|Bk11yf$_sjXi?7@SdNNln$?Zx}D20g9<|cAIcP3Nrp*nv> z>QQ|waCcctf(NP5QMqU1a72el_hG_9Au6#soF|jT{b<*-(vf)~Pw^*~AbJ}elhM#P z^jfBEUrf0PadNzRQAnF9K0Jt_T-uUvRvc&dXq zxGFKQm2;bC9YH(S6y}KHnv9+iYHCDAtc4RFxb*ojy3rd|jxt5RRS6|1Xj#KIm!=SxgXEl?T3UCnVYk^wQa z>e9CTU!XZssJB?JDr}!wRwp5seT8fJGxRL(kb*F8_+6DCdM&_EUk;<*dy#;JY>bq* z5HBTReL9UZ&nC`en;vp*)1w+hG4E8rJ`i9^{@+LS$5;>w#{Cr zM2LvvJcuCxbq2dTxpTU<8)zZ6tSp;=de=wkS%0Y(r$l8V7;r2!pKj*VMGV0h<*EEV zn=lRKa&uVn$_n^9A;#|DK<;x@{E2a88{KjA3efjRWqbHd$iaJ&aK;hmjij%r1+{>!V^XWgKJ2a%x z3P6qpudhsRt+o(VjTkcO>bp)m;tvlTifUWT+UC#pM+~aZ%SLtQdR~lN=iVl3UquM0 znM(i*FpTiy%)Xw-=)4Ye1%%0a@`OmnIR3y#SXVnt9Wx6WdJ$jNE(9^s!o#`|i((jn z-Lf=*q)=6kEo-RvMiXqYDn$L2+EL#j#Utji8zZ~S=#Ug>@(UJWTMU|ViRN8OH2?p2k zhfb$NTdT!AB6fWc^q;E~#{P5n=1@7!n@2W=L!S3TFaHpmc?3s*XizIgsijdAcjIJ= zl+Bo9g1okk>DvYoMBq!$OSQ~DX8b@ux79t&_Km1RQsj1sdo%!JX-wu-w81nTSf)CK z3{6@wMiFOkkc7w?(9#>#Q(R1;n>th2c4yI!C=}Au<6I2D6BrvcA=zf-qw>|L(!k7U zoTFxYIymiUvho}MY6JUItC6bhN0c|fcRLT_<{n%RQKXXrlocZh^LGi+$M*riOvWLP zMUAO$g>FzC>f{CyC#s{4U%9tt4OS$bnwwue^zD-Ya5@9IJv0TYSwHHE8%LEkp`|7P zWtf)j`Qd~m)6dh(0zozy{*5At8jOgdD6f;x-pN7zN6Luvs=AIBF>Q-D-g8I$ZPs{)!?02 z>gllE!D^-bPb(IOdW!PZ1-smT&3a~o?2JiE+Gu&xh@tYEL=aUF5k++oY`YFFJ6m(g zb&5LxgldfPY{^dnk6H1)4wilxt=+{M$4A!L{)=)S)!xRY`NyQUV-NAczMbbR7Hbsk z98_ckBN2v+`cqF)^ZezY1b?h1uQAzKiZbX$8V`+6MARtn2K{a_LDZTIYc$!?(cJ!h z;ssZkmGYs_u1?n54iW>2_%jfi9@gkk15BjtP3m}_gK3xBzWoa4)4t)^?n*lue8`Dg z>)c#-Hsx_js_i!hNoR17&u_)w;NBjm?o))JKSw=Ujj|8O+eU#tNh48a9+_aNV5>(W zl;0qm5E92Kb-&L2w`Kp-f1QjZ=2E6jN%bPC-!^2#q%y=I9AbI66L{D2x1#m|aFwmc zI2PL|ks6VQZ4{1~GuK&=>M_x0egjo31C*r`EV(g67x5do7Q(1%ap86$%KS4=c&HpAfcdAdFc7Nz=A+y^*7XJ? z9R(7&Qg3)s@8eQU)m92sP@=9kpuXB!HxRhml|^q$0oB$tVomc2ZG0j8Q{IZ>o0APT zx@{0Ej*XfVy2BeepuMT|y7y`QSH?LZOvE&``MV;W?D?3;fOZ8{7`GX3YLo0m94MO# zbX=Y0nG76OPA;ICeQa`^@1tQa#&G|eI>;Qi2&4AvRvlFv+pI5)L_mfl;3`~_dkX$( zo7gq&w`!)qY$8MnM{GU-wirPo5KE;+3*-`s{P`RRz=E>W_S#*9tE&;bIw!(xad;xQ zDt;pb>%>%JYuG*YLSQ=`6PHXi(S%Y4t0=o!Z$AmM%h%LO7u}u zpB=3rWDcLU%pq%k=7l{+b!KW*DmDFjgNO0(0*QsM{kk+Yt^o zFdKwNJYNA{hQ)Bnri(!Ph|F{gm%`+jC<2j>yN3c(~iJX-LOibCh`@E4{%r z;@irgXB6fyw4bmn+FO~K`*1fChr|@}egyL? eF;HyWqyGn{OF8MSu^G<*0000lkJmj+cqamnCfI3XYyp*HDR(Q82BWc87xY2nma;&0QsDtEH0c{v|NZPOk_B%w-me#&KCx4OU*uL zP!y$wG!Dx5F!N==4W{ZyznYZv!3Nj?a9PcLyP5qys<-v`sCD_T&1KMX2bAht7~FuT zE_4W2kuJ`~X}=*B_QcR53_d0_x-N9zj)Wjk#Bq z)??{2IEj*1k{SUDS$dI2=o>BG0Zgh!<1hi|C*1g58Utq}2Pd!yQ2dK>Z>ZMsq3n>U zb1XB1cE5;FZ;GIcD<9mcx|vP00|CPo8D`RK+du*lt-3YkvY`$h>F4L4auuQ2ak5M% zk7$b=qdBE<2ri;;V4#pvhxohP(PN+aZS$dgOKtXJ;G*TsOTR2<+8rGKwtl&8)Oa@M zU`icS+WFzDIDOZI8qbawQtOf~4Narxlx3TAUQK*{uD5h(``r6?i}ahuyYO%SSFLN> zv*~kkNJr!3pQ9|i&t&IQasU6DmmP*R?-KhWu*#Ilar|E8Pk^U zkC>l(ef)NR#s-;l3;OV`BaXGyj#B~Q(YBotLZP~etd_{(!HYY=_&juo1mE%;aBRIy zpn@X!Romncf4c}nNm9X5JB#|m1f3dSav=Zi>9Va(s`W_V^#3?|Aww~Yao$lBr_?xn zKuxf8rzvJ8fNX_3_QPN$B6=1gbA__9!!Vno5r=$rh212F3J;ORgiVKKm4&ergKH2M zS^2L7IaZ*(zuUV)Erj~dz$u6ExFQ(B>GYwwqM(L=#E>KV71*ISL+y~lt}t^0!Wq7zs2mu`T6`n~R9ox3(kS1{6VW9l+Zmx`JYkFd0Ze68nt= zJ1e7$M071Bk*G@z{e{9IiSj2*g>-fz`cI43mjN!d&~g<|%1BJVAwzS9`uG*`E;(XaMTbjy!#pZmA&oAU06S*NoeYy|L<)sw9WB(~JNbmDJ@7p9~Y$0y-0l zVEK>Iw>B8HezAUyP)MD2k<3ohfN+GIbaru?`497K^Jw$$yWGQSG+oJ|$*ou_6=ElK zb2KFsgc9>5esgV2N=*W*R;%nJAl{#^^C>68cVx%icK|CID}w|21L6b1gT`@ngaLNb zr?2V+CEyfrCph7j>#%)#Z+eZV*3S5|iy`fq(U~zYjWW$T4LU8MQc=rg5oa;4(y3Bq zf!&_g-pu~5{p%uYRoryhv`_wYo(b3I{#(?)x{hf7wio;R!GFU4QV6pNdk6^#wFon~ zMz{&GowLWX53-NAMw@K)QJUfO5A=_kjoH((E+#dtX&Phsi>hnR%UEVber{9=S6h@f z>e@h*8>g_1l35LS#aj(qHGCvg6xejxOls9Gi$v54stbPhf_e>t$NOf;S50HZbNb7R zVv2gr^35PS7F3l;0Dly`J7pU<&>FBk3kP!*Qx1W z-OJ$WP_~1SMbK!Z{hmeRRO!ZHb9DvFmUr)YkYKh^-3G8zxpVpf{ekk8_LT!sILbGw z8~t&`)0@6CJZSOD>G4k6)HQeykuHF)eqOF@D^G8VVG3;uf`W>7g}1_6c(?;x` z#A?Y3Ht}^WTF_cc+tWXNnN(ViJMtcK8roP9{`=nc%7meSkpzkzO)X0~BAiAs{*h6u zlcZDIn*3;fX@6NJA|Mhi5+{P)29ul6{fgAY zR0zY@irI=kcHvD=P4f~oW-)b4Mqh>Ly4eP~AncMI<93S|TG+*6vq_5d9(vZDneiWr z_p)H>9GdTXD2?_Fn>HY;f^oIUFNb&g+565DBxAQ@uiv<>xqZBBdb~d(-}<>P9MG#H zmTC;w+Le8b`a5>+drlLa$em>WUQNhM#90w-Imc`+s1>T!^_bc=ULKA}v`eV%bhAtu zpgRsb;yRvhST|PNuVqjg&)%C)?8fY>(5xq)Zs`T-Ja0@sgr9epJe0ipYly!@so6HFKA5~IzG>~$IqT6jGjKD-uuHH^XyPny=$5aw2Q8NU`}r@FFd_S} z`(HrodEV;i+H&ufQ}8@=9PBWn2q_M6SMcvgRflSEr(v`8onM2~r^Wv|dToMDf~C%7 zual-h5ulRMfdsEb!G&esa^6&)fw<(6`aQQ%m-p>^$pGxC6VLY zd{m*oelbCnCv@)x4=pqLG5SSI>r2rsKY8o|6a%(SLgw>Grbt*sb3Oy_zzCqgr7jj!9?E7rPS-?TESY-;$8 z1cUN|y3ZruK6C^f#DC@y!NwxI zT(I^8d>XF{p9F2b&mvY}7>iE&^S#@?Tb%6`(;=%c=2wbVzb`&5TUT`7ofR&dPB;Fr zNr-HdWcy5eu2mU?pRSl@l4+9p2dV~5gP*}0{`GgmR}%*=VxaCX>>R?$iqG_0~?spr%a00Tn4t&y4SQ$U+jtjH-P%R{K zdDDb&>VK7;u>_=M!YJ0e_z3UH8=CF~O{?Z*K|jJqB9ApBrB{=7lFM zM>lH5wzNL0D-BvNy1h=9AcFGTk}0rC=v@3W`(pohaTS~cJJobyjH84e6kixk`0v!R z*@-9*39aSg^VPQ@Viro04g^bNTen$k1X+bRNzp2>=YT}5x^Diz; zABeh8a?L+FL)~wtz%2j(^^aom#bg1sg9Z7!y~C+Z$4VEqrP?LBF(2aKopexodD40- zL>3pe3H(r{MSzD+mDE$rBHn%!Tl=SIujErf>tzYnttKlCBlG#RP?VVmLFAIF}us;Ym~ z#&VlYxUwYzYAF0tQVxG(pEm4&#j^;-GX>=OmQ}!%LE?L&*dFycjm#@4(;A!r=sa4p z+F*m@*`_-1n+FNo@1vObBjekr-C@6Y^p+&s7HjvHDGCW5rFsAayF|z!awEMbXMlSp zaq)t#s}i({zI^BHFNLM5Wl!ho2FI>BV4<4}(pWr%ry6X%D+sz)7Is;%sr(UZwu&s3 zV$+nuW}3JYYf0mm7j979a$hy1tj#{~9#h=eP2fa_Fi6FvRiPf#Wa{I*BG%AvDd0?w zyv~zdnRHHvfnY+>23mBfWX`s8Xy6Ln4tF&e9U!A7h+X2G?phOaIzwa25|s(5!20U0 zeJQD9QGI*FrL|p8Up9|WQo?l#$st2kt1sJvW*g3X`XGg7P4=4ja3>Y_=lF1;H&ZcGp`x`p z3BBrd@2Qe$w_%l{W0<(0k9Z`hX`159W9~F<(j05Y`N^+#44VBI6ZFg;OQr=UzLO;E zTFAy}hwPQhvFi2>6MJEp{O(r7%`{J+i6 z+>LNdB?^q-O*_)Y0<2YKf_{08ig*?bwp;zR4`zJAepWflF2))`PyXnvbi`bhz7#*% zLCtn9El}Ov6SITLg62tj+4WD>T-dY)82}Fjwk*HwChELAE>ttSk)iO%)bMje4sU)u zXD|^i@}zfrcH!aIm44G4#=+r0YfKxV?A)0NOCeF-!1)Icwjg80#o~*~oQ!gQ=gbx= zDGg(N1{5mh?_cP+o59zUru+u!a%Y-Y1-7+09it8oK_rS=PzbR3^lYfDxY({?XOdF> zJddHHDDLfK=*%5=&S9?tjcKqs+mQoYcu0KIzry*=97zeNqnjd70^*7iCK?G*QQB|D z0Tl<%0jKTk9ME0#p>YqTA^4x;+oddP=$6<5r1A1H?uf*^hBE7`nly_cGb4jdCQ;O# z)R3@A@xp;|#H>+I%AteI&~uMT!6-q5Z&%t{^fk!0tD5zEjnPv}Dy2>$Ul}Y6Wazdn zf7V$7-g?7ynxmLLNYA3`ryS7cwaLZzWsD~g1_jt76&uB6ppC~(1KFU7OLVqJtIT&c z7bXd^wvRCt(@05%EC`&cR#p!63^~@-L>{`-U6DvPzFj0YC$`M%jIy#`FJh1I>P2tF z`eQ0<6VM@>s+;p1`{AsoFoYQ@3SBorXHK@#V!(LR9}iNb+%ly0l8X_}HGG5>nxeRo zNOleL5|Tz&AV)?a3XeVu#oBNuIl8G2Ji+YPg*$ty6aK;jv}8ZH&mu(5q9voF+%j_{ z7Xec2U}-Yh`f;RKKoKjpUK`lZ#piR+v@rzE;|eNj62>K9@$sgL(`_)V=G0A(G+wWi zMq0HSaTSrnS!1@CY>L1oh{(q4&RGCs?8j(ATv5KRU8k0j=0ClwZQe|wd)qq2M%xIp z2O0t@2r~v@ga)M%jBF+)D-$sc5)sOjJTSh|o0)opTxX>%XqF~e<(up9G-V1wB}djr zY2)+vji^F%GQGCvewC0A)s`x{#`}6#H?G3-yd$FqjK3;7a2Ri~1DV156<^}7lI$Y! zBjG+cR7?eu`zc)w^4XZ_2CFoIklAf>aSenj5yf4GVB39O@qLp7)HI{9=`oB&xMG3V zm!@j3g{j|Oyj`ClqR&ji{^9BU6t(!-7Bp|vW8y*lE+mMPVt!UrL`neAd#gA#G@!XI+x^Ahz0=Jkh_Q@=Qw zzzctaOtJQ${%Ybs&Xm>MN^mJ7IOBB9)7XLnDL1%78FT#Aw2ZN8F^AkGr$5o6_I(FvRJk1}vuXdGlTw0R7$Oa2H z4Q~wo6|9)}&hc=6Nxbq%Rwz}C4JHs_nuGIZk|X*|Ps_qtC#m$>^i;fk5<=);S!?D) z+}a|sCZwfgc2?SHNTG|D)K$lS-~$>l06ZrR0=TZu=EZ8)9O6?)KDl0FE>`HnhU3K= zxRoeBe+`?4Fs)-6pgTLPndCPg6y+q6c-`ME==GxN8Lt8M_!I)rc1@%e}-0 zyI#3?_7MJ5;Imeix)`s#ZqxDk8>bwVfHJK#6mhVF40bN4!jl?F9$x9!P4M>AP%{=C zM|fEDr;+!0tOlY&>zmjS497JGW`AT>a_Q^^XbMnetT$9?13D@`${8@rV?1p}pEKTo zmX~+>1~cuyqf8RwJ5B}t$u4H{@tj?_@O{a~x5?J-viKcApDLf&@ZSzg=WVf}!;z+y z9Ou(q9TX?b7YJ^agaaqfJG4v6cd_Y;L%DCYK9L}$A{ki7%;g!(j8H5 zsS0E7;~oRrZaoU}h~A+imwcIyg)p944mP$r`%h$TnRNP3RaTulSvTk*PMNixkKSXf zZbXjZy7J{0FP5Utk42ewO_Xy;Tjt9RD82iB)E`**;*?r%GuDyqh+GUOG@3NC+U8i( zvJje(kHC8k7H)|OO#VSK#QY(&;^o-T+#}~VC1a?XFL#^9?cD?P!a;!dy@)64A_$O3 zK#&CK__}ZYwdcU-#M|E57XH2E7|kV-UAYu&^aHjEUW zw&jwOQ)-VN<2cFOLeO0{KO$|sk$IT*Hj1(5#Xj?O>SZ}_CG}W-V~i64tQX5j^UAXU zDvE<`NP`}UwcbVI!zV;0PmKjS`#rH%^xCIg=)rR`f#Fqzu0WyKA1i=&pXY$bL)UuL zBV;qg*he#Twkqqxx{u+F?}>xT#2MnQ@`5hY1IZZUd z>J2{d=GY9G+{i?QLQ69|eZp;Hi zQJ;-}7%_BNc`xUb6IC5>=B;Xhyc-E@_23!!2<>cfR)4&t`kK?t`TF*w!CW{7I36q% z$YYYS=MA8?BOyU{IHSYXjhe+sks4yo~up)(FkKuy#odK-m{FGg%l z_53CZQUoK@vf68zA5nyB5Y@UF8}PWRjxkkh$h*xW__N9S<8g_2Ib*oBrqtDj@`Bkw zu}4!Whb_CQJc~_YG~Z|ETCA}=9&1LYE!q`p7T|dpfYK`bLYLbndbg4d96@On@`Pso zV{tWUZRU>t_8n3B^yESkRKMaV$8T&;k>sU4lUg!PMa+Qu&NK-;WY49_9?9bP2Q7vm z*(?ML`gz*GE&!FS$;TDG+AaqH6#T=janE%f>)83Yq5WB=%qq%z;n z4b4?Gxw*Fy7bB7-sPHU~(M$O7v}@&Oxcr9HaZN8HNi=DMNzA`H%XL*{zF@5HWzWE~ zaxc`-u$xSnmM{@Q$C{OymH+7`6a!1VmGk#o zcCGr>{)QgX6?DxNhZ`bh<`EemdFD`dqlk7UbgN9N$KmV6`#4~mP|O_7DW$SjW!mib zDk%`#BeQAZOFyHvB^TEB%R&W3T)Z$&x{)8I)I~Iz#|&egTfHAOTY|vSD(6sLyagp` z74u-5-dMZv$$3Kf>}N!4F%%8MT2raO-wHMt7EyKQ1H*YoWCG>B;(y}N>$eO!+1I?3 zwX-G-<%{MT>pk$(A@7;4QEDdI&vQKE*a>h%S*QeG+$eyl!9Tyqae>2wuR@TcK#Vd` zBhi82mypL=<$EpH9A`^=(No}$JD8@sg6@6XI}1D^8f3948EMIi(>F+@`11Zlas*5& zSU*y5=SjxF^*FI*w&m9lGwJhsIv`K%FsUEUn4CzV-w~4Awe<xgzJUaFfJQ&Aj6vy#b{P`GTzEW?p7<7vci^ZqZEy**09uHzpbl5JC2 z#PJjq5>8~2NAXFbx}IX4m=uB4+>q?76;#ZpYK&MmI{i;1DMi@AdZ|9P034{u`_psZ zng}c>EQ^~wUxn(_8%6%PFt0?U$_C?u&SSJO&5LEcz`b~0#gY?yDe`~!-^(;M*g1(E zA$?T+JjjmeedV55j$l0dI$LvRDflQ1NMH#qVVo30AX(nxR?s1@bC4kR%uuozT%l;+ zuW!V#n2q^^P}V8;qVxa(z6>``yjn(50`KtD(NwrY(fHj~+(n5lJiu^W0qlY3tZp;+`jK-%$MOi_4@R7mg2X5N!xAgdHP~pMr8&0eVVHp zH^iTIz77HHk>WgAn1(JZUYG>T5Fl2% z?(tl+ER|JaIMw-$!@Mqg&tH^0K_`zC0-Zbiz=fc^7anDzLIW-ZZuo7xn)coxmPL5ojPVUF3HK1>`>09An~oPCI}o2m~_Gsj4D|;D=55D;EYNF?UUEBBjGwq@k$}2_|cPg?_X~C z(1CzlB>s}=IRfhGg#Gs6ROwvUzOi=uRW*|H5rjUIdI4(&bn~9vteC2bcz4ei7cjO8 zZG{798*-LUx*pr3l`GJW)2;1Amo5X8r!KiTOPB(MIY?)<gDz;KdmIGkruoWbO|ZnZ{{Zd|k1w4u z&_w-R+wLKqHHP0%*4KK!zsgFZny+4&kWE z-$X?`4q{?W3|p9|St){8)<=W;hcYG53alkRe#60W5)3QLAx7{AUj92_h0Hob+CoR~ zWzi*;Th(;iI73`sn2q04_42y6?1LU+PEDlp*xDKL5ga0ON-ZdC z?gSgvM$8ewx*oCA1fl2+hc@c<2_MT6RbX;9#3XcZ^9$dJcVcos-qg%ny(Pgsd*EAD zfa+<4FOf84MySP(huMw}Qq@ah49n`y)_pAkJ|in6u}zkEwQOLR#50l`4WtXg(i;lI z!|!4}^J=zM))ZIkvH2mw3CXptFx(m)f`hT&b;9W=v?hW_CP26{@A4c+yNT#zC#(T- zHnB@!61ji;4Z6p}Xg6NK1tQnpRMH+F<;NT%n|I7(DyKQ&2uGdg2Y7w)dJ%44`~iW| zFft$fB0lE1TTcGpV$fKuYJ_)q^C~9T3yf286sE*a0mj4lMi6RH0{JFZZl3tj9p@3B zc0w?w4SZg062<3B9@DPoY%|Nn{!1!z2ikc~+O=>1BFReYPDlS#m9N+FhCnfbJg91& zAXU&4I85s38q9nL%bD(h`39{2(-0SS&1v(|u~;U{ZGVQ}4{#-d&*ydbvyTYo99JBx zKfQYSf}g$`bRT0%`^waCcN2h)b_LMSzSl@j>rWz@Qv4R$2@%@Z?V@qgj^$?nl{mKJn)J8)81E3 zPTh=?@s)v*kO?DhKBmUp8Qggq6a!QIoUng002DYWHKp9phzt@ai~_)I8@a3Lr&ed} z%WfVel0BiKW^-vj&TO%ff=-~|RV?q`&!h!Nm#u9di4iIRhXKSD-}<(bu#TrQuc;?) zz`b}I!U%?u8qeJbsJLq|rS8oz#^WNXRGs@qxz`!zlpoYu-)Ba(ZL~YYhrT5Yk9~eQk_PDB;s0{!(46~U_6=(j} zZK>Iv_o1P^UysuL9=E(BwK_P~YGDGEU^u|)Yd&v*8yXqLUSwP&p>dJyTppE8dN3Nc zTn@Sd~Lwu6rW6x!N*KE!5mH1W=G~efLoIFn^mQgy!SM^NRW*$cA z$fUzOg$$Rz*<0GS-8XZ3GY%L*Mttmiuv31-JX&8h?ir7z^?*WG23mYQ9dArL^)@$v zT1IMKYUH1qS}dYl8lRa^?NMhV{8pSuOv@!G>D8oK3iL;a`P1yuAA90?E^SC}jNF$|-Jyj1tj3`B6^*UO=Z>=?r z&8zzN*g*>-cWfRB>_5}*_cxwqc^`0*(D5>>A1l~TZ-(XRwG^JCyH$XR%^JMAw>$$1 z2=X0?Nmh&$4?q197GkapKYzfS_gH zw7j@2yVK1&NylRINARwPt={hxZjsVT9}pfckzy$_pnrIpX?y9+Z^q*@ZYiwZ9+%7w zf_C=Z-{RnVSn&(@h=L=^SXwL%aHQO1d8;Xo1?K{FA zOU5NePlTlo0?1OtaC*}ra?YhefcUTOmqx+M6~1vBmJ@QX2dclV5gXk!lSan3`a57^ z&VKUS_YHN>YWCZ)1$1tr?Z|$#J|RnG|M0B{>s%c}6oYN`E5V1q#^7?TMFk;0X)gux zdaeGka#~TQ-aTQ!#QZHxhklt?(BVxM_-gg!_VE|cvG?=c-4?S$Xz87MX*H@o@^1o( z73m=c5S{qv-+mU9$ZCPgdppJD8kMapPT&QvxsETs0m5s$v}CR;mm=z0;G@E&fJw%n z)1*3o$;0020)=BXYsrcFo$Q~Ns^nCeUyfEA7PrP$qK>~to-O#>m%Qo!Qdmw)cp}kh z^7T0F2d^+t(7I3(&raFr5VUuB0>Ielpf!C6Sa7$QEHCY{^DrBNEyKmaxK!7!p}57N zvtqHioyu94z1_4HQDF&FL^uT~OlI`wwJ2`Rxz8rHbuSzXrA7STMLqwOsBFKSA+qw| S=YM}uC^>0msTv8>u>S`l_)6vg diff --git a/website/src/assets/images/protractor.png b/website/src/assets/images/protractor.png deleted file mode 100644 index 98e0162eb56aa1e9dbf3219c0b26848d4a3af746..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10186 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 diff --git a/website/src/assets/images/yeoman.png b/website/src/assets/images/yeoman.png deleted file mode 100644 index 92497addf96c5c009ac24007f55eb989f3bf74c6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13501 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 diff --git a/website/src/favicon.ico b/website/src/favicon.ico deleted file mode 100644 index 6d33018881c66e1409beb4a15a8fbc62bf25d16f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1150 zcmeH_u?@m75JkTQ5=c-)LZT%dl-U4AU<}HP;Pgltff0~c05)I*MzHA8nSV(xvP@_Y z>2UON=YRJvkrTl1N)qs%;xGo505*gYl}2Z%*J1_;L#_4MWuznzVp~p(`%lkgE%bQq z74_@hvGz7A=+{1tv0rtGPT%KCzyJH26By|H?wq@8kv#l%b>4IS(wCETeyN1KB^9l$ zb+ZD0?77qDBSe#V-7NJ>q;82Lu`k|Bj}5(H&RQmQawY1OT(m6g{YCEi7gt;0HUX^Z W<7ljzq9}m!>3y|g?}#2o@xA~=?c%Wj diff --git a/website/src/index.html b/website/src/index.html deleted file mode 100644 index 16087cfe..00000000 --- a/website/src/index.html +++ /dev/null @@ -1,54 +0,0 @@ - - - - - Serverless Blog - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - diff --git a/website/src/lib/aws-api-client/README.md b/website/src/lib/aws-api-client/README.md deleted file mode 100755 index 2a59ba66..00000000 --- a/website/src/lib/aws-api-client/README.md +++ /dev/null @@ -1,78 +0,0 @@ -# Prerequisites -For the JavaScript SDK to work your APIs need to support CORS. The Amazon API Gateway developer guide explains how to [setup CORS for an endpoint](). -The generated SDK depends on third-party libraries. Include all of the scripts in your webpage - - - - - - - - - - - - - -# Use the SDK in your project - -To initialize the most basic form of the SDK: - -``` -var apigClient = apigClientFactory.newClient(); -``` - -Calls to an API take the form outlined below. Each API call returns a promise, that invokes either a success and failure callback - -``` -var params = { - //This is where any header, path, or querystring request params go. The key is the parameter named as defined in the API - param0: '', - param1: '' -}; -var body = { - //This is where you define the body of the request -}; -var additionalParams = { - //If there are any unmodeled query parameters or headers that need to be sent with the request you can add them here - headers: { - param0: '', - param1: '' - }, - queryParams: { - param0: '', - param1: '' - } -}; - -apigClient.methodName(params, body, additionalParams) - .then(function(result){ - //This is where you would put a success callback - }).catch( function(result){ - //This is where you would put an error callback - }); -``` - -#Using AWS IAM for authorization -To initialize the SDK with AWS Credentials use the code below. Note, if you use credentials all requests to the API will be signed. This means you will have to set the appropiate CORS accept-* headers for each request. - -``` -var apigClient = apigClientFactory.newClient({ - accessKey: 'ACCESS_KEY', - secretKey: 'SECRET_KEY', - sessionToken: 'SESSION_TOKEN', //OPTIONAL: If you are using temporary credentials you must include the session token - region: 'eu-west-1' // OPTIONAL: The region where the API is deployed, by default this parameter is set to us-east-1 -}); -``` - -#Using API Keys -To use an API Key with the client SDK you can pass the key as a parameter to the Factory object. Note, if you use an apiKey it will be attached as the header 'x-api-key' to all requests to the API will be signed. This means you will have to set the appropiate CORS accept-* headers for each request. - -``` -var apigClient = apigClientFactory.newClient({ - apiKey: 'API_KEY' -}); -``` - - - diff --git a/website/src/lib/aws-api-client/apigClient.js b/website/src/lib/aws-api-client/apigClient.js deleted file mode 100755 index d4644dd2..00000000 --- a/website/src/lib/aws-api-client/apigClient.js +++ /dev/null @@ -1,375 +0,0 @@ -/* - * Copyright 2010-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -var apigClientFactory = {}; -apigClientFactory.newClient = function (config) { - var apigClient = { }; - if(config === undefined) { - config = { - accessKey: '', - secretKey: '', - sessionToken: '', - region: '', - apiKey: undefined, - defaultContentType: 'application/json', - defaultAcceptType: 'application/json' - }; - } - if(config.accessKey === undefined) { - config.accessKey = ''; - } - if(config.secretKey === undefined) { - config.secretKey = ''; - } - if(config.apiKey === undefined) { - config.apiKey = ''; - } - if(config.sessionToken === undefined) { - config.sessionToken = ''; - } - if(config.region === undefined) { - config.region = 'us-east-1'; - } - //If defaultContentType is not defined then default to application/json - if(config.defaultContentType === undefined) { - config.defaultContentType = 'application/json'; - } - //If defaultAcceptType is not defined then default to application/json - if(config.defaultAcceptType === undefined) { - config.defaultAcceptType = 'application/json'; - } - - - // extract endpoint and path from url - var invokeUrl = ''; - var endpoint = /(^https?:\/\/[^\/]+)/g.exec(invokeUrl)[1]; - var pathComponent = invokeUrl.substring(endpoint.length); - - var sigV4ClientConfig = { - accessKey: config.accessKey, - secretKey: config.secretKey, - sessionToken: config.sessionToken, - serviceName: 'execute-api', - region: config.region, - endpoint: endpoint, - defaultContentType: config.defaultContentType, - defaultAcceptType: config.defaultAcceptType - }; - - var authType = 'NONE'; - if (sigV4ClientConfig.accessKey !== undefined && sigV4ClientConfig.accessKey !== '' && sigV4ClientConfig.secretKey !== undefined && sigV4ClientConfig.secretKey !== '') { - authType = 'AWS_IAM'; - } - - var simpleHttpClientConfig = { - endpoint: endpoint, - defaultContentType: config.defaultContentType, - defaultAcceptType: config.defaultAcceptType - }; - - var apiGatewayClient = apiGateway.core.apiGatewayClientFactory.newClient(simpleHttpClientConfig, sigV4ClientConfig); - - - - apigClient.forumsGet = function (params, body, additionalParams) { - if(additionalParams === undefined) { additionalParams = {}; } - - apiGateway.core.utils.assertParametersDefined(params, [], ['body']); - - var forumsGetRequest = { - verb: 'get'.toUpperCase(), - path: pathComponent + uritemplate('/forums').expand(apiGateway.core.utils.parseParametersToObject(params, [])), - headers: apiGateway.core.utils.parseParametersToObject(params, []), - queryParams: apiGateway.core.utils.parseParametersToObject(params, []), - body: body - }; - - - return apiGatewayClient.makeRequest(forumsGetRequest, authType, additionalParams, config.apiKey); - }; - - - apigClient.forumsOptions = function (params, body, additionalParams) { - if(additionalParams === undefined) { additionalParams = {}; } - - apiGateway.core.utils.assertParametersDefined(params, ['id'], ['body']); - - var forumsOptionsRequest = { - verb: 'options'.toUpperCase(), - path: pathComponent + uritemplate('/forums').expand(apiGateway.core.utils.parseParametersToObject(params, ['id'])), - headers: apiGateway.core.utils.parseParametersToObject(params, []), - queryParams: apiGateway.core.utils.parseParametersToObject(params, []), - body: body - }; - - - return apiGatewayClient.makeRequest(forumsOptionsRequest, authType, additionalParams, config.apiKey); - }; - - - apigClient.forumsIdPostsGet = function (params, body, additionalParams) { - if(additionalParams === undefined) { additionalParams = {}; } - - apiGateway.core.utils.assertParametersDefined(params, ['id'], ['body']); - - var forumsIdPostsGetRequest = { - verb: 'get'.toUpperCase(), - path: pathComponent + uritemplate('/forums/{id}/posts').expand(apiGateway.core.utils.parseParametersToObject(params, ['id'])), - headers: apiGateway.core.utils.parseParametersToObject(params, []), - queryParams: apiGateway.core.utils.parseParametersToObject(params, []), - body: body - }; - - - return apiGatewayClient.makeRequest(forumsIdPostsGetRequest, authType, additionalParams, config.apiKey); - }; - - - apigClient.forumsIdPostsPost = function (params, body, additionalParams) { - if(additionalParams === undefined) { additionalParams = {}; } - - apiGateway.core.utils.assertParametersDefined(params, ['id'], ['body']); - - var forumsIdPostsPostRequest = { - verb: 'post'.toUpperCase(), - path: pathComponent + uritemplate('/forums/{id}/posts').expand(apiGateway.core.utils.parseParametersToObject(params, ['id'])), - headers: apiGateway.core.utils.parseParametersToObject(params, []), - queryParams: apiGateway.core.utils.parseParametersToObject(params, []), - body: body - }; - - - return apiGatewayClient.makeRequest(forumsIdPostsPostRequest, authType, additionalParams, config.apiKey); - }; - - - apigClient.forumsIdPostsOptions = function (params, body, additionalParams) { - if(additionalParams === undefined) { additionalParams = {}; } - - apiGateway.core.utils.assertParametersDefined(params, ['id'], ['body']); - - var forumsIdPostsOptionsRequest = { - verb: 'options'.toUpperCase(), - path: pathComponent + uritemplate('/forums/{id}/posts').expand(apiGateway.core.utils.parseParametersToObject(params, ['id'])), - headers: apiGateway.core.utils.parseParametersToObject(params, []), - queryParams: apiGateway.core.utils.parseParametersToObject(params, []), - body: body - }; - - - return apiGatewayClient.makeRequest(forumsIdPostsOptionsRequest, authType, additionalParams, config.apiKey); - }; - - - apigClient.loginPost = function (params, body, additionalParams) { - if(additionalParams === undefined) { additionalParams = {}; } - - apiGateway.core.utils.assertParametersDefined(params, [], ['body']); - - var loginPostRequest = { - verb: 'post'.toUpperCase(), - path: pathComponent + uritemplate('/login').expand(apiGateway.core.utils.parseParametersToObject(params, [])), - headers: apiGateway.core.utils.parseParametersToObject(params, []), - queryParams: apiGateway.core.utils.parseParametersToObject(params, []), - body: body - }; - - - return apiGatewayClient.makeRequest(loginPostRequest, authType, additionalParams, config.apiKey); - }; - - - apigClient.loginOptions = function (params, body, additionalParams) { - if(additionalParams === undefined) { additionalParams = {}; } - - apiGateway.core.utils.assertParametersDefined(params, [], ['body']); - - var loginOptionsRequest = { - verb: 'options'.toUpperCase(), - path: pathComponent + uritemplate('/login').expand(apiGateway.core.utils.parseParametersToObject(params, [])), - headers: apiGateway.core.utils.parseParametersToObject(params, []), - queryParams: apiGateway.core.utils.parseParametersToObject(params, []), - body: body - }; - - - return apiGatewayClient.makeRequest(loginOptionsRequest, authType, additionalParams, config.apiKey); - }; - - - apigClient.postsIdGet = function (params, body, additionalParams) { - if(additionalParams === undefined) { additionalParams = {}; } - - apiGateway.core.utils.assertParametersDefined(params, ['id'], ['body']); - - var postsIdGetRequest = { - verb: 'get'.toUpperCase(), - path: pathComponent + uritemplate('/posts/{id}').expand(apiGateway.core.utils.parseParametersToObject(params, ['id'])), - headers: apiGateway.core.utils.parseParametersToObject(params, []), - queryParams: apiGateway.core.utils.parseParametersToObject(params, []), - body: body - }; - - - return apiGatewayClient.makeRequest(postsIdGetRequest, authType, additionalParams, config.apiKey); - }; - - - apigClient.postsIdOptions = function (params, body, additionalParams) { - if(additionalParams === undefined) { additionalParams = {}; } - - apiGateway.core.utils.assertParametersDefined(params, ['id'], ['body']); - - var postsIdOptionsRequest = { - verb: 'options'.toUpperCase(), - path: pathComponent + uritemplate('/posts/{id}').expand(apiGateway.core.utils.parseParametersToObject(params, ['id'])), - headers: apiGateway.core.utils.parseParametersToObject(params, []), - queryParams: apiGateway.core.utils.parseParametersToObject(params, []), - body: body - }; - - - return apiGatewayClient.makeRequest(postsIdOptionsRequest, authType, additionalParams, config.apiKey); - }; - - - apigClient.postsIdCommentsGet = function (params, body, additionalParams) { - if(additionalParams === undefined) { additionalParams = {}; } - - apiGateway.core.utils.assertParametersDefined(params, ['id'], ['body']); - - var postsIdCommentsGetRequest = { - verb: 'get'.toUpperCase(), - path: pathComponent + uritemplate('/posts/{id}/comments').expand(apiGateway.core.utils.parseParametersToObject(params, ['id'])), - headers: apiGateway.core.utils.parseParametersToObject(params, []), - queryParams: apiGateway.core.utils.parseParametersToObject(params, []), - body: body - }; - - - return apiGatewayClient.makeRequest(postsIdCommentsGetRequest, authType, additionalParams, config.apiKey); - }; - - - apigClient.postsIdCommentsPost = function (params, body, additionalParams) { - if(additionalParams === undefined) { additionalParams = {}; } - - apiGateway.core.utils.assertParametersDefined(params, ['id'], ['body']); - - var postsIdCommentsPostRequest = { - verb: 'post'.toUpperCase(), - path: pathComponent + uritemplate('/posts/{id}/comments').expand(apiGateway.core.utils.parseParametersToObject(params, ['id'])), - headers: apiGateway.core.utils.parseParametersToObject(params, []), - queryParams: apiGateway.core.utils.parseParametersToObject(params, []), - body: body - }; - - - return apiGatewayClient.makeRequest(postsIdCommentsPostRequest, authType, additionalParams, config.apiKey); - }; - - - apigClient.postsIdCommentsOptions = function (params, body, additionalParams) { - if(additionalParams === undefined) { additionalParams = {}; } - - apiGateway.core.utils.assertParametersDefined(params, ['id'], ['body']); - - var postsIdCommentsOptionsRequest = { - verb: 'options'.toUpperCase(), - path: pathComponent + uritemplate('/posts/{id}/comments').expand(apiGateway.core.utils.parseParametersToObject(params, ['id'])), - headers: apiGateway.core.utils.parseParametersToObject(params, []), - queryParams: apiGateway.core.utils.parseParametersToObject(params, []), - body: body - }; - - - return apiGatewayClient.makeRequest(postsIdCommentsOptionsRequest, authType, additionalParams, config.apiKey); - }; - - - apigClient.postsIdCommentsCreatedAtGet = function (params, body, additionalParams) { - if(additionalParams === undefined) { additionalParams = {}; } - - apiGateway.core.utils.assertParametersDefined(params, ['created-at', 'id'], ['body']); - - var postsIdCommentsCreatedAtGetRequest = { - verb: 'get'.toUpperCase(), - path: pathComponent + uritemplate('/posts/{id}/comments/{created-at}').expand(apiGateway.core.utils.parseParametersToObject(params, ['created-at', 'id'])), - headers: apiGateway.core.utils.parseParametersToObject(params, []), - queryParams: apiGateway.core.utils.parseParametersToObject(params, []), - body: body - }; - - - return apiGatewayClient.makeRequest(postsIdCommentsCreatedAtGetRequest, authType, additionalParams, config.apiKey); - }; - - - apigClient.postsIdCommentsCreatedAtOptions = function (params, body, additionalParams) { - if(additionalParams === undefined) { additionalParams = {}; } - - apiGateway.core.utils.assertParametersDefined(params, ['created-at', 'id'], ['body']); - - var postsIdCommentsCreatedAtOptionsRequest = { - verb: 'options'.toUpperCase(), - path: pathComponent + uritemplate('/posts/{id}/comments/{created-at}').expand(apiGateway.core.utils.parseParametersToObject(params, ['created-at', 'id'])), - headers: apiGateway.core.utils.parseParametersToObject(params, []), - queryParams: apiGateway.core.utils.parseParametersToObject(params, []), - body: body - }; - - - return apiGatewayClient.makeRequest(postsIdCommentsCreatedAtOptionsRequest, authType, additionalParams, config.apiKey); - }; - - - apigClient.usersPost = function (params, body, additionalParams) { - if(additionalParams === undefined) { additionalParams = {}; } - - apiGateway.core.utils.assertParametersDefined(params, [], ['body']); - - var usersPostRequest = { - verb: 'post'.toUpperCase(), - path: pathComponent + uritemplate('/users').expand(apiGateway.core.utils.parseParametersToObject(params, [])), - headers: apiGateway.core.utils.parseParametersToObject(params, []), - queryParams: apiGateway.core.utils.parseParametersToObject(params, []), - body: body - }; - - - return apiGatewayClient.makeRequest(usersPostRequest, authType, additionalParams, config.apiKey); - }; - - - apigClient.usersOptions = function (params, body, additionalParams) { - if(additionalParams === undefined) { additionalParams = {}; } - - apiGateway.core.utils.assertParametersDefined(params, [], ['body']); - - var usersOptionsRequest = { - verb: 'options'.toUpperCase(), - path: pathComponent + uritemplate('/users').expand(apiGateway.core.utils.parseParametersToObject(params, [])), - headers: apiGateway.core.utils.parseParametersToObject(params, []), - queryParams: apiGateway.core.utils.parseParametersToObject(params, []), - body: body - }; - - - return apiGatewayClient.makeRequest(usersOptionsRequest, authType, additionalParams, config.apiKey); - }; - - - return apigClient; -}; diff --git a/website/src/lib/aws-api-client/lib/CryptoJS/components/enc-base64.js b/website/src/lib/aws-api-client/lib/CryptoJS/components/enc-base64.js deleted file mode 100755 index 739f4a84..00000000 --- a/website/src/lib/aws-api-client/lib/CryptoJS/components/enc-base64.js +++ /dev/null @@ -1,109 +0,0 @@ -/* -CryptoJS v3.1.2 -code.google.com/p/crypto-js -(c) 2009-2013 by Jeff Mott. All rights reserved. -code.google.com/p/crypto-js/wiki/License -*/ -(function () { - // Shortcuts - var C = CryptoJS; - var C_lib = C.lib; - var WordArray = C_lib.WordArray; - var C_enc = C.enc; - - /** - * Base64 encoding strategy. - */ - var Base64 = C_enc.Base64 = { - /** - * Converts a word array to a Base64 string. - * - * @param {WordArray} wordArray The word array. - * - * @return {string} The Base64 string. - * - * @static - * - * @example - * - * var base64String = CryptoJS.enc.Base64.stringify(wordArray); - */ - stringify: function (wordArray) { - // Shortcuts - var words = wordArray.words; - var sigBytes = wordArray.sigBytes; - var map = this._map; - - // Clamp excess bits - wordArray.clamp(); - - // Convert - var base64Chars = []; - for (var i = 0; i < sigBytes; i += 3) { - var byte1 = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff; - var byte2 = (words[(i + 1) >>> 2] >>> (24 - ((i + 1) % 4) * 8)) & 0xff; - var byte3 = (words[(i + 2) >>> 2] >>> (24 - ((i + 2) % 4) * 8)) & 0xff; - - var triplet = (byte1 << 16) | (byte2 << 8) | byte3; - - for (var j = 0; (j < 4) && (i + j * 0.75 < sigBytes); j++) { - base64Chars.push(map.charAt((triplet >>> (6 * (3 - j))) & 0x3f)); - } - } - - // Add padding - var paddingChar = map.charAt(64); - if (paddingChar) { - while (base64Chars.length % 4) { - base64Chars.push(paddingChar); - } - } - - return base64Chars.join(''); - }, - - /** - * Converts a Base64 string to a word array. - * - * @param {string} base64Str The Base64 string. - * - * @return {WordArray} The word array. - * - * @static - * - * @example - * - * var wordArray = CryptoJS.enc.Base64.parse(base64String); - */ - parse: function (base64Str) { - // Shortcuts - var base64StrLength = base64Str.length; - var map = this._map; - - // Ignore padding - var paddingChar = map.charAt(64); - if (paddingChar) { - var paddingIndex = base64Str.indexOf(paddingChar); - if (paddingIndex != -1) { - base64StrLength = paddingIndex; - } - } - - // Convert - var words = []; - var nBytes = 0; - for (var i = 0; i < base64StrLength; i++) { - if (i % 4) { - var bits1 = map.indexOf(base64Str.charAt(i - 1)) << ((i % 4) * 2); - var bits2 = map.indexOf(base64Str.charAt(i)) >>> (6 - (i % 4) * 2); - words[nBytes >>> 2] |= (bits1 | bits2) << (24 - (nBytes % 4) * 8); - nBytes++; - } - } - - return WordArray.create(words, nBytes); - }, - - _map: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=' - }; -}()); diff --git a/website/src/lib/aws-api-client/lib/CryptoJS/components/hmac.js b/website/src/lib/aws-api-client/lib/CryptoJS/components/hmac.js deleted file mode 100755 index b75cd0bf..00000000 --- a/website/src/lib/aws-api-client/lib/CryptoJS/components/hmac.js +++ /dev/null @@ -1,131 +0,0 @@ -/* -CryptoJS v3.1.2 -code.google.com/p/crypto-js -(c) 2009-2013 by Jeff Mott. All rights reserved. -code.google.com/p/crypto-js/wiki/License -*/ -(function () { - // Shortcuts - var C = CryptoJS; - var C_lib = C.lib; - var Base = C_lib.Base; - var C_enc = C.enc; - var Utf8 = C_enc.Utf8; - var C_algo = C.algo; - - /** - * HMAC algorithm. - */ - var HMAC = C_algo.HMAC = Base.extend({ - /** - * Initializes a newly created HMAC. - * - * @param {Hasher} hasher The hash algorithm to use. - * @param {WordArray|string} key The secret key. - * - * @example - * - * var hmacHasher = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256, key); - */ - init: function (hasher, key) { - // Init hasher - hasher = this._hasher = new hasher.init(); - - // Convert string to WordArray, else assume WordArray already - if (typeof key == 'string') { - key = Utf8.parse(key); - } - - // Shortcuts - var hasherBlockSize = hasher.blockSize; - var hasherBlockSizeBytes = hasherBlockSize * 4; - - // Allow arbitrary length keys - if (key.sigBytes > hasherBlockSizeBytes) { - key = hasher.finalize(key); - } - - // Clamp excess bits - key.clamp(); - - // Clone key for inner and outer pads - var oKey = this._oKey = key.clone(); - var iKey = this._iKey = key.clone(); - - // Shortcuts - var oKeyWords = oKey.words; - var iKeyWords = iKey.words; - - // XOR keys with pad constants - for (var i = 0; i < hasherBlockSize; i++) { - oKeyWords[i] ^= 0x5c5c5c5c; - iKeyWords[i] ^= 0x36363636; - } - oKey.sigBytes = iKey.sigBytes = hasherBlockSizeBytes; - - // Set initial values - this.reset(); - }, - - /** - * Resets this HMAC to its initial state. - * - * @example - * - * hmacHasher.reset(); - */ - reset: function () { - // Shortcut - var hasher = this._hasher; - - // Reset - hasher.reset(); - hasher.update(this._iKey); - }, - - /** - * Updates this HMAC with a message. - * - * @param {WordArray|string} messageUpdate The message to append. - * - * @return {HMAC} This HMAC instance. - * - * @example - * - * hmacHasher.update('message'); - * hmacHasher.update(wordArray); - */ - update: function (messageUpdate) { - this._hasher.update(messageUpdate); - - // Chainable - return this; - }, - - /** - * Finalizes the HMAC computation. - * Note that the finalize operation is effectively a destructive, read-once operation. - * - * @param {WordArray|string} messageUpdate (Optional) A final message update. - * - * @return {WordArray} The HMAC. - * - * @example - * - * var hmac = hmacHasher.finalize(); - * var hmac = hmacHasher.finalize('message'); - * var hmac = hmacHasher.finalize(wordArray); - */ - finalize: function (messageUpdate) { - // Shortcut - var hasher = this._hasher; - - // Compute HMAC - var innerHash = hasher.finalize(messageUpdate); - hasher.reset(); - var hmac = hasher.finalize(this._oKey.clone().concat(innerHash)); - - return hmac; - } - }); -}()); diff --git a/website/src/lib/aws-api-client/lib/CryptoJS/rollups/hmac-sha256.js b/website/src/lib/aws-api-client/lib/CryptoJS/rollups/hmac-sha256.js deleted file mode 100755 index c822cfb1..00000000 --- a/website/src/lib/aws-api-client/lib/CryptoJS/rollups/hmac-sha256.js +++ /dev/null @@ -1,18 +0,0 @@ -/* -CryptoJS v3.1.2 -code.google.com/p/crypto-js -(c) 2009-2013 by Jeff Mott. All rights reserved. -code.google.com/p/crypto-js/wiki/License -*/ -var CryptoJS=CryptoJS||function(h,s){var f={},g=f.lib={},q=function(){},m=g.Base={extend:function(a){q.prototype=this;var c=new q;a&&c.mixIn(a);c.hasOwnProperty("init")||(c.init=function(){c.$super.init.apply(this,arguments)});c.init.prototype=c;c.$super=this;return c},create:function(){var a=this.extend();a.init.apply(a,arguments);return a},init:function(){},mixIn:function(a){for(var c in a)a.hasOwnProperty(c)&&(this[c]=a[c]);a.hasOwnProperty("toString")&&(this.toString=a.toString)},clone:function(){return this.init.prototype.extend(this)}}, -r=g.WordArray=m.extend({init:function(a,c){a=this.words=a||[];this.sigBytes=c!=s?c:4*a.length},toString:function(a){return(a||k).stringify(this)},concat:function(a){var c=this.words,d=a.words,b=this.sigBytes;a=a.sigBytes;this.clamp();if(b%4)for(var e=0;e>>2]|=(d[e>>>2]>>>24-8*(e%4)&255)<<24-8*((b+e)%4);else if(65535>>2]=d[e>>>2];else c.push.apply(c,d);this.sigBytes+=a;return this},clamp:function(){var a=this.words,c=this.sigBytes;a[c>>>2]&=4294967295<< -32-8*(c%4);a.length=h.ceil(c/4)},clone:function(){var a=m.clone.call(this);a.words=this.words.slice(0);return a},random:function(a){for(var c=[],d=0;d>>2]>>>24-8*(b%4)&255;d.push((e>>>4).toString(16));d.push((e&15).toString(16))}return d.join("")},parse:function(a){for(var c=a.length,d=[],b=0;b>>3]|=parseInt(a.substr(b, -2),16)<<24-4*(b%8);return new r.init(d,c/2)}},n=l.Latin1={stringify:function(a){var c=a.words;a=a.sigBytes;for(var d=[],b=0;b>>2]>>>24-8*(b%4)&255));return d.join("")},parse:function(a){for(var c=a.length,d=[],b=0;b>>2]|=(a.charCodeAt(b)&255)<<24-8*(b%4);return new r.init(d,c)}},j=l.Utf8={stringify:function(a){try{return decodeURIComponent(escape(n.stringify(a)))}catch(c){throw Error("Malformed UTF-8 data");}},parse:function(a){return n.parse(unescape(encodeURIComponent(a)))}}, -u=g.BufferedBlockAlgorithm=m.extend({reset:function(){this._data=new r.init;this._nDataBytes=0},_append:function(a){"string"==typeof a&&(a=j.parse(a));this._data.concat(a);this._nDataBytes+=a.sigBytes},_process:function(a){var c=this._data,d=c.words,b=c.sigBytes,e=this.blockSize,f=b/(4*e),f=a?h.ceil(f):h.max((f|0)-this._minBufferSize,0);a=f*e;b=h.min(4*a,b);if(a){for(var g=0;gn;){var j;a:{j=k;for(var u=h.sqrt(j),t=2;t<=u;t++)if(!(j%t)){j=!1;break a}j=!0}j&&(8>n&&(m[n]=l(h.pow(k,0.5))),r[n]=l(h.pow(k,1/3)),n++);k++}var a=[],f=f.SHA256=q.extend({_doReset:function(){this._hash=new g.init(m.slice(0))},_doProcessBlock:function(c,d){for(var b=this._hash.words,e=b[0],f=b[1],g=b[2],j=b[3],h=b[4],m=b[5],n=b[6],q=b[7],p=0;64>p;p++){if(16>p)a[p]= -c[d+p]|0;else{var k=a[p-15],l=a[p-2];a[p]=((k<<25|k>>>7)^(k<<14|k>>>18)^k>>>3)+a[p-7]+((l<<15|l>>>17)^(l<<13|l>>>19)^l>>>10)+a[p-16]}k=q+((h<<26|h>>>6)^(h<<21|h>>>11)^(h<<7|h>>>25))+(h&m^~h&n)+r[p]+a[p];l=((e<<30|e>>>2)^(e<<19|e>>>13)^(e<<10|e>>>22))+(e&f^e&g^f&g);q=n;n=m;m=h;h=j+k|0;j=g;g=f;f=e;e=k+l|0}b[0]=b[0]+e|0;b[1]=b[1]+f|0;b[2]=b[2]+g|0;b[3]=b[3]+j|0;b[4]=b[4]+h|0;b[5]=b[5]+m|0;b[6]=b[6]+n|0;b[7]=b[7]+q|0},_doFinalize:function(){var a=this._data,d=a.words,b=8*this._nDataBytes,e=8*a.sigBytes; -d[e>>>5]|=128<<24-e%32;d[(e+64>>>9<<4)+14]=h.floor(b/4294967296);d[(e+64>>>9<<4)+15]=b;a.sigBytes=4*d.length;this._process();return this._hash},clone:function(){var a=q.clone.call(this);a._hash=this._hash.clone();return a}});s.SHA256=q._createHelper(f);s.HmacSHA256=q._createHmacHelper(f)})(Math); -(function(){var h=CryptoJS,s=h.enc.Utf8;h.algo.HMAC=h.lib.Base.extend({init:function(f,g){f=this._hasher=new f.init;"string"==typeof g&&(g=s.parse(g));var h=f.blockSize,m=4*h;g.sigBytes>m&&(g=f.finalize(g));g.clamp();for(var r=this._oKey=g.clone(),l=this._iKey=g.clone(),k=r.words,n=l.words,j=0;j>>2]|=(d[e>>>2]>>>24-8*(e%4)&255)<<24-8*((b+e)%4);else if(65535>>2]=d[e>>>2];else c.push.apply(c,d);this.sigBytes+=a;return this},clamp:function(){var a=this.words,c=this.sigBytes;a[c>>>2]&=4294967295<< -32-8*(c%4);a.length=h.ceil(c/4)},clone:function(){var a=j.clone.call(this);a.words=this.words.slice(0);return a},random:function(a){for(var c=[],d=0;d>>2]>>>24-8*(b%4)&255;d.push((e>>>4).toString(16));d.push((e&15).toString(16))}return d.join("")},parse:function(a){for(var c=a.length,d=[],b=0;b>>3]|=parseInt(a.substr(b, -2),16)<<24-4*(b%8);return new q.init(d,c/2)}},k=v.Latin1={stringify:function(a){var c=a.words;a=a.sigBytes;for(var d=[],b=0;b>>2]>>>24-8*(b%4)&255));return d.join("")},parse:function(a){for(var c=a.length,d=[],b=0;b>>2]|=(a.charCodeAt(b)&255)<<24-8*(b%4);return new q.init(d,c)}},l=v.Utf8={stringify:function(a){try{return decodeURIComponent(escape(k.stringify(a)))}catch(c){throw Error("Malformed UTF-8 data");}},parse:function(a){return k.parse(unescape(encodeURIComponent(a)))}}, -x=t.BufferedBlockAlgorithm=j.extend({reset:function(){this._data=new q.init;this._nDataBytes=0},_append:function(a){"string"==typeof a&&(a=l.parse(a));this._data.concat(a);this._nDataBytes+=a.sigBytes},_process:function(a){var c=this._data,d=c.words,b=c.sigBytes,e=this.blockSize,f=b/(4*e),f=a?h.ceil(f):h.max((f|0)-this._minBufferSize,0);a=f*e;b=h.min(4*a,b);if(a){for(var m=0;mk;){var l;a:{l=u;for(var x=h.sqrt(l),w=2;w<=x;w++)if(!(l%w)){l=!1;break a}l=!0}l&&(8>k&&(j[k]=v(h.pow(u,0.5))),q[k]=v(h.pow(u,1/3)),k++);u++}var a=[],f=f.SHA256=g.extend({_doReset:function(){this._hash=new t.init(j.slice(0))},_doProcessBlock:function(c,d){for(var b=this._hash.words,e=b[0],f=b[1],m=b[2],h=b[3],p=b[4],j=b[5],k=b[6],l=b[7],n=0;64>n;n++){if(16>n)a[n]= -c[d+n]|0;else{var r=a[n-15],g=a[n-2];a[n]=((r<<25|r>>>7)^(r<<14|r>>>18)^r>>>3)+a[n-7]+((g<<15|g>>>17)^(g<<13|g>>>19)^g>>>10)+a[n-16]}r=l+((p<<26|p>>>6)^(p<<21|p>>>11)^(p<<7|p>>>25))+(p&j^~p&k)+q[n]+a[n];g=((e<<30|e>>>2)^(e<<19|e>>>13)^(e<<10|e>>>22))+(e&f^e&m^f&m);l=k;k=j;j=p;p=h+r|0;h=m;m=f;f=e;e=r+g|0}b[0]=b[0]+e|0;b[1]=b[1]+f|0;b[2]=b[2]+m|0;b[3]=b[3]+h|0;b[4]=b[4]+p|0;b[5]=b[5]+j|0;b[6]=b[6]+k|0;b[7]=b[7]+l|0},_doFinalize:function(){var a=this._data,d=a.words,b=8*this._nDataBytes,e=8*a.sigBytes; -d[e>>>5]|=128<<24-e%32;d[(e+64>>>9<<4)+14]=h.floor(b/4294967296);d[(e+64>>>9<<4)+15]=b;a.sigBytes=4*d.length;this._process();return this._hash},clone:function(){var a=g.clone.call(this);a._hash=this._hash.clone();return a}});s.SHA256=g._createHelper(f);s.HmacSHA256=g._createHmacHelper(f)})(Math); diff --git a/website/src/lib/aws-api-client/lib/apiGatewayCore/apiGatewayClient.js b/website/src/lib/aws-api-client/lib/apiGatewayCore/apiGatewayClient.js deleted file mode 100755 index f138588c..00000000 --- a/website/src/lib/aws-api-client/lib/apiGatewayCore/apiGatewayClient.js +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2010-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -var apiGateway = apiGateway || {}; -apiGateway.core = apiGateway.core || {}; - -apiGateway.core.apiGatewayClientFactory = {}; -apiGateway.core.apiGatewayClientFactory.newClient = function (simpleHttpClientConfig, sigV4ClientConfig) { - var apiGatewayClient = { }; - //Spin up 2 httpClients, one for simple requests, one for SigV4 - var sigV4Client = apiGateway.core.sigV4ClientFactory.newClient(sigV4ClientConfig); - var simpleHttpClient = apiGateway.core.simpleHttpClientFactory.newClient(simpleHttpClientConfig); - - apiGatewayClient.makeRequest = function (request, authType, additionalParams, apiKey) { - //Default the request to use the simple http client - var clientToUse = simpleHttpClient; - - //Attach the apiKey to the headers request if one was provided - if (apiKey !== undefined && apiKey !== '' && apiKey !== null) { - request.headers['x-api-key'] = apiKey; - } - - if (request.body === undefined || request.body === '' || request.body === null || Object.keys(request.body).length === 0) { - request.body = undefined; - } - - // If the user specified any additional headers or query params that may not have been modeled - // merge them into the appropriate request properties - request.headers = apiGateway.core.utils.mergeInto(request.headers, additionalParams.headers); - request.queryParams = apiGateway.core.utils.mergeInto(request.queryParams, additionalParams.queryParams); - - //If an auth type was specified inject the appropriate auth client - if (authType === 'AWS_IAM') { - clientToUse = sigV4Client; - } - - //Call the selected http client to make the request, returning a promise once the request is sent - return clientToUse.makeRequest(request); - }; - return apiGatewayClient; -}; diff --git a/website/src/lib/aws-api-client/lib/apiGatewayCore/sigV4Client.js b/website/src/lib/aws-api-client/lib/apiGatewayCore/sigV4Client.js deleted file mode 100755 index 34624e7a..00000000 --- a/website/src/lib/aws-api-client/lib/apiGatewayCore/sigV4Client.js +++ /dev/null @@ -1,213 +0,0 @@ -/* - * Copyright 2010-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -var apiGateway = apiGateway || {}; -apiGateway.core = apiGateway.core || {}; - -apiGateway.core.sigV4ClientFactory = {}; -apiGateway.core.sigV4ClientFactory.newClient = function (config) { - var AWS_SHA_256 = 'AWS4-HMAC-SHA256'; - var AWS4_REQUEST = 'aws4_request'; - var AWS4 = 'AWS4'; - var X_AMZ_DATE = 'x-amz-date'; - var X_AMZ_SECURITY_TOKEN = 'x-amz-security-token'; - var HOST = 'host'; - var AUTHORIZATION = 'Authorization'; - - function hash(value) { - return CryptoJS.SHA256(value); - } - - function hexEncode(value) { - return value.toString(CryptoJS.enc.Hex); - } - - function hmac(secret, value) { - return CryptoJS.HmacSHA256(value, secret, {asBytes: true}); - } - - function buildCanonicalRequest(method, path, queryParams, headers, payload) { - return method + '\n' + - buildCanonicalUri(path) + '\n' + - buildCanonicalQueryString(queryParams) + '\n' + - buildCanonicalHeaders(headers) + '\n' + - buildCanonicalSignedHeaders(headers) + '\n' + - hexEncode(hash(payload)); - } - - function hashCanonicalRequest(request) { - return hexEncode(hash(request)); - } - - function buildCanonicalUri(uri) { - return encodeURI(uri); - } - - function buildCanonicalQueryString(queryParams) { - if (Object.keys(queryParams).length < 1) { - return ''; - } - - var sortedQueryParams = []; - for (var property in queryParams) { - if (queryParams.hasOwnProperty(property)) { - sortedQueryParams.push(property); - } - } - sortedQueryParams.sort(); - - var canonicalQueryString = ''; - for (var i = 0; i < sortedQueryParams.length; i++) { - canonicalQueryString += sortedQueryParams[i] + '=' + encodeURIComponent(queryParams[sortedQueryParams[i]]) + '&'; - } - return canonicalQueryString.substr(0, canonicalQueryString.length - 1); - } - - function buildCanonicalHeaders(headers) { - var canonicalHeaders = ''; - var sortedKeys = []; - for (var property in headers) { - if (headers.hasOwnProperty(property)) { - sortedKeys.push(property); - } - } - sortedKeys.sort(); - - for (var i = 0; i < sortedKeys.length; i++) { - canonicalHeaders += sortedKeys[i].toLowerCase() + ':' + headers[sortedKeys[i]] + '\n'; - } - return canonicalHeaders; - } - - function buildCanonicalSignedHeaders(headers) { - var sortedKeys = []; - for (var property in headers) { - if (headers.hasOwnProperty(property)) { - sortedKeys.push(property.toLowerCase()); - } - } - sortedKeys.sort(); - - return sortedKeys.join(';'); - } - - function buildStringToSign(datetime, credentialScope, hashedCanonicalRequest) { - return AWS_SHA_256 + '\n' + - datetime + '\n' + - credentialScope + '\n' + - hashedCanonicalRequest; - } - - function buildCredentialScope(datetime, region, service) { - return datetime.substr(0, 8) + '/' + region + '/' + service + '/' + AWS4_REQUEST - } - - function calculateSigningKey(secretKey, datetime, region, service) { - return hmac(hmac(hmac(hmac(AWS4 + secretKey, datetime.substr(0, 8)), region), service), AWS4_REQUEST); - } - - function calculateSignature(key, stringToSign) { - return hexEncode(hmac(key, stringToSign)); - } - - function buildAuthorizationHeader(accessKey, credentialScope, headers, signature) { - return AWS_SHA_256 + ' Credential=' + accessKey + '/' + credentialScope + ', SignedHeaders=' + buildCanonicalSignedHeaders(headers) + ', Signature=' + signature; - } - - var awsSigV4Client = { }; - if(config.accessKey === undefined || config.secretKey === undefined) { - return awsSigV4Client; - } - awsSigV4Client.accessKey = apiGateway.core.utils.assertDefined(config.accessKey, 'accessKey'); - awsSigV4Client.secretKey = apiGateway.core.utils.assertDefined(config.secretKey, 'secretKey'); - awsSigV4Client.sessionToken = config.sessionToken; - awsSigV4Client.serviceName = apiGateway.core.utils.assertDefined(config.serviceName, 'serviceName'); - awsSigV4Client.region = apiGateway.core.utils.assertDefined(config.region, 'region'); - awsSigV4Client.endpoint = apiGateway.core.utils.assertDefined(config.endpoint, 'endpoint'); - - awsSigV4Client.makeRequest = function (request) { - var verb = apiGateway.core.utils.assertDefined(request.verb, 'verb'); - var path = apiGateway.core.utils.assertDefined(request.path, 'path'); - var queryParams = apiGateway.core.utils.copy(request.queryParams); - if (queryParams === undefined) { - queryParams = {}; - } - var headers = apiGateway.core.utils.copy(request.headers); - if (headers === undefined) { - headers = {}; - } - - //If the user has not specified an override for Content type the use default - if(headers['Content-Type'] === undefined) { - headers['Content-Type'] = config.defaultContentType; - } - - //If the user has not specified an override for Accept type the use default - if(headers['Accept'] === undefined) { - headers['Accept'] = config.defaultAcceptType; - } - - var body = apiGateway.core.utils.copy(request.body); - if (body === undefined || verb === 'GET') { // override request body and set to empty when signing GET requests - body = ''; - } else { - body = JSON.stringify(body); - } - - //If there is no body remove the content-type header so it is not included in SigV4 calculation - if(body === '' || body === undefined || body === null) { - delete headers['Content-Type']; - } - - var datetime = new Date().toISOString().replace(/\.\d{3}Z$/, 'Z').replace(/[:\-]|\.\d{3}/g, ''); - headers[X_AMZ_DATE] = datetime; - var parser = document.createElement('a'); - parser.href = awsSigV4Client.endpoint; - headers[HOST] = parser.hostname; - - var canonicalRequest = buildCanonicalRequest(verb, path, queryParams, headers, body); - var hashedCanonicalRequest = hashCanonicalRequest(canonicalRequest); - var credentialScope = buildCredentialScope(datetime, awsSigV4Client.region, awsSigV4Client.serviceName); - var stringToSign = buildStringToSign(datetime, credentialScope, hashedCanonicalRequest); - var signingKey = calculateSigningKey(awsSigV4Client.secretKey, datetime, awsSigV4Client.region, awsSigV4Client.serviceName); - var signature = calculateSignature(signingKey, stringToSign); - headers[AUTHORIZATION] = buildAuthorizationHeader(awsSigV4Client.accessKey, credentialScope, headers, signature); - if(awsSigV4Client.sessionToken !== undefined && awsSigV4Client.sessionToken !== '') { - headers[X_AMZ_SECURITY_TOKEN] = awsSigV4Client.sessionToken; - } - delete headers[HOST]; - - var url = config.endpoint + path; - var queryString = buildCanonicalQueryString(queryParams); - if (queryString != '') { - url += '?' + queryString; - } - - //Need to re-attach Content-Type if it is not specified at this point - if(headers['Content-Type'] === undefined) { - headers['Content-Type'] = config.defaultContentType; - } - - var signedRequest = { - method: verb, - url: url, - headers: headers, - data: body - }; - return axios(signedRequest); - }; - - return awsSigV4Client; -}; diff --git a/website/src/lib/aws-api-client/lib/apiGatewayCore/simpleHttpClient.js b/website/src/lib/aws-api-client/lib/apiGatewayCore/simpleHttpClient.js deleted file mode 100755 index 3fb1e5ac..00000000 --- a/website/src/lib/aws-api-client/lib/apiGatewayCore/simpleHttpClient.js +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2010-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -var apiGateway = apiGateway || {}; -apiGateway.core = apiGateway.core || {}; - -apiGateway.core.simpleHttpClientFactory = {}; -apiGateway.core.simpleHttpClientFactory.newClient = function (config) { - function buildCanonicalQueryString(queryParams) { - //Build a properly encoded query string from a QueryParam object - if (Object.keys(queryParams).length < 1) { - return ''; - } - - var canonicalQueryString = ''; - for (var property in queryParams) { - if (queryParams.hasOwnProperty(property)) { - canonicalQueryString += encodeURIComponent(property) + '=' + encodeURIComponent(queryParams[property]) + '&'; - } - } - - return canonicalQueryString.substr(0, canonicalQueryString.length - 1); - } - - var simpleHttpClient = { }; - simpleHttpClient.endpoint = apiGateway.core.utils.assertDefined(config.endpoint, 'endpoint'); - - simpleHttpClient.makeRequest = function (request) { - var verb = apiGateway.core.utils.assertDefined(request.verb, 'verb'); - var path = apiGateway.core.utils.assertDefined(request.path, 'path'); - var queryParams = apiGateway.core.utils.copy(request.queryParams); - if (queryParams === undefined) { - queryParams = {}; - } - var headers = apiGateway.core.utils.copy(request.headers); - if (headers === undefined) { - headers = {}; - } - - //If the user has not specified an override for Content type the use default - if(headers['Content-Type'] === undefined) { - headers['Content-Type'] = config.defaultContentType; - } - - //If the user has not specified an override for Accept type the use default - if(headers['Accept'] === undefined) { - headers['Accept'] = config.defaultAcceptType; - } - - var body = apiGateway.core.utils.copy(request.body); - if (body === undefined) { - body = ''; - } - - var url = config.endpoint + path; - var queryString = buildCanonicalQueryString(queryParams); - if (queryString != '') { - url += '?' + queryString; - } - var simpleHttpRequest = { - method: verb, - url: url, - headers: headers, - data: body - }; - return axios(simpleHttpRequest); - }; - return simpleHttpClient; -}; \ No newline at end of file diff --git a/website/src/lib/aws-api-client/lib/apiGatewayCore/utils.js b/website/src/lib/aws-api-client/lib/apiGatewayCore/utils.js deleted file mode 100755 index 9bf73563..00000000 --- a/website/src/lib/aws-api-client/lib/apiGatewayCore/utils.js +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2010-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -var apiGateway = apiGateway || {}; -apiGateway.core = apiGateway.core || {}; - -apiGateway.core.utils = { - assertDefined: function (object, name) { - if (object === undefined) { - throw name + ' must be defined'; - } else { - return object; - } - }, - assertParametersDefined: function (params, keys, ignore) { - if (keys === undefined) { - return; - } - if (keys.length > 0 && params === undefined) { - params = {}; - } - for (var i = 0; i < keys.length; i++) { - if(!apiGateway.core.utils.contains(ignore, keys[i])) { - apiGateway.core.utils.assertDefined(params[keys[i]], keys[i]); - } - } - }, - parseParametersToObject: function (params, keys) { - if (params === undefined) { - return {}; - } - var object = { }; - for (var i = 0; i < keys.length; i++) { - object[keys[i]] = params[keys[i]]; - } - return object; - }, - contains: function(a, obj) { - if(a === undefined) { return false;} - var i = a.length; - while (i--) { - if (a[i] === obj) { - return true; - } - } - return false; - }, - copy: function (obj) { - if (null == obj || "object" != typeof obj) return obj; - var copy = obj.constructor(); - for (var attr in obj) { - if (obj.hasOwnProperty(attr)) copy[attr] = obj[attr]; - } - return copy; - }, - mergeInto: function (baseObj, additionalProps) { - if (null == baseObj || "object" != typeof baseObj) return baseObj; - var merged = baseObj.constructor(); - for (var attr in baseObj) { - if (baseObj.hasOwnProperty(attr)) merged[attr] = baseObj[attr]; - } - if (null == additionalProps || "object" != typeof additionalProps) return baseObj; - for (attr in additionalProps) { - if (additionalProps.hasOwnProperty(attr)) merged[attr] = additionalProps[attr]; - } - return merged; - } -}; diff --git a/website/src/lib/aws-api-client/lib/axios/dist/axios.standalone.js b/website/src/lib/aws-api-client/lib/axios/dist/axios.standalone.js deleted file mode 100755 index 19165251..00000000 --- a/website/src/lib/aws-api-client/lib/axios/dist/axios.standalone.js +++ /dev/null @@ -1,1089 +0,0 @@ -/* -axios v0.7.0 -Copyright (c) 2014 Matt Zabriskie - -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, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -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. -*/ -(function webpackUniversalModuleDefinition(root, factory) { - if(typeof exports === 'object' && typeof module === 'object') - module.exports = factory(); - else if(typeof define === 'function' && define.amd) - define([], factory); - else if(typeof exports === 'object') - exports["axios"] = factory(); - else - root["axios"] = factory(); -})(this, function() { -return /******/ (function(modules) { // webpackBootstrap -/******/ // The module cache -/******/ var installedModules = {}; -/******/ -/******/ // The require function -/******/ function __webpack_require__(moduleId) { -/******/ -/******/ // Check if module is in cache -/******/ if(installedModules[moduleId]) -/******/ return installedModules[moduleId].exports; -/******/ -/******/ // Create a new module (and put it into the cache) -/******/ var module = installedModules[moduleId] = { -/******/ exports: {}, -/******/ id: moduleId, -/******/ loaded: false -/******/ }; -/******/ -/******/ // Execute the module function -/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); -/******/ -/******/ // Flag the module as loaded -/******/ module.loaded = true; -/******/ -/******/ // Return the exports of the module -/******/ return module.exports; -/******/ } -/******/ -/******/ -/******/ // expose the modules object (__webpack_modules__) -/******/ __webpack_require__.m = modules; -/******/ -/******/ // expose the module cache -/******/ __webpack_require__.c = installedModules; -/******/ -/******/ // __webpack_public_path__ -/******/ __webpack_require__.p = ""; -/******/ -/******/ // Load entry module and return exports -/******/ return __webpack_require__(0); -/******/ }) -/************************************************************************/ -/******/ ([ -/* 0 */ -/***/ function(module, exports, __webpack_require__) { - - module.exports = __webpack_require__(1); - -/***/ }, -/* 1 */ -/***/ function(module, exports, __webpack_require__) { - - 'use strict'; - - var defaults = __webpack_require__(2); - var utils = __webpack_require__(3); - var dispatchRequest = __webpack_require__(4); - var InterceptorManager = __webpack_require__(12); - - var axios = module.exports = function (config) { - // Allow for axios('example/url'[, config]) a la fetch API - if (typeof config === 'string') { - config = utils.merge({ - url: arguments[0] - }, arguments[1]); - } - - config = utils.merge({ - method: 'get', - headers: {}, - timeout: defaults.timeout, - transformRequest: defaults.transformRequest, - transformResponse: defaults.transformResponse - }, config); - - // Don't allow overriding defaults.withCredentials - config.withCredentials = config.withCredentials || defaults.withCredentials; - - // Hook up interceptors middleware - var chain = [dispatchRequest, undefined]; - var promise = Promise.resolve(config); - - axios.interceptors.request.forEach(function (interceptor) { - chain.unshift(interceptor.fulfilled, interceptor.rejected); - }); - - axios.interceptors.response.forEach(function (interceptor) { - chain.push(interceptor.fulfilled, interceptor.rejected); - }); - - while (chain.length) { - promise = promise.then(chain.shift(), chain.shift()); - } - - return promise; - }; - - // Expose defaults - axios.defaults = defaults; - - // Expose all/spread - axios.all = function (promises) { - return Promise.all(promises); - }; - axios.spread = __webpack_require__(13); - - // Expose interceptors - axios.interceptors = { - request: new InterceptorManager(), - response: new InterceptorManager() - }; - - // Provide aliases for supported request methods - (function () { - function createShortMethods() { - utils.forEach(arguments, function (method) { - axios[method] = function (url, config) { - return axios(utils.merge(config || {}, { - method: method, - url: url - })); - }; - }); - } - - function createShortMethodsWithData() { - utils.forEach(arguments, function (method) { - axios[method] = function (url, data, config) { - return axios(utils.merge(config || {}, { - method: method, - url: url, - data: data - })); - }; - }); - } - - createShortMethods('delete', 'get', 'head'); - createShortMethodsWithData('post', 'put', 'patch'); - })(); - - -/***/ }, -/* 2 */ -/***/ function(module, exports, __webpack_require__) { - - 'use strict'; - - var utils = __webpack_require__(3); - - var PROTECTION_PREFIX = /^\)\]\}',?\n/; - var DEFAULT_CONTENT_TYPE = { - 'Content-Type': 'application/json' - }; - - module.exports = { - transformRequest: [function (data, headers) { - if(utils.isFormData(data)) { - return data; - } - if (utils.isArrayBuffer(data)) { - return data; - } - if (utils.isArrayBufferView(data)) { - return data.buffer; - } - if (utils.isObject(data) && !utils.isFile(data) && !utils.isBlob(data)) { - // Set application/json if no Content-Type has been specified - if (!utils.isUndefined(headers)) { - utils.forEach(headers, function (val, key) { - if (key.toLowerCase() === 'content-type') { - headers['Content-Type'] = val; - } - }); - - if (utils.isUndefined(headers['Content-Type'])) { - headers['Content-Type'] = 'application/json;charset=utf-8'; - } - } - return JSON.stringify(data); - } - return data; - }], - - transformResponse: [function (data) { - if (typeof data === 'string') { - data = data.replace(PROTECTION_PREFIX, ''); - try { - data = JSON.parse(data); - } catch (e) { /* Ignore */ } - } - return data; - }], - - headers: { - common: { - 'Accept': 'application/json, text/plain, */*' - }, - patch: utils.merge(DEFAULT_CONTENT_TYPE), - post: utils.merge(DEFAULT_CONTENT_TYPE), - put: utils.merge(DEFAULT_CONTENT_TYPE) - }, - - timeout: 0, - - xsrfCookieName: 'XSRF-TOKEN', - xsrfHeaderName: 'X-XSRF-TOKEN' - }; - - -/***/ }, -/* 3 */ -/***/ function(module, exports) { - - 'use strict'; - - /*global toString:true*/ - - // utils is a library of generic helper functions non-specific to axios - - var toString = Object.prototype.toString; - - /** - * Determine if a value is an Array - * - * @param {Object} val The value to test - * @returns {boolean} True if value is an Array, otherwise false - */ - function isArray(val) { - return toString.call(val) === '[object Array]'; - } - - /** - * Determine if a value is an ArrayBuffer - * - * @param {Object} val The value to test - * @returns {boolean} True if value is an ArrayBuffer, otherwise false - */ - function isArrayBuffer(val) { - return toString.call(val) === '[object ArrayBuffer]'; - } - - /** - * Determine if a value is a FormData - * - * @param {Object} val The value to test - * @returns {boolean} True if value is an FormData, otherwise false - */ - function isFormData(val) { - return toString.call(val) === '[object FormData]'; - } - - /** - * Determine if a value is a view on an ArrayBuffer - * - * @param {Object} val The value to test - * @returns {boolean} True if value is a view on an ArrayBuffer, otherwise false - */ - function isArrayBufferView(val) { - if ((typeof ArrayBuffer !== 'undefined') && (ArrayBuffer.isView)) { - return ArrayBuffer.isView(val); - } else { - return (val) && (val.buffer) && (val.buffer instanceof ArrayBuffer); - } - } - - /** - * Determine if a value is a String - * - * @param {Object} val The value to test - * @returns {boolean} True if value is a String, otherwise false - */ - function isString(val) { - return typeof val === 'string'; - } - - /** - * Determine if a value is a Number - * - * @param {Object} val The value to test - * @returns {boolean} True if value is a Number, otherwise false - */ - function isNumber(val) { - return typeof val === 'number'; - } - - /** - * Determine if a value is undefined - * - * @param {Object} val The value to test - * @returns {boolean} True if the value is undefined, otherwise false - */ - function isUndefined(val) { - return typeof val === 'undefined'; - } - - /** - * Determine if a value is an Object - * - * @param {Object} val The value to test - * @returns {boolean} True if value is an Object, otherwise false - */ - function isObject(val) { - return val !== null && typeof val === 'object'; - } - - /** - * Determine if a value is a Date - * - * @param {Object} val The value to test - * @returns {boolean} True if value is a Date, otherwise false - */ - function isDate(val) { - return toString.call(val) === '[object Date]'; - } - - /** - * Determine if a value is a File - * - * @param {Object} val The value to test - * @returns {boolean} True if value is a File, otherwise false - */ - function isFile(val) { - return toString.call(val) === '[object File]'; - } - - /** - * Determine if a value is a Blob - * - * @param {Object} val The value to test - * @returns {boolean} True if value is a Blob, otherwise false - */ - function isBlob(val) { - return toString.call(val) === '[object Blob]'; - } - - /** - * Trim excess whitespace off the beginning and end of a string - * - * @param {String} str The String to trim - * @returns {String} The String freed of excess whitespace - */ - function trim(str) { - return str.replace(/^\s*/, '').replace(/\s*$/, ''); - } - - /** - * Determine if a value is an Arguments object - * - * @param {Object} val The value to test - * @returns {boolean} True if value is an Arguments object, otherwise false - */ - function isArguments(val) { - return toString.call(val) === '[object Arguments]'; - } - - /** - * Determine if we're running in a standard browser environment - * - * This allows axios to run in a web worker, and react-native. - * Both environments support XMLHttpRequest, but not fully standard globals. - * - * web workers: - * typeof window -> undefined - * typeof document -> undefined - * - * react-native: - * typeof document.createelement -> undefined - */ - function isStandardBrowserEnv() { - return ( - typeof window !== 'undefined' && - typeof document !== 'undefined' && - typeof document.createElement === 'function' - ); - } - - /** - * Iterate over an Array or an Object invoking a function for each item. - * - * If `obj` is an Array or arguments callback will be called passing - * the value, index, and complete array for each item. - * - * If 'obj' is an Object callback will be called passing - * the value, key, and complete object for each property. - * - * @param {Object|Array} obj The object to iterate - * @param {Function} fn The callback to invoke for each item - */ - function forEach(obj, fn) { - // Don't bother if no value provided - if (obj === null || typeof obj === 'undefined') { - return; - } - - // Check if obj is array-like - var isArrayLike = isArray(obj) || isArguments(obj); - - // Force an array if not already something iterable - if (typeof obj !== 'object' && !isArrayLike) { - obj = [obj]; - } - - // Iterate over array values - if (isArrayLike) { - for (var i = 0, l = obj.length; i < l; i++) { - fn.call(null, obj[i], i, obj); - } - } - // Iterate over object keys - else { - for (var key in obj) { - if (obj.hasOwnProperty(key)) { - fn.call(null, obj[key], key, obj); - } - } - } - } - - /** - * Accepts varargs expecting each argument to be an object, then - * immutably merges the properties of each object and returns result. - * - * When multiple objects contain the same key the later object in - * the arguments list will take precedence. - * - * Example: - * - * ```js - * var result = merge({foo: 123}, {foo: 456}); - * console.log(result.foo); // outputs 456 - * ``` - * - * @param {Object} obj1 Object to merge - * @returns {Object} Result of all merge properties - */ - function merge(/*obj1, obj2, obj3, ...*/) { - var result = {}; - forEach(arguments, function (obj) { - forEach(obj, function (val, key) { - result[key] = val; - }); - }); - return result; - } - - module.exports = { - isArray: isArray, - isArrayBuffer: isArrayBuffer, - isFormData: isFormData, - isArrayBufferView: isArrayBufferView, - isString: isString, - isNumber: isNumber, - isObject: isObject, - isUndefined: isUndefined, - isDate: isDate, - isFile: isFile, - isBlob: isBlob, - isStandardBrowserEnv: isStandardBrowserEnv, - forEach: forEach, - merge: merge, - trim: trim - }; - - -/***/ }, -/* 4 */ -/***/ function(module, exports, __webpack_require__) { - - /* WEBPACK VAR INJECTION */(function(process) {'use strict'; - - /** - * Dispatch a request to the server using whichever adapter - * is supported by the current environment. - * - * @param {object} config The config that is to be used for the request - * @returns {Promise} The Promise to be fulfilled - */ - module.exports = function dispatchRequest(config) { - return new Promise(function (resolve, reject) { - try { - // For browsers use XHR adapter - if ((typeof XMLHttpRequest !== 'undefined') || (typeof ActiveXObject !== 'undefined')) { - __webpack_require__(6)(resolve, reject, config); - } - // For node use HTTP adapter - else if (typeof process !== 'undefined') { - __webpack_require__(6)(resolve, reject, config); - } - } catch (e) { - reject(e); - } - }); - }; - - - /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(5))) - -/***/ }, -/* 5 */ -/***/ function(module, exports) { - - // shim for using process in browser - - var process = module.exports = {}; - var queue = []; - var draining = false; - var currentQueue; - var queueIndex = -1; - - function cleanUpNextTick() { - draining = false; - if (currentQueue.length) { - queue = currentQueue.concat(queue); - } else { - queueIndex = -1; - } - if (queue.length) { - drainQueue(); - } - } - - function drainQueue() { - if (draining) { - return; - } - var timeout = setTimeout(cleanUpNextTick); - draining = true; - - var len = queue.length; - while(len) { - currentQueue = queue; - queue = []; - while (++queueIndex < len) { - if (currentQueue) { - currentQueue[queueIndex].run(); - } - } - queueIndex = -1; - len = queue.length; - } - currentQueue = null; - draining = false; - clearTimeout(timeout); - } - - process.nextTick = function (fun) { - var args = new Array(arguments.length - 1); - if (arguments.length > 1) { - for (var i = 1; i < arguments.length; i++) { - args[i - 1] = arguments[i]; - } - } - queue.push(new Item(fun, args)); - if (queue.length === 1 && !draining) { - setTimeout(drainQueue, 0); - } - }; - - // v8 likes predictible objects - function Item(fun, array) { - this.fun = fun; - this.array = array; - } - Item.prototype.run = function () { - this.fun.apply(null, this.array); - }; - process.title = 'browser'; - process.browser = true; - process.env = {}; - process.argv = []; - process.version = ''; // empty string to avoid regexp issues - process.versions = {}; - - function noop() {} - - process.on = noop; - process.addListener = noop; - process.once = noop; - process.off = noop; - process.removeListener = noop; - process.removeAllListeners = noop; - process.emit = noop; - - process.binding = function (name) { - throw new Error('process.binding is not supported'); - }; - - process.cwd = function () { return '/' }; - process.chdir = function (dir) { - throw new Error('process.chdir is not supported'); - }; - process.umask = function() { return 0; }; - - -/***/ }, -/* 6 */ -/***/ function(module, exports, __webpack_require__) { - - 'use strict'; - - /*global ActiveXObject:true*/ - - var defaults = __webpack_require__(2); - var utils = __webpack_require__(3); - var buildUrl = __webpack_require__(7); - var parseHeaders = __webpack_require__(8); - var transformData = __webpack_require__(9); - - module.exports = function xhrAdapter(resolve, reject, config) { - // Transform request data - var data = transformData( - config.data, - config.headers, - config.transformRequest - ); - - // Merge headers - var requestHeaders = utils.merge( - defaults.headers.common, - defaults.headers[config.method] || {}, - config.headers || {} - ); - - if (utils.isFormData(data)) { - // Content-Type needs to be sent in all requests so the mapping template can be applied - //delete requestHeaders['Content-Type']; // Let the browser set it - } - - // Create the request - var request = new (XMLHttpRequest || ActiveXObject)('Microsoft.XMLHTTP'); - request.open(config.method.toUpperCase(), buildUrl(config.url, config.params), true); - - // Set the request timeout in MS - request.timeout = config.timeout; - - // Listen for ready state - request.onreadystatechange = function () { - if (request && request.readyState === 4) { - // Prepare the response - var responseHeaders = parseHeaders(request.getAllResponseHeaders()); - var responseData = ['text', ''].indexOf(config.responseType || '') !== -1 ? request.responseText : request.response; - var response = { - data: transformData( - responseData, - responseHeaders, - config.transformResponse - ), - status: request.status, - statusText: request.statusText, - headers: responseHeaders, - config: config - }; - - // Resolve or reject the Promise based on the status - (request.status >= 200 && request.status < 300 ? - resolve : - reject)(response); - - // Clean up request - request = null; - } - }; - - // Add xsrf header - // This is only done if running in a standard browser environment. - // Specifically not if we're in a web worker, or react-native. - if (utils.isStandardBrowserEnv()) { - var cookies = __webpack_require__(10); - var urlIsSameOrigin = __webpack_require__(11); - - // Add xsrf header - var xsrfValue = urlIsSameOrigin(config.url) ? - cookies.read(config.xsrfCookieName || defaults.xsrfCookieName) : - undefined; - - if (xsrfValue) { - requestHeaders[config.xsrfHeaderName || defaults.xsrfHeaderName] = xsrfValue; - } - } - - // Add headers to the request - utils.forEach(requestHeaders, function (val, key) { - // Remove Content-Type if data is undefined - if (!data && key.toLowerCase() === 'content-type') { - delete requestHeaders[key]; - } - // Otherwise add header to the request - else { - request.setRequestHeader(key, val); - } - }); - - // Add withCredentials to request if needed - if (config.withCredentials) { - request.withCredentials = true; - } - - // Add responseType to request if needed - if (config.responseType) { - try { - request.responseType = config.responseType; - } catch (e) { - if (request.responseType !== 'json') { - throw e; - } - } - } - - if (utils.isArrayBuffer(data)) { - data = new DataView(data); - } - - // Send the request - request.send(data); - }; - - -/***/ }, -/* 7 */ -/***/ function(module, exports, __webpack_require__) { - - 'use strict'; - - var utils = __webpack_require__(3); - - function encode(val) { - return encodeURIComponent(val). - replace(/%40/gi, '@'). - replace(/%3A/gi, ':'). - replace(/%24/g, '$'). - replace(/%2C/gi, ','). - replace(/%20/g, '+'). - replace(/%5B/gi, '['). - replace(/%5D/gi, ']'); - } - - /** - * Build a URL by appending params to the end - * - * @param {string} url The base of the url (e.g., http://www.google.com) - * @param {object} [params] The params to be appended - * @returns {string} The formatted url - */ - module.exports = function buildUrl(url, params) { - if (!params) { - return url; - } - - var parts = []; - - utils.forEach(params, function (val, key) { - if (val === null || typeof val === 'undefined') { - return; - } - - if (utils.isArray(val)) { - key = key + '[]'; - } - - if (!utils.isArray(val)) { - val = [val]; - } - - utils.forEach(val, function (v) { - if (utils.isDate(v)) { - v = v.toISOString(); - } - else if (utils.isObject(v)) { - v = JSON.stringify(v); - } - parts.push(encode(key) + '=' + encode(v)); - }); - }); - - if (parts.length > 0) { - url += (url.indexOf('?') === -1 ? '?' : '&') + parts.join('&'); - } - - return url; - }; - - -/***/ }, -/* 8 */ -/***/ function(module, exports, __webpack_require__) { - - 'use strict'; - - var utils = __webpack_require__(3); - - /** - * Parse headers into an object - * - * ``` - * Date: Wed, 27 Aug 2014 08:58:49 GMT - * Content-Type: application/json - * Connection: keep-alive - * Transfer-Encoding: chunked - * ``` - * - * @param {String} headers Headers needing to be parsed - * @returns {Object} Headers parsed into an object - */ - module.exports = function parseHeaders(headers) { - var parsed = {}, key, val, i; - - if (!headers) { return parsed; } - - utils.forEach(headers.split('\n'), function(line) { - i = line.indexOf(':'); - key = utils.trim(line.substr(0, i)).toLowerCase(); - val = utils.trim(line.substr(i + 1)); - - if (key) { - parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val; - } - }); - - return parsed; - }; - - -/***/ }, -/* 9 */ -/***/ function(module, exports, __webpack_require__) { - - 'use strict'; - - var utils = __webpack_require__(3); - - /** - * Transform the data for a request or a response - * - * @param {Object|String} data The data to be transformed - * @param {Array} headers The headers for the request or response - * @param {Array|Function} fns A single function or Array of functions - * @returns {*} The resulting transformed data - */ - module.exports = function transformData(data, headers, fns) { - utils.forEach(fns, function (fn) { - data = fn(data, headers); - }); - - return data; - }; - - -/***/ }, -/* 10 */ -/***/ function(module, exports, __webpack_require__) { - - 'use strict'; - - /** - * WARNING: - * This file makes references to objects that aren't safe in all environments. - * Please see lib/utils.isStandardBrowserEnv before including this file. - */ - - var utils = __webpack_require__(3); - - module.exports = { - write: function write(name, value, expires, path, domain, secure) { - var cookie = []; - cookie.push(name + '=' + encodeURIComponent(value)); - - if (utils.isNumber(expires)) { - cookie.push('expires=' + new Date(expires).toGMTString()); - } - - if (utils.isString(path)) { - cookie.push('path=' + path); - } - - if (utils.isString(domain)) { - cookie.push('domain=' + domain); - } - - if (secure === true) { - cookie.push('secure'); - } - - document.cookie = cookie.join('; '); - }, - - read: function read(name) { - var match = document.cookie.match(new RegExp('(^|;\\s*)(' + name + ')=([^;]*)')); - return (match ? decodeURIComponent(match[3]) : null); - }, - - remove: function remove(name) { - this.write(name, '', Date.now() - 86400000); - } - }; - - -/***/ }, -/* 11 */ -/***/ function(module, exports, __webpack_require__) { - - 'use strict'; - - /** - * WARNING: - * This file makes references to objects that aren't safe in all environments. - * Please see lib/utils.isStandardBrowserEnv before including this file. - */ - - var utils = __webpack_require__(3); - var msie = /(msie|trident)/i.test(navigator.userAgent); - var urlParsingNode = document.createElement('a'); - var originUrl; - - /** - * Parse a URL to discover it's components - * - * @param {String} url The URL to be parsed - * @returns {Object} - */ - function urlResolve(url) { - var href = url; - - if (msie) { - // IE needs attribute set twice to normalize properties - urlParsingNode.setAttribute('href', href); - href = urlParsingNode.href; - } - - urlParsingNode.setAttribute('href', href); - - // urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils - return { - href: urlParsingNode.href, - protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '', - host: urlParsingNode.host, - search: urlParsingNode.search ? urlParsingNode.search.replace(/^\?/, '') : '', - hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '', - hostname: urlParsingNode.hostname, - port: urlParsingNode.port, - pathname: (urlParsingNode.pathname.charAt(0) === '/') ? - urlParsingNode.pathname : - '/' + urlParsingNode.pathname - }; - } - - originUrl = urlResolve(window.location.href); - - /** - * Determine if a URL shares the same origin as the current location - * - * @param {String} requestUrl The URL to test - * @returns {boolean} True if URL shares the same origin, otherwise false - */ - module.exports = function urlIsSameOrigin(requestUrl) { - var parsed = (utils.isString(requestUrl)) ? urlResolve(requestUrl) : requestUrl; - return (parsed.protocol === originUrl.protocol && - parsed.host === originUrl.host); - }; - - -/***/ }, -/* 12 */ -/***/ function(module, exports, __webpack_require__) { - - 'use strict'; - - var utils = __webpack_require__(3); - - function InterceptorManager() { - this.handlers = []; - } - - /** - * Add a new interceptor to the stack - * - * @param {Function} fulfilled The function to handle `then` for a `Promise` - * @param {Function} rejected The function to handle `reject` for a `Promise` - * - * @return {Number} An ID used to remove interceptor later - */ - InterceptorManager.prototype.use = function (fulfilled, rejected) { - this.handlers.push({ - fulfilled: fulfilled, - rejected: rejected - }); - return this.handlers.length - 1; - }; - - /** - * Remove an interceptor from the stack - * - * @param {Number} id The ID that was returned by `use` - */ - InterceptorManager.prototype.eject = function (id) { - if (this.handlers[id]) { - this.handlers[id] = null; - } - }; - - /** - * Iterate over all the registered interceptors - * - * This method is particularly useful for skipping over any - * interceptors that may have become `null` calling `remove`. - * - * @param {Function} fn The function to call for each interceptor - */ - InterceptorManager.prototype.forEach = function (fn) { - utils.forEach(this.handlers, function (h) { - if (h !== null) { - fn(h); - } - }); - }; - - module.exports = InterceptorManager; - - -/***/ }, -/* 13 */ -/***/ function(module, exports) { - - 'use strict'; - - /** - * Syntactic sugar for invoking a function and expanding an array for arguments. - * - * Common use case would be to use `Function.prototype.apply`. - * - * ```js - * function f(x, y, z) {} - * var args = [1, 2, 3]; - * f.apply(null, args); - * ``` - * - * With `spread` this example can be re-written. - * - * ```js - * spread(function(x, y, z) {})([1, 2, 3]); - * ``` - * - * @param {Function} callback - * @returns {Function} - */ - module.exports = function spread(callback) { - return function (arr) { - return callback.apply(null, arr); - }; - }; - - -/***/ } -/******/ ]) -}); -; -//# sourceMappingURL=axios.map \ No newline at end of file diff --git a/website/src/lib/aws-api-client/lib/url-template/url-template.js b/website/src/lib/aws-api-client/lib/url-template/url-template.js deleted file mode 100755 index 048c2c7b..00000000 --- a/website/src/lib/aws-api-client/lib/url-template/url-template.js +++ /dev/null @@ -1,438 +0,0 @@ -/* - UriTemplates Template Processor - Version: @VERSION - Dated: @DATE - (c) marc.portier@gmail.com - 2011-2012 - Licensed under APLv2 (http://opensource.org/licenses/Apache-2.0) - */ - -; -var uritemplate = (function() { - -// Below are the functions we originally used from jQuery. -// The implementations below are often more naive then what is inside jquery, but they suffice for our needs. - - function isFunction(fn) { - return typeof fn == 'function'; - } - - function isEmptyObject (obj) { - for(var name in obj){ - return false; - } - return true; - } - - function extend(base, newprops) { - for (var name in newprops) { - base[name] = newprops[name]; - } - return base; - } - - /** - * Create a runtime cache around retrieved values from the context. - * This allows for dynamic (function) results to be kept the same for multiple - * occuring expansions within one template. - * Note: Uses key-value tupples to be able to cache null values as well. - */ - //TODO move this into prep-processing - function CachingContext(context) { - this.raw = context; - this.cache = {}; - } - CachingContext.prototype.get = function(key) { - var val = this.lookupRaw(key); - var result = val; - - if (isFunction(val)) { // check function-result-cache - var tupple = this.cache[key]; - if (tupple !== null && tupple !== undefined) { - result = tupple.val; - } else { - result = val(this.raw); - this.cache[key] = {key: key, val: result}; - // NOTE: by storing tupples we make sure a null return is validly consistent too in expansions - } - } - return result; - }; - - CachingContext.prototype.lookupRaw = function(key) { - return CachingContext.lookup(this, this.raw, key); - }; - - CachingContext.lookup = function(me, context, key) { - var result = context[key]; - if (result !== undefined) { - return result; - } else { - var keyparts = key.split('.'); - var i = 0, keysplits = keyparts.length - 1; - for (i = 0; i0?f(d.shift()):a(null,e)};d.length>0?f(d.shift(),a):a(null,[])})},AWS.CognitoSyncManager.prototype.wipeData=function(){this.provider.clearCachedId(),this.local.wipeData()},AWS.CognitoSyncManager.prototype.getIdentityId=function(){return this.provider.identityId},AWS=AWS||{},AWS.CognitoSyncManager=AWS.CognitoSyncManager||{},AWS.CognitoSyncManager.Conflict=function(){var a=function(a,b){if(!a||!b)throw new Error("Remote and local records cannot be null.");if(!a.getKey||!b.getKey)throw new Error("Records are not record objects.");if(a.getKey()!==b.getKey())throw new Error("Remote and local keys do not match.");this.key=a.getKey(),this.remoteRecord=a,this.localRecord=b};return a.prototype.getKey=function(){return this.key},a.prototype.getRemoteRecord=function(){return this.remoteRecord},a.prototype.getLocalRecord=function(){return this.localRecord},a.prototype.resolveWithRemoteRecord=function(){return this.remoteRecord.setModified(!1),this.remoteRecord},a.prototype.resolveWithLocalRecord=function(){return this.localRecord.setSyncCount(this.remoteRecord.getSyncCount()),this.localRecord.setModified(!0),this.localRecord},a.prototype.resolveWithValue=function(a){return new AWS.CognitoSyncManager.Record({Key:this.remoteRecord.getKey(),Value:a,SyncCount:this.remoteRecord.getSyncCount(),LastModifiedDate:new Date,LastModifiedBy:this.localRecord.getLastModifiedBy(),DeviceLastModifiedDate:new Date,Modified:!0})},a}(),AWS=AWS||{},AWS.CognitoSyncManager=AWS.CognitoSyncManager||{},AWS.CognitoSyncManager.Dataset=function(){var a=function(a,b,c,d,e){this.MAX_RETRY=3,this.datasetName=a,this.provider=b,this.local=c,this.remote=d,this.logger=e||function(){}};return a.prototype.validateKey=function(a){var b=new RegExp("^[a-zA-Z0-9_.:-]{1,128}$");return b.test(a)},a.prototype.put=function(a,b,c){var d=typeof b;return this.validateKey(a)?"string"!==d?c(new Error("Value must be a string but was "+d+".")):void this.local.putValue(this.getIdentityId(),this.datasetName,a,b,c):c(new Error("Invalid key."))},a.prototype.remove=function(a,b){return this.validateKey(a)?void this.local.putValue(this.getIdentityId(),this.datasetName,a,null,b):b(new Error("Invalid key."))},a.prototype.get=function(a,b){return this.validateKey(a)?void this.local.getValue(this.getIdentityId(),this.datasetName,a,b):b(new Error("Invalid key."))},a.prototype.getAllRecords=function(a){this.local.getRecords(this.getIdentityId(),this.datasetName,a)},a.prototype.getDataStorage=function(a){this.getDatasetMetadata(function(b,c){return b?a(b):c?a(null,c.getDataStorage()):a(null,0)})},a.prototype.isChanged=function(a,b){return this.validateKey(a)?void this.local.getRecord(this.getIdentityId(),this.datasetName,a,function(a,c){b(null,c&&c.isModified())}):b(new Error("Invalid key."))},a.prototype.getDatasetMetadata=function(a){this.local.getDatasetMetadata(this.getIdentityId(),this.datasetName,a)},a.prototype.resolve=function(a,b){this.local.putRecords(this.getIdentityId(),this.datasetName,a,b)},a.prototype.putAll=function(a,b){var c=!0;for(var d in a)a.hasOwnProperty(d)&&(this.validateKey(d)||(c=!1));return c?void this.local.putAllValues(this.getIdentityId(),this.datasetName,a,b):b(new Error("Object contains invalid keys."))},a.prototype.getAll=function(a){var b,c={};this.local.getRecords(this.getIdentityId(),this.datasetName,function(d,e){if(d)return a(d);for(var f in e)e.hasOwnProperty(f)&&(b=e[f],b.isDeleted()||(c[b.getKey()]=b.getValue()));a(null,c)})},a.prototype.getIdentityId=function(){return this.provider.identityId},a.prototype.getModifiedRecords=function(a){this.local.getModifiedRecords(this.getIdentityId(),this.datasetName,a)},a.prototype.getLocalMergedDatasets=function(a){var b,c=[],d=this.datasetName+".";this.local.getDatasets(this.getIdentityId(),function(e,f){for(var g in f)f.hasOwnProperty(g)&&(b=f[g],0===b.getDatasetName().indexOf(d)&&c.push(b.getDatasetName()));a(null,c)})},a.prototype.synchronize=function(a,b){var c=this;return a=a||{},a.onSuccess=a.onSuccess||function(a,b){},a.onFailure=a.onFailure||function(a){},a.onConflict=a.onConflict||function(a,b,c){return c(!1)},a.onDatasetDeleted=a.onDatasetDeleted||function(a,b,c){return c(!1)},a.onDatasetsMerged=a.onDatasetsMerged||function(a,b,c){return c(!1)},void 0===b&&(b=this.MAX_RETRY),c.logger("Starting synchronization... (retires: "+b+")"),0>b?a.onFailure(new Error("Synchronize failed: exceeded maximum retry count.")):void this.getLocalMergedDatasets(function(d,e){return d&&a.onFailure(d),c.logger("Checking for locally merged datasets... found "+e.length+"."),e.length>0?(c.logging("Deferring to .onDatasetsMerged."),a.onDatasetsMerged(c,e,function(d){return d?c.synchronize(a,--b):a.onFailure(new Error("Synchronization cancelled by onDatasetsMerged() callback returning false."))})):void c.local.getLastSyncCount(c.getIdentityId(),c.datasetName,function(d,e){return d?a.onFailure(d):(c.logger("Detecting last sync count... "+e),void c.remote.listUpdates(c.datasetName,e,function(d,f){if(d)return a.onFailure(d);c.logger("Fetch remote updates... found "+f.records.length+".");var g=f.getMergedDatasetNameList();if(c.logger("Checking for remote merged datasets... found "+g.length+"."),g.length>0)return c.logger("Deferring to .onDatasetsMerged."),a.onDatasetsMerged(c,g,function(d){d?c._synchronizeInternal(a,--b):a.onFailure(new Error("Cancelled due to .onDatasetsMerged result."))});if(0!==e&&!f||f.isDeleted())return a.onDatasetDeleted(c,f.getDatasetName(),function(d){return c.logging("Dataset should be deleted. Deferring to .onDatasetDeleted."),d?(c.logging(".onDatasetDeleted returned true, purging dataset locally."),c.local.purgeDataset(c.getIdentityId(),c.datasetName,function(d){return d?a.onFailure(d):c._synchronizeInternal(a,--b)})):(c.logging(".onDatasetDeleted returned false, cancelling sync."),a.onFailure(new Error("Cancelled due to .onDatasetDeleted result.")))});var h=f.getRecords(),i=f.getSyncCount(),j=f.getSyncSessionToken();c.logger("Checking for remote updates since last sync count... found "+h.length+"."),h.length>0?c._synchronizeResolveLocal(h,function(d,f){return d?a.onFailure(d):(c.logger("Checking for conflicts... found "+f.length+"."),void(f.length>0?(c.logger("Conflicts detected. Deferring to .onConflict."),a.onConflict(c,f,function(d){return d?void c._synchronizePushRemote(j,e,function(){return c.synchronize(a,--b)}):(c.logger(".onConflict returned false. Cancelling sync."),a.onFailure(new Error("Sync cancelled. Conflict callback returned false.")))})):(c.logger("No conflicts. Updating local records."),c.local.putRecords(c.getIdentityId(),c.datasetName,h,function(d){return d?a.onFailure(d):void c.local.updateLastSyncCount(c.getIdentityId(),c.datasetName,i,function(d){return d?a.onFailure(d):(c.logger("Finished resolving records. Restarting sync."),c.synchronize(a,--b))})}))))}):(c.logger("Nothing updated remotely. Pushing local changes to remote."),c._synchronizePushRemote(j,i,function(d){return d?(c.logger("Remote push failed. Likely concurrent sync conflict. Retrying..."),c.synchronize(a,--b)):(c.logger("Sync successful."),a.onSuccess(c,h))}))}))})})},a.prototype._synchronizeResolveLocal=function(a,b){var c=this,d=[];return a&&a.length>0?void c.local.getRecords(c.getIdentityId(),c.datasetName,function(c,e){var f,g,h,i={};for(f=0;f0?void d.remote.putRecords(d.datasetName,e,a,function(a,b){a&&c(a),d.local.putRecords(d.getIdentityId(),d.datasetName,b,function(a){if(a)return c(a);var e=0;for(var f in b)b.hasOwnProperty(f)&&(e=e0?(i=f.shift(),e.putValue(a,b,i,c[i],h)):d(null,!0))};h(null,null)},a.prototype.getDatasets=function(a,b){var c=[];if(null!==this.meta){for(var d in this.meta)this.meta.hasOwnProperty(d)&&c.push(new AWS.CognitoSyncManager.DatasetMetadata(this.meta[d]));return b(null,c)}this.loadMetadataCache(a,function(a,d){for(var e in d)d.hasOwnProperty(e)&&c.push(new AWS.CognitoSyncManager.DatasetMetadata(d[e]));return b(null,c)})},a.prototype.updateDatasetMetadata=function(a,b,c){var d=this;this.getDatasetMetadata(a,b.getDatasetName(),function(e,f){e&&c(e),f||(f=new AWS.CognitoSyncManager.DatasetMetadata),f.setDatasetName(b.getDatasetName()).setCreationDate(b.getCreationDate()).setLastModifiedDate(b.getLastModifiedDate()).setLastModifiedBy(b.getLastModifiedBy()).setLastSyncCount(b.getLastSyncCount()).setRecordCount(b.getRecordCount()).setDataStorage(b.getDataStorage()),d.meta[d.getMetadataKey(a,b.getDatasetName())]=f.toJSON(),d.saveMetadataCache(a,d.meta,function(a){return a?c(a):c(null,f)})})},a.prototype.getRecord=function(a,b,c,d){this.store.get(a,b,c,function(a,b){return b?d(null,new AWS.CognitoSyncManager.Record(b)):d(new Error("Key doesn't exist."),null)})},a.prototype.getRecords=function(a,b,c){var d=[];this.store.getAll(a,b,function(a,b){for(var e in b)b.hasOwnProperty(e)&&d.push(new AWS.CognitoSyncManager.Record(b[e]));c(null,d)})},a.prototype.putRecords=function(a,b,c,d){var e=this;c=c||[];var f=function(){c.length>0&&e.updateAndClearRecord(a,b,c.shift(),function(a){return a?d(a):0===c.length?d(null,!0):void f()})};f()},a.prototype.deleteDataset=function(a,b,c){var d=this;this.store.removeAll(a,b,function(e){return e?c(e):void d.getDatasetMetadata(a,b,function(b,e){return b?c(b):(e.setLastModifiedDate(new Date),e.setSyncCount(-1),void d.updateDatasetMetadata(a,e,function(a){return a?c(a):c(null,!0)}))})})},a.prototype.purgeDataset=function(a,b,c){this.deleteDataset(a,b,function(d){d&&c(d),delete this.meta[b],this.saveMetadataCache(a,b,this.meta,function(a,b){c(null,b)})})},a.prototype.getLastSyncCount=function(a,b,c){this.getDatasetMetadata(a,b,function(a,b){return b?c(null,b.getLastSyncCount()):void c(new Error("Dataset doesn't exist."),null)})},a.prototype.getModifiedRecords=function(a,b,c){var d=[];this.getRecords(a,b,function(a,b){for(var e=0;e50}function c(b){return a.attributes[b]&&a.attributes[b].length>200}function d(a){return e.logger.error(a),null}var e=this,f=[];return f=Object.keys(a.metrics).filter(function(b){return"number"!=typeof a.metrics[b]}),"v2.0"!==a.version?d("Event must have version v2.0"):"string"!=typeof a.eventType?d("Event Type must be a string"):f.length>0?d("Event Metrics must be numeric ("+f[0]+")"):Object.keys(a.metrics).length+Object.keys(a.attributes).length>40?d("Event Metric and Attribute Count cannot exceed 40"):Object.keys(a.attributes).filter(b).length?d("Event Attribute names must be 1-50 characters"):Object.keys(a.metrics).filter(b).length?d("Event Metric names must be 1-50 characters"):Object.keys(a.attributes).filter(c).length?d("Event Attribute values cannot be longer than 200 characters"):a},a.prototype.createEvent=function(a,b,c,e){var f=this;this.logger.log("[Function:(AMA.Client).createEvent]"+(a?"\neventType:"+a:"")+(b?"\nsession:"+b:"")+(c?"\nattributes:"+JSON.stringify(c):"")+(e?"\nmetrics:"+JSON.stringify(e):"")),c=c||{},e=e||{},d.Util.mergeObjects(c,this.options.globalAttributes),d.Util.mergeObjects(e,this.options.globalMetrics),Object.keys(c).forEach(function(a){if("string"!=typeof c[a])try{c[a]=JSON.stringify(c[a])}catch(b){f.logger.warn("Error parsing attribute "+a)}});var g={eventType:a,timestamp:(new Date).toISOString(),session:{id:b.id,startTimestamp:b.startTimestamp},version:"v2.0",attributes:c,metrics:e};return b.stopTimestamp&&(g.session.stopTimestamp=b.stopTimestamp,g.session.duration=new Date(g.stopTimestamp).getTime()-new Date(g.startTimestamp).getTime()),this.validateEvent(g)},a.prototype.pushEvent=function(a){if(!a)return-1;this.logger.log("[Function:(AMA.Client).pushEvent]"+(a?"\nevent:"+JSON.stringify(a):""));var b=this.outputs.events.push(a);return this.storage.set(this.StorageKeys.EVENTS,this.outputs.events),b-1},a.prototype.recordEvent=function(a,b,c,e){this.logger.log("[Function:(AMA.Client).recordEvent]"+(a?"\neventType:"+a:"")+(b?"\nsession:"+b:"")+(c?"\nattributes:"+JSON.stringify(c):"")+(e?"\nmetrics:"+JSON.stringify(e):""));var f,g=this.createEvent(a,b,c,e);return g?(f=this.pushEvent(g),d.Util.getRequestBodySize(this.outputs.events)>=this.options.batchSizeLimit&&this.submitEvents(),this.outputs.events[f]):null},a.prototype.recordMonetizationEvent=function(a,b,c,d){return this.logger.log("[Function:(AMA.Client).recordMonetizationEvent]"+(a?"\nsession:"+a:"")+(b?"\nmonetizationDetails:"+JSON.stringify(b):"")+(c?"\nattributes:"+JSON.stringify(c):"")+(d?"\nmetrics:"+JSON.stringify(d):"")),c=c||{},d=d||{},c._currency=b.currency||c._currency,c._product_id=b.productId||c._product_id,d._quantity=b.quantity||d._quantity,"number"==typeof b.price?d._item_price=b.price||d._item_price:c._item_price_formatted=b.price||c._item_price_formatted,this.recordEvent("_monetization.purchase",a,c,d)},a.prototype.submitEvents=function(a){a=a||{},a.submitCallback=a.submitCallback||this.options.submitCallback,this.logger.log("[Function:(AMA.Client).submitEvents]"+(a?"\noptions:"+JSON.stringify(a):"")),this.options.autoSubmitEvents&&(clearTimeout(this.outputs.timeoutReference),this.outputs.timeoutReference=setTimeout(this.submitEvents.bind(this),this.options.autoSubmitInterval));var b;return this.outputs.isThrottled&&this.throttlingSuppressionFunction()0?b="Prevented submission while batches are in flight":0===this.outputs.batches.length&&0===this.outputs.events.length?b="No batches or events to be submitted":this.outputs.lastSubmitTimestamp&&d.Util.timestamp()-this.outputs.lastSubmitTimestamp<1e3&&(b="Prevented multiple submissions in under a second"),b?(this.logger.warn(b),[]):(this.generateBatches(),this.outputs.lastSubmitTimestamp=d.Util.timestamp(),this.outputs.isThrottled?(this.logger.warn("Is throttled submitting first batch"),a.batchId=this.outputs.batchIndex[0],[this.submitBatchById(a)]):this.submitAllBatches(a))},a.prototype.throttlingSuppressionFunction=function(a){return a=a||d.Util.timestamp(),Math.pow(a-this.outputs.lastSubmitTimestamp,2)/Math.pow(6e4,2)},a.prototype.generateBatches=function(){for(;this.outputs.events.length>0;){var a=this.outputs.events.length;for(this.logger.log(this.outputs.events.length+" events to be submitted");a>1&&d.Util.getRequestBodySize(this.outputs.events.slice(0,a))>this.options.batchSizeLimit;)this.logger.log("Finding Batch Size ("+this.options.batchSizeLimit+"): "+a+"("+d.Util.getRequestBodySize(this.outputs.events.slice(0,a))+")"),a-=1;this.persistBatch(this.outputs.events.slice(0,a))&&(this.outputs.events.splice(0,a),this.storage.set(this.StorageKeys.EVENTS,this.outputs.events))}},a.prototype.persistBatch=function(a){if(this.logger.log(a.length+" events in batch"),d.Util.getRequestBodySize(a)<512e3){var b=d.Util.GUID();return this.outputs.batches[b]=a,this.storage.set(this.StorageKeys.BATCHES,this.outputs.batches),this.outputs.batchIndex.push(b),this.storage.set(this.StorageKeys.BATCH_INDEX,this.outputs.batchIndex),!0}return this.logger.error("Events too large"),!1},a.prototype.submitAllBatches=function(a){a.submitCallback=a.submitCallback||this.options.submitCallback,this.logger.log("[Function:(AMA.Client).submitAllBatches]"+(a?"\noptions:"+JSON.stringify(a):""));var b=[],c=this;return this.outputs.batchIndex.forEach(function(d){a.batchId=d,a.clientContext=a.clientContext||c.options.clientContext,c.outputs.batchesInFlight[d]||b.push(c.submitBatchById(a))}),b},a.NON_RETRYABLE_EXCEPTIONS=["BadRequestException","SerializationException","ValidationException"],a.prototype.submitBatchById=function(a){if("object"!=typeof a||!a.batchId)return void this.logger.error("Invalid Options passed to submitBatchById");a.submitCallback=a.submitCallback||this.options.submitCallback,this.logger.log("[Function:(AMA.Client).submitBatchById]"+(a?"\noptions:"+JSON.stringify(a):""));var b={events:this.outputs.batches[a.batchId],clientContext:JSON.stringify(a.clientContext||this.options.clientContext)};return this.outputs.batchesInFlight[a.batchId]=d.Util.timestamp(),this.outputs.MobileAnalytics.putEvents(b,this.handlePutEventsResponse(a.batchId,a.submitCallback)),a.batchId},a.prototype.handlePutEventsResponse=function(b,c){var d=this;return function(e,f){var g=!0,h=d.outputs.isThrottled;e?(d.logger.error(e,f),(void 0===e.statusCode||400===e.statusCode)&&(a.NON_RETRYABLE_EXCEPTIONS.indexOf(e.code)<0&&(g=!1),d.outputs.isThrottled="ThrottlingException"===e.code,d.outputs.isThrottled&&d.logger.warn("Application is currently throttled"))):(d.logger.info("Events Submitted Successfully"),d.outputs.isThrottled=!1),g&&d.clearBatchById(b),delete d.outputs.batchesInFlight[b],c(e,f,b),h&&!d.outputs.isThrottled&&(d.logger.warn("Was throttled flushing remaining batches",c),d.submitAllBatches({submitCallback:c}))}},a.prototype.clearBatchById=function(a){this.logger.log("[Function:(AMA.Client).clearBatchById]"+(a?"\nbatchId:"+a:"")),-1!==this.outputs.batchIndex.indexOf(a)&&(delete this.outputs.batches[a],this.outputs.batchIndex.splice(this.outputs.batchIndex.indexOf(a),1),this.storage.set(this.StorageKeys.BATCH_INDEX,this.outputs.batchIndex),this.storage.set(this.StorageKeys.BATCHES,this.outputs.batches))},a}(),b.exports=d.Client}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"./MobileAnalyticsUtilities.js":4,"./StorageClients/LocalStorage.js":5,"./StorageClients/StorageKeys.js":6}],2:[function(a,b){(function(c){var d=c.AMA;d.Storage=a("./StorageClients/LocalStorage.js"),d.StorageKeys=a("./StorageClients/StorageKeys.js"),d.Util=a("./MobileAnalyticsUtilities.js"),d.Session=function(){"use strict";var a=function(a){this.options=a||{},this.options.logger=this.options.logger||{},this.logger={log:this.options.logger.log||d.Util.NOP,info:this.options.logger.info||d.Util.NOP,warn:this.options.logger.warn||d.Util.NOP,error:this.options.logger.error||d.Util.NOP},this.logger.log=this.logger.log.bind(this.options.logger),this.logger.info=this.logger.info.bind(this.options.logger),this.logger.warn=this.logger.warn.bind(this.options.logger),this.logger.error=this.logger.error.bind(this.options.logger),this.logger.log("[Function:(AWS.MobileAnalyticsClient)Session Constructor]"+(a?"\noptions:"+JSON.stringify(a):"")),this.options.expirationCallback=this.options.expirationCallback||d.Util.NOP,this.id=this.options.sessionId||d.Util.GUID(),this.sessionLength=this.options.sessionLength||6e5,this.StorageKeys={SESSION_ID:d.StorageKeys.SESSION_ID+this.id,SESSION_EXPIRATION:d.StorageKeys.SESSION_EXPIRATION+this.id,SESSION_START_TIMESTAMP:d.StorageKeys.SESSION_START_TIMESTAMP+this.id},this.startTimestamp=this.options.startTime||this.options.storage.get(this.StorageKeys.SESSION_START_TIMESTAMP)||(new Date).toISOString(),this.expirationDate=parseInt(this.options.storage.get(this.StorageKeys.SESSION_EXPIRATION),10),isNaN(this.expirationDate)&&(this.expirationDate=(new Date).getTime()+this.sessionLength),this.options.storage.set(this.StorageKeys.SESSION_ID,this.id),this.options.storage.set(this.StorageKeys.SESSION_EXPIRATION,this.expirationDate),this.options.storage.set(this.StorageKeys.SESSION_START_TIMESTAMP,this.startTimestamp),this.sessionTimeoutReference=setTimeout(this.expireSession.bind(this),this.sessionLength)};return a.prototype.expireSession=function(a){this.logger.log("[Function:(Session).expireSession]"),a=a||this.options.expirationCallback;var b=a(this);"boolean"==typeof b&&b&&(b=this.options.sessionLength),"number"==typeof b?this.extendSession(b):this.clearSession()},a.prototype.clearSession=function(){this.logger.log("[Function:(Session).clearSession]"),clearTimeout(this.sessionTimeoutReference),this.options.storage["delete"](this.StorageKeys.SESSION_ID),this.options.storage["delete"](this.StorageKeys.SESSION_EXPIRATION),this.options.storage["delete"](this.StorageKeys.SESSION_START_TIMESTAMP)},a.prototype.extendSession=function(a){this.logger.log("[Function:(Session).extendSession]"+(a?"\nsessionExtensionLength:"+a:"")),a=a||this.sessionLength,this.setSessionTimeout(this.expirationDate+parseInt(a,10))},a.prototype.stopSession=function(a){this.logger.log("[Function:(Session).stopSession]"+(a?"\nstopDate:"+a:"")),this.stopTimestamp=a||(new Date).toISOString()},a.prototype.resetSessionTimeout=function(a){this.logger.log("[Function:(Session).resetSessionTimeout]"+(a?"\nmilliseconds:"+a:"")),a=a||this.sessionLength,this.setSessionTimeout((new Date).getTime()+a)},a.prototype.setSessionTimeout=function(a){this.logger.log("[Function:(Session).setSessionTimeout]"+(a?"\ntimeout:"+a:"")),clearTimeout(this.sessionTimeoutReference),this.expirationDate=a,this.options.storage.set(this.StorageKeys.SESSION_EXPIRATION,this.expirationDate),this.sessionTimeoutReference=setTimeout(this.expireSession.bind(this),this.expirationDate-(new Date).getTime())},a}(),b.exports=d.Session}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"./MobileAnalyticsUtilities.js":4,"./StorageClients/LocalStorage.js":5,"./StorageClients/StorageKeys.js":6}],3:[function(a,b){(function(c){var d=c.AMA;d.Storage=a("./StorageClients/LocalStorage.js"),d.StorageKeys=a("./StorageClients/StorageKeys.js"),d.Session=a("./MobileAnalyticsSession.js"),d.Client=a("./MobileAnalyticsClient.js"),d.Manager=function(){"use strict";var a=function(a){function b(a){a.client.storage.each(function(b){0===b.indexOf(d.StorageKeys.SESSION_ID)&&(a.outputs.session=new d.Session({storage:a.client.storage,sessionId:a.client.storage.get(b),sessionLength:a.options.sessionLength,expirationCallback:function(b){var c=a.options.expirationCallback(b);return c===!0||"number"==typeof c?c:void a.stopSession()}}),(new Date).getTime()>a.outputs.session.expirationDate&&(a.outputs.session.expireSession(),delete a.outputs.session))})}a instanceof d.Client?this.client=a:(a._autoSubmitEvents=a.autoSubmitEvents,a.autoSubmitEvents=!1,this.client=new d.Client(a),a.autoSubmitEvents=a._autoSubmitEvents!==!1,delete a._autoSubmitEvents),this.options=this.client.options,this.outputs=this.client.outputs,this.options.expirationCallback=this.options.expirationCallback||d.Util.NOP,b(this),this.outputs.session||this.startSession(),this.options.autoSubmitEvents&&this.client.submitEvents()};return a.prototype.submitEvents=function(a){return this.client.submitEvents(a)},a.prototype.startSession=function(){return this.client.logger.log("[Function:(AMA.Manager).startSession]"),this.outputs.session&&this.outputs.session.clearSession(),this.outputs.session=new d.Session({storage:this.client.storage,logger:this.client.options.logger,sessionLength:this.options.sessionLength,expirationCallback:function(a){var b=this.options.expirationCallback(a);return b===!0||"number"==typeof b?b:void this.stopSession()}.bind(this)}),this.recordEvent("_session.start")},a.prototype.extendSession=function(a){return this.outputs.session.extendSession(a||this.options.sessionLength)},a.prototype.stopSession=function(){return this.client.logger.log("[Function:(AMA.Manager).stopSession]"),this.outputs.session.stopSession(),this.outputs.session.expireSession(d.Util.NOP),this.recordEvent("_session.stop")},a.prototype.renewSession=function(){return this.stopSession(),this.startSession(),this.outputs.session},a.prototype.createEvent=function(a,b,c){return this.client.createEvent(a,this.outputs.session,b,c)},a.prototype.recordEvent=function(a,b,c){return this.client.recordEvent(a,this.outputs.session,b,c)},a.prototype.recordMonetizationEvent=function(a,b,c){return this.client.recordMonetizationEvent(this.outputs.session,a,b,c)},a}(),b.exports=d.Manager}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"./MobileAnalyticsClient.js":1,"./MobileAnalyticsSession.js":2,"./StorageClients/LocalStorage.js":5,"./StorageClients/StorageKeys.js":6}],4:[function(a,b){(function(a){var c=a.AMA;c.Util=function(){"use strict";function a(){return Math.floor(65536*(1+Math.random())).toString(16).substring(1)}function b(a){"string"!=typeof a&&(a=JSON.stringify(a));var b,c,d=a.length;for(b=a.length-1;b>=0;b-=1)c=a.charCodeAt(b),c>127&&2047>=c?d+=1:c>2047&&65535>=c&&(d+=2),c>=56320&&57343>=c&&(b-=1);return d}function c(){return a()+a()+"-"+a()+"-"+a()+"-"+a()+"-"+a()+a()+a()}function d(a,b){return Object.keys(b).forEach(function(c){b.hasOwnProperty(c)&&(a[c]=a[c]||b[c])}),a}function e(a,b){return d(JSON.parse(JSON.stringify(a)),b||{})}function f(){return void 0}function g(){return(new Date).getTime()}return{copy:e,GUID:c,getRequestBodySize:b,mergeObjects:d,NOP:f,timestamp:g}}(),b.exports=c.Util}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}],5:[function(a,b){(function(c){var d=c.AMA;d.Util=a("../MobileAnalyticsUtilities.js"),d.Storage=function(){"use strict";var a=function(a){this.storageKey="AWSMobileAnalyticsStorage-"+a,c[this.storageKey]=c[this.storageKey]||{},this.cache=c[this.storageKey],this.cache.id=this.cache.id||d.Util.GUID(),this.logger={log:d.Util.NOP,info:d.Util.NOP,warn:d.Util.NOP,error:d.Util.NOP},this.reload()};if("object"==typeof localStorage&&"object"===Storage)try{localStorage.setItem("TestLocalStorage",1),localStorage.removeItem("TestLocalStorage")}catch(b){Storage.prototype._setItem=Storage.prototype.setItem,Storage.prototype.setItem=d.Util.NOP,console.warn('Your web browser does not support storing settings locally. In Safari, the most common cause of this is using "Private Browsing Mode". Some settings may not save or some features may not work properly for you.')}return a.prototype.type="LOCAL_STORAGE",a.prototype.get=function(a){return this.cache[a]},a.prototype.set=function(a,b){return this.cache[a]=b,this.saveToLocalStorage()},a.prototype["delete"]=function(a){delete this.cache[a],this.saveToLocalStorage()},a.prototype.each=function(a){var b;for(b in this.cache)this.cache.hasOwnProperty(b)&&a(b,this.cache[b])},a.prototype.saveToLocalStorage=function(){if(this.supportsLocalStorage())try{this.logger.log("[Function:(AWS.MobileAnalyticsClient.Storage).saveToLocalStorage]"),window.localStorage.setItem(this.storageKey,JSON.stringify(this.cache)),this.logger.log("LocalStorage Cache: "+JSON.stringify(this.cache))}catch(a){this.logger.log("Error saving to LocalStorage: "+JSON.stringify(a))}else this.logger.log("LocalStorage is not available")},a.prototype.reload=function(){if(this.supportsLocalStorage()){var a;try{if(this.logger.log("[Function:(AWS.MobileAnalyticsClient.Storage).loadLocalStorage]"),a=window.localStorage.getItem(this.storageKey),this.logger.log("LocalStorage Cache: "+a),a)try{this.cache=JSON.parse(a)}catch(b){this.clearLocalStorage()}}catch(c){this.logger.log("Error loading LocalStorage: "+JSON.stringify(c)),this.clearLocalStorage()}}else this.logger.log("LocalStorage is not available")},a.prototype.setLogger=function(a){this.logger=a},a.prototype.supportsLocalStorage=function(){try{return window&&window.localStorage}catch(a){return!1}},a.prototype.clearLocalStorage=function(){if(this.cache={},this.supportsLocalStorage())try{this.logger.log("[Function:(AWS.MobileAnalyticsClient.Storage).clearLocalStorage]"),window.localStorage.removeItem(this.storageKey),c[this.storageKey]={}}catch(a){this.logger.log("Error clearing LocalStorage: "+JSON.stringify(a))}else this.logger.log("LocalStorage is not available")},a}(),b.exports=d.Storage}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"../MobileAnalyticsUtilities.js":4}],6:[function(a,b){(function(a){var c=a.AMA;c.StorageKeys={CLIENT_ID:"AWSMobileAnalyticsClientId",GLOBAL_ATTRIBUTES:"AWSMobileAnalyticsGlobalAttributes",GLOBAL_METRICS:"AWSMobileAnalyticsGlobalMetrics",SESSION_ID:"MobileAnalyticsSessionId",SESSION_EXPIRATION:"MobileAnalyticsSessionExpiration",SESSION_START_TIMESTAMP:"MobileAnalyticsSessionStartTimeStamp"},b.exports=c.StorageKeys}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}],7:[function(a,b){(function(c){c.AMA=c.AMA||{},a("./MobileAnalyticsClient.js"),a("./MobileAnalyticsUtilities.js"),a("./StorageClients/StorageKeys.js"),a("./StorageClients/LocalStorage.js"),a("./MobileAnalyticsSession.js"),a("./MobileAnalyticsSessionManager.js"),b.exports=c.AMA}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"./MobileAnalyticsClient.js":1,"./MobileAnalyticsSession.js":2,"./MobileAnalyticsSessionManager.js":3,"./MobileAnalyticsUtilities.js":4,"./StorageClients/LocalStorage.js":5,"./StorageClients/StorageKeys.js":6}]},{},[7]); \ No newline at end of file diff --git a/well-architected.md b/well-architected.md new file mode 100644 index 00000000..89660ac2 --- /dev/null +++ b/well-architected.md @@ -0,0 +1,55 @@ +# Serverless Reference Architecture: Well-Architected Review - Serverless Application Lens + +Using the Serverless Application Lens, we focus on how to design, deploy, and architect your serverless application workloads on the AWS Cloud. It covers scenarios such as RESTful Microservices, Mobile back-ends, Stream Processing, and Web Application. By using this Well-Architected lens you will learn best practices for building serverless application workloads on AWS. + +### Questions and best practices summarized + +**OPS 1. How do you understand the health of your Serverless application?** + +- Using application, business, and operations metrics + +The application makes use of [Amazon CloudWatch Embedded Metric Format](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Embedded_Metric_Format_Specification.html) to send custom metrics to Amazon CloudWatch. + +We generate additional business metrics the outcomes of each AWS Lambda function and pass it as Amazon CloudWatch Metrics. + +**OPS 2. How do you approach application lifecycle management?** + +Most of the heavy-lifting is done by using [SAM](https://aws.amazon.com/serverless/sam/) to create our application, manage the differences between deploys, test locally and remotelly, and deploy in different environments. + +For this specific scenario, we have not implemented the CI/CD aspect of it, but To help you start right with serverless, AWS has added a Create application experience to the Lambda console. This enables you to create serverless applications from ready-to-use sample applications, which follow these best practices: + +Use infrastructure as code (IaC) for defining application resources +Provide a continuous integration and continuous deployment (CI/CD) pipeline for deployment +Exemplify best practices in serverless application structure and methods. + +Learn more about this [here](https://aws.amazon.com/blogs/compute/improving-the-getting-started-experience-with-aws-lambda/). + +**REL 1. How are you regulating inbound request rates?** + +The main entry point for the functionalities on our App is the Amazon API Gateway endpoints. We make use of [Throttling and Daily Quota](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-request-throttling.html) to prevent misuse of the application by users. +The API Usage plan on our case, is defined globally. To understand and create your own Usage Plan definition, please refer to the [documentation on SAM](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-property-api-apiusageplan.html). + +**REL 2. How are you building resiliency into your Serverless application?** + +Our application itself don't have any long-running, complicated transactions at any given point. We have error handling both at backend and frontend. + +**SEC 1: How do you control access to your Serverless API?** + +We follow the documentation and protect our Amazon API Gateway endpoing using Amazon Cognito. To do the same, please follow the [guide in our documentation](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-integrate-with-cognito.html). + +**SEC 2: How are you managing the security boundaries of your Serverless Application?** + +All functionality contained here is extracted to smaller functions, which follows the SRP (Single Responsibility Principle). Given that, each AWS Lambda have strict policies and permissions, allowing us to better control the scope and the access patterns of our services. Learn more about AWS Lambda permissions [here](https://docs.aws.amazon.com/lambda/latest/dg/lambda-permissions.html). + +**SEC 3: How do you implement Application Security in your workload?** + +While the CI/CD aspect of the application should add the security review of the dependencies; We do validate all request for the necessary and valid request parameters in place before calling downstream services. + +**PERF 1. How have you optimized the performance of your Serverless* *Application?** + +To make sure we're using the best performance settings for our AWS Lambda functions, we've used [AWS Lambda Power Tuning](https://github.com/alexcasalboni/aws-lambda-power-tuning). AWS Lambda Power Tuning is an open-source tool that can help you visualize and fine-tune the memory/power configuration of Lambda functions. + +**COST 1. How do you optimize your costs?** + +Same as the `PERF 1` question, by using [AWS Lambda Power Tuning](https://github.com/alexcasalboni/aws-lambda-power-tuning) we can understand what is the right-sizing approach that we should take with our AWS Lambda parameters and set it for being the most cost-effective one. +Other considerations around long-pooling, using the appropriated services when needed, and creating and caching resources outside the main handler. \ No newline at end of file diff --git a/www/package.json b/www/package.json new file mode 100644 index 00000000..723fd1e0 --- /dev/null +++ b/www/package.json @@ -0,0 +1,36 @@ +{ + "name": "serverless-todo", + "version": "0.1.0", + "private": true, + "dependencies": { + "axios": "^0.19.0", + "bootstrap": "^4.3.1", + "json": "^9.0.6", + "react": "^16.9.0", + "react-dom": "^16.9.0", + "react-router-dom": "^5.0.1", + "react-scripts": "3.1.1", + "reactstrap": "^8.0.1" + }, + "scripts": { + "start": "PORT=8080 react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test", + "eject": "react-scripts eject" + }, + "eslintConfig": { + "extends": "react-app" + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + } +} diff --git a/www/public/index.html b/www/public/index.html new file mode 100644 index 00000000..2a4d3de2 --- /dev/null +++ b/www/public/index.html @@ -0,0 +1,41 @@ + + + + + + + + + + + + + Serverless Webapp on AWS + + + + +
+ + + + \ No newline at end of file diff --git a/www/src/App.css b/www/src/App.css new file mode 100644 index 00000000..474bc711 --- /dev/null +++ b/www/src/App.css @@ -0,0 +1,18 @@ +.App { + text-align: left; + margin-top: 20px; + color: #FFF; +} + +.logo img { + height: 40px; +} + +.jumbotron { + background-color: #37475A; +} + +.btn-primary { + background-color: #FF9900; + color: #FFFFFF; +} diff --git a/www/src/App.js b/www/src/App.js new file mode 100644 index 00000000..42ef31fd --- /dev/null +++ b/www/src/App.js @@ -0,0 +1,181 @@ +import React, { useState, useEffect } from 'react'; +import { Container, Jumbotron, Row, Col, Alert, Button } from 'reactstrap'; +import axios from 'axios'; +import ToDo from './ToDo' + +import './App.css'; +import logo from './aws.png'; + +import config from './config'; + +function App() { + const [alert, setAlert] = useState(); + const [alertStyle, setAlertStyle] = useState('info'); + const [alertVisible, setAlertVisible] = useState(false); + const [alertDismissable, setAlertDismissable] = useState(false); + const [idToken, setIdToken] = useState(''); + const [toDos, setToDos] = useState([]); + + useEffect(() => { + getIdToken(); + if (idToken.length > 0) { + getAllTodos(); + } + }, [idToken]); + + axios.interceptors.response.use(response => { + console.log('Response was received'); + return response; + }, error => { + window.location.href = config.redirect_url; + return Promise.reject(error); + }); + + function onDismiss() { + setAlertVisible(false); + } + + function updateAlert({ alert, style, visible, dismissable }) { + setAlert(alert ? alert : ''); + setAlertStyle(style ? style : 'info'); + setAlertVisible(visible); + setAlertDismissable(dismissable ? dismissable : null); + } + + const clearCredentials = () => { + window.location.href = config.redirect_url; + } + + const getIdToken = () => { + const hash = window.location.hash.substr(1); + const objects = hash.split("&"); + objects.forEach(object => { + const keyVal = object.split("="); + if (keyVal[0] === "id_token") { + setIdToken(keyVal[1]); + } + }); + }; + + const getAllTodos = async () => { + const result = await axios({ + url: `${config.api_base_url}/item/`, + headers: { + Authorization: idToken + } + }).catch(error => { + console.log(error); + }); + + console.log(result); + + if (result && result.status === 401) { + clearCredentials(); + } else if (result && result.status === 200) { + console.log(result.data.Items); + setToDos(result.data.Items); + } + }; + + const addToDo = async (event) => { + const newToDoInput = document.getElementById('newToDo'); + const item = newToDoInput.value; + console.log(item); + if (!item || item === '') return; + + const newToDo = { + "item": item, + "completed": false + }; + + const result = await axios({ + method: 'POST', + url: `${config.api_base_url}/item/`, + headers: { + Authorization: idToken + }, + data: newToDo + }); + + if (result && result.status === 401) { + clearCredentials(); + } else if (result && result.status === 200) { + getAllTodos(); + newToDoInput.value = ''; + } + } + + const deleteToDo = async (indexToRemove, itemId) => { + if (indexToRemove === null) return; + if (itemId === null) return; + + const result = await axios({ + method: 'DELETE', + url: `${config.api_base_url}/item/${itemId}`, + headers: { + Authorization: idToken + } + }); + + if (result && result.status === 401) { + clearCredentials(); + } else if (result && result.status === 200) { + const newToDos = toDos.filter((item, index) => index !== indexToRemove); + setToDos(newToDos); + } + } + + const completeToDo = async (itemId) => { + if (itemId === null) return; + + const result = await axios({ + method: 'POST', + url: `${config.api_base_url}/item/${itemId}/done`, + headers: { + Authorization: idToken + } + }); + + if (result && result.status === 200) { + getAllTodos(); + } + } + + return ( +
+ + +

+
+ + + +

Serverless Todo

+

This is a demo that showcases AWS serverless.

+

The application is built using the SAM CLI toolchain, and uses AWS Lambda, Amazon DynamoDB, and Amazon API Gateway for API services and Amazon Cognito for identity.

+ + Logo + + + {idToken.length > 0 ? + ( + + ) : ( + + ) + } + +
+
+
+
+ ); +} + +export default App; diff --git a/www/src/ToDo.css b/www/src/ToDo.css new file mode 100644 index 00000000..5d635b9f --- /dev/null +++ b/www/src/ToDo.css @@ -0,0 +1,17 @@ +.ToDo { + text-align: left; + font-size: 14px; + color: #232F3E; +} + +.toDoButton { + margin-right: 2px; +} + +.completed { + text-decoration: line-through; +} + +.form-group { + margin-bottom: 0 !important; +} \ No newline at end of file diff --git a/www/src/ToDo.js b/www/src/ToDo.js new file mode 100644 index 00000000..637f6556 --- /dev/null +++ b/www/src/ToDo.js @@ -0,0 +1,58 @@ +import React, { useState } from 'react'; +import { Button, ButtonGroup, Form, FormGroup, Input, Label, Row, Col } from 'reactstrap'; + +import './ToDo.css'; + +function ToDo({ toDos, addToDo, deleteToDo, completeToDo }) { + const [filter, setFilter] = useState('all'); + + const changeFilter = (newFilter) => { + setFilter(newFilter); + }; + + return ( +
+ + +
+ + + + + +
+ + + + + + + + + +
    + {toDos.filter(item => ((filter === 'all') || (filter === 'complete' && item.completed) || (filter === 'incomplete' && !item.completed))).map((item, index) => ( +
  • + + + {item.item} + + + + + + +
  • + ))} +
+ +
+
+ ); +} + +export default ToDo; diff --git a/www/src/aws.png b/www/src/aws.png new file mode 100644 index 0000000000000000000000000000000000000000..5ed0a6a6e3772ac4be793670ff217ef1091f4a7b GIT binary patch literal 3850 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 M07*qoM6N<$f)tiDZ2$lO literal 0 HcmV?d00001 diff --git a/www/src/config.default.js b/www/src/config.default.js new file mode 100644 index 00000000..5fa6cfbf --- /dev/null +++ b/www/src/config.default.js @@ -0,0 +1,13 @@ +// You can obtain these values by running: +// aws cloudformation describe-stacks --stack-name --query "Stacks[0].Outputs[]" + +const config = { + "aws_cognito_region": "AWS_COGNITO_REGION", + "aws_user_pools_id": "AWS_USER_POOLS_ID", // CognitoID + "aws_user_pools_web_client_id": "AWS_USER_POOLS_WEB_CLIENT_ID", // CognitoClientID + "api_base_url": "API_BASE_URL", // TodoFunctionApi + "coginto_hosted_domain": "COGNITO_HOSTED_DOMAIN", // CognitoDomainName + "redirect_url": "REDIRECT_URL" // AmplifyURL +}; + +export default config; diff --git a/www/src/config.js b/www/src/config.js new file mode 100644 index 00000000..d489772e --- /dev/null +++ b/www/src/config.js @@ -0,0 +1,13 @@ +// You can obtain these values by running: +// aws cloudformation describe-stacks --stack-name --query "Stacks[0].Outputs[]" + +const config = { + "aws_cognito_region": "us-east-1", + "aws_user_pools_id": "None", // CognitoID + "aws_user_pools_web_client_id": "None", // CognitoClientID + "api_base_url": "None", // TodoFunctionApi + "coginto_hosted_domain": "None", // CognitoDomainName + "redirect_url": "None" // AmplifyURL +}; + +export default config; diff --git a/www/src/index.css b/www/src/index.css new file mode 100644 index 00000000..26e1d74e --- /dev/null +++ b/www/src/index.css @@ -0,0 +1,15 @@ +body { + background-color: #232F3E; + margin: 0; + padding: 20px; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", + "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +code { + font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", + monospace; +} diff --git a/www/src/index.js b/www/src/index.js new file mode 100644 index 00000000..b9f3a767 --- /dev/null +++ b/www/src/index.js @@ -0,0 +1,14 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; + +import 'bootstrap/dist/css/bootstrap.css'; +import './index.css'; +import App from './App'; +import * as serviceWorker from './serviceWorker'; + +ReactDOM.render(, document.getElementById('root')); + +// If you want your app to work offline and load faster, you can change +// unregister() to register() below. Note this comes with some pitfalls. +// Learn more about service workers: https://bit.ly/CRA-PWA +serviceWorker.unregister(); diff --git a/www/src/serviceWorker.js b/www/src/serviceWorker.js new file mode 100644 index 00000000..f8c7e50c --- /dev/null +++ b/www/src/serviceWorker.js @@ -0,0 +1,135 @@ +// This optional code is used to register a service worker. +// register() is not called by default. + +// This lets the app load faster on subsequent visits in production, and gives +// it offline capabilities. However, it also means that developers (and users) +// will only see deployed updates on subsequent visits to a page, after all the +// existing tabs open on the page have been closed, since previously cached +// resources are updated in the background. + +// To learn more about the benefits of this model and instructions on how to +// opt-in, read https://bit.ly/CRA-PWA + +const isLocalhost = Boolean( + window.location.hostname === 'localhost' || + // [::1] is the IPv6 localhost address. + window.location.hostname === '[::1]' || + // 127.0.0.1/8 is considered localhost for IPv4. + window.location.hostname.match( + /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ + ) +); + +export function register(config) { + if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { + // The URL constructor is available in all browsers that support SW. + const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); + if (publicUrl.origin !== window.location.origin) { + // Our service worker won't work if PUBLIC_URL is on a different origin + // from what our page is served on. This might happen if a CDN is used to + // serve assets; see https://github.com/facebook/create-react-app/issues/2374 + return; + } + + window.addEventListener('load', () => { + const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; + + if (isLocalhost) { + // This is running on localhost. Let's check if a service worker still exists or not. + checkValidServiceWorker(swUrl, config); + + // Add some additional logging to localhost, pointing developers to the + // service worker/PWA documentation. + navigator.serviceWorker.ready.then(() => { + console.log( + 'This web app is being served cache-first by a service ' + + 'worker. To learn more, visit https://bit.ly/CRA-PWA' + ); + }); + } else { + // Is not localhost. Just register service worker + registerValidSW(swUrl, config); + } + }); + } +} + +function registerValidSW(swUrl, config) { + navigator.serviceWorker + .register(swUrl) + .then(registration => { + registration.onupdatefound = () => { + const installingWorker = registration.installing; + if (installingWorker == null) { + return; + } + installingWorker.onstatechange = () => { + if (installingWorker.state === 'installed') { + if (navigator.serviceWorker.controller) { + // At this point, the updated precached content has been fetched, + // but the previous service worker will still serve the older + // content until all client tabs are closed. + console.log( + 'New content is available and will be used when all ' + + 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' + ); + + // Execute callback + if (config && config.onUpdate) { + config.onUpdate(registration); + } + } else { + // At this point, everything has been precached. + // It's the perfect time to display a + // "Content is cached for offline use." message. + console.log('Content is cached for offline use.'); + + // Execute callback + if (config && config.onSuccess) { + config.onSuccess(registration); + } + } + } + }; + }; + }) + .catch(error => { + console.error('Error during service worker registration:', error); + }); +} + +function checkValidServiceWorker(swUrl, config) { + // Check if the service worker can be found. If it can't reload the page. + fetch(swUrl) + .then(response => { + // Ensure service worker exists, and that we really are getting a JS file. + const contentType = response.headers.get('content-type'); + if ( + response.status === 404 || + (contentType != null && contentType.indexOf('javascript') === -1) + ) { + // No service worker found. Probably a different app. Reload the page. + navigator.serviceWorker.ready.then(registration => { + registration.unregister().then(() => { + window.location.reload(); + }); + }); + } else { + // Service worker found. Proceed as normal. + registerValidSW(swUrl, config); + } + }) + .catch(() => { + console.log( + 'No internet connection found. App is running in offline mode.' + ); + }); +} + +export function unregister() { + if ('serviceWorker' in navigator) { + navigator.serviceWorker.ready.then(registration => { + registration.unregister(); + }); + } +}