├── .nvmrc ├── frontend ├── config │ ├── __init__.py │ ├── settings │ │ ├── __init__.py │ │ ├── test.py │ │ └── local.py │ ├── wsgi.py │ └── urls.py ├── lee_fishing │ ├── users │ │ ├── __init__.py │ │ ├── tests │ │ │ ├── __init__.py │ │ │ ├── test_models.py │ │ │ ├── factories.py │ │ │ ├── test_urls.py │ │ │ ├── test_forms.py │ │ │ └── test_views.py │ │ ├── management │ │ │ ├── __init__.py │ │ │ └── commands │ │ │ │ ├── __init__.py │ │ │ │ └── make_user.py │ │ ├── migrations │ │ │ └── __init__.py │ │ ├── apps.py │ │ ├── urls.py │ │ ├── models.py │ │ ├── admin.py │ │ ├── adapters.py │ │ ├── middleware.py │ │ ├── forms.py │ │ └── views.py │ ├── static │ │ ├── fonts │ │ │ └── .gitkeep │ │ ├── sass │ │ │ ├── custom_bootstrap_vars.scss │ │ │ └── project.scss │ │ ├── js │ │ │ └── project.js │ │ ├── images │ │ │ ├── 400x400.png │ │ │ └── favicons │ │ │ │ └── favicon.ico │ │ └── css │ │ │ └── project.css │ ├── fishing_net │ │ ├── __init__.py │ │ ├── migrations │ │ │ ├── __init__.py │ │ │ ├── 0002_auto_20181009_0554.py │ │ │ ├── 0003_basket_order_id.py │ │ │ └── 0001_initial.py │ │ ├── tests.py │ │ ├── admin.py │ │ ├── apps.py │ │ ├── context_processors.py │ │ ├── managers.py │ │ ├── backend.py │ │ ├── urls.py │ │ ├── models.py │ │ └── views.py │ ├── fishing_equipment │ │ ├── __init__.py │ │ ├── migrations │ │ │ ├── __init__.py │ │ │ ├── 0005_auto_20181009_0554.py │ │ │ ├── 0006_auto_20181010_0920.py │ │ │ ├── 0002_auto_20181002_2025.py │ │ │ ├── 0003_auto_20181002_2026.py │ │ │ ├── 0004_auto_20181005_0919.py │ │ │ └── 0001_initial.py │ │ ├── tests.py │ │ ├── managers.py │ │ ├── apps.py │ │ ├── urls.py │ │ ├── admin.py │ │ ├── views.py │ │ ├── fixtures │ │ │ └── category.json │ │ └── models.py │ ├── templates │ │ ├── pages │ │ │ ├── about.html │ │ │ └── home.html │ │ ├── fishing_net │ │ │ ├── congrats.html │ │ │ ├── ping.html │ │ │ └── basket_detail.html │ │ ├── 404.html │ │ ├── 403_csrf.html │ │ ├── account │ │ │ ├── base.html │ │ │ ├── account_inactive.html │ │ │ ├── password_reset_from_key_done.html │ │ │ ├── signup_closed.html │ │ │ ├── verification_sent.html │ │ │ ├── password_set.html │ │ │ ├── password_reset_done.html │ │ │ ├── password_change.html │ │ │ ├── logout.html │ │ │ ├── signup.html │ │ │ ├── verified_email_required.html │ │ │ ├── password_reset.html │ │ │ ├── password_reset_from_key.html │ │ │ ├── email_confirm.html │ │ │ ├── login.html │ │ │ └── email.html │ │ ├── 500.html │ │ ├── users │ │ │ ├── user_list.html │ │ │ ├── user_form.html │ │ │ └── user_detail.html │ │ └── fishing_equipment │ │ │ ├── product_detail.html │ │ │ ├── category_list.html │ │ │ └── product_list.html │ ├── __init__.py │ ├── contrib │ │ ├── __init__.py │ │ └── sites │ │ │ ├── __init__.py │ │ │ └── migrations │ │ │ ├── __init__.py │ │ │ ├── 0002_alter_domain_unique.py │ │ │ ├── 0003_set_site_domain_and_name.py │ │ │ └── 0001_initial.py │ └── conftest.py ├── .gitattributes ├── requirements.txt ├── pytest.ini ├── docs │ ├── __init__.py │ ├── deploy.rst │ ├── install.rst │ ├── pycharm │ │ ├── images │ │ │ ├── 1.png │ │ │ ├── 2.png │ │ │ ├── 3.png │ │ │ ├── 4.png │ │ │ ├── 7.png │ │ │ ├── 8.png │ │ │ ├── f1.png │ │ │ ├── f2.png │ │ │ ├── f3.png │ │ │ ├── f4.png │ │ │ ├── issue1.png │ │ │ └── issue2.png │ │ └── configuration.rst │ └── index.rst ├── .coveragerc ├── locale │ └── README.rst ├── .pylintrc ├── utility │ ├── requirements-trusty.apt │ ├── requirements-xenial.apt │ ├── requirements-jessie.apt │ ├── requirements-stretch.apt │ ├── install_python_dependencies.sh │ └── install_os_dependencies.sh ├── setup.cfg ├── .editorconfig ├── requirements │ ├── production.txt │ ├── base.txt │ └── local.txt ├── manage.py ├── .ebextensions │ └── aws.config └── README.rst ├── docs ├── .gitignore ├── .python-version ├── images │ ├── pong.png │ ├── .DS_Store │ ├── cloud9.png │ ├── cdk_destroy.png │ ├── gradlebuild.png │ ├── yumwarning.png │ ├── backend_deploy.png │ ├── backend_upload.png │ ├── cf_behaviors_2.png │ ├── cloud9_warning.png │ ├── cwl_processing.png │ ├── eb_console_1.png │ ├── eb_console_2.png │ ├── maxiterminal.png │ ├── cdk_wherediditgo.png │ ├── cf_behaviors_tab.png │ ├── cf_invalidations.png │ ├── elasticbeanstalk.png │ ├── exercise_0_arch.png │ ├── exercise_2_arch.png │ ├── exercise_3_arch.png │ ├── frontend_deploy.png │ ├── frontend_upload.png │ ├── xray_servicemap.png │ ├── cdk_destroy_error.png │ ├── cf_create_behavior.png │ ├── cf_edge_definition.png │ ├── cloud9_newterminal.png │ ├── xray_trace_detail.png │ ├── backend_lambda_upload.png │ ├── frontend_database_url.png │ └── xray_trace_backend_detail.png ├── requirements.txt ├── Makefile ├── make.bat ├── exercise_2_post.rst ├── exercise_1_post.rst ├── cleanup.rst ├── troubleshooting.rst └── exercise_3_post.rst ├── frontend_minisite ├── .env.example ├── .eslintignore ├── .prettierrc ├── .DS_Store ├── assets │ ├── js │ │ ├── plugins │ │ │ ├── bootstrap.js │ │ │ └── fontawesome.js │ │ └── app.js │ └── sass │ │ ├── _variables.scss │ │ └── app.scss ├── .gitignore ├── views │ ├── partials │ │ ├── footer.hbs │ │ └── header.hbs │ ├── layout.hbs │ ├── pages │ │ └── index.hbs │ └── helpers │ │ └── extend.js ├── .editorconfig ├── .eslintrc.js ├── README.md ├── LICENSE └── package.json ├── .gitignore ├── null_lambda ├── settings.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── .gitignore ├── src │ └── main │ │ └── java │ │ └── fishing │ │ └── lee │ │ └── backend │ │ ├── StreamLambdaHandler.java │ │ └── SNSLambdaHandler.java ├── build.gradle └── gradlew.bat ├── sqs_order_forwarder ├── settings.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── .gitignore ├── build.gradle ├── src │ └── main │ │ └── java │ │ └── fishing │ │ └── lee │ │ └── sqsforwarder │ │ ├── OrderForwarder.java │ │ └── QueueGrabber.java └── gradlew.bat ├── infrastructure ├── .gitignore ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── src │ └── main │ │ ├── resources │ │ ├── dummy_edge.js │ │ └── xray_enabler.py │ │ └── java │ │ └── fishing │ │ └── lee │ │ └── infrastructure │ │ ├── InfrastructureApp.java │ │ ├── Decoupling.java │ │ ├── DeploymentAssets.java │ │ ├── PostgresProps.java │ │ ├── ApiGatewayVpcPolicyDocument.java │ │ ├── QueueProxyProps.java │ │ ├── BastionProps.java │ │ ├── ScheduledApiCallerProps.java │ │ ├── ApiGatewayXrayEnablerResourceProps.java │ │ ├── ShopStack.java │ │ ├── ShopVpc.java │ │ ├── Bastion.java │ │ └── ScheduledApiCaller.java ├── app.sh └── cdk.json ├── backend ├── src │ ├── main │ │ ├── resources │ │ │ ├── application-local.properties │ │ │ ├── application.properties │ │ │ └── application-lambda.properties │ │ └── java │ │ │ └── fishing │ │ │ └── lee │ │ │ └── backend │ │ │ ├── OrderModel.java │ │ │ ├── BasketRepository.java │ │ │ ├── ArgonConfig.java │ │ │ ├── BasketNotFoundException.java │ │ │ ├── PingController.java │ │ │ ├── OrderController.java │ │ │ ├── BasketModel.java │ │ │ ├── WebConfig.java │ │ │ ├── StreamLambdaHandler.java │ │ │ ├── BasketController.java │ │ │ └── Application.java │ └── test │ │ └── java │ │ └── fishing │ │ └── lee │ │ └── backend │ │ ├── BasketRepositoryIntegrationTest.java │ │ ├── BasketControllerTest.java │ │ └── BaseDynamoDBTest.java ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── .gitignore └── gradlew.bat ├── .github └── PULL_REQUEST_TEMPLATE.md ├── README.md ├── CODE_OF_CONDUCT.md ├── LICENSE └── lambda_at_edge └── index.js /.nvmrc: -------------------------------------------------------------------------------- 1 | 10.15.0 2 | -------------------------------------------------------------------------------- /frontend/config/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | _build 2 | .vscode 3 | -------------------------------------------------------------------------------- /docs/.python-version: -------------------------------------------------------------------------------- 1 | fishing-docs 2 | -------------------------------------------------------------------------------- /frontend/config/settings/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/lee_fishing/users/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /frontend/lee_fishing/static/fonts/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend_minisite/.env.example: -------------------------------------------------------------------------------- 1 | PORT= 2 | -------------------------------------------------------------------------------- /frontend_minisite/.eslintignore: -------------------------------------------------------------------------------- 1 | /dist 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | .DS_Store 3 | .vscode 4 | -------------------------------------------------------------------------------- /frontend/lee_fishing/fishing_net/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/lee_fishing/users/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/lee_fishing/fishing_equipment/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/lee_fishing/users/management/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/lee_fishing/users/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/lee_fishing/fishing_net/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/lee_fishing/users/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/lee_fishing/fishing_equipment/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/lee_fishing/static/sass/custom_bootstrap_vars.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /null_lambda/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'dummy' 2 | 3 | -------------------------------------------------------------------------------- /frontend/requirements.txt: -------------------------------------------------------------------------------- 1 | 2 | -r ./requirements/production.txt 3 | -------------------------------------------------------------------------------- /frontend/lee_fishing/templates/pages/about.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} -------------------------------------------------------------------------------- /frontend/lee_fishing/templates/pages/home.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} -------------------------------------------------------------------------------- /sqs_order_forwarder/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'sqsforward' 2 | 3 | -------------------------------------------------------------------------------- /frontend/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | DJANGO_SETTINGS_MODULE=config.settings.test 3 | -------------------------------------------------------------------------------- /frontend_minisite/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true 4 | } 5 | -------------------------------------------------------------------------------- /frontend/lee_fishing/static/js/project.js: -------------------------------------------------------------------------------- 1 | /* Project specific Javascript goes here. */ 2 | -------------------------------------------------------------------------------- /infrastructure/.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | out 3 | .idea 4 | *.iml 5 | .classpath.txt 6 | target 7 | 8 | -------------------------------------------------------------------------------- /backend/src/main/resources/application-local.properties: -------------------------------------------------------------------------------- 1 | amazon.aws.mode=local 2 | amazon.xray.selfhosted=true 3 | -------------------------------------------------------------------------------- /backend/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | amazon.aws.mode=beanstalk 2 | amazon.xray.selfhosted=true 3 | -------------------------------------------------------------------------------- /frontend/docs/__init__.py: -------------------------------------------------------------------------------- 1 | # Included so that Django's startproject comment runs against the docs directory 2 | -------------------------------------------------------------------------------- /frontend/lee_fishing/fishing_net/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /backend/src/main/resources/application-lambda.properties: -------------------------------------------------------------------------------- 1 | amazon.aws.mode=lambda 2 | amazon.xray.selfhosted=false 3 | -------------------------------------------------------------------------------- /docs/images/pong.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/serverless-retail-workshop/HEAD/docs/images/pong.png -------------------------------------------------------------------------------- /frontend/lee_fishing/fishing_net/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /docs/images/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/serverless-retail-workshop/HEAD/docs/images/.DS_Store -------------------------------------------------------------------------------- /docs/images/cloud9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/serverless-retail-workshop/HEAD/docs/images/cloud9.png -------------------------------------------------------------------------------- /frontend/lee_fishing/fishing_equipment/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /frontend/docs/deploy.rst: -------------------------------------------------------------------------------- 1 | Deploy 2 | ======== 3 | 4 | This is where you describe how the project is deployed in production. 5 | -------------------------------------------------------------------------------- /backend/src/main/java/fishing/lee/backend/OrderModel.java: -------------------------------------------------------------------------------- 1 | package fishing.lee.backend; 2 | 3 | public class OrderModel { 4 | } 5 | -------------------------------------------------------------------------------- /docs/images/cdk_destroy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/serverless-retail-workshop/HEAD/docs/images/cdk_destroy.png -------------------------------------------------------------------------------- /docs/images/gradlebuild.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/serverless-retail-workshop/HEAD/docs/images/gradlebuild.png -------------------------------------------------------------------------------- /docs/images/yumwarning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/serverless-retail-workshop/HEAD/docs/images/yumwarning.png -------------------------------------------------------------------------------- /frontend/docs/install.rst: -------------------------------------------------------------------------------- 1 | Install 2 | ========= 3 | 4 | This is where you write how to get a new laptop to run this project. 5 | -------------------------------------------------------------------------------- /frontend_minisite/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/serverless-retail-workshop/HEAD/frontend_minisite/.DS_Store -------------------------------------------------------------------------------- /docs/images/backend_deploy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/serverless-retail-workshop/HEAD/docs/images/backend_deploy.png -------------------------------------------------------------------------------- /docs/images/backend_upload.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/serverless-retail-workshop/HEAD/docs/images/backend_upload.png -------------------------------------------------------------------------------- /docs/images/cf_behaviors_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/serverless-retail-workshop/HEAD/docs/images/cf_behaviors_2.png -------------------------------------------------------------------------------- /docs/images/cloud9_warning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/serverless-retail-workshop/HEAD/docs/images/cloud9_warning.png -------------------------------------------------------------------------------- /docs/images/cwl_processing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/serverless-retail-workshop/HEAD/docs/images/cwl_processing.png -------------------------------------------------------------------------------- /docs/images/eb_console_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/serverless-retail-workshop/HEAD/docs/images/eb_console_1.png -------------------------------------------------------------------------------- /docs/images/eb_console_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/serverless-retail-workshop/HEAD/docs/images/eb_console_2.png -------------------------------------------------------------------------------- /docs/images/maxiterminal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/serverless-retail-workshop/HEAD/docs/images/maxiterminal.png -------------------------------------------------------------------------------- /frontend/.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | include = lee_fishing/* 3 | omit = *migrations*, *tests* 4 | plugins = 5 | django_coverage_plugin 6 | -------------------------------------------------------------------------------- /docs/images/cdk_wherediditgo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/serverless-retail-workshop/HEAD/docs/images/cdk_wherediditgo.png -------------------------------------------------------------------------------- /docs/images/cf_behaviors_tab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/serverless-retail-workshop/HEAD/docs/images/cf_behaviors_tab.png -------------------------------------------------------------------------------- /docs/images/cf_invalidations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/serverless-retail-workshop/HEAD/docs/images/cf_invalidations.png -------------------------------------------------------------------------------- /docs/images/elasticbeanstalk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/serverless-retail-workshop/HEAD/docs/images/elasticbeanstalk.png -------------------------------------------------------------------------------- /docs/images/exercise_0_arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/serverless-retail-workshop/HEAD/docs/images/exercise_0_arch.png -------------------------------------------------------------------------------- /docs/images/exercise_2_arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/serverless-retail-workshop/HEAD/docs/images/exercise_2_arch.png -------------------------------------------------------------------------------- /docs/images/exercise_3_arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/serverless-retail-workshop/HEAD/docs/images/exercise_3_arch.png -------------------------------------------------------------------------------- /docs/images/frontend_deploy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/serverless-retail-workshop/HEAD/docs/images/frontend_deploy.png -------------------------------------------------------------------------------- /docs/images/frontend_upload.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/serverless-retail-workshop/HEAD/docs/images/frontend_upload.png -------------------------------------------------------------------------------- /docs/images/xray_servicemap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/serverless-retail-workshop/HEAD/docs/images/xray_servicemap.png -------------------------------------------------------------------------------- /docs/images/cdk_destroy_error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/serverless-retail-workshop/HEAD/docs/images/cdk_destroy_error.png -------------------------------------------------------------------------------- /docs/images/cf_create_behavior.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/serverless-retail-workshop/HEAD/docs/images/cf_create_behavior.png -------------------------------------------------------------------------------- /docs/images/cf_edge_definition.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/serverless-retail-workshop/HEAD/docs/images/cf_edge_definition.png -------------------------------------------------------------------------------- /docs/images/cloud9_newterminal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/serverless-retail-workshop/HEAD/docs/images/cloud9_newterminal.png -------------------------------------------------------------------------------- /docs/images/xray_trace_detail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/serverless-retail-workshop/HEAD/docs/images/xray_trace_detail.png -------------------------------------------------------------------------------- /frontend/docs/pycharm/images/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/serverless-retail-workshop/HEAD/frontend/docs/pycharm/images/1.png -------------------------------------------------------------------------------- /frontend/docs/pycharm/images/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/serverless-retail-workshop/HEAD/frontend/docs/pycharm/images/2.png -------------------------------------------------------------------------------- /frontend/docs/pycharm/images/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/serverless-retail-workshop/HEAD/frontend/docs/pycharm/images/3.png -------------------------------------------------------------------------------- /frontend/docs/pycharm/images/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/serverless-retail-workshop/HEAD/frontend/docs/pycharm/images/4.png -------------------------------------------------------------------------------- /frontend/docs/pycharm/images/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/serverless-retail-workshop/HEAD/frontend/docs/pycharm/images/7.png -------------------------------------------------------------------------------- /frontend/docs/pycharm/images/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/serverless-retail-workshop/HEAD/frontend/docs/pycharm/images/8.png -------------------------------------------------------------------------------- /frontend/docs/pycharm/images/f1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/serverless-retail-workshop/HEAD/frontend/docs/pycharm/images/f1.png -------------------------------------------------------------------------------- /frontend/docs/pycharm/images/f2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/serverless-retail-workshop/HEAD/frontend/docs/pycharm/images/f2.png -------------------------------------------------------------------------------- /frontend/docs/pycharm/images/f3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/serverless-retail-workshop/HEAD/frontend/docs/pycharm/images/f3.png -------------------------------------------------------------------------------- /frontend/docs/pycharm/images/f4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/serverless-retail-workshop/HEAD/frontend/docs/pycharm/images/f4.png -------------------------------------------------------------------------------- /docs/images/backend_lambda_upload.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/serverless-retail-workshop/HEAD/docs/images/backend_lambda_upload.png -------------------------------------------------------------------------------- /docs/images/frontend_database_url.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/serverless-retail-workshop/HEAD/docs/images/frontend_database_url.png -------------------------------------------------------------------------------- /frontend/docs/pycharm/images/issue1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/serverless-retail-workshop/HEAD/frontend/docs/pycharm/images/issue1.png -------------------------------------------------------------------------------- /frontend/docs/pycharm/images/issue2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/serverless-retail-workshop/HEAD/frontend/docs/pycharm/images/issue2.png -------------------------------------------------------------------------------- /frontend/lee_fishing/templates/fishing_net/congrats.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block content %} 4 |

Stuff

5 | {% endblock %} 6 | -------------------------------------------------------------------------------- /backend/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/serverless-retail-workshop/HEAD/backend/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /docs/images/xray_trace_backend_detail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/serverless-retail-workshop/HEAD/docs/images/xray_trace_backend_detail.png -------------------------------------------------------------------------------- /infrastructure/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/serverless-retail-workshop/HEAD/infrastructure/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /infrastructure/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.5.4/apache-maven-3.5.4-bin.zip -------------------------------------------------------------------------------- /null_lambda/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/serverless-retail-workshop/HEAD/null_lambda/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /frontend/lee_fishing/static/images/400x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/serverless-retail-workshop/HEAD/frontend/lee_fishing/static/images/400x400.png -------------------------------------------------------------------------------- /frontend/locale/README.rst: -------------------------------------------------------------------------------- 1 | Translations 2 | ============ 3 | 4 | Translations will be placed in this folder when running:: 5 | 6 | python manage.py makemessages 7 | -------------------------------------------------------------------------------- /frontend_minisite/assets/js/plugins/bootstrap.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import BootstrapVue from 'bootstrap-vue/dist/bootstrap-vue.esm' 3 | 4 | Vue.use(BootstrapVue) 5 | -------------------------------------------------------------------------------- /frontend_minisite/.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | node_modules/ 3 | 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | .idea 9 | .vscode 10 | .env 11 | yarn.lock 12 | -------------------------------------------------------------------------------- /frontend/lee_fishing/static/images/favicons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/serverless-retail-workshop/HEAD/frontend/lee_fishing/static/images/favicons/favicon.ico -------------------------------------------------------------------------------- /frontend/lee_fishing/templates/fishing_net/ping.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block content %} 4 |

Artifishial Intelligence says: {{ pong }}

5 | {% endblock %} 6 | -------------------------------------------------------------------------------- /sqs_order_forwarder/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/serverless-retail-workshop/HEAD/sqs_order_forwarder/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /frontend/lee_fishing/fishing_net/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class FishingNetConfig(AppConfig): 5 | name = 'lee_fishing.fishing_net' 6 | verbose_name = 'Fishing Nets (Baskets)' 7 | -------------------------------------------------------------------------------- /frontend/lee_fishing/fishing_equipment/managers.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | class ProductManager(models.Manager): 5 | def not_deleted(self): 6 | return self.filter(deleted_at__isnull=True) 7 | -------------------------------------------------------------------------------- /infrastructure/src/main/resources/dummy_edge.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.index_rewrite = (event, context, callback) => { 4 | var request = event.Records[0].cf.request; 5 | callback(null, request) 6 | } 7 | -------------------------------------------------------------------------------- /frontend/lee_fishing/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "0.1.0" 2 | __version_info__ = tuple( 3 | [ 4 | int(num) if num.isdigit() else num 5 | for num in __version__.replace("-", ".", 1).split(".") 6 | ] 7 | ) 8 | -------------------------------------------------------------------------------- /frontend/lee_fishing/fishing_equipment/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class FishingEquipmentConfig(AppConfig): 5 | name = 'lee_fishing.fishing_equipment' 6 | verbose_name = 'Fishing Equipment' 7 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | *Issue #, if available:* 2 | 3 | *Description of changes:* 4 | 5 | 6 | By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice. 7 | -------------------------------------------------------------------------------- /frontend/lee_fishing/contrib/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | To understand why this file is here, please read: 3 | 4 | http://cookiecutter-django.readthedocs.io/en/latest/faq.html#why-is-there-a-django-contrib-sites-directory-in-cookiecutter-django 5 | """ 6 | -------------------------------------------------------------------------------- /frontend/lee_fishing/contrib/sites/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | To understand why this file is here, please read: 3 | 4 | http://cookiecutter-django.readthedocs.io/en/latest/faq.html#why-is-there-a-django-contrib-sites-directory-in-cookiecutter-django 5 | """ 6 | -------------------------------------------------------------------------------- /frontend/lee_fishing/contrib/sites/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | To understand why this file is here, please read: 3 | 4 | http://cookiecutter-django.readthedocs.io/en/latest/faq.html#why-is-there-a-django-contrib-sites-directory-in-cookiecutter-django 5 | """ 6 | -------------------------------------------------------------------------------- /backend/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /frontend/lee_fishing/templates/404.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}Page not found{% endblock %} 4 | 5 | {% block content %} 6 |

Page not found

7 | 8 |

This is not the page you were looking for.

9 | {% endblock content %} 10 | -------------------------------------------------------------------------------- /frontend_minisite/views/partials/footer.hbs: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /frontend/lee_fishing/templates/403_csrf.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}Forbidden (403){% endblock %} 4 | 5 | {% block content %} 6 |

Forbidden (403)

7 | 8 |

CSRF verification failed. Request aborted.

9 | {% endblock content %} 10 | -------------------------------------------------------------------------------- /frontend_minisite/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | indent_style = space 8 | indent_size = 2 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | 14 | -------------------------------------------------------------------------------- /frontend/lee_fishing/users/tests/test_models.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from django.conf import settings 3 | 4 | pytestmark = pytest.mark.django_db 5 | 6 | 7 | def test_user_get_absolute_url(user: settings.AUTH_USER_MODEL): 8 | assert user.get_absolute_url() == f"/users/{user.username}/" 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Serverless Retail Workshop 2 | 3 | A sample shop frontend, backend and mini-site for showing how you can quickly deal with back pressure against legacy backend services 4 | 5 | ## License Summary 6 | 7 | This sample code is made available under a modified MIT license. See the LICENSE file. 8 | -------------------------------------------------------------------------------- /null_lambda/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Oct 11 09:54:04 BST 2018 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip 7 | -------------------------------------------------------------------------------- /sqs_order_forwarder/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Oct 11 09:54:04 BST 2018 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip 7 | -------------------------------------------------------------------------------- /frontend/lee_fishing/static/css/project.css: -------------------------------------------------------------------------------- 1 | /* These styles are generated from project.scss. */ 2 | 3 | .alert-debug { 4 | color: black; 5 | background-color: white; 6 | border-color: #d6e9c6; 7 | } 8 | 9 | .alert-error { 10 | color: #b94a48; 11 | background-color: #f2dede; 12 | border-color: #eed3d7; 13 | } 14 | -------------------------------------------------------------------------------- /frontend/lee_fishing/fishing_net/context_processors.py: -------------------------------------------------------------------------------- 1 | from lee_fishing.fishing_net.models import Basket 2 | 3 | 4 | def current_basket(request): 5 | basket = None 6 | if request.user.is_authenticated: 7 | basket = Basket.objects.current_for_user(request.user) 8 | 9 | return {'current_basket': basket} 10 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /frontend/.pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | load-plugins=pylint_common, pylint_django 3 | 4 | [FORMAT] 5 | max-line-length=120 6 | 7 | [MESSAGES CONTROL] 8 | disable=missing-docstring,invalid-name 9 | 10 | [DESIGN] 11 | max-parents=13 12 | 13 | [TYPECHECK] 14 | generated-members=REQUEST,acl_users,aq_parent,"[a-zA-Z]+_set{1,2}",save,delete 15 | -------------------------------------------------------------------------------- /frontend/lee_fishing/templates/account/base.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}{% block head_title %}{% endblock head_title %}{% endblock title %} 3 | 4 | {% block content %} 5 |
6 |
7 | {% block inner %}{% endblock %} 8 |
9 |
10 | {% endblock %} 11 | -------------------------------------------------------------------------------- /frontend/lee_fishing/templates/account/account_inactive.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | 5 | {% block head_title %}{% trans "Account Inactive" %}{% endblock %} 6 | 7 | {% block inner %} 8 |

{% trans "Account Inactive" %}

9 | 10 |

{% trans "This account is inactive." %}

11 | {% endblock %} 12 | 13 | -------------------------------------------------------------------------------- /frontend_minisite/assets/sass/_variables.scss: -------------------------------------------------------------------------------- 1 | // Fonts 2 | $icon-font-path: "~bootstrap-sass/assets/fonts/bootstrap/"; 3 | $fa-font-path: "~font-awesome/fonts/"; 4 | 5 | // Slick 6 | $slick-font-path: "~slick-carousel/slick/fonts/"; 7 | $slick-loader-path: "~slick-carousel/slick/"; 8 | 9 | $slick-arrow-color: black; 10 | $slick-dot-color: black; 11 | -------------------------------------------------------------------------------- /frontend/lee_fishing/templates/account/password_reset_from_key_done.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | {% block head_title %}{% trans "Change Password" %}{% endblock %} 5 | 6 | {% block inner %} 7 |

{% trans "Change Password" %}

8 |

{% trans 'Your password is now changed.' %}

9 | {% endblock %} 10 | 11 | -------------------------------------------------------------------------------- /frontend/lee_fishing/templates/account/signup_closed.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | 5 | {% block head_title %}{% trans "Sign Up Closed" %}{% endblock %} 6 | 7 | {% block inner %} 8 |

{% trans "Sign Up Closed" %}

9 | 10 |

{% trans "We are sorry, but the sign up is currently closed." %}

11 | {% endblock %} 12 | 13 | -------------------------------------------------------------------------------- /backend/src/main/java/fishing/lee/backend/BasketRepository.java: -------------------------------------------------------------------------------- 1 | package fishing.lee.backend; 2 | 3 | import org.socialsignin.spring.data.dynamodb.repository.EnableScan; 4 | import org.springframework.data.repository.CrudRepository; 5 | 6 | import java.util.UUID; 7 | 8 | @EnableScan 9 | public interface BasketRepository extends CrudRepository { 10 | } 11 | -------------------------------------------------------------------------------- /infrastructure/app.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # This script is configured in cdk.json to be used to execute 3 | # the CDK java app by the command-line toolkit. 4 | # The file .classpath.txt is created by when `mvn package` is called 5 | # The first argument will be used as argv[0] 6 | exec java -cp target/classes:$(cat .classpath.txt) fishing.lee.infrastructure.InfrastructureApp fishing $@ 7 | -------------------------------------------------------------------------------- /infrastructure/src/main/java/fishing/lee/infrastructure/InfrastructureApp.java: -------------------------------------------------------------------------------- 1 | package fishing.lee.infrastructure; 2 | 3 | import software.amazon.awscdk.App; 4 | 5 | public class InfrastructureApp { 6 | public static void main(final String argv[]) { 7 | App app = new App(); 8 | 9 | new ShopStack(app, "TheFishingShopWorkshop"); 10 | 11 | app.run(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /frontend/lee_fishing/templates/500.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}Server Error{% endblock %} 4 | 5 | {% block content %} 6 |

Ooops!!! 500

7 | 8 |

Looks like something went wrong!

9 | 10 |

We track these errors automatically, but if the problem persists feel free to contact us. In the meantime, try refreshing.

11 | {% endblock content %} 12 | 13 | 14 | -------------------------------------------------------------------------------- /frontend_minisite/views/partials/header.hbs: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

Lee Fishing

5 | 8 |
9 |
10 |
11 | -------------------------------------------------------------------------------- /frontend/lee_fishing/fishing_net/managers.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | class BasketManager(models.Manager): 5 | def current_for_user(self, user): 6 | try: 7 | return self.filter( 8 | created_by=user, deleted_at__isnull=True, order_id__isnull=True, 9 | ).latest('created_date') 10 | except self.model.DoesNotExist: 11 | return self.model(created_by=user) 12 | -------------------------------------------------------------------------------- /backend/src/main/java/fishing/lee/backend/ArgonConfig.java: -------------------------------------------------------------------------------- 1 | package fishing.lee.backend; 2 | 3 | import de.mkammerer.argon2.Argon2; 4 | import de.mkammerer.argon2.Argon2Factory; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | 8 | @Configuration 9 | public class ArgonConfig { 10 | @Bean 11 | public Argon2 argon() { 12 | return Argon2Factory.create(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /frontend_minisite/views/layout.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{title}} 7 | 8 | 9 |
10 | {{> header }} 11 | 12 |
13 | {{{body}}} 14 |
15 | 16 | {{> footer }} 17 |
18 | 19 | 20 | -------------------------------------------------------------------------------- /backend/src/main/java/fishing/lee/backend/BasketNotFoundException.java: -------------------------------------------------------------------------------- 1 | package fishing.lee.backend; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.web.bind.annotation.ResponseStatus; 5 | 6 | /** 7 | * Return a 404 when a basket isn't found. 8 | * 9 | * @see ResponseStatus 10 | */ 11 | @ResponseStatus(code = HttpStatus.NOT_FOUND, reason = "basket not found") 12 | class BasketNotFoundException extends RuntimeException { 13 | } 14 | -------------------------------------------------------------------------------- /frontend/lee_fishing/fishing_equipment/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from lee_fishing.fishing_equipment.views import CategoryDetailView, CategoryListView, ProductDetailView 4 | 5 | app_name = 'equipment' 6 | urlpatterns = [ 7 | path("", view=CategoryListView.as_view(), name="list"), 8 | path("/", view=CategoryDetailView.as_view(), name="detail"), 9 | path("//", view=ProductDetailView.as_view(), name="product"), 10 | ] 11 | -------------------------------------------------------------------------------- /backend/src/main/java/fishing/lee/backend/PingController.java: -------------------------------------------------------------------------------- 1 | package fishing.lee.backend; 2 | 3 | import org.springframework.web.bind.annotation.GetMapping; 4 | import org.springframework.web.bind.annotation.RestController; 5 | 6 | @RestController 7 | public class PingController { 8 | @GetMapping("/") 9 | String pingRoot() { 10 | return "PONG (from root)"; 11 | } 12 | 13 | @GetMapping("/ping") 14 | String ping() { 15 | return "PONG"; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /frontend/lee_fishing/fishing_equipment/migrations/0005_auto_20181009_0554.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.8 on 2018-10-09 05:54 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('fishing_equipment', '0004_auto_20181005_0919'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterModelOptions( 14 | name='product', 15 | options={'ordering': ['title']}, 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /frontend/lee_fishing/fishing_equipment/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from lee_fishing.fishing_equipment.models import Product, Category 4 | 5 | 6 | @admin.register(Product) 7 | class ProductAdmin(admin.ModelAdmin): 8 | list_display = ('title','in_stock','price') 9 | prepopulated_fields = {"slug": ("title",)} 10 | 11 | 12 | @admin.register(Category) 13 | class CategoryAdmin(admin.ModelAdmin): 14 | list_display = ('title',) 15 | prepopulated_fields = {"slug": ("title",)} 16 | -------------------------------------------------------------------------------- /frontend_minisite/assets/js/plugins/fontawesome.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import { library } from '@fortawesome/fontawesome-svg-core' 3 | 4 | import { faCode, faHeart } from '@fortawesome/free-solid-svg-icons' 5 | 6 | import {} from '@fortawesome/free-regular-svg-icons' 7 | 8 | import {} from '@fortawesome/free-brands-svg-icons' 9 | 10 | import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome' 11 | 12 | library.add(faCode, faHeart) 13 | 14 | Vue.component('font-awesome-icon', FontAwesomeIcon) 15 | -------------------------------------------------------------------------------- /frontend/lee_fishing/users/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | # noinspection PyUnresolvedReferences 3 | from aws_xray_sdk.core import patch 4 | 5 | 6 | class UsersAppConfig(AppConfig): 7 | 8 | name = "lee_fishing.users" 9 | verbose_name = "Users" 10 | 11 | def ready(self): 12 | try: 13 | # noinspection PyPackageRequirements 14 | import users.signals # noqa F401 15 | except ImportError: 16 | pass 17 | 18 | patch(('requests',)) 19 | -------------------------------------------------------------------------------- /backend/.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | out/ 3 | .idea/ 4 | *.iml 5 | .gradle/ 6 | 7 | # Compiled class file 8 | *.class 9 | 10 | # Log file 11 | *.log 12 | 13 | # BlueJ files 14 | *.ctxt 15 | 16 | # Mobile Tools for Java (J2ME) 17 | .mtj.tmp/ 18 | 19 | # Package Files # 20 | *.jar 21 | *.war 22 | *.nar 23 | *.ear 24 | *.zip 25 | *.tar.gz 26 | *.rar 27 | 28 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 29 | hs_err_pid* 30 | 31 | # Make sure we don't ignore gradle wrapper 32 | !gradle-wrapper.jar -------------------------------------------------------------------------------- /frontend/lee_fishing/users/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from lee_fishing.users.views import ( 4 | user_list_view, 5 | user_redirect_view, 6 | user_update_view, 7 | user_detail_view, 8 | ) 9 | 10 | app_name = "users" 11 | urlpatterns = [ 12 | path("", view=user_list_view, name="list"), 13 | path("~redirect/", view=user_redirect_view, name="redirect"), 14 | path("~update/", view=user_update_view, name="update"), 15 | path("/", view=user_detail_view, name="detail"), 16 | ] 17 | -------------------------------------------------------------------------------- /frontend/lee_fishing/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from django.conf import settings 3 | from django.test import RequestFactory 4 | 5 | from lee_fishing.users.tests.factories import UserFactory 6 | 7 | 8 | @pytest.fixture(autouse=True) 9 | def media_storage(settings, tmpdir): 10 | settings.MEDIA_ROOT = tmpdir.strpath 11 | 12 | 13 | @pytest.fixture 14 | def user() -> settings.AUTH_USER_MODEL: 15 | return UserFactory() 16 | 17 | 18 | @pytest.fixture 19 | def request_factory() -> RequestFactory: 20 | return RequestFactory() 21 | -------------------------------------------------------------------------------- /frontend/lee_fishing/fishing_equipment/migrations/0006_auto_20181010_0920.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.8 on 2018-10-10 09:20 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('fishing_equipment', '0005_auto_20181009_0554'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterModelOptions( 14 | name='category', 15 | options={'ordering': ['title'], 'verbose_name_plural': 'Categories'}, 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /null_lambda/.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | out/ 3 | .idea/ 4 | *.iml 5 | .gradle/ 6 | 7 | # Compiled class file 8 | *.class 9 | 10 | # Log file 11 | *.log 12 | 13 | # BlueJ files 14 | *.ctxt 15 | 16 | # Mobile Tools for Java (J2ME) 17 | .mtj.tmp/ 18 | 19 | # Package Files # 20 | *.jar 21 | *.war 22 | *.nar 23 | *.ear 24 | *.zip 25 | *.tar.gz 26 | *.rar 27 | 28 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 29 | hs_err_pid* 30 | 31 | # Make sure we don't ignore gradle wrapper 32 | !gradle-wrapper.jar -------------------------------------------------------------------------------- /frontend/lee_fishing/templates/account/verification_sent.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | 5 | {% block head_title %}{% trans "Verify Your E-mail Address" %}{% endblock %} 6 | 7 | {% block inner %} 8 |

{% trans "Verify Your E-mail Address" %}

9 | 10 |

{% blocktrans %}We have sent an e-mail to you for verification. Follow the link provided to finalize the signup process. Please contact us if you do not receive it within a few minutes.{% endblocktrans %}

11 | 12 | {% endblock %} 13 | 14 | -------------------------------------------------------------------------------- /frontend/lee_fishing/templates/users/user_list.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load static i18n %} 3 | {% block title %}Members{% endblock %} 4 | 5 | {% block content %} 6 |
7 |

Users

8 | 9 |
10 | {% for user in user_list %} 11 | 12 |

{{ user.username }}

13 |
14 | {% endfor %} 15 |
16 |
17 | {% endblock content %} 18 | -------------------------------------------------------------------------------- /sqs_order_forwarder/.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | out/ 3 | .idea/ 4 | *.iml 5 | .gradle/ 6 | 7 | # Compiled class file 8 | *.class 9 | 10 | # Log file 11 | *.log 12 | 13 | # BlueJ files 14 | *.ctxt 15 | 16 | # Mobile Tools for Java (J2ME) 17 | .mtj.tmp/ 18 | 19 | # Package Files # 20 | *.jar 21 | *.war 22 | *.nar 23 | *.ear 24 | *.zip 25 | *.tar.gz 26 | *.rar 27 | 28 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 29 | hs_err_pid* 30 | 31 | # Make sure we don't ignore gradle wrapper 32 | !gradle-wrapper.jar -------------------------------------------------------------------------------- /frontend/lee_fishing/fishing_net/migrations/0002_auto_20181009_0554.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.8 on 2018-10-09 05:54 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('fishing_equipment', '0005_auto_20181009_0554'), 10 | ('fishing_net', '0001_initial'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterUniqueTogether( 15 | name='basketitem', 16 | unique_together={('basket', 'item_sku')}, 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /frontend/lee_fishing/fishing_net/migrations/0003_basket_order_id.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.8 on 2018-10-10 09:20 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('fishing_net', '0002_auto_20181009_0554'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='basket', 15 | name='order_id', 16 | field=models.TextField(blank=True, max_length=500, null=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /frontend/lee_fishing/users/models.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import AbstractUser 2 | from django.db.models import CharField 3 | from django.urls import reverse 4 | from django.utils.translation import ugettext_lazy as _ 5 | 6 | 7 | class User(AbstractUser): 8 | 9 | # First Name and Last Name do not cover name patterns 10 | # around the globe. 11 | name = CharField(_("Name of User"), blank=True, max_length=255) 12 | 13 | def get_absolute_url(self): 14 | return reverse("users:detail", kwargs={"username": self.username}) 15 | -------------------------------------------------------------------------------- /frontend_minisite/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | browser: true, 5 | jquery: true 6 | }, 7 | parserOptions: { 8 | parser: 'babel-eslint', 9 | }, 10 | extends: [ 11 | 'plugin:vue/recommended', 12 | 'plugin:prettier/recommended' 13 | ], 14 | plugins: [ 15 | 'vue', 16 | 'prettier' 17 | ], 18 | rules: { 19 | 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 20 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | alabaster==0.7.12 2 | Babel==2.6.0 3 | certifi==2018.8.24 4 | chardet==3.0.4 5 | doc8==0.8.0 6 | docutils==0.14 7 | idna==2.7 8 | imagesize==1.1.0 9 | Jinja2==2.10.1 10 | MarkupSafe==1.0 11 | packaging==18.0 12 | pbr==5.1.0 13 | Pygments==2.2.0 14 | pyparsing==2.2.2 15 | pytz==2018.5 16 | requests==2.20.0 17 | restructuredtext-lint==1.1.3 18 | six==1.11.0 19 | snowballstemmer==1.2.1 20 | Sphinx==1.8.1 21 | sphinx-rtd-theme==0.4.2 22 | sphinx-tabs==1.1.9 23 | sphinxcontrib-websupport==1.1.0 24 | stevedore==1.30.0 25 | urllib3==1.24.2 26 | -------------------------------------------------------------------------------- /frontend/utility/requirements-trusty.apt: -------------------------------------------------------------------------------- 1 | ##basic build dependencies of various Django apps for Ubuntu Trusty 14.04 2 | #build-essential metapackage install: make, gcc, g++, 3 | build-essential 4 | #required to translate 5 | gettext 6 | python3-dev 7 | 8 | ##shared dependencies of: 9 | ##Pillow, pylibmc 10 | zlib1g-dev 11 | 12 | ##Postgresql and psycopg2 dependencies 13 | libpq-dev 14 | 15 | ##Pillow dependencies 16 | libtiff4-dev 17 | libjpeg8-dev 18 | libfreetype6-dev 19 | liblcms1-dev 20 | libwebp-dev 21 | 22 | ##django-extensions 23 | graphviz-dev 24 | -------------------------------------------------------------------------------- /frontend/utility/requirements-xenial.apt: -------------------------------------------------------------------------------- 1 | ##basic build dependencies of various Django apps for Ubuntu Xenial 16.04 2 | #build-essential metapackage install: make, gcc, g++, 3 | build-essential 4 | #required to translate 5 | gettext 6 | python3-dev 7 | 8 | ##shared dependencies of: 9 | ##Pillow, pylibmc 10 | zlib1g-dev 11 | 12 | ##Postgresql and psycopg2 dependencies 13 | libpq-dev 14 | 15 | ##Pillow dependencies 16 | libtiff5-dev 17 | libjpeg8-dev 18 | libfreetype6-dev 19 | liblcms2-dev 20 | libwebp-dev 21 | 22 | ##django-extensions 23 | graphviz-dev 24 | -------------------------------------------------------------------------------- /frontend/utility/requirements-jessie.apt: -------------------------------------------------------------------------------- 1 | ##basic build dependencies of various Django apps for Debian Jessie 8.x 2 | #build-essential metapackage install: make, gcc, g++, 3 | build-essential 4 | #required to translate 5 | gettext 6 | python3-dev 7 | 8 | ##shared dependencies of: 9 | ##Pillow, pylibmc 10 | zlib1g-dev 11 | 12 | ##Postgresql and psycopg2 dependencies 13 | libpq-dev 14 | 15 | ##Pillow dependencies 16 | libtiff5-dev 17 | libjpeg62-turbo-dev 18 | libfreetype6-dev 19 | liblcms2-dev 20 | libwebp-dev 21 | 22 | ##django-extensions 23 | graphviz-dev 24 | -------------------------------------------------------------------------------- /frontend/utility/requirements-stretch.apt: -------------------------------------------------------------------------------- 1 | ##basic build dependencies of various Django apps for Debian Jessie 9.x 2 | #build-essential metapackage install: make, gcc, g++, 3 | build-essential 4 | #required to translate 5 | gettext 6 | python3-dev 7 | 8 | ##shared dependencies of: 9 | ##Pillow, pylibmc 10 | zlib1g-dev 11 | 12 | ##Postgresql and psycopg2 dependencies 13 | libpq-dev 14 | 15 | ##Pillow dependencies 16 | libtiff5-dev 17 | libjpeg62-turbo-dev 18 | libfreetype6-dev 19 | liblcms2-dev 20 | libwebp-dev 21 | 22 | ##django-extensions 23 | graphviz-dev 24 | -------------------------------------------------------------------------------- /frontend/lee_fishing/templates/users/user_form.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load crispy_forms_tags %} 3 | 4 | {% block title %}{{ user.username }}{% endblock %} 5 | 6 | {% block content %} 7 |

{{ user.username }}

8 |
9 | {% csrf_token %} 10 | {{ form|crispy }} 11 |
12 |
13 | 14 |
15 |
16 |
17 | {% endblock %} 18 | -------------------------------------------------------------------------------- /frontend/lee_fishing/templates/account/password_set.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | {% load crispy_forms_tags %} 5 | 6 | {% block head_title %}{% trans "Set Password" %}{% endblock %} 7 | 8 | {% block inner %} 9 |

{% trans "Set Password" %}

10 | 11 |
12 | {% csrf_token %} 13 | {{ form|crispy }} 14 | 15 |
16 | {% endblock %} 17 | 18 | -------------------------------------------------------------------------------- /frontend/lee_fishing/templates/account/password_reset_done.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | {% load account %} 5 | 6 | {% block head_title %}{% trans "Password Reset" %}{% endblock %} 7 | 8 | {% block inner %} 9 |

{% trans "Password Reset" %}

10 | 11 | {% if user.is_authenticated %} 12 | {% include "account/snippets/already_logged_in.html" %} 13 | {% endif %} 14 | 15 |

{% blocktrans %}We have sent you an e-mail. Please contact us if you do not receive it within a few minutes.{% endblocktrans %}

16 | {% endblock %} 17 | 18 | -------------------------------------------------------------------------------- /frontend/lee_fishing/fishing_equipment/migrations/0002_auto_20181002_2025.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.8 on 2018-10-02 20:25 2 | 3 | from django.db import migrations 4 | import imagekit.models.fields 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('fishing_equipment', '0001_initial'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='product', 16 | name='smaller_picture', 17 | field=imagekit.models.fields.ProcessedImageField(upload_to='product_thumbs'), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /frontend/docs/index.rst: -------------------------------------------------------------------------------- 1 | .. Lee Fishing documentation master file, created by 2 | sphinx-quickstart. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to Lee Fishing's documentation! 7 | ==================================================================== 8 | 9 | Contents: 10 | 11 | .. toctree:: 12 | :maxdepth: 2 13 | 14 | install 15 | deploy 16 | docker_ec2 17 | tests 18 | 19 | 20 | 21 | Indices and tables 22 | ================== 23 | 24 | * :ref:`genindex` 25 | * :ref:`modindex` 26 | * :ref:`search` 27 | -------------------------------------------------------------------------------- /frontend/lee_fishing/templates/account/password_change.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | {% load crispy_forms_tags %} 5 | 6 | {% block head_title %}{% trans "Change Password" %}{% endblock %} 7 | 8 | {% block inner %} 9 |

{% trans "Change Password" %}

10 | 11 |
12 | {% csrf_token %} 13 | {{ form|crispy }} 14 | 15 |
16 | {% endblock %} 17 | 18 | -------------------------------------------------------------------------------- /frontend/lee_fishing/users/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.contrib.auth import admin as auth_admin 3 | from django.contrib.auth import get_user_model 4 | 5 | from lee_fishing.users.forms import UserChangeForm, UserCreationForm 6 | 7 | User = get_user_model() 8 | 9 | 10 | @admin.register(User) 11 | class UserAdmin(auth_admin.UserAdmin): 12 | 13 | form = UserChangeForm 14 | add_form = UserCreationForm 15 | fieldsets = (("User", {"fields": ("name",)}),) + auth_admin.UserAdmin.fieldsets 16 | list_display = ["username", "name", "is_superuser"] 17 | search_fields = ["name"] 18 | -------------------------------------------------------------------------------- /frontend/setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 120 3 | exclude = .tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules 4 | 5 | [pycodestyle] 6 | max-line-length = 120 7 | exclude = .tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules 8 | 9 | [mypy] 10 | python_version = 3.6 11 | check_untyped_defs = True 12 | ignore_errors = False 13 | ignore_missing_imports = True 14 | strict_optional = True 15 | warn_unused_ignores = True 16 | warn_redundant_casts = True 17 | warn_unused_configs = True 18 | 19 | [mypy-*.migrations.*] 20 | # Django migrations should not produce any errors: 21 | ignore_errors = True 22 | -------------------------------------------------------------------------------- /frontend/.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.{py,rst,ini}] 12 | indent_style = space 13 | indent_size = 4 14 | 15 | [*.py] 16 | line_length=120 17 | known_first_party=lee_fishing 18 | multi_line_output=3 19 | default_section=THIRDPARTY 20 | 21 | [*.{html,css,scss,json,yml}] 22 | indent_style = space 23 | indent_size = 2 24 | 25 | [*.md] 26 | trim_trailing_whitespace = false 27 | 28 | [Makefile] 29 | indent_style = tab 30 | 31 | [nginx.conf] 32 | indent_style = space 33 | indent_size = 2 34 | -------------------------------------------------------------------------------- /frontend/lee_fishing/fishing_net/backend.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | import requests 3 | 4 | 5 | class BackendClient(object): 6 | def __init__(self): 7 | if settings.BACKEND_DOMAIN.startswith("localhost"): 8 | self._url = 'http://localhost:8080' 9 | else: 10 | self._url = ''.join([settings.BACKEND_PROTOCOL, '://', settings.BACKEND_DOMAIN, settings.BACKEND_URI_PREFIX]) 11 | 12 | def place_order(self): 13 | r = requests.post(self._url + '/order', json={}) 14 | return r.text 15 | 16 | def ping(self): 17 | r = requests.get(self._url + '/ping') 18 | return r.text 19 | -------------------------------------------------------------------------------- /infrastructure/cdk.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": "/bin/bash ./app.sh", 3 | "context": { 4 | "availability-zones:965191607613:eu-west-1": [ 5 | "eu-west-1a", 6 | "eu-west-1b", 7 | "eu-west-1c" 8 | ], 9 | "ssm:965191607613:eu-west-1:/aws/service/ami-amazon-linux-latest/amzn-ami-hvm-x86_64-gp2": "ami-047bb4163c506cd98", 10 | "availability-zones:account=965191607613:region=eu-west-1": [ 11 | "eu-west-1a", 12 | "eu-west-1b", 13 | "eu-west-1c" 14 | ], 15 | "ssm:account=965191607613:parameterName=/aws/service/ami-amazon-linux-latest/amzn-ami-hvm-x86_64-gp2:region=eu-west-1": "ami-047bb4163c506cd98" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SOURCEDIR = . 8 | BUILDDIR = _build 9 | 10 | # Put it first so that "make" without argument is like "make help". 11 | help: 12 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 13 | 14 | .PHONY: help Makefile 15 | 16 | # Catch-all target: route all unknown targets to Sphinx using the new 17 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 18 | %: Makefile 19 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /frontend/lee_fishing/users/management/commands/make_user.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from django.core.management.base import BaseCommand 4 | from django.contrib.auth import get_user_model 5 | User = get_user_model() 6 | 7 | 8 | class Command(BaseCommand): 9 | def handle(self, *args, **options): 10 | username = os.environ['DEFAULT_USER'] 11 | email = os.environ['DEFAULT_EMAIL'] 12 | password = os.environ['DEFAULT_PASSWORD'] 13 | if not User.objects.filter(username=username).exists(): 14 | User.objects.create_superuser( 15 | username, 16 | email, 17 | password 18 | ) 19 | -------------------------------------------------------------------------------- /frontend/lee_fishing/templates/account/logout.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | 5 | {% block head_title %}{% trans "Sign Out" %}{% endblock %} 6 | 7 | {% block inner %} 8 |

{% trans "Sign Out" %}

9 | 10 |

{% trans 'Are you sure you want to sign out?' %}

11 | 12 |
13 | {% csrf_token %} 14 | {% if redirect_field_value %} 15 | 16 | {% endif %} 17 | 18 |
19 | 20 | 21 | {% endblock %} 22 | 23 | -------------------------------------------------------------------------------- /frontend/lee_fishing/contrib/sites/migrations/0002_alter_domain_unique.py: -------------------------------------------------------------------------------- 1 | import django.contrib.sites.models 2 | from django.db import migrations, models 3 | 4 | 5 | class Migration(migrations.Migration): 6 | 7 | dependencies = [("sites", "0001_initial")] 8 | 9 | operations = [ 10 | migrations.AlterField( 11 | model_name="site", 12 | name="domain", 13 | field=models.CharField( 14 | max_length=100, 15 | unique=True, 16 | validators=[django.contrib.sites.models._simple_domain_name_validator], 17 | verbose_name="domain name", 18 | ), 19 | ) 20 | ] 21 | -------------------------------------------------------------------------------- /frontend/lee_fishing/users/adapters.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from allauth.account.adapter import DefaultAccountAdapter 4 | from allauth.socialaccount.adapter import DefaultSocialAccountAdapter 5 | from django.conf import settings 6 | from django.http import HttpRequest 7 | 8 | 9 | class AccountAdapter(DefaultAccountAdapter): 10 | 11 | def is_open_for_signup(self, request: HttpRequest): 12 | return getattr(settings, "ACCOUNT_ALLOW_REGISTRATION", True) 13 | 14 | 15 | class SocialAccountAdapter(DefaultSocialAccountAdapter): 16 | 17 | def is_open_for_signup(self, request: HttpRequest, sociallogin: Any): 18 | return getattr(settings, "ACCOUNT_ALLOW_REGISTRATION", True) 19 | -------------------------------------------------------------------------------- /infrastructure/src/main/java/fishing/lee/infrastructure/Decoupling.java: -------------------------------------------------------------------------------- 1 | package fishing.lee.infrastructure; 2 | 3 | import software.amazon.awscdk.Construct; 4 | import software.amazon.awscdk.services.sns.Topic; 5 | import software.amazon.awscdk.services.sns.TopicProps; 6 | import software.amazon.awscdk.services.sqs.Queue; 7 | import software.amazon.awscdk.services.sqs.QueueProps; 8 | 9 | class Decoupling extends Construct { 10 | Decoupling(Construct parent, String id) { 11 | super(parent, id); 12 | 13 | new Queue(this, "Queue", QueueProps.builder() 14 | .build()); 15 | 16 | new Topic(this, "Topic", TopicProps.builder() 17 | .build()); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /frontend/lee_fishing/users/middleware.py: -------------------------------------------------------------------------------- 1 | 2 | class DontCacheAuthenticatedMiddleware(object): 3 | """ 4 | This is a very crude middleware - but we just assume 5 | that if you're logged in that things are not cached. 6 | """ 7 | def __init__(self, get_response): 8 | self.get_response = get_response 9 | 10 | def __call__(self, request): 11 | response = self.get_response(request) 12 | response['Cache-Control'] = 'public, max-age=60' 13 | 14 | try: 15 | if request.user.is_authenticated: 16 | response['Cache-Control'] = 'private' 17 | 18 | except AttributeError: 19 | response['Cache-Control'] = 'private' 20 | 21 | return response 22 | -------------------------------------------------------------------------------- /frontend/lee_fishing/fishing_net/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from lee_fishing.fishing_net.views import ( 4 | BasketDetailView, 5 | BasketDeleteItemView, 6 | BasketAddItemView, 7 | CheckoutView, 8 | DummyView, 9 | CongratulationsView) 10 | 11 | app_name = "basket" 12 | urlpatterns = [ 13 | path("", view=BasketDetailView.as_view(), name="basket"), 14 | path("add/", view=BasketAddItemView.as_view(), name="add_item"), 15 | path("delete/", view=BasketDeleteItemView.as_view(), name="delete_item"), 16 | path("checkout", view=CheckoutView.as_view(), name="checkout"), 17 | path("bye", view=CongratulationsView.as_view(), name="congrats"), 18 | path("ping", view=DummyView.as_view(), name="ping") 19 | ] 20 | -------------------------------------------------------------------------------- /infrastructure/src/main/java/fishing/lee/infrastructure/DeploymentAssets.java: -------------------------------------------------------------------------------- 1 | package fishing.lee.infrastructure; 2 | 3 | import software.amazon.awscdk.Construct; 4 | import software.amazon.awscdk.Output; 5 | import software.amazon.awscdk.OutputProps; 6 | import software.amazon.awscdk.services.s3.Bucket; 7 | import software.amazon.awscdk.services.s3.BucketProps; 8 | 9 | class DeploymentAssets extends Construct { 10 | DeploymentAssets(Construct parent, String id) { 11 | super(parent, id); 12 | 13 | Bucket bucket = new Bucket(this, "Bucket", BucketProps.builder() 14 | .build()); 15 | 16 | new Output(this, "DeploymentBucket", OutputProps.builder() 17 | .withValue(bucket.getBucketName()) 18 | .build()); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /frontend/lee_fishing/static/sass/project.scss: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | // project specific CSS goes here 6 | 7 | //////////////////////////////// 8 | //Variables// 9 | //////////////////////////////// 10 | 11 | // Alert colors 12 | 13 | $white: #fff; 14 | $mint-green: #d6e9c6; 15 | $black: #000; 16 | $pink: #f2dede; 17 | $dark-pink: #eed3d7; 18 | $red: #b94a48; 19 | 20 | //////////////////////////////// 21 | //Alerts// 22 | //////////////////////////////// 23 | 24 | // bootstrap alert CSS, translated to the django-standard levels of 25 | // debug, info, success, warning, error 26 | 27 | .alert-debug { 28 | background-color: $white; 29 | border-color: $mint-green; 30 | color: $black; 31 | } 32 | 33 | .alert-error { 34 | background-color: $pink; 35 | border-color: $dark-pink; 36 | color: $red; 37 | } 38 | -------------------------------------------------------------------------------- /frontend/requirements/production.txt: -------------------------------------------------------------------------------- 1 | # PRECAUTION: avoid production dependencies that aren't in development 2 | 3 | -r ./base.txt 4 | 5 | gunicorn==19.8.1 # https://github.com/benoitc/gunicorn 6 | psycopg2==2.7.4 --no-binary psycopg2 # https://github.com/psycopg/psycopg2 7 | Collectfast==0.6.2 # https://github.com/antonagestam/collectfast 8 | 9 | # Django 10 | # ------------------------------------------------------------------------------ 11 | django-storages[boto3]==1.7.1 # https://github.com/jschneier/django-storages 12 | django-anymail[mailgun]==4.2 # https://github.com/anymail/django-anymail 13 | 14 | django-debug-toolbar==1.10.1 # https://github.com/jazzband/django-debug-toolbar 15 | django-extensions==2.1.2 # https://github.com/django-extensions/django-extensions 16 | 17 | # Xray 18 | aws-xray-sdk==2.1.0 19 | -------------------------------------------------------------------------------- /frontend/lee_fishing/fishing_equipment/migrations/0003_auto_20181002_2026.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.8 on 2018-10-02 20:26 2 | 3 | from django.db import migrations, models 4 | import imagekit.models.fields 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('fishing_equipment', '0002_auto_20181002_2025'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='product', 16 | name='picture', 17 | field=models.ImageField(null=True, upload_to='products'), 18 | ), 19 | migrations.AlterField( 20 | model_name='product', 21 | name='smaller_picture', 22 | field=imagekit.models.fields.ProcessedImageField(null=True, upload_to='product_thumbs'), 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /frontend/lee_fishing/templates/account/signup.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | {% load crispy_forms_tags %} 5 | 6 | {% block head_title %}{% trans "Signup" %}{% endblock %} 7 | 8 | {% block inner %} 9 |

{% trans "Sign Up" %}

10 | 11 |

{% blocktrans %}Already have an account? Then please sign in.{% endblocktrans %}

12 | 13 | 21 | 22 | {% endblock %} 23 | 24 | -------------------------------------------------------------------------------- /frontend_minisite/views/pages/index.hbs: -------------------------------------------------------------------------------- 1 | {{#extend "layout"}} 2 | 10 | 11 |
12 |

Muskie Casting Rod

13 |

Hunting down big muskies requires a rod that can handle big, heavy rigs and crushing hits. Fast-action, SCII graphite, with medium-heavy to extra-heavy power, tames hard-thrashing fish. All have aluminum-oxide guides, tip tops with zirconium inserts, premium-grade cork split-grip handles and DPS reel seats.

14 | 15 |
16 | 17 |
18 | 19 | 20 |
21 | {{/extend}} 22 | -------------------------------------------------------------------------------- /frontend/lee_fishing/fishing_equipment/migrations/0004_auto_20181005_0919.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.8 on 2018-10-05 09:19 2 | 3 | from django.db import migrations, models 4 | import imagekit.models.fields 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('fishing_equipment', '0003_auto_20181002_2026'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='product', 16 | name='picture', 17 | field=models.ImageField(blank=True, null=True, upload_to='products'), 18 | ), 19 | migrations.AlterField( 20 | model_name='product', 21 | name='smaller_picture', 22 | field=imagekit.models.fields.ProcessedImageField(blank=True, null=True, upload_to='product_thumbs'), 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /frontend_minisite/views/helpers/extend.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const handlebars = require('handlebars') 3 | 4 | handlebars.registerPartial( 5 | 'header', 6 | fs.readFileSync('./views/partials/header.hbs', 'utf8') 7 | ) 8 | handlebars.registerPartial( 9 | 'footer', 10 | fs.readFileSync('./views/partials/footer.hbs', 'utf8') 11 | ) 12 | 13 | module.exports = (name, context, options) => { 14 | const htmlWebpackPluginOptions = context.data.root.htmlWebpackPlugin.options 15 | 16 | handlebars.registerHelper('active-route', (name, context) => { 17 | return name === htmlWebpackPluginOptions.filename ? 'active' : '' 18 | }) 19 | 20 | const layout = handlebars.compile( 21 | fs.readFileSync(`./views/${name}.hbs`, 'utf8') 22 | ) 23 | 24 | return layout({ 25 | title: htmlWebpackPluginOptions.title, 26 | body: context.fn(this) 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /frontend/lee_fishing/users/tests/factories.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Sequence 2 | 3 | from django.contrib.auth import get_user_model 4 | from factory import DjangoModelFactory, Faker, post_generation 5 | 6 | 7 | class UserFactory(DjangoModelFactory): 8 | 9 | username = Faker("user_name") 10 | email = Faker("email") 11 | name = Faker("name") 12 | 13 | @post_generation 14 | def password(self, create: bool, extracted: Sequence[Any], **kwargs): 15 | password = Faker( 16 | "password", 17 | length=42, 18 | special_chars=True, 19 | digits=True, 20 | upper_case=True, 21 | lower_case=True, 22 | ).generate( 23 | extra_kwargs={} 24 | ) 25 | self.set_password(password) 26 | 27 | class Meta: 28 | model = get_user_model() 29 | django_get_or_create = ["username"] 30 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /frontend/lee_fishing/templates/fishing_equipment/product_detail.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% load static i18n %} 4 | 5 | {% block title %}{{ object.title }}{% endblock %} 6 | 7 | {% block content %} 8 |
9 |
10 |   11 |
12 |
13 |
14 | Obvious Placeholder Cage Image 15 |
16 |
17 |

{{ object.title }}

18 | Add to Basket 19 |
20 |
21 |
22 |
23 |   24 |
25 |
26 |
27 |
28 | {{ object.description }} 29 |
30 |
31 |
32 | {% endblock %} 33 | -------------------------------------------------------------------------------- /frontend/lee_fishing/users/tests/test_urls.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from django.conf import settings 3 | from django.urls import reverse, resolve 4 | 5 | pytestmark = pytest.mark.django_db 6 | 7 | 8 | def test_detail(user: settings.AUTH_USER_MODEL): 9 | assert ( 10 | reverse("users:detail", kwargs={"username": user.username}) 11 | == f"/users/{user.username}/" 12 | ) 13 | assert resolve(f"/users/{user.username}/").view_name == "users:detail" 14 | 15 | 16 | def test_list(): 17 | assert reverse("users:list") == "/users/" 18 | assert resolve("/users/").view_name == "users:list" 19 | 20 | 21 | def test_update(): 22 | assert reverse("users:update") == "/users/~update/" 23 | assert resolve("/users/~update/").view_name == "users:update" 24 | 25 | 26 | def test_redirect(): 27 | assert reverse("users:redirect") == "/users/~redirect/" 28 | assert resolve("/users/~redirect/").view_name == "users:redirect" 29 | -------------------------------------------------------------------------------- /frontend/lee_fishing/templates/account/verified_email_required.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | 5 | {% block head_title %}{% trans "Verify Your E-mail Address" %}{% endblock %} 6 | 7 | {% block inner %} 8 |

{% trans "Verify Your E-mail Address" %}

9 | 10 | {% url 'account_email' as email_url %} 11 | 12 |

{% blocktrans %}This part of the site requires us to verify that 13 | you are who you claim to be. For this purpose, we require that you 14 | verify ownership of your e-mail address. {% endblocktrans %}

15 | 16 |

{% blocktrans %}We have sent an e-mail to you for 17 | verification. Please click on the link inside this e-mail. Please 18 | contact us if you do not receive it within a few minutes.{% endblocktrans %}

19 | 20 |

{% blocktrans %}Note: you can still change your e-mail address.{% endblocktrans %}

21 | 22 | 23 | {% endblock %} 24 | 25 | -------------------------------------------------------------------------------- /frontend/lee_fishing/templates/users/user_detail.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load static %} 3 | 4 | {% block title %}User: {{ object.username }}{% endblock %} 5 | 6 | {% block content %} 7 |
8 | 9 |
10 |
11 | 12 |

{{ object.username }}

13 | {% if object.name %} 14 |

{{ object.name }}

15 | {% endif %} 16 |
17 |
18 | 19 | {% if object == request.user %} 20 | 21 |
22 | 23 |
24 | My Info 25 | E-Mail 26 | 27 |
28 | 29 |
30 | 31 | {% endif %} 32 | 33 | 34 |
35 | {% endblock content %} 36 | 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 4 | software and associated documentation files (the "Software"), to deal in the Software 5 | without restriction, including without limitation the rights to use, copy, modify, 6 | merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 7 | permit persons to whom the Software is furnished to do so. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 10 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 11 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 12 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 13 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 14 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | -------------------------------------------------------------------------------- /frontend/lee_fishing/users/forms.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth import get_user_model, forms 2 | from django.core.exceptions import ValidationError 3 | from django.utils.translation import ugettext_lazy as _ 4 | 5 | User = get_user_model() 6 | 7 | 8 | class UserChangeForm(forms.UserChangeForm): 9 | 10 | class Meta(forms.UserChangeForm.Meta): 11 | model = User 12 | 13 | 14 | class UserCreationForm(forms.UserCreationForm): 15 | 16 | error_message = forms.UserCreationForm.error_messages.update( 17 | {"duplicate_username": _("This username has already been taken.")} 18 | ) 19 | 20 | class Meta(forms.UserCreationForm.Meta): 21 | model = User 22 | 23 | def clean_username(self): 24 | username = self.cleaned_data["username"] 25 | 26 | try: 27 | User.objects.get(username=username) 28 | except User.DoesNotExist: 29 | return username 30 | 31 | raise ValidationError(self.error_messages["duplicate_username"]) 32 | -------------------------------------------------------------------------------- /lambda_at_edge/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Add index.html to the source uri to handle a default URI 5 | * in a single behaviour with other content. 6 | * 7 | * Taken from Ronnie Eichler's blog post: 8 | * https://aws.amazon.com/blogs/compute/implementing-default-directory-indexes-in-amazon-s3-backed-amazon-cloudfront-origins-using-lambdaedge/ 9 | */ 10 | exports.index_rewrite = (event, context, callback) => { 11 | // Extract the request from the CloudFront event that is sent to Lambda@Edge 12 | var request = event.Records[0].cf.request; 13 | 14 | // Extract the URI from the request 15 | var olduri = request.uri; 16 | 17 | // Match any '/' that occurs at the end of a URI. Replace it with a default index 18 | var newuri = olduri.replace(/\/$/, '\/index.html'); 19 | 20 | // Replace the received URI with the URI that includes the index page 21 | request.uri = newuri; 22 | 23 | // Return to CloudFront 24 | return callback(null, request); 25 | }; 26 | -------------------------------------------------------------------------------- /frontend/lee_fishing/templates/account/password_reset.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | {% load account %} 5 | {% load crispy_forms_tags %} 6 | 7 | {% block head_title %}{% trans "Password Reset" %}{% endblock %} 8 | 9 | {% block inner %} 10 | 11 |

{% trans "Password Reset" %}

12 | {% if user.is_authenticated %} 13 | {% include "account/snippets/already_logged_in.html" %} 14 | {% endif %} 15 | 16 |

{% trans "Forgotten your password? Enter your e-mail address below, and we'll send you an e-mail allowing you to reset it." %}

17 | 18 |
19 | {% csrf_token %} 20 | {{ form|crispy }} 21 | 22 |
23 | 24 |

{% blocktrans %}Please contact us if you have any trouble resetting your password.{% endblocktrans %}

25 | {% endblock %} 26 | 27 | -------------------------------------------------------------------------------- /backend/src/main/java/fishing/lee/backend/OrderController.java: -------------------------------------------------------------------------------- 1 | package fishing.lee.backend; 2 | 3 | import de.mkammerer.argon2.Argon2; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | import org.springframework.web.bind.annotation.PathVariable; 7 | import org.springframework.web.bind.annotation.PostMapping; 8 | import org.springframework.web.bind.annotation.RestController; 9 | 10 | import java.util.UUID; 11 | 12 | @RestController 13 | public class OrderController { 14 | private final Argon2 argon; 15 | 16 | @Autowired 17 | public OrderController(Argon2 argon) { 18 | this.argon = argon; 19 | } 20 | 21 | @GetMapping("/order/{id}") 22 | String one(@PathVariable UUID id) { 23 | return "whatever"; 24 | } 25 | 26 | @PostMapping("/order") 27 | String post() { 28 | return argon.hash(75, 65536, Math.min(Runtime.getRuntime().availableProcessors() / 2, 1), "flibble"); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /docs/exercise_2_post.rst: -------------------------------------------------------------------------------- 1 | Exercise #2 - Checklist for completion 2 | ====================================== 3 | 4 | 1. Pop over to the CloudFront service in the Console and copy the domain name 5 | for the Distribution and load it up in a new tab. 6 | 2. Click Rods and then the Muskie Casting Rod 7 | 3. Buy the item (it will make you sign up etc.) 8 | 9 | .. Attention:: **DO NOT ENTER CREDIT CARD INFORMATION ON THIS FORM** 10 | 11 | Below you can see a picture of how are architecture looks now. The area with 12 | the green background is the area that has changed since our initial deployment. 13 | 14 | .. image:: images/exercise_2_arch.png 15 | :height: 600px 16 | :align: center 17 | 18 | Because we have replaced the existing service with an Amazon API Gateway and 19 | a Lambda Function, we have more flexibity to override methods in our existing 20 | service and benefit from AWS Lambda being in control of scaling. Because AWS 21 | Lambda takes care of scaling for you, there's no need to configure Auto Scaling 22 | Groups. 23 | -------------------------------------------------------------------------------- /infrastructure/src/main/java/fishing/lee/infrastructure/PostgresProps.java: -------------------------------------------------------------------------------- 1 | package fishing.lee.infrastructure; 2 | 3 | import java.util.Objects; 4 | 5 | interface PostgresProps { 6 | ShopVpc getShopVpc(); 7 | 8 | static PostgresProps.Builder builder() { 9 | return new PostgresProps.Builder(); 10 | } 11 | 12 | final class Builder { 13 | ShopVpc _shopVpc; 14 | 15 | PostgresProps.Builder withShopVpc(ShopVpc shopVpc) { 16 | this._shopVpc = shopVpc; 17 | return this; 18 | } 19 | 20 | PostgresProps build() { 21 | return new PostgresProps() { 22 | private ShopVpc shopVpc; 23 | 24 | { 25 | shopVpc = Objects.requireNonNull(Builder.this._shopVpc, "shopVpc Required"); 26 | } 27 | 28 | @Override 29 | public ShopVpc getShopVpc() { 30 | return this.shopVpc; 31 | } 32 | }; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /frontend_minisite/README.md: -------------------------------------------------------------------------------- 1 | # HTML Boilerplate 2 | 3 | > Frontend HTML boilerplate based on Bootstrap 4, Webpack for assets management, and Handlebars as webpack html template engine. Useful for quick interactive mockup development, easily transposable to any Backend Framework. 4 | 5 | ## Frontend features 6 | 7 | * HTML5 boilerplate with basic home-about-contact pages 8 | * Bootstrap 4 beta 9 | * Slick Carrousel 10 | * Sweet Alert 2 11 | * Vue.js 12 | 13 | ## Quick start 14 | 15 | ```shell 16 | yarn 17 | yarn dev 18 | ``` 19 | 20 | Just as easy ! 21 | Web site should be accessible from localhost:3000. 22 | Port is configurable by `.env` file settings (just copy `.env.example`). 23 | 24 | ## Development 25 | 26 | ### Commands 27 | 28 | * `yarn dev` 29 | Start Webpack assets compilation and express server with hot reloading support. 30 | 31 | * `yarn build` 32 | Compile all JS application into redistributable static files into `dist` folder. 33 | 34 | ## License 35 | 36 | This project is open-sourced software licensed under the [MIT license](https://adr1enbe4udou1n.mit-license.org). 37 | -------------------------------------------------------------------------------- /frontend/lee_fishing/templates/account/password_reset_from_key.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | {% load crispy_forms_tags %} 5 | {% block head_title %}{% trans "Change Password" %}{% endblock %} 6 | 7 | {% block inner %} 8 |

{% if token_fail %}{% trans "Bad Token" %}{% else %}{% trans "Change Password" %}{% endif %}

9 | 10 | {% if token_fail %} 11 | {% url 'account_reset_password' as passwd_reset_url %} 12 |

{% blocktrans %}The password reset link was invalid, possibly because it has already been used. Please request a new password reset.{% endblocktrans %}

13 | {% else %} 14 | {% if form %} 15 |
16 | {% csrf_token %} 17 | {{ form|crispy }} 18 | 19 |
20 | {% else %} 21 |

{% trans 'Your password is now changed.' %}

22 | {% endif %} 23 | {% endif %} 24 | {% endblock %} 25 | 26 | -------------------------------------------------------------------------------- /frontend/lee_fishing/templates/account/email_confirm.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | {% load account %} 5 | 6 | {% block head_title %}{% trans "Confirm E-mail Address" %}{% endblock %} 7 | 8 | 9 | {% block inner %} 10 |

{% trans "Confirm E-mail Address" %}

11 | 12 | {% if confirmation %} 13 | 14 | {% user_display confirmation.email_address.user as user_display %} 15 | 16 |

{% blocktrans with confirmation.email_address.email as email %}Please confirm that {{ email }} is an e-mail address for user {{ user_display }}.{% endblocktrans %}

17 | 18 |
19 | {% csrf_token %} 20 | 21 |
22 | 23 | {% else %} 24 | 25 | {% url 'account_email' as email_url %} 26 | 27 |

{% blocktrans %}This e-mail confirmation link expired or is invalid. Please issue a new e-mail confirmation request.{% endblocktrans %}

28 | 29 | {% endif %} 30 | 31 | {% endblock %} 32 | 33 | -------------------------------------------------------------------------------- /frontend_minisite/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Adrien Beaudouin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /frontend/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.local") 7 | 8 | try: 9 | from django.core.management import execute_from_command_line 10 | except ImportError: 11 | # The above import may fail for some other reason. Ensure that the 12 | # issue is really that Django is missing to avoid masking other 13 | # exceptions on Python 2. 14 | try: 15 | import django # noqa 16 | except ImportError: 17 | raise ImportError( 18 | "Couldn't import Django. Are you sure it's installed and " 19 | "available on your PYTHONPATH environment variable? Did you " 20 | "forget to activate a virtual environment?" 21 | ) 22 | 23 | raise 24 | 25 | # This allows easy placement of apps within the interior 26 | # lee_fishing directory. 27 | current_path = os.path.dirname(os.path.abspath(__file__)) 28 | sys.path.append(os.path.join(current_path, "lee_fishing")) 29 | 30 | execute_from_command_line(sys.argv) 31 | -------------------------------------------------------------------------------- /infrastructure/src/main/resources/xray_enabler.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | import boto3 4 | import cfnresponse 5 | 6 | def lambda_handler(event, context): 7 | try: 8 | response_status = cfnresponse.SUCCESS 9 | if 'RequestType' in event: 10 | rest_api_id = event['ResourceProperties']['RestApiId'] 11 | stage_name = event['ResourceProperties']['StageName'] 12 | 13 | new_value = 'false' if event['RequestType'] == 'Delete' else 'true' 14 | client = boto3.client('apigateway') 15 | client.update_stage( 16 | restApiId=rest_api_id, 17 | stageName=stage_name, 18 | patchOperations=[ 19 | { 20 | 'op': 'replace', 21 | 'path': '/tracingEnabled', 22 | 'value' : new_value, 23 | } 24 | ] 25 | ) 26 | 27 | cfnresponse.send(event, context, response_status, {}, '') 28 | except Exception: 29 | # Whatever happens, send the failure! 30 | cfnresponse.send(event, context, cfnresponse.FAILED, {}, '') 31 | raise -------------------------------------------------------------------------------- /frontend/requirements/base.txt: -------------------------------------------------------------------------------- 1 | pytz==2018.5 # https://github.com/stub42/pytz 2 | python-slugify==1.2.6 # https://github.com/un33k/python-slugify 3 | Pillow==5.2.0 # https://github.com/python-pillow/Pillow 4 | argon2-cffi==18.3.0 # https://github.com/hynek/argon2_cffi 5 | redis>=2.10.5 # https://github.com/antirez/redis 6 | 7 | # Django 8 | # ------------------------------------------------------------------------------ 9 | django==2.0.8 # pyup: < 2.1 # https://www.djangoproject.com/ 10 | django-environ==0.4.5 # https://github.com/joke2k/django-environ 11 | django-model-utils==3.1.2 # https://github.com/jazzband/django-model-utils 12 | django-allauth==0.37.1 # https://github.com/pennersr/django-allauth 13 | django-crispy-forms==1.7.2 # https://github.com/django-crispy-forms/django-crispy-forms 14 | django-redis==4.9.0 # https://github.com/niwinz/django-redis 15 | 16 | # Django REST Framework 17 | djangorestframework==3.8.2 # https://github.com/encode/django-rest-framework 18 | coreapi==2.3.3 # https://github.com/core-api/python-client 19 | 20 | # Image processing 21 | django-imagekit==4.0.2 # https://github.com/matthewwithanm/django-imagekit 22 | 23 | # Call outs 24 | requests==2.20.0 25 | -------------------------------------------------------------------------------- /frontend/lee_fishing/contrib/sites/migrations/0003_set_site_domain_and_name.py: -------------------------------------------------------------------------------- 1 | """ 2 | To understand why this file is here, please read: 3 | 4 | http://cookiecutter-django.readthedocs.io/en/latest/faq.html#why-is-there-a-django-contrib-sites-directory-in-cookiecutter-django 5 | """ 6 | from django.conf import settings 7 | from django.db import migrations 8 | 9 | 10 | def update_site_forward(apps, schema_editor): 11 | """Set site domain and name.""" 12 | Site = apps.get_model("sites", "Site") 13 | Site.objects.update_or_create( 14 | id=settings.SITE_ID, 15 | defaults={ 16 | "domain": "lee.fishing", 17 | "name": "Lee Fishing", 18 | }, 19 | ) 20 | 21 | 22 | def update_site_backward(apps, schema_editor): 23 | """Revert site domain and name to default.""" 24 | Site = apps.get_model("sites", "Site") 25 | Site.objects.update_or_create( 26 | id=settings.SITE_ID, defaults={"domain": "example.com", "name": "example.com"} 27 | ) 28 | 29 | 30 | class Migration(migrations.Migration): 31 | 32 | dependencies = [("sites", "0002_alter_domain_unique")] 33 | 34 | operations = [migrations.RunPython(update_site_forward, update_site_backward)] 35 | -------------------------------------------------------------------------------- /backend/src/test/java/fishing/lee/backend/BasketRepositoryIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package fishing.lee.backend; 2 | 3 | import com.amazonaws.services.dynamodbv2.AmazonDynamoDB; 4 | import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper; 5 | import com.amazonaws.services.dynamodbv2.local.main.ServerRunner; 6 | import com.amazonaws.services.dynamodbv2.local.server.DynamoDBProxyServer; 7 | import com.amazonaws.xray.AWSXRay; 8 | import org.junit.*; 9 | import org.junit.runner.RunWith; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.boot.test.context.SpringBootTest; 12 | import org.springframework.test.context.ActiveProfiles; 13 | import org.springframework.test.context.TestPropertySource; 14 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 15 | import org.springframework.test.context.web.WebAppConfiguration; 16 | 17 | import static fishing.lee.backend.DynamoDBConfig.checkOrCreateTable; 18 | 19 | public class BasketRepositoryIntegrationTest extends BaseDynamoDBTest { 20 | @Test 21 | public void sampleTestCase() { 22 | BasketModel basketModel = new BasketModel(); 23 | basketRepository.save(basketModel); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /null_lambda/src/main/java/fishing/lee/backend/StreamLambdaHandler.java: -------------------------------------------------------------------------------- 1 | package fishing.lee.backend; 2 | 3 | import com.amazonaws.services.lambda.runtime.Context; 4 | import com.amazonaws.services.lambda.runtime.LambdaLogger; 5 | import com.amazonaws.services.lambda.runtime.RequestHandler; 6 | import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; 7 | import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; 8 | 9 | import java.util.HashMap; 10 | 11 | @SuppressWarnings("unused") 12 | public class StreamLambdaHandler implements RequestHandler { 13 | @Override 14 | public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent input, Context context) { 15 | LambdaLogger logger = context.getLogger(); 16 | logger.log("This is a sample responder - it'll only respond with DUMMY"); 17 | 18 | return new APIGatewayProxyResponseEvent() 19 | .withHeaders(new HashMap() {{ 20 | put("Content-Type", "application/json"); 21 | }}) 22 | .withStatusCode(200) 23 | .withBody("DUMMY " + input.getPath()); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /frontend/requirements/local.txt: -------------------------------------------------------------------------------- 1 | -r ./base.txt 2 | 3 | Werkzeug==0.14.1 # https://github.com/pallets/werkzeug 4 | ipdb==0.11 # https://github.com/gotcha/ipdb 5 | Sphinx==1.8.1 # https://github.com/sphinx-doc/sphinx 6 | psycopg2-binary==2.7.5 # https://github.com/psycopg/psycopg2 7 | 8 | # Testing 9 | # ------------------------------------------------------------------------------ 10 | mypy==0.630 # https://github.com/python/mypy 11 | pytest==3.8.1 # https://github.com/pytest-dev/pytest 12 | pytest-sugar==0.9.1 # https://github.com/Frozenball/pytest-sugar 13 | 14 | # Code quality 15 | # ------------------------------------------------------------------------------ 16 | flake8==3.5.0 # https://github.com/PyCQA/flake8 17 | coverage==4.5.1 # https://github.com/nedbat/coveragepy 18 | 19 | # Django 20 | # ------------------------------------------------------------------------------ 21 | factory-boy==2.11.1 # https://github.com/FactoryBoy/factory_boy 22 | 23 | django-debug-toolbar==1.10.1 # https://github.com/jazzband/django-debug-toolbar 24 | django-extensions==2.1.2 # https://github.com/django-extensions/django-extensions 25 | django-coverage-plugin==1.6.0 # https://github.com/nedbat/django_coverage_plugin 26 | pytest-django==3.4.3 # https://github.com/pytest-dev/pytest-django 27 | -------------------------------------------------------------------------------- /frontend/lee_fishing/users/tests/test_forms.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from lee_fishing.users.forms import UserCreationForm 4 | from lee_fishing.users.tests.factories import UserFactory 5 | 6 | pytestmark = pytest.mark.django_db 7 | 8 | 9 | class TestUserCreationForm: 10 | 11 | def test_clean_username(self): 12 | # A user with proto_user params does not exist yet. 13 | proto_user = UserFactory.build() 14 | 15 | form = UserCreationForm( 16 | { 17 | "username": proto_user.username, 18 | "password1": proto_user._password, 19 | "password2": proto_user._password, 20 | } 21 | ) 22 | 23 | assert form.is_valid() 24 | assert form.clean_username() == proto_user.username 25 | 26 | # Creating a user. 27 | form.save() 28 | 29 | # The user with proto_user params already exists, 30 | # hence cannot be created. 31 | form = UserCreationForm( 32 | { 33 | "username": proto_user.username, 34 | "password1": proto_user._password, 35 | "password2": proto_user._password, 36 | } 37 | ) 38 | 39 | assert not form.is_valid() 40 | assert len(form.errors) == 1 41 | assert "username" in form.errors 42 | -------------------------------------------------------------------------------- /null_lambda/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | mavenCentral() 4 | maven { 5 | url "https://plugins.gradle.org/m2/" 6 | } 7 | } 8 | dependencies { 9 | classpath 'com.github.jengelman.gradle.plugins:shadow:4.0.1' 10 | classpath 'com.github.ben-manes:gradle-versions-plugin:0.20.0' 11 | } 12 | } 13 | 14 | plugins { 15 | id 'java' 16 | } 17 | 18 | apply plugin: 'com.github.ben-manes.versions' 19 | apply plugin: 'com.github.johnrengelman.shadow' 20 | 21 | group 'fishing.lee' 22 | version '1.0-SNAPSHOT' 23 | 24 | sourceCompatibility = 1.8 25 | 26 | repositories { 27 | mavenCentral() 28 | } 29 | 30 | dependencies { 31 | compile('com.amazonaws:aws-lambda-java-core:1.2.0') 32 | compile('com.amazonaws:aws-lambda-java-events:2.2.2') 33 | compile('com.amazonaws:aws-lambda-java-log4j:1.0.0') 34 | compile('com.amazonaws:aws-lambda-java-log4j2:1.1.0') 35 | 36 | compile('com.fasterxml.jackson:jackson-base:2.9.7') 37 | compile 'com.fasterxml.jackson.core:jackson-annotations:2.9.7' 38 | compile 'com.fasterxml.jackson.core:jackson-databind:2.9.7' 39 | 40 | compile 'com.amazonaws:aws-java-sdk-core:1.11.433' 41 | compile 'com.amazonaws:aws-java-sdk-sns:1.11.433' 42 | 43 | compile group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.5.6' 44 | 45 | testCompile group: 'junit', name: 'junit', version: '4.12' 46 | } 47 | -------------------------------------------------------------------------------- /docs/exercise_1_post.rst: -------------------------------------------------------------------------------- 1 | Exercise #1 - Checklist for completion 2 | ====================================== 3 | 4 | We have now deployed our super basic shop. The architecture we're using 5 | at this point has not changed since Exercise #0. 6 | 7 | 1. Get the hostname of the Backend order service 8 | 9 | :: 10 | 11 | ./tools get_value ShopBackendBackendUrl 12 | 13 | 2. In the Cloud9 terminal you can ssh to the Bastion host doing the following: 14 | 15 | :: 16 | 17 | ./tools ssh_to_bastion 18 | 19 | 3. Once SSH’d into the instance you can curl the backend by getting the 20 | hostname from ElasticBeanstalk and doing 21 | 22 | :: 23 | 24 | curl /ping 25 | 26 | You should see ‘PONG’ back. 27 | 28 | To get back to the Cloud9 instance you can just type logout followed by 29 | enter/return. 30 | 31 | 4. Let's go to our shop, run the following command in the Cloud9 terminal to 32 | get the URL to go to 33 | 34 | :: 35 | 36 | ./tools get_value CDNCloudFrontUrl 37 | 38 | 5. Take the output and enter into your browser, you should be greeted by the 39 | shop. If not, it might be cached, you can go to `/shop` and it'll likely 40 | work. If not, put your hand up for a Solutions Architect to come and give 41 | you a hand! 42 | 43 | You have now tested that we have deployed our Shop Frontend and Backend 44 | services successfully. 45 | 46 | .. centered:: Exercise #1 is complete, go ahead and start Exercise #2 47 | -------------------------------------------------------------------------------- /frontend/lee_fishing/users/views.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth import get_user_model 2 | from django.contrib.auth.mixins import LoginRequiredMixin 3 | from django.urls import reverse 4 | from django.views.generic import DetailView, ListView, RedirectView, UpdateView 5 | 6 | User = get_user_model() 7 | 8 | 9 | class UserDetailView(LoginRequiredMixin, DetailView): 10 | 11 | model = User 12 | slug_field = "username" 13 | slug_url_kwarg = "username" 14 | 15 | 16 | user_detail_view = UserDetailView.as_view() 17 | 18 | 19 | class UserListView(LoginRequiredMixin, ListView): 20 | 21 | model = User 22 | slug_field = "username" 23 | slug_url_kwarg = "username" 24 | 25 | 26 | user_list_view = UserListView.as_view() 27 | 28 | 29 | class UserUpdateView(LoginRequiredMixin, UpdateView): 30 | 31 | model = User 32 | fields = ["name"] 33 | 34 | def get_success_url(self): 35 | return reverse("users:detail", kwargs={"username": self.request.user.username}) 36 | 37 | def get_object(self): 38 | return User.objects.get(username=self.request.user.username) 39 | 40 | 41 | user_update_view = UserUpdateView.as_view() 42 | 43 | 44 | class UserRedirectView(LoginRequiredMixin, RedirectView): 45 | 46 | permanent = False 47 | 48 | def get_redirect_url(self): 49 | return reverse("users:detail", kwargs={"username": self.request.user.username}) 50 | 51 | 52 | user_redirect_view = UserRedirectView.as_view() 53 | -------------------------------------------------------------------------------- /docs/cleanup.rst: -------------------------------------------------------------------------------- 1 | Cleanup 2 | ======= 3 | 4 | In this section, we are going to clean up everything we made for this 5 | workshop. 6 | 7 | 1. We first need clean up the Amazon S3 buckets. This is because you cannot 8 | delete a non-empty S3 bucket. This only works because we did not enable 9 | versioning on our buckets. 10 | 11 | .. code-block:: bash 12 | :linenos: 13 | 14 | cd 15 | cd environment 16 | aws s3 rm s3://`./tools get_value DeploymentAssetsDeploymentBucket` --recursive 17 | aws s3 rm s3://`./tools get_value CDNStaticBucket` --recursive 18 | aws s3 rm s3://`aws s3 ls | grep thefishingshopworkshop-shopfrontendbucket | awk '{print $3}'` --recursive 19 | aws ec2 delete-key-pair --key-name FishingKey 20 | 21 | 2. With that done, we need to destroy the main stack. 22 | 23 | .. Attention:: This will take an exceptionally long time (read: 30+ minutes) 24 | to run! You can leave this running and leave the workshop 25 | however as AWS Cloud9 will continue executing this in the 26 | background. 27 | 28 | .. code-block:: bash 29 | :linenos: 30 | 31 | cd infrastructure 32 | cdk destroy 33 | 34 | .. image:: images/cdk_destroy.png 35 | 36 | 3. Go to the 37 | `AWS Cloud9 `_ 38 | console 39 | 4. Delete the Fishing environment. 40 | 41 | You have now successfully cleaned up everything created by this workshop. 42 | -------------------------------------------------------------------------------- /infrastructure/src/main/java/fishing/lee/infrastructure/ApiGatewayVpcPolicyDocument.java: -------------------------------------------------------------------------------- 1 | package fishing.lee.infrastructure; 2 | 3 | import software.amazon.awscdk.services.ec2.CfnVPCEndpoint; 4 | import software.amazon.awscdk.services.iam.*; 5 | 6 | import java.util.HashMap; 7 | 8 | class ApiGatewayVpcPolicyDocument { 9 | private CfnVPCEndpoint vpcEndpointResource; 10 | 11 | ApiGatewayVpcPolicyDocument(CfnVPCEndpoint vpcEndpointResource) { 12 | this.vpcEndpointResource = vpcEndpointResource; 13 | } 14 | 15 | PolicyDocument getPolicyDocument() { 16 | PolicyStatement denyNotVpc = new PolicyStatement(); 17 | denyNotVpc.deny(); 18 | denyNotVpc.addAction("execute-api:Invoke"); 19 | denyNotVpc.addResource("execute-api:/*"); 20 | denyNotVpc.addPrincipal(new AnyPrincipal()); 21 | denyNotVpc.addCondition("StringNotEquals", new HashMap() {{ 22 | put("aws:sourceVpce", vpcEndpointResource.getVpcEndpointId()); 23 | }}); 24 | 25 | PolicyStatement allow = new PolicyStatement(); 26 | allow.allow(); 27 | allow.addPrincipal(new AnyPrincipal()); 28 | allow.addResource("execute-api:/*"); 29 | allow.addAction("execute-api:Invoke"); 30 | 31 | PolicyDocument policyDocument = new PolicyDocument(); 32 | policyDocument.addStatement(denyNotVpc); 33 | policyDocument.addStatement(allow); 34 | return policyDocument; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /sqs_order_forwarder/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | mavenCentral() 4 | maven { 5 | url "https://plugins.gradle.org/m2/" 6 | } 7 | } 8 | dependencies { 9 | classpath 'com.github.jengelman.gradle.plugins:shadow:4.0.1' 10 | classpath 'com.github.ben-manes:gradle-versions-plugin:0.20.0' 11 | } 12 | } 13 | 14 | plugins { 15 | id 'java' 16 | } 17 | 18 | apply plugin: 'com.github.ben-manes.versions' 19 | apply plugin: 'com.github.johnrengelman.shadow' 20 | 21 | group 'fishing.lee' 22 | version '1.0-SNAPSHOT' 23 | 24 | sourceCompatibility = 1.8 25 | 26 | repositories { 27 | mavenCentral() 28 | } 29 | 30 | dependencies { 31 | compile('com.amazonaws:aws-lambda-java-core:1.2.0') 32 | compile('com.amazonaws:aws-lambda-java-events:2.2.2') 33 | compile('com.amazonaws:aws-lambda-java-log4j:1.0.0') 34 | compile('com.amazonaws:aws-lambda-java-log4j2:1.1.0') 35 | 36 | compile('com.fasterxml.jackson:jackson-base:2.9.7') 37 | compile 'com.fasterxml.jackson.core:jackson-annotations:2.9.7' 38 | compile 'com.fasterxml.jackson.core:jackson-databind:2.9.7' 39 | 40 | compile 'com.amazonaws:aws-java-sdk-core:1.11.433' 41 | compile 'com.amazonaws:aws-java-sdk-dynamodb:1.11.433' 42 | compile 'com.amazonaws:aws-java-sdk-sqs:1.11.433' 43 | 44 | compile group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.5.6' 45 | 46 | testCompile group: 'junit', name: 'junit', version: '4.12' 47 | } 48 | -------------------------------------------------------------------------------- /frontend_minisite/assets/js/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * jQuery and Bootstrap loading 3 | */ 4 | import 'bootstrap' 5 | 6 | // Plugins 7 | import './plugins/bootstrap' 8 | import './plugins/fontawesome' 9 | 10 | /** 11 | * Axios loading 12 | */ 13 | import axios from 'axios' 14 | 15 | /** 16 | * Vue Init 17 | */ 18 | import Vue from 'vue' 19 | 20 | /** 21 | * Frontend plugins loading 22 | */ 23 | import 'slick-carousel' 24 | import swal from 'sweetalert2' 25 | 26 | window.axios = axios 27 | 28 | new Vue().$mount('#app') 29 | ;(function($) { 30 | /** 31 | * Bind all bootstrap tooltips 32 | */ 33 | $('[data-toggle="tooltip"]').tooltip() 34 | 35 | /** 36 | * Bind all bootstrap popovers 37 | */ 38 | $('[data-toggle="popover"]').popover() 39 | 40 | $('.slider') 41 | .not('.slick-initialized') 42 | .removeAttr('hidden') 43 | .slick({ 44 | dots: true, 45 | infinite: true, 46 | speed: 300, 47 | slidesToShow: 3, 48 | slidesToScroll: 1 49 | }) 50 | 51 | $('button.sweet').click(() => { 52 | axios 53 | .get(API_URL) 54 | .then(response => { 55 | swal({ 56 | title: 'Yo!', 57 | text: response.data, 58 | type: 'success', 59 | confirmButtonText: 'Cool' 60 | }) 61 | }) 62 | .catch(error => { 63 | swal({ 64 | title: 'Yo!', 65 | text: error, 66 | type: 'error', 67 | confirmButtonText: 'Doh!' 68 | }) 69 | }) 70 | }) 71 | })(window.jQuery) 72 | -------------------------------------------------------------------------------- /infrastructure/src/main/java/fishing/lee/infrastructure/QueueProxyProps.java: -------------------------------------------------------------------------------- 1 | package fishing.lee.infrastructure; 2 | 3 | import java.util.Objects; 4 | 5 | interface QueueProxyProps { 6 | ShopVpc getShopVpc(); 7 | String getApiUrl(); 8 | 9 | static QueueProxyProps.Builder builder() { 10 | return new QueueProxyProps.Builder(); 11 | } 12 | 13 | final class Builder { 14 | ShopVpc _shopVpc; 15 | String _apiUrl; 16 | 17 | QueueProxyProps.Builder withShopVpc(ShopVpc shopVpc) { 18 | this._shopVpc = shopVpc; 19 | return this; 20 | } 21 | 22 | QueueProxyProps.Builder withApiUrl(String apiUrl) { 23 | this._apiUrl = apiUrl; 24 | return this; 25 | } 26 | 27 | QueueProxyProps build() { 28 | return new QueueProxyProps() { 29 | private ShopVpc shopVpc; 30 | private String apiUrl; 31 | 32 | { 33 | shopVpc = Objects.requireNonNull(Builder.this._shopVpc, "shopVpc Required"); 34 | apiUrl = Objects.requireNonNull(Builder.this._apiUrl); 35 | } 36 | 37 | @Override 38 | public ShopVpc getShopVpc() { 39 | return this.shopVpc; 40 | } 41 | 42 | @Override 43 | public String getApiUrl() { 44 | return this.apiUrl; 45 | } 46 | }; 47 | } 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /frontend/lee_fishing/contrib/sites/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | import django.contrib.sites.models 2 | from django.contrib.sites.models import _simple_domain_name_validator 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [] 9 | 10 | operations = [ 11 | migrations.CreateModel( 12 | name="Site", 13 | fields=[ 14 | ( 15 | "id", 16 | models.AutoField( 17 | verbose_name="ID", 18 | serialize=False, 19 | auto_created=True, 20 | primary_key=True, 21 | ), 22 | ), 23 | ( 24 | "domain", 25 | models.CharField( 26 | max_length=100, 27 | verbose_name="domain name", 28 | validators=[_simple_domain_name_validator], 29 | ), 30 | ), 31 | ("name", models.CharField(max_length=50, verbose_name="display name")), 32 | ], 33 | options={ 34 | "ordering": ("domain",), 35 | "db_table": "django_site", 36 | "verbose_name": "site", 37 | "verbose_name_plural": "sites", 38 | }, 39 | bases=(models.Model,), 40 | managers=[("objects", django.contrib.sites.models.SiteManager())], 41 | ) 42 | ] 43 | -------------------------------------------------------------------------------- /frontend/lee_fishing/fishing_equipment/views.py: -------------------------------------------------------------------------------- 1 | from django.db.models import Count 2 | from django.http import HttpResponse 3 | from django.urls import reverse 4 | from django.views.generic import DetailView, ListView, RedirectView 5 | 6 | from lee_fishing.fishing_equipment.models import Product, Category 7 | 8 | 9 | class RootRedirect(RedirectView): 10 | permanent = False 11 | 12 | def get(self, request, *args, **kwargs): 13 | if self.request.META['HTTP_USER_AGENT'] == 'ELB-HealthChecker/2.0': 14 | return HttpResponse("Move along... nothing to see here.".encode('utf-8'), content_type="text/plain") 15 | 16 | return super(RootRedirect, self).get(request, *args, **kwargs) 17 | 18 | def get_redirect_url(self, *args, **kwargs): 19 | return reverse('equipment:list') 20 | 21 | 22 | class ProductDetailView(DetailView): 23 | queryset = Product.objects.not_deleted() 24 | 25 | def get_queryset(self): 26 | queryset = super(ProductDetailView, self).get_queryset() 27 | return queryset.filter(primary_category__slug=self.kwargs['category']) 28 | 29 | 30 | class CategoryListView(ListView): 31 | model = Category 32 | paginate_by = 10 33 | 34 | def get_queryset(self): 35 | return self.model.objects.annotate( 36 | products=Count('primary_products') 37 | ).order_by('-products', 'title') 38 | 39 | 40 | class CategoryDetailView(ListView): 41 | model = Category 42 | paginate_by = 10 43 | 44 | def get_queryset(self): 45 | return self.model.objects.get(slug=self.kwargs['slug']).primary_products.all() 46 | -------------------------------------------------------------------------------- /frontend/lee_fishing/templates/fishing_equipment/category_list.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block content %} 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | {% for category in category_list %} 14 | 15 | 16 | 19 | 22 | 23 | {% endfor %} 24 | 25 |
#Category NameProduct Count
{{ forloop.counter }} 17 | {{ category.title }} 18 | 20 | {{ category.primary_products.count }} 21 |
26 | 27 | {% if is_paginated %} 28 |
    29 | {% if page_obj.has_previous %} 30 |
  • «
  • 31 | {% else %} 32 |
  • «
  • 33 | {% endif %} 34 | {% for i in paginator.page_range %} 35 | {% if page_obj.number == i %} 36 |
  • {{ i }} (current)
  • 37 | {% else %} 38 |
  • {{ i }}
  • 39 | {% endif %} 40 | {% endfor %} 41 | {% if page_obj.has_next %} 42 |
  • »
  • 43 | {% else %} 44 |
  • »
  • 45 | {% endif %} 46 |
47 | {% endif %} 48 | {% endblock %} 49 | -------------------------------------------------------------------------------- /infrastructure/src/main/java/fishing/lee/infrastructure/BastionProps.java: -------------------------------------------------------------------------------- 1 | package fishing.lee.infrastructure; 2 | 3 | import java.util.Objects; 4 | 5 | interface BastionProps { 6 | ShopVpc getShopVpc(); 7 | String getSshKeyPairName(); 8 | 9 | static BastionProps.Builder builder() { 10 | return new BastionProps.Builder(); 11 | } 12 | 13 | final class Builder { 14 | ShopVpc _shopVpc; 15 | String _sshKeyPairName; 16 | 17 | BastionProps.Builder withShopVpc(ShopVpc shopVpc) { 18 | this._shopVpc = shopVpc; 19 | return this; 20 | } 21 | 22 | BastionProps.Builder withSshKeyPairName(@SuppressWarnings("SameParameterValue") String sshKeyPairName) { 23 | this._sshKeyPairName = sshKeyPairName; 24 | return this; 25 | } 26 | 27 | BastionProps build() { 28 | return new BastionProps() { 29 | private ShopVpc shopVpc; 30 | private String sshKeyPairName; 31 | 32 | { 33 | shopVpc = Objects.requireNonNull(Builder.this._shopVpc, "shopVpc Required"); 34 | sshKeyPairName = Objects.requireNonNull(Builder.this._sshKeyPairName, "sshKeyPairName Required"); 35 | } 36 | 37 | @Override 38 | public ShopVpc getShopVpc() { 39 | return this.shopVpc; 40 | } 41 | 42 | @Override 43 | public String getSshKeyPairName() { 44 | return this.sshKeyPairName; 45 | } 46 | }; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /frontend/lee_fishing/templates/fishing_equipment/product_list.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block content %} 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | {% for product in product_list %} 14 | 15 | 16 | 19 | 22 | 23 | {% endfor %} 24 | 25 |
#ProductPrice
{{ forloop.counter }} 17 | {{ product.title }} 18 | 20 | {{ product.formatted_price }} 21 |
26 | 27 | {% if is_paginated %} 28 |
    29 | {% if page_obj.has_previous %} 30 |
  • «
  • 31 | {% else %} 32 |
  • «
  • 33 | {% endif %} 34 | {% for i in paginator.page_range %} 35 | {% if page_obj.number == i %} 36 |
  • {{ i }} (current)
  • 37 | {% else %} 38 |
  • {{ i }}
  • 39 | {% endif %} 40 | {% endfor %} 41 | {% if page_obj.has_next %} 42 |
  • »
  • 43 | {% else %} 44 |
  • »
  • 45 | {% endif %} 46 |
47 | {% endif %} 48 | {% endblock %} 49 | -------------------------------------------------------------------------------- /infrastructure/src/main/java/fishing/lee/infrastructure/ScheduledApiCallerProps.java: -------------------------------------------------------------------------------- 1 | package fishing.lee.infrastructure; 2 | 3 | import java.util.Objects; 4 | 5 | public interface ScheduledApiCallerProps { 6 | ShopVpc getShopVpc(); 7 | String getApiToPing(); 8 | 9 | static ScheduledApiCallerProps.Builder builder() { 10 | return new ScheduledApiCallerProps.Builder(); 11 | } 12 | 13 | final class Builder { 14 | private ShopVpc _shopVpc; 15 | private String _apiToPing; 16 | 17 | ScheduledApiCallerProps.Builder withShopVpc(ShopVpc shopVpc) { 18 | this._shopVpc = shopVpc; 19 | return this; 20 | } 21 | 22 | ScheduledApiCallerProps.Builder withApiToPing(String apiToPing) { 23 | this._apiToPing = apiToPing; 24 | return this; 25 | } 26 | 27 | ScheduledApiCallerProps build() { 28 | return new ScheduledApiCallerProps() { 29 | private ShopVpc shopVpc; 30 | private String apiToPing; 31 | 32 | { 33 | shopVpc = Objects.requireNonNull(ScheduledApiCallerProps.Builder.this._shopVpc, "shopVpc is required"); 34 | apiToPing = Objects.requireNonNull(ScheduledApiCallerProps.Builder.this._apiToPing); 35 | } 36 | 37 | @Override 38 | public ShopVpc getShopVpc() { 39 | return shopVpc; 40 | } 41 | 42 | @Override 43 | public String getApiToPing() { 44 | return apiToPing; 45 | } 46 | }; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /infrastructure/src/main/java/fishing/lee/infrastructure/ApiGatewayXrayEnablerResourceProps.java: -------------------------------------------------------------------------------- 1 | package fishing.lee.infrastructure; 2 | 3 | import java.util.Objects; 4 | 5 | interface ApiGatewayXrayEnablerResourceProps { 6 | String getRestApiId(); 7 | String getStageName(); 8 | 9 | static ApiGatewayXrayEnablerResourceProps.Builder build() { 10 | return new ApiGatewayXrayEnablerResourceProps.Builder(); 11 | } 12 | 13 | final class Builder { 14 | String _restApiId; 15 | String _stageName; 16 | 17 | ApiGatewayXrayEnablerResourceProps.Builder withRestApiId(String restApiId) { 18 | this._restApiId = restApiId; 19 | return this; 20 | } 21 | 22 | ApiGatewayXrayEnablerResourceProps.Builder withStageName(String stageName) { 23 | this._stageName = stageName; 24 | return this; 25 | } 26 | 27 | ApiGatewayXrayEnablerResourceProps build() { 28 | return new ApiGatewayXrayEnablerResourceProps() { 29 | private String restApiId; 30 | private String stageName; 31 | 32 | { 33 | restApiId = Objects.requireNonNull(Builder.this._restApiId); 34 | stageName = Objects.requireNonNull(Builder.this._stageName); 35 | } 36 | 37 | @Override 38 | public String getRestApiId() { 39 | return restApiId; 40 | } 41 | 42 | @Override 43 | public String getStageName() { 44 | return stageName; 45 | } 46 | }; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /frontend/lee_fishing/templates/account/login.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | {% load account socialaccount %} 5 | {% load crispy_forms_tags %} 6 | 7 | {% block head_title %}{% trans "Sign In" %}{% endblock %} 8 | 9 | {% block inner %} 10 | 11 |

{% trans "Sign In" %}

12 | 13 | {% get_providers as socialaccount_providers %} 14 | 15 | {% if socialaccount_providers %} 16 |

{% blocktrans with site.name as site_name %}Please sign in with one 17 | of your existing third party accounts. Or, sign up 18 | for a {{ site_name }} account and sign in below:{% endblocktrans %}

19 | 20 |
21 | 22 |
    23 | {% include "socialaccount/snippets/provider_list.html" with process="login" %} 24 |
25 | 26 | 27 | 28 |
29 | 30 | {% include "socialaccount/snippets/login_extra.html" %} 31 | 32 | {% else %} 33 |

{% blocktrans %}If you have not created an account yet, then please 34 | sign up first.{% endblocktrans %}

35 | {% endif %} 36 | 37 | 46 | 47 | {% endblock %} 48 | 49 | -------------------------------------------------------------------------------- /frontend/lee_fishing/users/tests/test_views.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from django.conf import settings 3 | from django.test import RequestFactory 4 | 5 | from lee_fishing.users.views import UserRedirectView, UserUpdateView 6 | 7 | pytestmark = pytest.mark.django_db 8 | 9 | 10 | class TestUserUpdateView: 11 | """ 12 | TODO: 13 | extracting view initialization code as class-scoped fixture 14 | would be great if only pytest-django supported non-function-scoped 15 | fixture db access -- this is a work-in-progress for now: 16 | https://github.com/pytest-dev/pytest-django/pull/258 17 | """ 18 | 19 | def test_get_success_url( 20 | self, user: settings.AUTH_USER_MODEL, request_factory: RequestFactory 21 | ): 22 | view = UserUpdateView() 23 | request = request_factory.get("/fake-url/") 24 | request.user = user 25 | 26 | view.request = request 27 | 28 | assert view.get_success_url() == f"/users/{user.username}/" 29 | 30 | def test_get_object( 31 | self, user: settings.AUTH_USER_MODEL, request_factory: RequestFactory 32 | ): 33 | view = UserUpdateView() 34 | request = request_factory.get("/fake-url/") 35 | request.user = user 36 | 37 | view.request = request 38 | 39 | assert view.get_object() == user 40 | 41 | 42 | class TestUserRedirectView: 43 | 44 | def test_get_redirect_url( 45 | self, user: settings.AUTH_USER_MODEL, request_factory: RequestFactory 46 | ): 47 | view = UserRedirectView() 48 | request = request_factory.get("/fake-url") 49 | request.user = user 50 | 51 | view.request = request 52 | 53 | assert view.get_redirect_url() == f"/users/{user.username}/" 54 | -------------------------------------------------------------------------------- /null_lambda/src/main/java/fishing/lee/backend/SNSLambdaHandler.java: -------------------------------------------------------------------------------- 1 | package fishing.lee.backend; 2 | 3 | import com.amazonaws.services.lambda.runtime.Context; 4 | import com.amazonaws.services.lambda.runtime.LambdaLogger; 5 | import com.amazonaws.services.lambda.runtime.RequestHandler; 6 | import com.amazonaws.services.lambda.runtime.events.SNSEvent; 7 | import org.apache.http.client.methods.CloseableHttpResponse; 8 | import org.apache.http.client.methods.HttpGet; 9 | import org.apache.http.impl.client.CloseableHttpClient; 10 | import org.apache.http.impl.client.HttpClients; 11 | import org.apache.http.util.EntityUtils; 12 | 13 | import java.io.IOException; 14 | 15 | @SuppressWarnings("unused") 16 | public class SNSLambdaHandler implements RequestHandler { 17 | private LambdaLogger logger; 18 | 19 | private void callApiPing() { 20 | final String url = System.getenv("SHOPBACKEND_API_URL"); 21 | 22 | try (CloseableHttpClient httpclient = HttpClients.createDefault()) { 23 | HttpGet httpget = new HttpGet(url); 24 | logger.log("Executing request " + httpget.getRequestLine()); 25 | 26 | CloseableHttpResponse response = httpclient.execute(httpget); 27 | 28 | logger.log(String.valueOf(response.getStatusLine())); 29 | logger.log(EntityUtils.toString(response.getEntity())); 30 | } catch (IOException e) { 31 | // We're cool and we'll ignore it 32 | e.printStackTrace(); 33 | } 34 | } 35 | 36 | @Override 37 | public Object handleRequest(SNSEvent input, Context context) { 38 | logger = context.getLogger(); 39 | 40 | input.getRecords().forEach(snsRecord -> callApiPing()); 41 | return null; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /docs/troubleshooting.rst: -------------------------------------------------------------------------------- 1 | Troubleshooting 2 | =============== 3 | 4 | General 5 | ------- 6 | 7 | **I lost my terminal in the Cloud9 Window** 8 | 9 | In the Cloud9 menus, go to the Window menu and New Terminal. 10 | 11 | .. image:: images/cloud9_newterminal.png 12 | 13 | Exercise #0 14 | ----------- 15 | 16 | **When typing ./tools create_ssh_key I get an error** 17 | 18 | You got this? 19 | 20 | :: 21 | 22 | An error occurred (InvalidKeyPair.Duplicate) when calling the CreateKeyPair operation: The keypair 'FishingKey' already exists. 23 | Key created and secured 24 | 25 | No problem! Do the following: 26 | 27 | 1. Go to `Services -> EC2 -> Key Pairs `_. 28 | 2. Delete the `FishingKey` 29 | 3. In Cloud9 execute the following command: 30 | 31 | .. code-block:: bash 32 | :linenos: 33 | 34 | rm ~/.ssh/fishing-key.pem 35 | 36 | 2. Retry your the command that failed 37 | 38 | **When typing 'cdk' as a command I get command not found** 39 | 40 | .. image:: images/cdk_wherediditgo.png 41 | 42 | 1. Type the following in to Cloud9 terminal: 43 | 44 | .. code-block:: bash 45 | :linenos: 46 | 47 | cd 48 | cd infrastructure 49 | nvm install 50 | 51 | 2. Retry your the command that failed 52 | 53 | Cleanup 54 | ------- 55 | 56 | **I got errors during Cleanup saying it couldn't delete some items** 57 | 58 | .. image:: images/cdk_destroy_error.png 59 | 60 | 1. Make sure the buckets referenced are empty by using the commands given 61 | in the Cleanup Exercise. 62 | 2. If you're left with the `CDNLambdaBackend` item then that means you just 63 | need to wait. You're not charged for this item and you can delete it later 64 | from within the console if you wish. 65 | -------------------------------------------------------------------------------- /frontend/.ebextensions/aws.config: -------------------------------------------------------------------------------- 1 | packages: 2 | yum: 3 | libxml2: [] 4 | libxml2-devel: [] 5 | libxslt: [] 6 | libxslt-devel: [] 7 | libffi-devel: [] 8 | postgresql96-devel: [] 9 | mysql57-devel: [] 10 | zlib-devel: [] 11 | libjpeg-turbo-devel: [] 12 | container_commands: 13 | 00_fix_mysqllib: 14 | command: "cd /usr/lib64 && sudo ln -sf mysql57/libmysqlclient.so.1020" 15 | 01_migrate: 16 | command: "source /opt/python/run/venv/bin/activate && python manage.py migrate --noinput" 17 | leader_only: true 18 | 02_makeuser: 19 | command: "source /opt/python/run/venv/bin/activate && python manage.py make_user" 20 | leader_only: true 21 | 03_collectstatic: 22 | command: "source /opt/python/run/venv/bin/activate && python manage.py collectstatic --noinput" 23 | leader_only: true 24 | 04_categories: 25 | command: "source /opt/python/run/venv/bin/activate && python manage.py loaddata lee_fishing/fishing_equipment/fixtures/category.json" 26 | leader_only: true 27 | 05_products: 28 | command: "source /opt/python/run/venv/bin/activate && python manage.py loaddata lee_fishing/fishing_equipment/fixtures/product.json" 29 | leader_only: true 30 | option_settings: 31 | "aws:elasticbeanstalk:application:environment": 32 | "DJANGO_SETTINGS_MODULE": "config.settings.production" 33 | "DJANGO_ALLOWED_HOSTS": ".elasticbeanstalk.com,.cloudfront.net,.elb.amazonaws.com,lee.fishing" 34 | "DEFAULT_USER": "leepac" 35 | "DEFAULT_EMAIL": "leepac@amazon.com" 36 | "DEFAULT_PASSWORD": ",5PHsBSgQy" 37 | "aws:elasticbeanstalk:container:python": 38 | WSGIPath: config/wsgi.py 39 | NumProcesses: 3 40 | NumThreads: 20 41 | "aws:elasticbeanstalk:container:python:staticfiles": 42 | "/static/": "static/" 43 | -------------------------------------------------------------------------------- /backend/src/main/java/fishing/lee/backend/BasketModel.java: -------------------------------------------------------------------------------- 1 | package fishing.lee.backend; 2 | 3 | import com.amazonaws.services.dynamodbv2.datamodeling.*; 4 | import com.fasterxml.jackson.annotation.JsonFormat; 5 | import com.fasterxml.jackson.annotation.JsonProperty; 6 | import org.socialsignin.spring.data.dynamodb.marshaller.Instant2EpocheDynamoDBMarshaller; 7 | 8 | import java.time.Instant; 9 | import java.util.Set; 10 | import java.util.UUID; 11 | 12 | @DynamoDBTable(tableName = "Basket") 13 | public class BasketModel { 14 | @DynamoDBHashKey 15 | @DynamoDBAutoGeneratedKey() 16 | private UUID id; 17 | 18 | @DynamoDBTypeConverted(converter = Instant2EpocheDynamoDBMarshaller.class) 19 | private Instant creationPoint; 20 | 21 | @DynamoDBAttribute 22 | private Set items; 23 | 24 | @JsonProperty("id") 25 | public UUID getId() { 26 | return id; 27 | } 28 | 29 | @JsonProperty("id") 30 | public void setId(UUID id) { 31 | this.id = id; 32 | } 33 | 34 | @JsonProperty("creation_point") 35 | public Instant getCreationPoint() { 36 | return creationPoint; 37 | } 38 | 39 | @JsonProperty("creation_point") 40 | @JsonFormat(shape = JsonFormat.Shape.NUMBER_INT) 41 | public void setCreationPoint(Instant creationPoint) { 42 | this.creationPoint = creationPoint != null ? creationPoint : Instant.now(); 43 | } 44 | 45 | @JsonProperty("items") 46 | public Set getItems() { 47 | return items; 48 | } 49 | 50 | @JsonProperty("items") 51 | public void setItems(Set items) { 52 | this.items = items; 53 | } 54 | 55 | public void addItem(String item) { 56 | this.items.add(item); 57 | } 58 | 59 | public void deleteItem(String item) { 60 | this.items.remove(item); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /frontend/utility/install_python_dependencies.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | WORK_DIR="$(dirname "$0")" 4 | PROJECT_DIR="$(dirname "$WORK_DIR")" 5 | 6 | pip --version >/dev/null 2>&1 || { 7 | echo >&2 -e "\npip is required but it's not installed." 8 | echo >&2 -e "You can install it by running the following command:\n" 9 | echo >&2 "wget https://bootstrap.pypa.io/get-pip.py --output-document=get-pip.py; chmod +x get-pip.py; sudo -H python3 get-pip.py" 10 | echo >&2 -e "\n" 11 | echo >&2 -e "\nFor more information, see pip documentation: https://pip.pypa.io/en/latest/" 12 | exit 1; 13 | } 14 | 15 | virtualenv --version >/dev/null 2>&1 || { 16 | echo >&2 -e "\nvirtualenv is required but it's not installed." 17 | echo >&2 -e "You can install it by running the following command:\n" 18 | echo >&2 "sudo -H pip3 install virtualenv" 19 | echo >&2 -e "\n" 20 | echo >&2 -e "\nFor more information, see virtualenv documentation: https://virtualenv.pypa.io/en/latest/" 21 | exit 1; 22 | } 23 | 24 | if [ -z "$VIRTUAL_ENV" ]; then 25 | echo >&2 -e "\nYou need activate a virtualenv first" 26 | echo >&2 -e 'If you do not have a virtualenv created, run the following command to create and automatically activate a new virtualenv named "venv" on current folder:\n' 27 | echo >&2 -e "virtualenv venv --python=\`which python3\`" 28 | echo >&2 -e "\nTo leave/disable the currently active virtualenv, run the following command:\n" 29 | echo >&2 "deactivate" 30 | echo >&2 -e "\nTo activate the virtualenv again, run the following command:\n" 31 | echo >&2 "source venv/bin/activate" 32 | echo >&2 -e "\nFor more information, see virtualenv documentation: https://virtualenv.pypa.io/en/latest/" 33 | echo >&2 -e "\n" 34 | exit 1; 35 | else 36 | 37 | pip install -r $PROJECT_DIR/requirements/local.txt 38 | 39 | fi 40 | -------------------------------------------------------------------------------- /backend/src/main/java/fishing/lee/backend/WebConfig.java: -------------------------------------------------------------------------------- 1 | package fishing.lee.backend; 2 | 3 | import com.amazonaws.xray.AWSXRay; 4 | import com.amazonaws.xray.AWSXRayRecorderBuilder; 5 | import com.amazonaws.xray.javax.servlet.AWSXRayServletFilter; 6 | import com.amazonaws.xray.plugins.EC2Plugin; 7 | import com.amazonaws.xray.plugins.ElasticBeanstalkPlugin; 8 | import org.springframework.beans.factory.annotation.Value; 9 | import org.springframework.context.annotation.Bean; 10 | import org.springframework.context.annotation.Configuration; 11 | 12 | import javax.servlet.*; 13 | import java.io.IOException; 14 | 15 | @Configuration 16 | public class WebConfig { 17 | @Value("${amazon.xray.selfhosted}") 18 | private Boolean xrayEnabled = false; 19 | 20 | @Bean 21 | Boolean shouldCreateSegments() { 22 | return xrayEnabled; 23 | } 24 | 25 | @Bean 26 | public Filter TracingFilter() { 27 | if (xrayEnabled) { 28 | return new AWSXRayServletFilter("Backend"); 29 | } 30 | 31 | // Return an empty filter 32 | return new Filter() { 33 | @Override 34 | public void init(FilterConfig filterConfig) { 35 | 36 | } 37 | 38 | @Override 39 | public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { 40 | chain.doFilter(request, response); 41 | } 42 | 43 | @Override 44 | public void destroy() { 45 | 46 | } 47 | }; 48 | } 49 | 50 | static { 51 | AWSXRayRecorderBuilder builder = AWSXRayRecorderBuilder.standard() 52 | .withPlugin(new EC2Plugin()) 53 | .withPlugin(new ElasticBeanstalkPlugin()); 54 | AWSXRay.setGlobalRecorder(builder.build()); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /frontend/config/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for Lee Fishing project. 3 | 4 | This module contains the WSGI application used by Django's development server 5 | and any production WSGI deployments. It should expose a module-level variable 6 | named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover 7 | this application via the ``WSGI_APPLICATION`` setting. 8 | 9 | Usually you will have the standard Django WSGI application here, but it also 10 | might make sense to replace the whole Django WSGI application with a custom one 11 | that later delegates to the Django one. For example, you could introduce WSGI 12 | middleware here, or combine a Django application with an application of another 13 | framework. 14 | 15 | """ 16 | import os 17 | import sys 18 | 19 | from django.core.wsgi import get_wsgi_application 20 | 21 | # This allows easy placement of apps within the interior 22 | # lee_fishing directory. 23 | app_path = os.path.abspath(os.path.join( 24 | os.path.dirname(os.path.abspath(__file__)), os.pardir)) 25 | sys.path.append(os.path.join(app_path, 'lee_fishing')) 26 | 27 | # We defer to a DJANGO_SETTINGS_MODULE already in the environment. This breaks 28 | # if running multiple sites in the same mod_wsgi process. To fix this, use 29 | # mod_wsgi daemon mode with each site in its own daemon process, or use 30 | # os.environ["DJANGO_SETTINGS_MODULE"] = "config.settings.production" 31 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.production") 32 | 33 | # This application object is used by any WSGI server configured to use this 34 | # file. This includes Django's development server, if the WSGI_APPLICATION 35 | # setting points here. 36 | application = get_wsgi_application() 37 | 38 | # Apply WSGI middleware here. 39 | # from helloworld.wsgi import HelloWorldApplication 40 | # application = HelloWorldApplication(application) 41 | -------------------------------------------------------------------------------- /docs/exercise_3_post.rst: -------------------------------------------------------------------------------- 1 | Exercise #3 - Checklist for completion 2 | ====================================== 3 | 4 | 1. Pop over to the CloudFront service in the Console and copy the domain 5 | name for the Distribution and load it up in a new tab. 6 | 2. Click Rods and then the Muskie Casting Rod 7 | 3. The site will look completely different! 8 | 4. Click the `Sweet - Buy it now` button 9 | 10 | .. image:: images/pong.png 11 | 12 | 5. You will see the PONG response above. If you don't then you might have 13 | missed a step. Go back through :doc:`exercise_3` and check you covered 14 | everything. 15 | 16 | .. Note:: This is using a mock endpoint rather than the ordering endpoint. 17 | We're just showing how we can talk to our backend service via a 18 | different API Gateway. 19 | 20 | You can see the updated architecture diagram below. The new areas are in 21 | green. Of note is that the Lambda function is still within our VPC. We just 22 | have a new API Gateway outside the VPC which the static site can talk to. 23 | 24 | CORS is configured within the Lambda function rather than the API Gateway 25 | because of how we are using Proxy support. This means the original service 26 | is in full control. 27 | 28 | We are also only diverting traffic to our most popular product via the static 29 | website. This allows the shop to continue functioning the way it was for 30 | full priced traffic. Essentially, we are helping with pressure points of the 31 | shop. 32 | 33 | .. Note:: If we were designing this architecture from scratch, we would 34 | certainly do things a little differently. But this demonstrates 35 | a viable way to migrate elements of the same application to 36 | cloud native architectures while while re-platforming. 37 | 38 | .. image:: images/exercise_3_arch.png 39 | :height: 800px 40 | :align: center 41 | -------------------------------------------------------------------------------- /backend/src/main/java/fishing/lee/backend/StreamLambdaHandler.java: -------------------------------------------------------------------------------- 1 | package fishing.lee.backend; 2 | 3 | import com.amazonaws.serverless.exceptions.ContainerInitializationException; 4 | import com.amazonaws.serverless.proxy.model.AwsProxyRequest; 5 | import com.amazonaws.serverless.proxy.model.AwsProxyResponse; 6 | import com.amazonaws.serverless.proxy.spring.SpringBootLambdaContainerHandler; 7 | import com.amazonaws.services.lambda.runtime.Context; 8 | import com.amazonaws.services.lambda.runtime.RequestStreamHandler; 9 | 10 | import java.io.IOException; 11 | import java.io.InputStream; 12 | import java.io.OutputStream; 13 | 14 | /** 15 | * The Lambda handler. Notice we have a 'static' entry here for creating the proxy 16 | * which would never be touched in a normal run of the service. 17 | */ 18 | @SuppressWarnings("unused") 19 | public class StreamLambdaHandler implements RequestStreamHandler { 20 | private static SpringBootLambdaContainerHandler handler; 21 | 22 | static { 23 | try { 24 | handler = SpringBootLambdaContainerHandler.getAwsProxyHandler(Application.class); 25 | } catch (ContainerInitializationException e) { 26 | e.printStackTrace(); 27 | throw new RuntimeException("Could not initialize Spring Boot application", e); 28 | } 29 | } 30 | 31 | /** 32 | * Handle a request via the proxy 33 | * 34 | * @param input input stream from the API Gateway 35 | * @param output output stream to head to the API Gateway 36 | * @param context context detail of the Lambda call 37 | * @throws IOException 38 | */ 39 | @Override 40 | public void handleRequest(InputStream input, OutputStream output, Context context) throws IOException { 41 | handler.proxyStream(input, output, context); 42 | output.close(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /frontend/lee_fishing/templates/fishing_net/basket_detail.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block title %}Basket{% endblock %} 4 | 5 | {% block content %} 6 |
7 |
8 |   9 |
10 |
11 |
12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | {% for basket_item in basket.items_with_quantity %} 24 | 25 | 28 | 32 | 35 | 39 | 40 | {% endfor %} 41 | 42 |
ProductQuantityPrice 
26 | {{ basket_item.item_sku.title }} 27 | 29 | {{ basket_item.quantity }} / 31 | 33 | {{ basket_item.total_price_formatted }} 34 | 36 | 38 |
43 |
44 |
45 |
46 | Checkout 47 |
48 |
49 |
50 |
51 |
52 | {% endblock %} 53 | -------------------------------------------------------------------------------- /frontend/lee_fishing/fishing_net/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.8 on 2018-10-03 07:44 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | import uuid 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | initial = True 12 | 13 | dependencies = [ 14 | ('fishing_equipment', '0003_auto_20181002_2026'), 15 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 16 | ] 17 | 18 | operations = [ 19 | migrations.CreateModel( 20 | name='Basket', 21 | fields=[ 22 | ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), 23 | ('deleted_at', models.DateTimeField(blank=True, null=True)), 24 | ('created_date', models.DateTimeField(auto_now_add=True)), 25 | ('modified_date', models.DateTimeField(auto_now=True)), 26 | ('created_by', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to=settings.AUTH_USER_MODEL)), 27 | ], 28 | ), 29 | migrations.CreateModel( 30 | name='BasketItem', 31 | fields=[ 32 | ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), 33 | ('item_price', models.IntegerField()), 34 | ('quantity', models.IntegerField(default=0)), 35 | ('created_date', models.DateTimeField(auto_now_add=True)), 36 | ('modified_date', models.DateTimeField(auto_now=True)), 37 | ('basket', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='fishing_net.Basket')), 38 | ('item_sku', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='fishing_equipment.Product')), 39 | ], 40 | ), 41 | ] 42 | -------------------------------------------------------------------------------- /sqs_order_forwarder/src/main/java/fishing/lee/sqsforwarder/OrderForwarder.java: -------------------------------------------------------------------------------- 1 | package fishing.lee.sqsforwarder; 2 | 3 | import com.amazonaws.services.lambda.runtime.Context; 4 | import com.amazonaws.services.lambda.runtime.LambdaLogger; 5 | import com.amazonaws.services.lambda.runtime.RequestHandler; 6 | import com.amazonaws.services.lambda.runtime.events.SQSEvent; 7 | import org.apache.http.client.methods.CloseableHttpResponse; 8 | import org.apache.http.client.methods.HttpPost; 9 | import org.apache.http.entity.StringEntity; 10 | import org.apache.http.impl.client.CloseableHttpClient; 11 | import org.apache.http.impl.client.HttpClients; 12 | import org.apache.http.util.EntityUtils; 13 | 14 | import java.io.IOException; 15 | 16 | @SuppressWarnings("unused") 17 | public class OrderForwarder implements RequestHandler { 18 | private LambdaLogger logger; 19 | 20 | @Override 21 | public String handleRequest(SQSEvent input, Context context) { 22 | logger = context.getLogger(); 23 | 24 | input.getRecords().forEach(sqsMessage -> { 25 | String orderDetail = sqsMessage.getBody(); 26 | sendOrder(orderDetail); 27 | }); 28 | 29 | return ""; 30 | } 31 | 32 | private void sendOrder(String orderDetail) { 33 | logger.log("PROCESSING " + orderDetail); 34 | 35 | final String url = System.getenv("SHOPBACKEND_ORDER_URL") + "/order"; 36 | 37 | try (CloseableHttpClient httpclient = HttpClients.createDefault()) { 38 | HttpPost httpPost = new HttpPost(url); 39 | httpPost.addHeader("Content-Type", "application/json"); 40 | httpPost.setEntity(new StringEntity(orderDetail)); 41 | 42 | CloseableHttpResponse response = httpclient.execute(httpPost); 43 | logger.log(EntityUtils.toString(response.getEntity())); 44 | } catch (IOException e) { 45 | // We're cool and we'll ignore it 46 | e.printStackTrace(); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /frontend/README.rst: -------------------------------------------------------------------------------- 1 | Lee Fishing 2 | =========== 3 | 4 | Keeping it Reel! 5 | 6 | .. image:: https://img.shields.io/badge/built%20with-Cookiecutter%20Django-ff69b4.svg 7 | :target: https://github.com/pydanny/cookiecutter-django/ 8 | :alt: Built with Cookiecutter Django 9 | 10 | 11 | :License: MIT 12 | 13 | 14 | Settings 15 | -------- 16 | 17 | Moved to settings_. 18 | 19 | .. _settings: http://cookiecutter-django.readthedocs.io/en/latest/settings.html 20 | 21 | Basic Commands 22 | -------------- 23 | 24 | Setting Up Your Users 25 | ^^^^^^^^^^^^^^^^^^^^^ 26 | 27 | * To create a **normal user account**, just go to Sign Up and fill out the form. Once you submit it, you'll see a "Verify Your E-mail Address" page. Go to your console to see a simulated email verification message. Copy the link into your browser. Now the user's email should be verified and ready to go. 28 | 29 | * To create an **superuser account**, use this command:: 30 | 31 | $ python manage.py createsuperuser 32 | 33 | For convenience, you can keep your normal user logged in on Chrome and your superuser logged in on Firefox (or similar), so that you can see how the site behaves for both kinds of users. 34 | 35 | Type checks 36 | ^^^^^^^^^^^ 37 | 38 | Running type checks with mypy: 39 | 40 | :: 41 | 42 | $ mypy lee_fishing 43 | 44 | Test coverage 45 | ^^^^^^^^^^^^^ 46 | 47 | To run the tests, check your test coverage, and generate an HTML coverage report:: 48 | 49 | $ coverage run -m pytest 50 | $ coverage html 51 | $ open htmlcov/index.html 52 | 53 | Running tests with py.test 54 | ~~~~~~~~~~~~~~~~~~~~~~~~~~ 55 | 56 | :: 57 | 58 | $ pytest 59 | 60 | Live reloading and Sass CSS compilation 61 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 62 | 63 | Moved to `Live reloading and SASS compilation`_. 64 | 65 | .. _`Live reloading and SASS compilation`: http://cookiecutter-django.readthedocs.io/en/latest/live-reloading-and-sass-compilation.html 66 | 67 | 68 | 69 | 70 | 71 | Deployment 72 | ---------- 73 | 74 | The following details how to deploy this application. 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /infrastructure/src/main/java/fishing/lee/infrastructure/ShopStack.java: -------------------------------------------------------------------------------- 1 | package fishing.lee.infrastructure; 2 | 3 | import software.amazon.awscdk.App; 4 | import software.amazon.awscdk.Stack; 5 | import software.amazon.awscdk.StackProps; 6 | 7 | class ShopStack extends Stack { 8 | ShopStack(final App parent, final String name) { 9 | this(parent, name, null); 10 | } 11 | 12 | private ShopStack(final App parent, final String name, final StackProps props) { 13 | super(parent, name, props); 14 | 15 | ShopVpc shopVpc = new ShopVpc(this, "Vpc"); 16 | 17 | Postgres postgres = new Postgres(this, "ShopDatabase", PostgresProps.builder() 18 | .withShopVpc(shopVpc) 19 | .build()); 20 | 21 | Bastion bastion = new Bastion(this, "Bastion", BastionProps.builder() 22 | .withShopVpc(shopVpc) 23 | .withSshKeyPairName("FishingKey") 24 | .build()); 25 | 26 | ShopBackend shopBackend = new ShopBackend(this, "ShopBackend", ShopBackendProps.builder() 27 | .withShopVpc(shopVpc) 28 | .withBastionSecurityGroup(bastion.getBastionSecurityGroup()) 29 | .build()); 30 | 31 | new ScheduledApiCaller(this, "ApiPing", ScheduledApiCallerProps.builder() 32 | .withShopVpc(shopVpc) 33 | .withApiToPing(shopBackend.getLambdaApiUrl()) 34 | .build()); 35 | 36 | new ShopFrontend(this, "ShopFrontend", ShopFrontendProps.builder() 37 | .withShopVpc(shopVpc) 38 | .withShopBackend(shopBackend) 39 | .withPostgres(postgres) 40 | .build()); 41 | 42 | new QueueProxy(this, "QueueProxy", QueueProxyProps.builder() 43 | .withShopVpc(shopVpc) 44 | .withApiUrl(shopBackend.getLambdaApiUrl()) 45 | .build()); 46 | 47 | new Decoupling(this, "Decoupling"); 48 | 49 | new DeploymentAssets(this, "DeploymentAssets"); 50 | 51 | new ContentDistribution(this, "CDN"); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /frontend/config/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.urls import include, path 3 | from django.conf.urls.static import static 4 | from django.contrib import admin 5 | from django.views.generic import TemplateView 6 | from django.views import defaults as default_views 7 | 8 | from lee_fishing.fishing_equipment.views import RootRedirect 9 | 10 | urlpatterns = [ 11 | path("", RootRedirect.as_view(), name="home"), 12 | path( 13 | "about/", 14 | TemplateView.as_view(template_name="pages/about.html"), 15 | name="about", 16 | ), 17 | # Django Admin, use {% url 'admin:index' %} 18 | path(settings.ADMIN_URL, admin.site.urls), 19 | # User management 20 | path( 21 | "users/", 22 | include("lee_fishing.users.urls", namespace="users"), 23 | ), 24 | path("accounts/", include("allauth.urls")), 25 | # Your stuff: custom urls includes go here 26 | path("basket/", include("lee_fishing.fishing_net.urls", namespace="baskets")), 27 | path("shop/", include("lee_fishing.fishing_equipment.urls", namespace="products")), 28 | ] + static( 29 | settings.MEDIA_URL, document_root=settings.MEDIA_ROOT 30 | ) 31 | 32 | if settings.DEBUG: 33 | # This allows the error pages to be debugged during development, just visit 34 | # these url in browser to see how these error pages look like. 35 | urlpatterns += [ 36 | path( 37 | "400/", 38 | default_views.bad_request, 39 | kwargs={"exception": Exception("Bad Request!")}, 40 | ), 41 | path( 42 | "403/", 43 | default_views.permission_denied, 44 | kwargs={"exception": Exception("Permission Denied")}, 45 | ), 46 | path( 47 | "404/", 48 | default_views.page_not_found, 49 | kwargs={"exception": Exception("Page not Found")}, 50 | ), 51 | path("500/", default_views.server_error), 52 | ] 53 | if "debug_toolbar" in settings.INSTALLED_APPS: 54 | import debug_toolbar 55 | 56 | urlpatterns = [path("__debug__/", include(debug_toolbar.urls))] + urlpatterns 57 | -------------------------------------------------------------------------------- /infrastructure/src/main/java/fishing/lee/infrastructure/ShopVpc.java: -------------------------------------------------------------------------------- 1 | package fishing.lee.infrastructure; 2 | 3 | import software.amazon.awscdk.AwsRegion; 4 | import software.amazon.awscdk.Construct; 5 | import software.amazon.awscdk.services.ec2.*; 6 | 7 | import java.util.Collections; 8 | import java.util.stream.Collectors; 9 | 10 | class ShopVpc extends Construct { 11 | private final VpcNetwork vpc; 12 | private final SecurityGroup apiEndpointSecurityGroup; 13 | private final CfnVPCEndpoint apiEndpoint; 14 | 15 | ShopVpc(Construct parent, String id) { 16 | super(parent, id); 17 | 18 | vpc = new VpcNetwork(this, "Fishing", VpcNetworkProps.builder() 19 | .withMaxAZs(2) 20 | .withCidr("10.0.0.0/16") 21 | .withEnableDnsSupport(true) 22 | .withEnableDnsHostnames(true) 23 | .withNatGateways(1) 24 | .build()); 25 | 26 | apiEndpointSecurityGroup = new SecurityGroup(this, "APISecurityGroup", SecurityGroupProps.builder() 27 | .withVpc(vpc) 28 | .build()); 29 | 30 | apiEndpoint = new CfnVPCEndpoint(this, "APIEndpoint", CfnVPCEndpointProps.builder() 31 | .withVpcId(vpc.getVpcId()) 32 | .withServiceName("com.amazonaws." + new AwsRegion() + ".execute-api") 33 | .withPrivateDnsEnabled(true) 34 | .withSecurityGroupIds(Collections.singletonList(apiEndpointSecurityGroup.getSecurityGroupId())) 35 | .withVpcEndpointType("Interface") 36 | .withSubnetIds(vpc.getPrivateSubnets() 37 | .stream() 38 | .map(VpcSubnetRef::getSubnetId) 39 | .collect(Collectors.toList())) 40 | .build()); 41 | } 42 | 43 | VpcNetwork getVpc() { 44 | return vpc; 45 | } 46 | 47 | void addSecurityGroupToApi(SecurityGroup securityGroup) { 48 | apiEndpointSecurityGroup.addIngressRule(securityGroup, new TcpPort(443)); 49 | } 50 | 51 | CfnVPCEndpoint getApiEndpoint() { 52 | return apiEndpoint; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /frontend/lee_fishing/fishing_net/models.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | 3 | from django.db import models 4 | from django.contrib.auth import get_user_model 5 | 6 | from lee_fishing.fishing_net.managers import BasketManager 7 | 8 | 9 | class Basket(models.Model): 10 | id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) 11 | created_by = models.ForeignKey(get_user_model(), on_delete=models.DO_NOTHING) 12 | 13 | deleted_at = models.DateTimeField(null=True, blank=True) 14 | created_date = models.DateTimeField(auto_now_add=True) 15 | modified_date = models.DateTimeField(auto_now=True) 16 | order_id = models.TextField(null=True, blank=True, max_length=500) 17 | 18 | objects = BasketManager() 19 | 20 | def items_with_quantity(self): 21 | return self.basketitem_set.filter(quantity__gt=0) 22 | 23 | def add_item(self, item): 24 | if not self.modified_date: 25 | self.save() 26 | 27 | basket_item, created = self.basketitem_set.update_or_create( 28 | item_sku=item, 29 | defaults={ 30 | 'basket': self, 31 | 'item_price': item.price, 32 | } 33 | ) 34 | 35 | basket_item.quantity = 1 if created else basket_item.quantity+1 36 | basket_item.save() 37 | 38 | def remove_item(self, item): 39 | if not self.modified_date: 40 | return 41 | 42 | self.basketitem_set.filter(item_sku=item).update(quantity=0) 43 | 44 | 45 | class BasketItem(models.Model): 46 | id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) 47 | basket = models.ForeignKey('Basket', on_delete=models.DO_NOTHING) 48 | item_sku = models.ForeignKey('fishing_equipment.Product', on_delete=models.DO_NOTHING) 49 | item_price = models.IntegerField() 50 | quantity = models.IntegerField(default=0) 51 | 52 | created_date = models.DateTimeField(auto_now_add=True) 53 | modified_date = models.DateTimeField(auto_now=True) 54 | 55 | class Meta: 56 | unique_together = ('basket', 'item_sku',) 57 | 58 | @property 59 | def total_price_formatted(self): 60 | v = self.item_price * self.quantity 61 | return '$%.2f' % (v / 100) 62 | -------------------------------------------------------------------------------- /frontend/lee_fishing/fishing_equipment/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.8 on 2018-10-02 20:24 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | import imagekit.models.fields 6 | import uuid 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | initial = True 12 | 13 | dependencies = [ 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name='Category', 19 | fields=[ 20 | ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), 21 | ('title', models.CharField(max_length=100)), 22 | ('slug', models.SlugField(unique=True)), 23 | ('created_date', models.DateTimeField(auto_now_add=True)), 24 | ('modified_date', models.DateTimeField(auto_now=True)), 25 | ], 26 | options={ 27 | 'verbose_name_plural': 'Categories', 28 | }, 29 | ), 30 | migrations.CreateModel( 31 | name='Product', 32 | fields=[ 33 | ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), 34 | ('title', models.CharField(max_length=100)), 35 | ('description', models.TextField(blank=True)), 36 | ('slug', models.SlugField(unique=True)), 37 | ('picture', models.ImageField(upload_to='products')), 38 | ('smaller_picture', imagekit.models.fields.ProcessedImageField(upload_to='')), 39 | ('in_stock', models.BooleanField()), 40 | ('price', models.IntegerField()), 41 | ('deleted_at', models.DateTimeField(blank=True, null=True)), 42 | ('created_date', models.DateTimeField(auto_now_add=True)), 43 | ('modified_date', models.DateTimeField(auto_now=True)), 44 | ('categories', models.ManyToManyField(blank=True, to='fishing_equipment.Category')), 45 | ('primary_category', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='primary_products', to='fishing_equipment.Category')), 46 | ], 47 | ), 48 | ] 49 | -------------------------------------------------------------------------------- /frontend/config/settings/test.py: -------------------------------------------------------------------------------- 1 | """ 2 | With these settings, tests run faster. 3 | """ 4 | 5 | from .base import * # noqa 6 | from .base import env 7 | 8 | # GENERAL 9 | # ------------------------------------------------------------------------------ 10 | # https://docs.djangoproject.com/en/dev/ref/settings/#debug 11 | DEBUG = False 12 | # https://docs.djangoproject.com/en/dev/ref/settings/#secret-key 13 | SECRET_KEY = env("DJANGO_SECRET_KEY", default="Btn1bmrIAResyjcP90xmqUrie5vtuz8zew5XLm96W5O2ij4ZVuL7y8MPSSu8IeAI") 14 | # https://docs.djangoproject.com/en/dev/ref/settings/#test-runner 15 | TEST_RUNNER = "django.test.runner.DiscoverRunner" 16 | 17 | # CACHES 18 | # ------------------------------------------------------------------------------ 19 | # https://docs.djangoproject.com/en/dev/ref/settings/#caches 20 | CACHES = { 21 | "default": { 22 | "BACKEND": "django.core.cache.backends.locmem.LocMemCache", "LOCATION": "" 23 | } 24 | } 25 | 26 | # PASSWORDS 27 | # ------------------------------------------------------------------------------ 28 | # https://docs.djangoproject.com/en/dev/ref/settings/#password-hashers 29 | PASSWORD_HASHERS = ["django.contrib.auth.hashers.MD5PasswordHasher"] 30 | 31 | # TEMPLATES 32 | # ------------------------------------------------------------------------------ 33 | # https://docs.djangoproject.com/en/dev/ref/settings/#templates 34 | TEMPLATES[0]["OPTIONS"]["debug"] = DEBUG # noqa F405 35 | TEMPLATES[0]["OPTIONS"]["loaders"] = [ # noqa F405 36 | ( 37 | "django.template.loaders.cached.Loader", 38 | [ 39 | "django.template.loaders.filesystem.Loader", 40 | "django.template.loaders.app_directories.Loader", 41 | ], 42 | ) 43 | ] 44 | 45 | # EMAIL 46 | # ------------------------------------------------------------------------------ 47 | # https://docs.djangoproject.com/en/dev/ref/settings/#email-backend 48 | EMAIL_BACKEND = "django.core.mail.backends.locmem.EmailBackend" 49 | # https://docs.djangoproject.com/en/dev/ref/settings/#email-host 50 | EMAIL_HOST = "localhost" 51 | # https://docs.djangoproject.com/en/dev/ref/settings/#email-port 52 | EMAIL_PORT = 1025 53 | 54 | # Your stuff... 55 | # ------------------------------------------------------------------------------ 56 | -------------------------------------------------------------------------------- /infrastructure/src/main/java/fishing/lee/infrastructure/Bastion.java: -------------------------------------------------------------------------------- 1 | package fishing.lee.infrastructure; 2 | 3 | import software.amazon.awscdk.Construct; 4 | import software.amazon.awscdk.Output; 5 | import software.amazon.awscdk.OutputProps; 6 | import software.amazon.awscdk.services.autoscaling.AutoScalingGroup; 7 | import software.amazon.awscdk.services.autoscaling.AutoScalingGroupProps; 8 | import software.amazon.awscdk.services.ec2.*; 9 | 10 | class Bastion extends Construct { 11 | private final SecurityGroup bastionSecurityGroup; 12 | 13 | Bastion(Construct parent, String id, BastionProps properties) { 14 | super(parent, id); 15 | 16 | AutoScalingGroup bastionAsg = new AutoScalingGroup(this, "AutoScalingGroup", AutoScalingGroupProps.builder() 17 | .withAllowAllOutbound(true) 18 | .withDesiredCapacity(1) 19 | .withMinSize(1) 20 | .withMaxSize(1) 21 | .withVpc(properties.getShopVpc().getVpc()) 22 | .withVpcPlacement(VpcPlacementStrategy.builder() 23 | .withSubnetsToUse(SubnetType.Public) 24 | .build()) 25 | .withInstanceType(new InstanceTypePair(InstanceClass.Burstable3, InstanceSize.Micro)) 26 | .withMachineImage(new AmazonLinuxImage()) 27 | .withKeyName(properties.getSshKeyPairName()) 28 | .build()); 29 | 30 | bastionSecurityGroup = new SecurityGroup(this, "SecurityGroup", SecurityGroupProps.builder() 31 | .withVpc(properties.getShopVpc().getVpc()) 32 | .build()); 33 | 34 | properties.getShopVpc().addSecurityGroupToApi(bastionSecurityGroup); 35 | 36 | bastionAsg.addSecurityGroup(bastionSecurityGroup); 37 | bastionAsg.addUserData( 38 | "yum update -y", 39 | "yum install -y jq" 40 | ); 41 | 42 | bastionSecurityGroup.addIngressRule(new AnyIPv4(), new TcpPort(22)); 43 | 44 | new Output(this, "AutoScalingGroupName", OutputProps.builder() 45 | .withValue(bastionAsg.getAutoScalingGroupName()) 46 | .build()); 47 | } 48 | 49 | SecurityGroup getBastionSecurityGroup() { 50 | return bastionSecurityGroup; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /frontend_minisite/assets/sass/app.scss: -------------------------------------------------------------------------------- 1 | // Variables 2 | @import "variables"; 3 | 4 | // Bootstrap 5 | @import "~bootstrap/scss/bootstrap"; 6 | @import "~bootstrap-vue/dist/bootstrap-vue.css"; 7 | 8 | // Plugins 9 | @import "~sweetalert2/src/sweetalert2"; 10 | @import "~slick-carousel/slick/slick"; 11 | @import "~slick-carousel/slick/slick-theme"; 12 | 13 | /* Space out content a bit */ 14 | body { 15 | padding-top: 20px; 16 | padding-bottom: 20px; 17 | } 18 | 19 | [v-cloak] { 20 | display: none; 21 | } 22 | 23 | /* Everything but the jumbotron gets side spacing for mobile first views */ 24 | .header, 25 | .marketing, 26 | .footer { 27 | padding-left: 15px; 28 | padding-right: 15px; 29 | } 30 | 31 | /* Custom page header */ 32 | .header { 33 | border-bottom: 1px solid #e5e5e5; 34 | 35 | /* Make the masthead heading the same height as the navigation */ 36 | h3 { 37 | margin-top: 0; 38 | margin-bottom: 0; 39 | line-height: 40px; 40 | padding-bottom: 19px; 41 | } 42 | } 43 | 44 | /* Custom page footer */ 45 | .footer { 46 | padding-top: 19px; 47 | color: #777; 48 | border-top: 1px solid #e5e5e5; 49 | } 50 | 51 | .container-narrow > hr { 52 | margin: 30px 0; 53 | } 54 | 55 | /* Main marketing message and sign up button */ 56 | .jumbotron { 57 | text-align: center; 58 | border-bottom: 1px solid #e5e5e5; 59 | .btn { 60 | font-size: 21px; 61 | padding: 14px 24px; 62 | } 63 | } 64 | 65 | /* Supporting marketing content */ 66 | .marketing { 67 | margin: 40px 0; 68 | p + h4 { 69 | margin-top: 28px; 70 | } 71 | } 72 | 73 | .slick-list { 74 | .slick-loading & { 75 | background: none; 76 | } 77 | } 78 | 79 | .slick-slide { 80 | text-align: center; 81 | 82 | h3 { 83 | line-height: 200px; 84 | background-color: #eee; 85 | margin: 0 10px; 86 | } 87 | } 88 | 89 | .btn { 90 | background-color: lightgray; 91 | } 92 | 93 | /* Responsive: Portrait tablets and up */ 94 | @media screen and (min-width: 768px) { 95 | .container { 96 | max-width: 730px; 97 | } 98 | 99 | /* Remove the padding we set earlier */ 100 | .header, 101 | .marketing, 102 | .footer { 103 | padding-left: 0; 104 | padding-right: 0; 105 | } 106 | 107 | /* Space out the masthead */ 108 | .header { 109 | margin-bottom: 30px; 110 | } 111 | 112 | /* Remove the bottom border on the jumbotron for visual effect */ 113 | .jumbotron { 114 | border-bottom: 0; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /frontend_minisite/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "html-boilerplate", 3 | "version": "1.0.0", 4 | "description": "HTML Frontend boilerplate based on Bootstrap for pure showcase/prototype sites, with Webpack for assets management.", 5 | "scripts": { 6 | "dev": "cross-env NODE_ENV=development webpack-dev-server --mode development --hot --progress --open", 7 | "build": "rimraf dist && cross-env NODE_ENV=production webpack --mode production --progress", 8 | "lint": "eslint --ext .js,.vue assets/js views" 9 | }, 10 | "engines": { 11 | "node": ">=6" 12 | }, 13 | "author": "Adrien Beaudouin", 14 | "license": "MIT", 15 | "devDependencies": { 16 | "@babel/core": "^7.1.2", 17 | "@babel/preset-env": "^7.1.0", 18 | "@fortawesome/fontawesome-svg-core": "^1.2.4", 19 | "@fortawesome/free-brands-svg-icons": "^5.3.1", 20 | "@fortawesome/free-regular-svg-icons": "^5.3.1", 21 | "@fortawesome/free-solid-svg-icons": "^5.3.1", 22 | "@fortawesome/vue-fontawesome": "^0.1.1", 23 | "autoprefixer": "^9.1.5", 24 | "axios": "^0.18.0", 25 | "babel-eslint": "^10.0.1", 26 | "babel-loader": "^8.0.4", 27 | "bootstrap": "^4.1.1", 28 | "bootstrap-vue": "^2.0.0-rc.10", 29 | "cross-env": "^5.1.5", 30 | "css-loader": "^1.0.0", 31 | "cssnano": "^4.1.4", 32 | "dotenv": "^6.0.0", 33 | "eslint": "^5.5.0", 34 | "eslint-config-prettier": "^3.1.0", 35 | "eslint-loader": "^2.1.0", 36 | "eslint-plugin-prettier": "^2.7.0", 37 | "eslint-plugin-vue": "^5.0.0-beta.3", 38 | "file-loader": "^2.0.0", 39 | "friendly-errors-webpack-plugin": "^1.7.0", 40 | "handlebars": "^4.0.11", 41 | "handlebars-loader": "^1.7.0", 42 | "html-loader": "^0.5.5", 43 | "html-webpack-plugin": "^3.2.0", 44 | "jquery": "^3.3.1", 45 | "mini-css-extract-plugin": "^0.4.0", 46 | "node-sass": "^4.9.0", 47 | "popper.js": "^1.14.3", 48 | "postcss-loader": "^3.0.0", 49 | "prettier": "^1.14.3", 50 | "rimraf": "^2.6.2", 51 | "sass-loader": "^7.0.1", 52 | "slick-carousel": "^1.8.1", 53 | "style-loader": "^0.23.0", 54 | "sweetalert2": "^7.28.4", 55 | "url-loader": "^1.1.1", 56 | "vue": "^2.5.16", 57 | "vue-loader": "^15.1.0", 58 | "vue-template-compiler": "^2.5.16", 59 | "webpack": "^4.20.2", 60 | "webpack-cli": "^3.1.1", 61 | "webpack-dev-server": "^3.1.4", 62 | "webpack-manifest-plugin": "^2.0.2", 63 | "webpack-notifier": "^1.6.0" 64 | }, 65 | "dependencies": {}, 66 | "browserslist": [ 67 | "> 1%", 68 | "last 2 versions" 69 | ] 70 | } 71 | -------------------------------------------------------------------------------- /frontend/lee_fishing/fishing_equipment/fixtures/category.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "model": "fishing_equipment.category", 4 | "pk": "021831c9-22ab-4437-9505-edb6b24e2e0f", 5 | "fields": { 6 | "title": "Rods", 7 | "slug": "rods", 8 | "created_date": "2018-10-02T20:28:29.073Z", 9 | "modified_date": "2018-10-02T20:37:13.183Z" 10 | } 11 | }, 12 | { 13 | "model": "fishing_equipment.category", 14 | "pk": "9be69ffd-1fb9-4cba-8203-1446898857c1", 15 | "fields": { 16 | "title": "Reels", 17 | "slug": "reels", 18 | "created_date": "2018-10-02T20:28:29.073Z", 19 | "modified_date": "2018-10-02T20:37:13.183Z" 20 | } 21 | }, 22 | { 23 | "model": "fishing_equipment.category", 24 | "pk": "be3f1c9d-c3bd-45e7-acb0-80efb40c61ef", 25 | "fields": { 26 | "title": "Hard baits", 27 | "slug": "hard-baits", 28 | "created_date": "2018-10-02T20:28:29.073Z", 29 | "modified_date": "2018-10-02T20:37:13.183Z" 30 | } 31 | }, 32 | { 33 | "model": "fishing_equipment.category", 34 | "pk": "282a8539-a263-49e1-b028-1bb4acc7216d", 35 | "fields": { 36 | "title": "Soft baits", 37 | "slug": "soft-baits", 38 | "created_date": "2018-10-02T20:28:29.073Z", 39 | "modified_date": "2018-10-02T20:37:13.183Z" 40 | } 41 | }, 42 | { 43 | "model": "fishing_equipment.category", 44 | "pk": "21edc280-8357-479f-a35e-94402bab367d", 45 | "fields": { 46 | "title": "Swimbaits", 47 | "slug": "swim-baits", 48 | "created_date": "2018-10-02T20:28:29.073Z", 49 | "modified_date": "2018-10-02T20:37:13.183Z" 50 | } 51 | }, 52 | { 53 | "model": "fishing_equipment.category", 54 | "pk": "9608833d-18ed-4952-bc61-faf83e7514f8", 55 | "fields": { 56 | "title": "Tackle storage", 57 | "slug": "tackle-storage", 58 | "created_date": "2018-10-02T20:28:29.073Z", 59 | "modified_date": "2018-10-02T20:37:13.183Z" 60 | } 61 | }, 62 | { 63 | "model": "fishing_equipment.category", 64 | "pk": "50b2418b-b9e8-4d7e-9ff6-2bdf801239ea", 65 | "fields": { 66 | "title": "Waders", 67 | "slug": "waders", 68 | "created_date": "2018-10-02T20:28:29.073Z", 69 | "modified_date": "2018-10-02T20:37:13.183Z" 70 | } 71 | } 72 | ] -------------------------------------------------------------------------------- /backend/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /null_lambda/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /sqs_order_forwarder/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /frontend/lee_fishing/fishing_net/views.py: -------------------------------------------------------------------------------- 1 | from timeit import default_timer 2 | 3 | from django.contrib import messages 4 | from django.contrib.auth.mixins import LoginRequiredMixin 5 | from django.http import HttpResponseRedirect 6 | from django.urls import reverse 7 | from django.views.generic import TemplateView, DetailView, RedirectView 8 | 9 | from lee_fishing.fishing_equipment.models import Product 10 | from lee_fishing.fishing_net.backend import BackendClient 11 | from lee_fishing.fishing_net.models import Basket 12 | 13 | 14 | class DummyView(TemplateView): 15 | template_name = 'fishing_net/ping.html' 16 | 17 | def get(self, request, *args, **kwargs): 18 | backend_client = BackendClient() 19 | kwargs['pong'] = backend_client.ping() 20 | 21 | return super().get(request, *args, **kwargs) 22 | 23 | 24 | class BasketDetailView(LoginRequiredMixin, DetailView): 25 | model = Basket 26 | 27 | def get_object(self, queryset=None): 28 | return self.model.objects.current_for_user(self.request.user) 29 | 30 | 31 | class BasketDeleteItemView(LoginRequiredMixin, RedirectView): 32 | permanent = False 33 | 34 | def get_redirect_url(self, *args, **kwargs): 35 | basket = Basket.objects.current_for_user(self.request.user) 36 | basket.remove_item(Product.objects.get(slug=self.kwargs['item'])) 37 | return reverse('baskets:basket') 38 | 39 | 40 | class BasketAddItemView(LoginRequiredMixin, RedirectView): 41 | permanent = False 42 | 43 | def get_redirect_url(self, *args, **kwargs): 44 | basket = Basket.objects.current_for_user(self.request.user) 45 | basket.add_item(Product.objects.not_deleted().get(slug=self.kwargs['item'])) 46 | return reverse('baskets:basket') 47 | 48 | 49 | class CheckoutView(LoginRequiredMixin, TemplateView): 50 | template_name = 'fishing_net/checkout.html' 51 | 52 | def get(self, request, *args, **kwargs): 53 | return super().get(request, *args, **kwargs) 54 | 55 | def post(self, request, *args, **kwargs): 56 | start = default_timer() 57 | basket = Basket.objects.current_for_user(self.request.user) 58 | backend_client = BackendClient() 59 | 60 | r = backend_client.place_order() 61 | basket.order_id = r 62 | basket.save() 63 | 64 | end = default_timer() 65 | 66 | messages.add_message(request, messages.INFO, "It took " + str(end-start) + " seconds to process the order") 67 | 68 | return HttpResponseRedirect(reverse('basket:congrats')) 69 | 70 | 71 | class CongratulationsView(LoginRequiredMixin, TemplateView): 72 | template_name = 'fishing_net/congrats.html' 73 | -------------------------------------------------------------------------------- /backend/src/test/java/fishing/lee/backend/BasketControllerTest.java: -------------------------------------------------------------------------------- 1 | package fishing.lee.backend; 2 | 3 | import org.junit.Test; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | 6 | import java.util.HashSet; 7 | import java.util.UUID; 8 | 9 | public class BasketControllerTest extends BaseDynamoDBTest { 10 | @Autowired 11 | BasketController basketController; 12 | 13 | @Test(expected = BasketNotFoundException.class) 14 | public void oneUnknown() { 15 | basketController.one(UUID.randomUUID()); 16 | } 17 | 18 | private BasketModel getMockedBasketModel(String singleItem) { 19 | BasketModel basketModel = new BasketModel(); 20 | basketModel.setItems(new HashSet<>()); 21 | basketModel.setCreationPoint(null); 22 | basketModel.addItem(singleItem); 23 | return basketModel; 24 | } 25 | 26 | @Test 27 | public void newBasket() { 28 | BasketModel basketModel = getMockedBasketModel("test"); 29 | 30 | BasketModel result = basketController.newBasket(basketModel); 31 | assert(result.getItems().contains("test")); 32 | assert(result.getId() != null); 33 | assert(result.getCreationPoint() != null); 34 | 35 | BasketModel storedResult = basketController.one(result.getId()); 36 | assert(result.getId().equals(storedResult.getId())); 37 | } 38 | 39 | @Test 40 | public void addItem() { 41 | BasketModel basketModel = getMockedBasketModel("dummy"); 42 | 43 | BasketModel result = basketController.newBasket(basketModel); 44 | basketController.addItem(result.getId(), "item"); 45 | 46 | BasketModel storedResult = basketController.one(result.getId()); 47 | assert(storedResult.getItems().contains("item")); 48 | } 49 | 50 | @Test(expected = BasketNotFoundException.class) 51 | public void deleteBasket() { 52 | BasketModel basketModel = getMockedBasketModel("dummy"); 53 | 54 | BasketModel result = basketController.newBasket(basketModel); 55 | basketController.deleteBasket(result.getId()); 56 | 57 | basketController.one(result.getId()); 58 | } 59 | 60 | @Test 61 | public void deleteItem() { 62 | BasketModel basketModel = getMockedBasketModel("dummy"); 63 | basketModel.addItem("deletedItem"); 64 | 65 | BasketModel result = basketController.newBasket(basketModel); 66 | result = basketController.deleteItem(result.getId(), "deletedItem"); 67 | assert(!result.getItems().contains("deletedItem")); 68 | 69 | BasketModel storedResult = basketController.one(result.getId()); 70 | assert(!storedResult.getItems().contains("deletedItem")); 71 | } 72 | } -------------------------------------------------------------------------------- /backend/src/test/java/fishing/lee/backend/BaseDynamoDBTest.java: -------------------------------------------------------------------------------- 1 | package fishing.lee.backend; 2 | 3 | import com.amazonaws.services.dynamodbv2.AmazonDynamoDB; 4 | import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper; 5 | import com.amazonaws.services.dynamodbv2.local.main.ServerRunner; 6 | import com.amazonaws.services.dynamodbv2.local.server.DynamoDBProxyServer; 7 | import com.amazonaws.xray.AWSXRay; 8 | import org.junit.*; 9 | import org.junit.runner.RunWith; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.boot.test.context.SpringBootTest; 12 | import org.springframework.test.context.ActiveProfiles; 13 | import org.springframework.test.context.TestPropertySource; 14 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 15 | import org.springframework.test.context.web.WebAppConfiguration; 16 | 17 | import static fishing.lee.backend.DynamoDBConfig.checkOrCreateTable; 18 | 19 | @RunWith(SpringJUnit4ClassRunner.class) 20 | @SpringBootTest(classes = Application.class) 21 | @WebAppConfiguration 22 | @ActiveProfiles("local") 23 | @TestPropertySource(properties = { 24 | "amazon.aws.mode=local" 25 | }) 26 | public abstract class BaseDynamoDBTest { 27 | private static DynamoDBProxyServer serverRunner; 28 | 29 | @Autowired 30 | protected AmazonDynamoDB amazonDynamoDB; 31 | 32 | @Autowired 33 | protected BasketRepository basketRepository; 34 | 35 | @Autowired 36 | protected DynamoDBMapper dynamoDBMapper; 37 | 38 | @BeforeClass 39 | public static void runDynamoDB() { 40 | System.setProperty("sqlite4java.library.path", "./build/libs/"); 41 | 42 | final String[] localArgs = { "-inMemory", "-port", "8001" }; 43 | 44 | try { 45 | serverRunner = ServerRunner.createServerFromCommandLineArgs(localArgs); 46 | serverRunner.start(); 47 | 48 | } catch (Exception e) { 49 | e.printStackTrace(); 50 | Assert.fail(e.getMessage()); 51 | } 52 | } 53 | 54 | @AfterClass 55 | public static void shutdownDynamoDB() { 56 | if (serverRunner != null) { 57 | try { 58 | serverRunner.stop(); 59 | } catch (Exception e) { 60 | e.printStackTrace(); 61 | } 62 | } 63 | } 64 | 65 | @Before 66 | public void setup() { 67 | AWSXRay.beginSegment("BasketRepositoryIntegrationTest"); 68 | 69 | checkOrCreateTable(amazonDynamoDB, dynamoDBMapper, BasketModel.class); 70 | 71 | dynamoDBMapper.batchDelete( 72 | basketRepository.findAll() 73 | ); 74 | } 75 | 76 | @After 77 | public void tearDown() { 78 | AWSXRay.endSegment(); 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /frontend/lee_fishing/fishing_equipment/models.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | 3 | from django.db import models 4 | from imagekit.models import ProcessedImageField 5 | from imagekit.processors import ResizeToFill 6 | 7 | from lee_fishing.fishing_equipment.managers import ProductManager 8 | 9 | 10 | class Category(models.Model): 11 | id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) 12 | title = models.CharField(max_length=100, null=False, blank=False) 13 | slug = models.SlugField(null=False, blank=False, unique=True) 14 | 15 | created_date = models.DateTimeField(auto_now_add=True) 16 | modified_date = models.DateTimeField(auto_now=True) 17 | 18 | def __str__(self): 19 | return self.title 20 | 21 | class Meta: 22 | ordering = ['title'] 23 | verbose_name_plural = 'Categories' 24 | 25 | 26 | class Product(models.Model): 27 | id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) 28 | title = models.CharField(max_length=100, null=False, blank=False) 29 | description = models.TextField(blank=True) 30 | slug = models.SlugField(null=False, blank=False, unique=True) 31 | 32 | primary_category = models.ForeignKey(Category, 33 | on_delete=models.DO_NOTHING, null=False, blank=False, 34 | related_name='primary_products' 35 | ) 36 | categories = models.ManyToManyField(Category, blank=True) 37 | 38 | picture = models.ImageField(upload_to='products', null=True, blank=True) 39 | smaller_picture = ProcessedImageField(upload_to='product_thumbs', 40 | processors=[ResizeToFill(200, 200)], 41 | format='JPEG', 42 | options={'quality': 70}, 43 | null=True, blank=True) 44 | 45 | in_stock = models.BooleanField() 46 | price = models.IntegerField() 47 | 48 | deleted_at = models.DateTimeField(null=True, blank=True) 49 | 50 | created_date = models.DateTimeField(auto_now_add=True) 51 | modified_date = models.DateTimeField(auto_now=True) 52 | 53 | objects = ProductManager() 54 | 55 | @property 56 | def formatted_price(self): 57 | """ 58 | Formatted price (as a string) for this product. There's so much 59 | wrong with this, you shouldn't use this for any serious 60 | ecommerce site! 61 | 62 | :return: formatted price... ish 63 | :rtype: str 64 | """ 65 | 66 | return '$%.2f' % (self.price / 100) 67 | 68 | def __str__(self): 69 | return self.title 70 | 71 | class Meta: 72 | ordering = ['title'] 73 | -------------------------------------------------------------------------------- /sqs_order_forwarder/src/main/java/fishing/lee/sqsforwarder/QueueGrabber.java: -------------------------------------------------------------------------------- 1 | package fishing.lee.sqsforwarder; 2 | 3 | import com.amazonaws.services.lambda.runtime.Context; 4 | import com.amazonaws.services.lambda.runtime.LambdaLogger; 5 | import com.amazonaws.services.lambda.runtime.RequestHandler; 6 | import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; 7 | import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; 8 | import com.amazonaws.services.sqs.AmazonSQS; 9 | import com.amazonaws.services.sqs.AmazonSQSClientBuilder; 10 | import com.amazonaws.services.sqs.model.SendMessageResult; 11 | 12 | import java.util.HashMap; 13 | 14 | @SuppressWarnings("unused") 15 | public class QueueGrabber implements RequestHandler { 16 | @Override 17 | public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent input, Context context) { 18 | final String sqsQueueName = System.getenv("SHOPBACKEND_SQS_QUEUE"); 19 | LambdaLogger logger = context.getLogger(); 20 | 21 | if (input.getHttpMethod().equals("OPTIONS")) { 22 | return new APIGatewayProxyResponseEvent() 23 | .withHeaders(new HashMap() {{ 24 | put("Access-Control-Allow-Origin", "*"); 25 | put("Access-Control-Allow-Methods", "GET,HEAD,POST"); 26 | put("Access-Control-Max-Age", "1800"); 27 | put("Allow", "GET, HEAD, POST, PUT, DELETE, OPTIONS, PATCH"); 28 | }}) 29 | .withStatusCode(200); 30 | } 31 | 32 | switch (input.getPath()) { 33 | case "/ping": 34 | return new APIGatewayProxyResponseEvent() 35 | .withHeaders(new HashMap() {{ 36 | put("Content-Type", "application/json"); 37 | }}) 38 | .withStatusCode(200) 39 | .withBody("PONG"); 40 | case "/order": 41 | AmazonSQS amazonSQS = AmazonSQSClientBuilder.defaultClient(); 42 | SendMessageResult result = amazonSQS.sendMessage(sqsQueueName, input.getBody()); 43 | 44 | return new APIGatewayProxyResponseEvent() 45 | .withHeaders(new HashMap() {{ 46 | put("Content-Type", "application/json"); 47 | }}) 48 | .withStatusCode(200) 49 | .withBody(result.getMessageId()); 50 | default: 51 | return new APIGatewayProxyResponseEvent() 52 | .withStatusCode(404); 53 | } 54 | 55 | 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /frontend/docs/pycharm/configuration.rst: -------------------------------------------------------------------------------- 1 | Docker Remote Debugging 2 | ======================= 3 | 4 | To connect to python remote interpreter inside docker, you have to make sure first, that Pycharm is aware of your docker. 5 | 6 | Go to *Settings > Build, Execution, Deployment > Docker*. If you are on linux, you can use docker directly using its socket `unix:///var/run/docker.sock`, if you are on Windows or Mac, make sure that you have docker-machine installed, then you can simply *Import credentials from Docker Machine*. 7 | 8 | .. image:: images/1.png 9 | 10 | Configure Remote Python Interpreter 11 | ----------------------------------- 12 | 13 | This repository comes with already prepared "Run/Debug Configurations" for docker. 14 | 15 | .. image:: images/2.png 16 | 17 | But as you can see, at the beggining there is something wrong with them. They have red X on django icon, and they cannot be used, without configuring remote python interpteter. To do that, you have to go to *Settings > Build, Execution, Deployment* first. 18 | 19 | 20 | Next, you have to add new remote python interpreter, based on already tested deployment settings. Go to *Settings > Project > Project Interpreter*. Click on the cog icon, and click *Add Remote*. 21 | 22 | .. image:: images/3.png 23 | 24 | Switch to *Docker Compose* and select `local.yml` file from directory of your project, next set *Service name* to `django` 25 | 26 | .. image:: images/4.png 27 | 28 | Having that, click *OK*. Close *Settings* panel, and wait few seconds... 29 | 30 | .. image:: images/7.png 31 | 32 | After few seconds, all *Run/Debug Configurations* should be ready to use. 33 | 34 | .. image:: images/8.png 35 | 36 | **Things you can do with provided configuration**: 37 | 38 | * run and debug python code 39 | .. image:: images/f1.png 40 | * run and debug tests 41 | .. image:: images/f2.png 42 | .. image:: images/f3.png 43 | * run and debug migrations or different django management commands 44 | .. image:: images/f4.png 45 | * and many others.. 46 | 47 | Known issues 48 | ------------ 49 | 50 | * Pycharm hangs on "Connecting to Debugger" 51 | 52 | .. image:: images/issue1.png 53 | 54 | This might be fault of your firewall. Take a look on this ticket - https://youtrack.jetbrains.com/issue/PY-18913 55 | 56 | * Modified files in `.idea` directory 57 | 58 | Most of the files from `.idea/` were added to `.gitignore` with a few exceptions, which were made, to provide "ready to go" configuration. After adding remote interpreter some of these files are altered by PyCharm: 59 | 60 | .. image:: images/issue2.png 61 | 62 | In theory you can remove them from repository, but then, other people will lose a ability to initialize a project from provided configurations as you did. To get rid of this annoying state, you can run command:: 63 | 64 | $ git update-index --assume-unchanged lee_fishing.iml 65 | -------------------------------------------------------------------------------- /frontend/config/settings/local.py: -------------------------------------------------------------------------------- 1 | from .base import * # noqa 2 | from .base import env 3 | 4 | # GENERAL 5 | # ------------------------------------------------------------------------------ 6 | # https://docs.djangoproject.com/en/dev/ref/settings/#debug 7 | DEBUG = True 8 | # https://docs.djangoproject.com/en/dev/ref/settings/#secret-key 9 | SECRET_KEY = env('DJANGO_SECRET_KEY', default='rgr6jdNR1qFrcSnpxEJgDCX4lovbVWbOPw2masEwuAvy4K8BQ76IliMlirUyv4BB') 10 | # https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts 11 | ALLOWED_HOSTS = [ 12 | "localhost", 13 | "0.0.0.0", 14 | "127.0.0.1", 15 | ] 16 | 17 | # CACHES 18 | # ------------------------------------------------------------------------------ 19 | # https://docs.djangoproject.com/en/dev/ref/settings/#caches 20 | CACHES = { 21 | 'default': { 22 | 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', 23 | 'LOCATION': '' 24 | } 25 | } 26 | 27 | # TEMPLATES 28 | # ------------------------------------------------------------------------------ 29 | # https://docs.djangoproject.com/en/dev/ref/settings/#templates 30 | TEMPLATES[0]['OPTIONS']['debug'] = DEBUG # noqa F405 31 | 32 | # EMAIL 33 | # ------------------------------------------------------------------------------ 34 | # https://docs.djangoproject.com/en/dev/ref/settings/#email-backend 35 | EMAIL_BACKEND = env('DJANGO_EMAIL_BACKEND', default='django.core.mail.backends.console.EmailBackend') 36 | # https://docs.djangoproject.com/en/dev/ref/settings/#email-host 37 | EMAIL_HOST = 'localhost' 38 | # https://docs.djangoproject.com/en/dev/ref/settings/#email-port 39 | EMAIL_PORT = 1025 40 | 41 | # django-debug-toolbar 42 | # ------------------------------------------------------------------------------ 43 | # https://django-debug-toolbar.readthedocs.io/en/latest/installation.html#prerequisites 44 | INSTALLED_APPS += ['debug_toolbar'] # noqa F405 45 | # https://django-debug-toolbar.readthedocs.io/en/latest/installation.html#middleware 46 | MIDDLEWARE += ['debug_toolbar.middleware.DebugToolbarMiddleware'] # noqa F405 47 | # https://django-debug-toolbar.readthedocs.io/en/latest/configuration.html#debug-toolbar-config 48 | DEBUG_TOOLBAR_CONFIG = { 49 | 'DISABLE_PANELS': [ 50 | 'debug_toolbar.panels.redirects.RedirectsPanel', 51 | ], 52 | 'SHOW_TEMPLATE_CONTEXT': True, 53 | } 54 | # https://django-debug-toolbar.readthedocs.io/en/latest/installation.html#internal-ips 55 | INTERNAL_IPS = ['127.0.0.1', '10.0.2.2'] 56 | 57 | 58 | # django-extensions 59 | # ------------------------------------------------------------------------------ 60 | # https://django-extensions.readthedocs.io/en/latest/installation_instructions.html#configuration 61 | INSTALLED_APPS += ['django_extensions'] # noqa F405 62 | 63 | # Your stuff... 64 | # ------------------------------------------------------------------------------ 65 | -------------------------------------------------------------------------------- /infrastructure/src/main/java/fishing/lee/infrastructure/ScheduledApiCaller.java: -------------------------------------------------------------------------------- 1 | package fishing.lee.infrastructure; 2 | 3 | import software.amazon.awscdk.Construct; 4 | import software.amazon.awscdk.services.ec2.*; 5 | import software.amazon.awscdk.services.events.EventRule; 6 | import software.amazon.awscdk.services.events.EventRuleProps; 7 | import software.amazon.awscdk.services.lambda.*; 8 | import software.amazon.awscdk.services.lambda.Runtime; 9 | 10 | import software.amazon.awscdk.services.s3.Bucket; 11 | import software.amazon.awscdk.services.s3.BucketRef; 12 | import software.amazon.awscdk.services.s3.BucketRefProps; 13 | import software.amazon.awscdk.services.sns.*; 14 | 15 | import java.util.Collections; 16 | import java.util.HashMap; 17 | 18 | class ScheduledApiCaller extends Construct { 19 | private SecurityGroup securityGroup; 20 | 21 | ScheduledApiCaller(Construct parent, String id, ScheduledApiCallerProps properties) { 22 | super(parent, id); 23 | 24 | BucketRef bucket = Bucket.import_(this, "Bucket", BucketRefProps.builder() 25 | .withBucketName("leepac-fishing-assets") 26 | .build()); 27 | 28 | securityGroup = new SecurityGroup(this, "SecurityGroup", SecurityGroupProps.builder() 29 | .withVpc(properties.getShopVpc().getVpc()) 30 | .build()); 31 | 32 | properties.getShopVpc().addSecurityGroupToApi(securityGroup); 33 | 34 | Function function = new Function(this, "Function", FunctionProps.builder() 35 | .withCode(Code.bucket(bucket, "dummy-1.0-SNAPSHOT-all.jar")) 36 | .withHandler("fishing.lee.backend.SNSLambdaHandler::handleRequest") 37 | .withMemorySize(1024) 38 | .withTimeout(60) 39 | .withTracing(Tracing.Active) 40 | .withRuntime(Runtime.JAVA8) 41 | .withVpc(properties.getShopVpc().getVpc()) 42 | .withVpcPlacement(VpcPlacementStrategy.builder() 43 | .withSubnetsToUse(SubnetType.Private) 44 | .build()) 45 | .withSecurityGroup(securityGroup) 46 | .withEnvironment(new HashMap() {{ 47 | put("SHOPBACKEND_API_URL", properties.getApiToPing() + "/ping"); 48 | }}) 49 | .build()); 50 | 51 | Topic topic = new Topic(this, "Topic", TopicProps.builder() 52 | .build()); 53 | 54 | topic.subscribeLambda(function); 55 | 56 | new EventRule(this, "EventRule", EventRuleProps.builder() 57 | .withTargets(Collections.singletonList(topic)) 58 | .withScheduleExpression("rate(2 minutes)") 59 | .build()); 60 | } 61 | 62 | public SecurityGroup getSecurityGroup() { 63 | return securityGroup; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /frontend/utility/install_os_dependencies.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | WORK_DIR="$(dirname "$0")" 4 | DISTRO_NAME=$(lsb_release -sc) 5 | OS_REQUIREMENTS_FILENAME="requirements-$DISTRO_NAME.apt" 6 | 7 | cd $WORK_DIR 8 | 9 | # Check if a requirements file exist for the current distribution. 10 | if [ ! -r "$OS_REQUIREMENTS_FILENAME" ]; then 11 | cat <<-EOF >&2 12 | There is no requirements file for your distribution. 13 | You can see one of the files listed below to help search the equivalent package in your system: 14 | $(find ./ -name "requirements-*.apt" -printf " - %f\n") 15 | EOF 16 | exit 1; 17 | fi 18 | 19 | # Handle call with wrong command 20 | function wrong_command() 21 | { 22 | echo "${0##*/} - unknown command: '${1}'" >&2 23 | usage_message 24 | } 25 | 26 | # Print help / script usage 27 | function usage_message() 28 | { 29 | cat <<-EOF 30 | Usage: $WORK_DIR/${0##*/} 31 | Available commands are: 32 | list Print a list of all packages defined on ${OS_REQUIREMENTS_FILENAME} file 33 | help Print this help 34 | 35 | Commands that require superuser permission: 36 | install Install packages defined on ${OS_REQUIREMENTS_FILENAME} file. Note: This 37 | does not upgrade the packages already installed for new versions, even if 38 | new version is available in the repository. 39 | upgrade Same that install, but upgrade the already installed packages, if new 40 | version is available. 41 | EOF 42 | } 43 | 44 | # Read the requirements.apt file, and remove comments and blank lines 45 | function list_packages(){ 46 | grep -v "#" "${OS_REQUIREMENTS_FILENAME}" | grep -v "^$"; 47 | } 48 | 49 | function install_packages() 50 | { 51 | list_packages | xargs apt-get --no-upgrade install -y; 52 | } 53 | 54 | function upgrade_packages() 55 | { 56 | list_packages | xargs apt-get install -y; 57 | } 58 | 59 | function install_or_upgrade() 60 | { 61 | P=${1} 62 | PARAN=${P:-"install"} 63 | 64 | if [[ $EUID -ne 0 ]]; then 65 | cat <<-EOF >&2 66 | You must run this script with root privilege 67 | Please do: 68 | sudo $WORK_DIR/${0##*/} $PARAN 69 | EOF 70 | exit 1 71 | else 72 | 73 | apt-get update 74 | 75 | # Install the basic compilation dependencies and other required libraries of this project 76 | if [ "$PARAN" == "install" ]; then 77 | install_packages; 78 | else 79 | upgrade_packages; 80 | fi 81 | 82 | # cleaning downloaded packages from apt-get cache 83 | apt-get clean 84 | 85 | exit 0 86 | fi 87 | } 88 | 89 | # Handle command argument 90 | case "$1" in 91 | install) install_or_upgrade;; 92 | upgrade) install_or_upgrade "upgrade";; 93 | list) list_packages;; 94 | help|"") usage_message;; 95 | *) wrong_command "$1";; 96 | esac 97 | -------------------------------------------------------------------------------- /backend/src/main/java/fishing/lee/backend/BasketController.java: -------------------------------------------------------------------------------- 1 | package fishing.lee.backend; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.web.bind.annotation.*; 5 | 6 | import java.util.Objects; 7 | import java.util.Optional; 8 | import java.util.UUID; 9 | 10 | /** 11 | * Baskets, they're a thing... provide an API to them 12 | */ 13 | @RestController 14 | public class BasketController { 15 | private final BasketRepository basketRepository; 16 | 17 | @Autowired 18 | public BasketController(BasketRepository basketRepository) { 19 | this.basketRepository = basketRepository; 20 | } 21 | 22 | /** 23 | * Get a single basket 24 | * 25 | * @param id UUID of basket to fetch 26 | * @return Basket instance 27 | * @throws BasketNotFoundException for returning a 404 via HTTP 28 | */ 29 | @SuppressWarnings("WeakerAccess") 30 | @GetMapping("/baskets/{id}") 31 | BasketModel one(@PathVariable UUID id) throws BasketNotFoundException { 32 | Optional basket = basketRepository.findById(id); 33 | return basket.orElseThrow(BasketNotFoundException::new); 34 | } 35 | 36 | @PostMapping("/baskets") 37 | BasketModel newBasket(@RequestBody BasketModel basketModel) { 38 | // TODO(leepac): This feels suboptimal but the only way to guarantee it's set 39 | if (Objects.isNull(basketModel.getCreationPoint())) { 40 | basketModel.setCreationPoint(null); 41 | } 42 | 43 | return basketRepository.save(basketModel); 44 | } 45 | 46 | /** 47 | * Add an item to a basket 48 | * 49 | * @param id the ID of the Basket 50 | * @param item the item to add 51 | * @return new version of BasketModel 52 | */ 53 | @PostMapping("/baskets/{id}/add/{item}") 54 | BasketModel addItem(@PathVariable UUID id, @PathVariable String item) { 55 | BasketModel basketModel = one(id); 56 | basketModel.addItem(item); 57 | basketRepository.save(basketModel); 58 | return basketModel; 59 | } 60 | 61 | /** 62 | * Delete a basket 63 | * 64 | * @param id the ID of the basket 65 | */ 66 | @DeleteMapping("/baskets/{id}") 67 | void deleteBasket(@PathVariable UUID id) { 68 | BasketModel basketModel = one(id); 69 | basketRepository.delete(basketModel); 70 | } 71 | 72 | /** 73 | * Delete an item from a basket 74 | * 75 | * @param id the ID of the basket 76 | * @param item the item to remove 77 | * @return the modified basket 78 | */ 79 | @DeleteMapping("/baskets/{id}/add/{item}") 80 | BasketModel deleteItem(@PathVariable UUID id, @SuppressWarnings("SameParameterValue") @PathVariable String item) { 81 | BasketModel basketModel = one(id); 82 | basketModel.deleteItem(item); 83 | basketRepository.save(basketModel); 84 | return basketModel; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /frontend/lee_fishing/templates/account/email.html: -------------------------------------------------------------------------------- 1 | 2 | {% extends "account/base.html" %} 3 | 4 | {% load i18n %} 5 | {% load crispy_forms_tags %} 6 | 7 | {% block head_title %}{% trans "Account" %}{% endblock %} 8 | 9 | {% block inner %} 10 |

{% trans "E-mail Addresses" %}

11 | 12 | {% if user.emailaddress_set.all %} 13 |

{% trans 'The following e-mail addresses are associated with your account:' %}

14 | 15 | 44 | 45 | {% else %} 46 |

{% trans 'Warning:'%} {% trans "You currently do not have any e-mail address set up. You should really add an e-mail address so you can receive notifications, reset your password, etc." %}

47 | 48 | {% endif %} 49 | 50 | 51 |

{% trans "Add E-mail Address" %}

52 | 53 |
54 | {% csrf_token %} 55 | {{ form|crispy }} 56 | 57 |
58 | 59 | {% endblock %} 60 | 61 | 62 | {% block javascript %} 63 | {{ block.super }} 64 | 79 | {% endblock %} 80 | 81 | -------------------------------------------------------------------------------- /backend/src/main/java/fishing/lee/backend/Application.java: -------------------------------------------------------------------------------- 1 | package fishing.lee.backend; 2 | 3 | import com.amazonaws.services.dynamodbv2.AmazonDynamoDB; 4 | import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper; 5 | import com.amazonaws.xray.AWSXRay; 6 | import org.socialsignin.spring.data.dynamodb.repository.config.EnableDynamoDBRepositories; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.boot.CommandLineRunner; 9 | import org.springframework.boot.SpringApplication; 10 | import org.springframework.boot.autoconfigure.SpringBootApplication; 11 | import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; 12 | import org.springframework.context.ConfigurableApplicationContext; 13 | import org.springframework.context.annotation.Bean; 14 | import org.springframework.context.annotation.Configuration; 15 | import org.springframework.context.annotation.Import; 16 | import org.springframework.context.annotation.PropertySource; 17 | import org.springframework.web.servlet.config.annotation.CorsRegistry; 18 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 19 | 20 | import static fishing.lee.backend.DynamoDBConfig.checkOrCreateTable; 21 | 22 | @SpringBootApplication 23 | @EnableDynamoDBRepositories 24 | @Configuration 25 | @Import(DynamoDBConfig.class) 26 | @PropertySource("classpath:application-lambda.properties") 27 | public class Application extends SpringBootServletInitializer { 28 | 29 | @Value("${logging.level.root:OFF}") 30 | String message = ""; 31 | 32 | /** 33 | * Main entry point of the application 34 | * 35 | * @param args list of arguments 36 | */ 37 | public static void main(String[] args) { 38 | SpringApplication.run(Application.class, args); 39 | } 40 | 41 | /** 42 | * Bootstrap DynamoDB if required on startup 43 | * 44 | * @param ctx Application Context (unused here) 45 | * @param amazonDynamoDB Autowired DynamoDB instance 46 | * @param dynamoDBMapper Autowired DynamoDBMapper instance 47 | * @return CommandLineRunner inline function 48 | */ 49 | @Bean 50 | public CommandLineRunner custom(ConfigurableApplicationContext ctx, AmazonDynamoDB amazonDynamoDB, DynamoDBMapper dynamoDBMapper, Boolean amazonAWSUseLocalDynamoDB) { 51 | return (args) -> { 52 | if (amazonAWSUseLocalDynamoDB) 53 | AWSXRay.beginSegment("custom"); 54 | 55 | checkOrCreateTable(amazonDynamoDB, dynamoDBMapper, BasketModel.class); 56 | 57 | if (amazonAWSUseLocalDynamoDB) 58 | AWSXRay.endSegment(); 59 | }; 60 | } 61 | 62 | @Bean 63 | public WebMvcConfigurer corsConfigurer() { 64 | return new WebMvcConfigurer() { 65 | @Override 66 | public void addCorsMappings(CorsRegistry registry) { 67 | registry.addMapping("/**") 68 | .allowedOrigins("*"); 69 | } 70 | }; 71 | } 72 | } 73 | --------------------------------------------------------------------------------