├── .github
├── dependabot.yml
└── workflows
│ └── dependabot.yml
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── event-driven-developers-tale
├── .gitignore
├── .graphqlconfig.yml
├── .vscode
│ └── settings.json
├── LICENSE
├── README.md
├── amplify
│ ├── .config
│ │ └── project-config.json
│ ├── README.md
│ ├── backend
│ │ ├── api
│ │ │ └── vacationtracker
│ │ │ │ ├── parameters.json
│ │ │ │ ├── resolvers
│ │ │ │ ├── Mutation.submitVacationRequest.req.vtl
│ │ │ │ ├── Mutation.submitVacationRequest.res.vtl
│ │ │ │ ├── Mutation.updateVacationRequest.req.vtl
│ │ │ │ ├── Mutation.updateVacationRequest.res.vtl
│ │ │ │ └── README.md
│ │ │ │ ├── schema.graphql
│ │ │ │ ├── stacks
│ │ │ │ └── CustomResources.json
│ │ │ │ └── transform.conf.json
│ │ ├── auth
│ │ │ └── vacationtrackerd86ca2cd
│ │ │ │ ├── parameters.json
│ │ │ │ └── vacationtrackerd86ca2cd-cloudformation-template.yml
│ │ ├── backend-config.json
│ │ ├── hosting
│ │ │ └── amplifyhosting
│ │ │ │ └── amplifyhosting-template.json
│ │ └── tags.json
│ ├── cli.json
│ ├── hooks
│ │ └── README.md
│ └── team-provider-info.json
├── functions
│ ├── createVacationRequest
│ │ ├── .aws-sam
│ │ │ └── build.toml
│ │ ├── .npmignore
│ │ ├── app.js
│ │ ├── events
│ │ │ └── event.json
│ │ ├── package.json
│ │ └── template.yaml
│ ├── updateVacationRequest
│ │ ├── .npmignore
│ │ ├── app.js
│ │ ├── events
│ │ │ └── event.json
│ │ ├── package.json
│ │ └── template.yaml
│ └── vacationRequestValidation
│ │ ├── .npmignore
│ │ ├── app.js
│ │ ├── events
│ │ └── event.json
│ │ ├── package.json
│ │ └── template.yaml
├── images
│ ├── addtogroup.png
│ ├── eventbridgeatlas.png
│ ├── eventbridgeregistry.png
│ ├── eventstormingsessionexample.png
│ ├── register.png
│ ├── submitVacationRequestFlow.png
│ └── xray.png
├── infrastructure
│ ├── .gitignore
│ ├── .npmignore
│ ├── README.md
│ ├── bin
│ │ └── infrastructure.js
│ ├── cdk.json
│ ├── lib
│ │ └── infrastructure-stack.js
│ ├── package-lock.json
│ ├── package.json
│ └── src
│ │ └── graphql
│ │ └── schema.json
├── package-lock.json
├── package.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ ├── logo192.png
│ ├── manifest.json
│ └── robots.txt
├── src
│ ├── App.css
│ ├── App.js
│ ├── components
│ │ ├── AddCategory.js
│ │ ├── AddVacation.js
│ │ ├── CategoryItem.js
│ │ ├── CustomTable.js
│ │ ├── Modal.js
│ │ ├── NavBar.js
│ │ └── ProfileLink.js
│ ├── graphql
│ │ ├── mutations.js
│ │ ├── queries.js
│ │ ├── schema.json
│ │ └── subscriptions.js
│ ├── images
│ │ └── profile-pic.jpg
│ ├── index.css
│ ├── index.js
│ └── pages
│ │ ├── People.js
│ │ ├── Profile.js
│ │ ├── Settings.js
│ │ ├── Teams.js
│ │ └── Vacations.js
└── tailwind.config.js
├── hexagonal-architectures
├── .gitignore
├── README.md
├── events
│ ├── create-product-evb-wrong-payload.json
│ ├── create-product-evb.json
│ ├── create-product-wrong-payload.json
│ ├── create-product.json
│ ├── delete-product.json
│ ├── get-product.json
│ └── get-products.json
├── images
│ ├── diagram.png
│ └── hex.png
├── jest.config.js
├── junit.xml
├── package-lock.json
├── package.json
├── src
│ ├── adapters
│ │ ├── api-gateway.ts
│ │ └── event-bridge.ts
│ ├── domain
│ │ └── Products.ts
│ ├── functions
│ │ ├── delete-product
│ │ │ └── delete-product.ts
│ │ ├── get-product
│ │ │ └── get-product.ts
│ │ ├── get-products
│ │ │ └── get-products.ts
│ │ └── put-product
│ │ │ └── put-product.ts
│ ├── model
│ │ └── product.ts
│ ├── store
│ │ ├── dynamodb
│ │ │ └── dynamodb-store.ts
│ │ └── product-store.ts
│ └── tests
│ │ └── domain.test.ts
├── template.yaml
├── tsconfig.json
└── webpack.config.js
├── lambda-powertools-feature-flags
├── README.md
├── app-config-store
│ ├── cdk
│ │ ├── .gitignore
│ │ ├── .npmignore
│ │ ├── README.md
│ │ ├── bin
│ │ │ └── cdk.ts
│ │ ├── cdk.json
│ │ ├── jest.config.js
│ │ ├── lib
│ │ │ └── cdk-stack.ts
│ │ ├── package-lock.json
│ │ ├── package.json
│ │ ├── test
│ │ │ └── cdk.test.ts
│ │ └── tsconfig.json
│ └── cfn
│ │ ├── feature-config.json
│ │ └── template.yaml
├── app
│ ├── .coveragerc
│ ├── .gitignore
│ ├── Makefile
│ ├── Pipfile
│ ├── Pipfile.lock
│ ├── README.md
│ ├── events
│ │ └── hello_world_event.json
│ ├── products
│ │ ├── __init__.py
│ │ ├── app.py
│ │ └── requirements.txt
│ ├── samconfig.toml
│ ├── template.yaml
│ └── tests
│ │ ├── conftest.py
│ │ ├── sytem
│ │ ├── __init__.py
│ │ └── test_app.py
│ │ └── unit
│ │ ├── __init__.py
│ │ ├── test_app.py
│ │ └── test_feature_flags.py
└── docs
│ ├── feature_toggles.drawio
│ └── feature_toggles.png
├── latest_episode.json
├── micro-frontends-module-federation
├── .gitignore
├── LICENSE
├── README.md
├── accountdetails
│ ├── package-lock.json
│ ├── package.json
│ ├── src
│ │ └── AccountDetails.js
│ └── webpack.config.js
├── appshell
│ ├── package-lock.json
│ ├── package.json
│ ├── public
│ │ └── index.html
│ ├── src
│ │ ├── App.js
│ │ ├── Main.js
│ │ ├── bootstrap.js
│ │ └── index.js
│ └── webpack.config.js
├── atriom.dat
├── catalogue
│ ├── imgs
│ │ ├── black1.jpeg
│ │ ├── black2.jpeg
│ │ └── white.jpeg
│ ├── package-lock.json
│ ├── package.json
│ ├── src
│ │ ├── Catalogue.js
│ │ ├── Details.js
│ │ ├── Home.js
│ │ └── Product.js
│ └── webpack.config.js
├── modFedDiagram.png
├── modFedExample.png
├── myaccount
│ ├── package-lock.json
│ ├── package.json
│ ├── src
│ │ └── MyAccount.js
│ └── webpack.config.js
├── paymentdetails
│ ├── package-lock.json
│ ├── package.json
│ ├── src
│ │ └── PaymentDetails.js
│ └── webpack.config.js
└── signin
│ ├── package-lock.json
│ ├── package.json
│ ├── src
│ └── SignIn.js
│ └── webpack.config.js
├── serverless-lambda-java
└── README.md
└── spa-blue-green-deployments
├── README.md
├── app
├── .browserslistrc
├── .editorconfig
├── .eslintrc.json
├── .gitignore
├── .travis.yml
├── CNAME
├── LICENSE.txt
├── README.md
├── angular.json
├── e2e
│ ├── app.e2e-spec.ts
│ ├── app.po.ts
│ └── tsconfig.e2e.json
├── karma.conf.js
├── logo.png
├── ngsw-config.json
├── package.json
├── protractor.conf.js
├── src
│ ├── app
│ │ ├── app-routing.module.ts
│ │ ├── app.component.html
│ │ ├── app.component.ts
│ │ ├── app.module.ts
│ │ ├── article
│ │ │ ├── article-comment.component.html
│ │ │ ├── article-comment.component.ts
│ │ │ ├── article-resolver.service.ts
│ │ │ ├── article-routing.module.ts
│ │ │ ├── article.component.html
│ │ │ ├── article.component.ts
│ │ │ ├── article.module.ts
│ │ │ └── markdown.pipe.ts
│ │ ├── auth
│ │ │ ├── auth-routing.module.ts
│ │ │ ├── auth.component.html
│ │ │ ├── auth.component.ts
│ │ │ ├── auth.module.ts
│ │ │ └── no-auth-guard.service.ts
│ │ ├── core
│ │ │ ├── core.module.ts
│ │ │ ├── index.ts
│ │ │ ├── interceptors
│ │ │ │ ├── http.token.interceptor.ts
│ │ │ │ └── index.ts
│ │ │ ├── models
│ │ │ │ ├── article-list-config.model.ts
│ │ │ │ ├── article.model.ts
│ │ │ │ ├── comment.model.ts
│ │ │ │ ├── errors.model.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── profile.model.ts
│ │ │ │ └── user.model.ts
│ │ │ └── services
│ │ │ │ ├── api.service.ts
│ │ │ │ ├── articles.service.ts
│ │ │ │ ├── auth-guard.service.ts
│ │ │ │ ├── comments.service.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── jwt.service.ts
│ │ │ │ ├── profiles.service.ts
│ │ │ │ ├── tags.service.ts
│ │ │ │ └── user.service.ts
│ │ ├── editor
│ │ │ ├── editable-article-resolver.service.ts
│ │ │ ├── editor-routing.module.ts
│ │ │ ├── editor.component.html
│ │ │ ├── editor.component.ts
│ │ │ └── editor.module.ts
│ │ ├── home
│ │ │ ├── home-auth-resolver.service.ts
│ │ │ ├── home-routing.module.ts
│ │ │ ├── home.component.css
│ │ │ ├── home.component.html
│ │ │ ├── home.component.ts
│ │ │ └── home.module.ts
│ │ ├── index.ts
│ │ ├── profile
│ │ │ ├── profile-articles.component.html
│ │ │ ├── profile-articles.component.ts
│ │ │ ├── profile-favorites.component.html
│ │ │ ├── profile-favorites.component.ts
│ │ │ ├── profile-resolver.service.ts
│ │ │ ├── profile-routing.module.ts
│ │ │ ├── profile.component.html
│ │ │ ├── profile.component.ts
│ │ │ └── profile.module.ts
│ │ ├── settings
│ │ │ ├── settings-routing.module.ts
│ │ │ ├── settings.component.html
│ │ │ ├── settings.component.ts
│ │ │ └── settings.module.ts
│ │ └── shared
│ │ │ ├── article-helpers
│ │ │ ├── article-list.component.css
│ │ │ ├── article-list.component.html
│ │ │ ├── article-list.component.ts
│ │ │ ├── article-meta.component.html
│ │ │ ├── article-meta.component.ts
│ │ │ ├── article-preview.component.html
│ │ │ ├── article-preview.component.ts
│ │ │ └── index.ts
│ │ │ ├── buttons
│ │ │ ├── favorite-button.component.html
│ │ │ ├── favorite-button.component.ts
│ │ │ ├── follow-button.component.html
│ │ │ ├── follow-button.component.ts
│ │ │ └── index.ts
│ │ │ ├── index.ts
│ │ │ ├── layout
│ │ │ ├── footer.component.html
│ │ │ ├── footer.component.ts
│ │ │ ├── header.component.html
│ │ │ ├── header.component.ts
│ │ │ └── index.ts
│ │ │ ├── list-errors.component.html
│ │ │ ├── list-errors.component.ts
│ │ │ ├── shared.module.ts
│ │ │ └── show-authed.directive.ts
│ ├── assets
│ │ ├── .gitkeep
│ │ ├── .npmignore
│ │ └── icons
│ │ │ ├── icon-128x128.png
│ │ │ ├── icon-144x144.png
│ │ │ ├── icon-152x152.png
│ │ │ ├── icon-192x192.png
│ │ │ ├── icon-384x384.png
│ │ │ ├── icon-512x512.png
│ │ │ ├── icon-72x72.png
│ │ │ └── icon-96x96.png
│ ├── environments
│ │ ├── environment.prod.ts
│ │ └── environment.ts
│ ├── favicon.ico
│ ├── index.html
│ ├── main.ts
│ ├── manifest.webmanifest
│ ├── polyfills.ts
│ ├── styles.css
│ ├── test.ts
│ ├── tsconfig.app.json
│ ├── tsconfig.spec.json
│ └── typings.d.ts
├── tsconfig.json
└── yarn.lock
└── cdk
├── .gitignore
├── README.md
├── cdk.go
├── cdk.json
├── go.mod
└── go.sum
/.github/workflows/dependabot.yml:
--------------------------------------------------------------------------------
1 | name: Dependabot auto-approve
2 | on: pull_request
3 |
4 | permissions:
5 | contents: write
6 |
7 | jobs:
8 | dependabot:
9 | runs-on: ubuntu-latest
10 | if: ${{ github.actor == 'dependabot[bot]' }}
11 | steps:
12 | - name: Dependabot metadata
13 | id: metadata
14 | uses: dependabot/fetch-metadata@v2.4.0
15 | with:
16 | github-token: "${{ secrets.GITHUB_TOKEN }}"
17 |
18 | - name: Enable auto-merge for Dependabot PRs
19 | if: ${{ (steps.metadata.outputs.update-type == 'version-update:semver-minor') || (steps.metadata.outputs.update-type == 'version-update:semver-patch') }}
20 | run: gh pr merge --auto --merge "$PR_URL"
21 | env:
22 | PR_URL: ${{ github.event.pull_request.html_url }}
23 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
24 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of
4 | this software and associated documentation files (the "Software"), to deal in
5 | the Software without restriction, including without limitation the rights to
6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
7 | the Software, and to permit persons to whom the Software is furnished to do so.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
10 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
11 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
12 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
13 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
14 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
15 |
16 |
--------------------------------------------------------------------------------
/event-driven-developers-tale/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
3 |
4 | # dependencies
5 | /node_modules
6 | /.pnp
7 | .pnp.js
8 |
9 | # testing
10 | /coverage
11 |
12 | # production
13 | /build
14 |
15 | # misc
16 | .DS_Store
17 | .env.local
18 | .env.development.local
19 | .env.test.local
20 | .env.production.local
21 |
22 | npm-debug.log*
23 | yarn-debug.log*
24 | yarn-error.log*
25 |
26 | #amplify-do-not-edit-begin
27 | amplify/\#current-cloud-backend
28 | amplify/.config/local-*
29 | amplify/logs
30 | amplify/mock-data
31 | amplify/backend/amplify-meta.json
32 | amplify/backend/awscloudformation
33 | amplify/backend/.temp
34 | build/
35 | dist/
36 | node_modules/
37 | aws-exports.js
38 | awsconfiguration.json
39 | amplifyconfiguration.json
40 | amplifyconfiguration.dart
41 | amplify-build-config.json
42 | amplify-gradle-config.json
43 | amplifytools.xcconfig
44 | .secret-*
45 | **.sample
46 | #amplify-do-not-edit-end
47 |
--------------------------------------------------------------------------------
/event-driven-developers-tale/.graphqlconfig.yml:
--------------------------------------------------------------------------------
1 | projects:
2 | vacationtracker:
3 | schemaPath: src/graphql/schema.json
4 | includes:
5 | - src/graphql/**/*.js
6 | excludes:
7 | - ./amplify/**
8 | extensions:
9 | amplify:
10 | codeGenTarget: javascript
11 | generatedFileName: ''
12 | docsFilePath: src/graphql
13 | extensions:
14 | amplify:
15 | version: 3
16 |
--------------------------------------------------------------------------------
/event-driven-developers-tale/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "files.exclude": {
3 | "amplify/.config": true,
4 | "amplify/**/*-parameters.json": true,
5 | "amplify/**/amplify.state": true,
6 | "amplify/**/transform.conf.json": true,
7 | "amplify/#current-cloud-backend": true,
8 | "amplify/backend/amplify-meta.json": true,
9 | "amplify/backend/awscloudformation": true
10 | }
11 | }
--------------------------------------------------------------------------------
/event-driven-developers-tale/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Tiago Barbosa
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.
22 |
--------------------------------------------------------------------------------
/event-driven-developers-tale/amplify/.config/project-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "projectName": "vacationtracker",
3 | "version": "3.1",
4 | "frontend": "javascript",
5 | "javascript": {
6 | "framework": "react",
7 | "config": {
8 | "SourceDir": "src",
9 | "DistributionDir": "build",
10 | "BuildCommand": "npm run-script build",
11 | "StartCommand": "npm run-script start"
12 | }
13 | },
14 | "providers": [
15 | "awscloudformation"
16 | ]
17 | }
--------------------------------------------------------------------------------
/event-driven-developers-tale/amplify/README.md:
--------------------------------------------------------------------------------
1 | # Getting Started with Amplify CLI
2 | This directory was generated by [Amplify CLI](https://docs.amplify.aws/cli).
3 |
4 | Helpful resources:
5 | - Amplify documentation: https://docs.amplify.aws
6 | - Amplify CLI documentation: https://docs.amplify.aws/cli
7 | - More details on this folder & generated files: https://docs.amplify.aws/cli/reference/files
8 | - Join Amplify's community: https://amplify.aws/community/
9 |
--------------------------------------------------------------------------------
/event-driven-developers-tale/amplify/backend/api/vacationtracker/parameters.json:
--------------------------------------------------------------------------------
1 | {
2 | "AppSyncApiName": "vacationtracker",
3 | "DynamoDBBillingMode": "PAY_PER_REQUEST",
4 | "DynamoDBEnableServerSideEncryption": false,
5 | "AuthCognitoUserPoolId": {
6 | "Fn::GetAtt": [
7 | "authvacationtrackerd86ca2cd",
8 | "Outputs.UserPoolId"
9 | ]
10 | }
11 | }
--------------------------------------------------------------------------------
/event-driven-developers-tale/amplify/backend/api/vacationtracker/resolvers/Mutation.submitVacationRequest.req.vtl:
--------------------------------------------------------------------------------
1 | ## [Start] Set default values. **
2 | $util.qr($context.args.input.put("id", $util.defaultIfNull($ctx.args.input.id, $util.autoId())))
3 | #set( $createdAt = $util.time.nowISO8601() )
4 | ## Automatically set the createdAt timestamp. **
5 | $util.qr($context.args.input.put("createdAt", $util.defaultIfNull($ctx.args.input.createdAt, $createdAt)))
6 | ## Automatically set the updatedAt timestamp. **
7 | $util.qr($context.args.input.put("updatedAt", $util.defaultIfNull($ctx.args.input.updatedAt, $createdAt)))
8 | ## Automatically set the owner. **
9 | $util.qr($context.args.input.put("owner", $util.defaultIfNull($ctx.args.input.owner, $ctx.identity.username)))
10 |
11 | $util.qr($ctx.stash.put("typeName", "Mutation"))
12 | $util.qr($ctx.stash.put("fieldName", "submitVacationRequest"))
13 | ## [End] Set default values. **
14 |
15 | ## [Start] Invoke AWS Lambda data source: CreateVacationRequestLambdaDataSource. **
16 | {
17 | "version": "2018-05-29",
18 | "operation": "Invoke",
19 | "payload": {
20 | "typeName": "$ctx.stash.get("typeName")",
21 | "fieldName": "$ctx.stash.get("fieldName")",
22 | "arguments": $util.toJson($ctx.arguments),
23 | "identity": $util.toJson($ctx.identity),
24 | "source": $util.toJson($ctx.source),
25 | "request": $util.toJson($ctx.request),
26 | "prev": $util.toJson($ctx.prev)
27 | }
28 | }
29 | ## [End] Invoke AWS Lambda data source: CreateVacationRequestLambdaDataSource. **
--------------------------------------------------------------------------------
/event-driven-developers-tale/amplify/backend/api/vacationtracker/resolvers/Mutation.submitVacationRequest.res.vtl:
--------------------------------------------------------------------------------
1 | ## [Start] Handle error or return result. **
2 | #if( $ctx.error )
3 | $util.error($ctx.error.message, $ctx.error.type)
4 | #end
5 | $util.toJson($ctx.result)
6 | ## [End] Handle error or return result. **
--------------------------------------------------------------------------------
/event-driven-developers-tale/amplify/backend/api/vacationtracker/resolvers/Mutation.updateVacationRequest.req.vtl:
--------------------------------------------------------------------------------
1 | ## [Start] Set default values. **
2 | #set( $createdAt = $util.time.nowISO8601() )
3 | ## Automatically set the updatedAt timestamp. **
4 | $util.qr($context.args.input.put("updatedAt", $util.defaultIfNull($ctx.args.input.updatedAt, $createdAt)))
5 | ## Automatically set the owner. **
6 | $util.qr($context.args.input.put("owner", $util.defaultIfNull($ctx.args.input.owner, $ctx.identity.username)))
7 |
8 | $util.qr($ctx.stash.put("typeName", "Mutation"))
9 | $util.qr($ctx.stash.put("fieldName", "updateVacationRequest"))
10 | ## [End] Set default values. **
11 |
12 | ## [Start] Invoke AWS Lambda data source: UpdateVacationRequestLambdaDataSource. **
13 | {
14 | "version": "2018-05-29",
15 | "operation": "Invoke",
16 | "payload": {
17 | "typeName": "$ctx.stash.get("typeName")",
18 | "fieldName": "$ctx.stash.get("fieldName")",
19 | "arguments": $util.toJson($ctx.arguments),
20 | "identity": $util.toJson($ctx.identity),
21 | "source": $util.toJson($ctx.source),
22 | "request": $util.toJson($ctx.request),
23 | "prev": $util.toJson($ctx.prev)
24 | }
25 | }
26 | ## [End] Invoke AWS Lambda data source: UpdateVacationRequestLambdaDataSource. **
--------------------------------------------------------------------------------
/event-driven-developers-tale/amplify/backend/api/vacationtracker/resolvers/Mutation.updateVacationRequest.res.vtl:
--------------------------------------------------------------------------------
1 | ## [Start] Handle error or return result. **
2 | #if( $ctx.error )
3 | $util.error($ctx.error.message, $ctx.error.type)
4 | #end
5 | $util.toJson($ctx.result)
6 | ## [End] Handle error or return result. **
--------------------------------------------------------------------------------
/event-driven-developers-tale/amplify/backend/api/vacationtracker/resolvers/README.md:
--------------------------------------------------------------------------------
1 | Any resolvers that you add in this directory will override the ones automatically generated by Amplify CLI and will be directly copied to the cloud.
2 | For more information, visit [https://docs.amplify.aws/cli/graphql-transformer/resolvers](https://docs.amplify.aws/cli/graphql-transformer/resolvers)
--------------------------------------------------------------------------------
/event-driven-developers-tale/amplify/backend/api/vacationtracker/stacks/CustomResources.json:
--------------------------------------------------------------------------------
1 | {
2 | "AWSTemplateFormatVersion": "2010-09-09",
3 | "Description": "An auto-generated nested stack.",
4 | "Metadata": {},
5 | "Parameters": {
6 | "AppSyncApiId": {
7 | "Type": "String",
8 | "Description": "The id of the AppSync API associated with this project."
9 | },
10 | "AppSyncApiName": {
11 | "Type": "String",
12 | "Description": "The name of the AppSync API",
13 | "Default": "AppSyncSimpleTransform"
14 | },
15 | "env": {
16 | "Type": "String",
17 | "Description": "The environment name. e.g. Dev, Test, or Production",
18 | "Default": "NONE"
19 | },
20 | "S3DeploymentBucket": {
21 | "Type": "String",
22 | "Description": "The S3 bucket containing all deployment assets for the project."
23 | },
24 | "S3DeploymentRootKey": {
25 | "Type": "String",
26 | "Description": "An S3 key relative to the S3DeploymentBucket that points to the root\nof the deployment directory."
27 | }
28 | },
29 | "Resources": {
30 | "EmptyResource": {
31 | "Type": "Custom::EmptyResource",
32 | "Condition": "AlwaysFalse"
33 | }
34 | },
35 | "Conditions": {
36 | "HasEnvironmentParameter": {
37 | "Fn::Not": [
38 | {
39 | "Fn::Equals": [
40 | {
41 | "Ref": "env"
42 | },
43 | "NONE"
44 | ]
45 | }
46 | ]
47 | },
48 | "AlwaysFalse": {
49 | "Fn::Equals": ["true", "false"]
50 | }
51 | },
52 | "Outputs": {
53 | "EmptyOutput": {
54 | "Description": "An empty output. You may delete this if you have at least one resource above.",
55 | "Value": ""
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/event-driven-developers-tale/amplify/backend/api/vacationtracker/transform.conf.json:
--------------------------------------------------------------------------------
1 | {
2 | "Version": 5,
3 | "ElasticsearchWarning": true
4 | }
--------------------------------------------------------------------------------
/event-driven-developers-tale/amplify/backend/backend-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "auth": {
3 | "vacationtrackerd86ca2cd": {
4 | "service": "Cognito",
5 | "providerPlugin": "awscloudformation",
6 | "dependsOn": [],
7 | "customAuth": false,
8 | "frontendAuthConfig": {
9 | "loginMechanisms": [],
10 | "signupAttributes": [
11 | "EMAIL"
12 | ],
13 | "passwordProtectionSettings": {
14 | "passwordPolicyMinLength": 8,
15 | "passwordPolicyCharacters": []
16 | },
17 | "mfaConfiguration": "OFF",
18 | "mfaTypes": [
19 | "SMS"
20 | ],
21 | "verificationMechanisms": [
22 | "EMAIL"
23 | ]
24 | }
25 | }
26 | },
27 | "api": {
28 | "vacationtracker": {
29 | "service": "AppSync",
30 | "providerPlugin": "awscloudformation",
31 | "output": {
32 | "authConfig": {
33 | "defaultAuthentication": {
34 | "authenticationType": "AMAZON_COGNITO_USER_POOLS",
35 | "userPoolConfig": {
36 | "userPoolId": "authvacationtrackerd86ca2cd"
37 | }
38 | },
39 | "additionalAuthenticationProviders": [
40 | {
41 | "authenticationType": "AWS_IAM"
42 | }
43 | ]
44 | }
45 | }
46 | }
47 | },
48 | "function": {},
49 | "hosting": {
50 | "amplifyhosting": {
51 | "service": "amplifyhosting",
52 | "providerPlugin": "awscloudformation",
53 | "type": "manual"
54 | }
55 | }
56 | }
--------------------------------------------------------------------------------
/event-driven-developers-tale/amplify/backend/hosting/amplifyhosting/amplifyhosting-template.json:
--------------------------------------------------------------------------------
1 | {
2 | "AWSTemplateFormatVersion": "2010-09-09",
3 | "Description": "{\"createdOn\":\"Mac\",\"createdBy\":\"Amplify\",\"createdWith\":\"9.0.0\",\"stackType\":\"hosting-amplifyhosting\",\"metadata\":{}}",
4 | "Parameters": {
5 | "env": {
6 | "Type": "String"
7 | },
8 | "appId": {
9 | "Type": "String"
10 | },
11 | "type": {
12 | "Type": "String"
13 | }
14 | },
15 | "Conditions": {
16 | "isManual": {
17 | "Fn::Equals": [
18 | {
19 | "Ref": "type"
20 | },
21 | "manual"
22 | ]
23 | }
24 | },
25 | "Resources": {
26 | "AmplifyBranch": {
27 | "Condition": "isManual",
28 | "Type": "AWS::Amplify::Branch",
29 | "Properties": {
30 | "BranchName": {
31 | "Ref": "env"
32 | },
33 | "AppId": {
34 | "Ref": "appId"
35 | }
36 | }
37 | }
38 | }
39 | }
--------------------------------------------------------------------------------
/event-driven-developers-tale/amplify/backend/tags.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "Key": "user:Stack",
4 | "Value": "{project-env}"
5 | },
6 | {
7 | "Key": "user:Application",
8 | "Value": "{project-name}"
9 | }
10 | ]
--------------------------------------------------------------------------------
/event-driven-developers-tale/amplify/cli.json:
--------------------------------------------------------------------------------
1 | {
2 | "features": {
3 | "graphqltransformer": {
4 | "addmissingownerfields": true,
5 | "improvepluralization": true,
6 | "validatetypenamereservedwords": true,
7 | "useexperimentalpipelinedtransformer": false,
8 | "enableiterativegsiupdates": true,
9 | "secondarykeyasgsi": true,
10 | "skipoverridemutationinputtypes": true,
11 | "securityEnhancementNotification": false,
12 | "showfieldauthnotification": false
13 | },
14 | "frontend-ios": {
15 | "enablexcodeintegration": true
16 | },
17 | "auth": {
18 | "enablecaseinsensitivity": true,
19 | "useinclusiveterminology": true,
20 | "breakcirculardependency": true,
21 | "forcealiasattributes": false
22 | },
23 | "codegen": {
24 | "useappsyncmodelgenplugin": true,
25 | "usedocsgeneratorplugin": true,
26 | "usetypesgeneratorplugin": true,
27 | "cleangeneratedmodelsdirectory": true,
28 | "retaincasestyle": true,
29 | "addtimestampfields": true,
30 | "handlelistnullabilitytransparently": true,
31 | "emitauthprovider": true,
32 | "generateindexrules": true,
33 | "enabledartnullsafety": true
34 | },
35 | "appsync": {
36 | "generategraphqlpermissions": true
37 | },
38 | "project": {
39 | "overrides": true
40 | }
41 | }
42 | }
--------------------------------------------------------------------------------
/event-driven-developers-tale/amplify/hooks/README.md:
--------------------------------------------------------------------------------
1 | # Command Hooks
2 |
3 | Command hooks can be used to run custom scripts upon Amplify CLI lifecycle events like pre-push, post-add-function, etc.
4 |
5 | To get started, add your script files based on the expected naming convention in this directory.
6 |
7 | Learn more about the script file naming convention, hook parameters, third party dependencies, and advanced configurations at https://docs.amplify.aws/cli/usage/command-hooks
8 |
--------------------------------------------------------------------------------
/event-driven-developers-tale/amplify/team-provider-info.json:
--------------------------------------------------------------------------------
1 | {
2 | "dev": {
3 | "awscloudformation": {
4 | "AuthRoleName": "amplify-vacationtracker-dev-225120-authRole",
5 | "UnauthRoleArn": "arn:aws:iam::414275540131:role/amplify-vacationtracker-dev-225120-unauthRole",
6 | "AuthRoleArn": "arn:aws:iam::414275540131:role/amplify-vacationtracker-dev-225120-authRole",
7 | "Region": "eu-west-1",
8 | "DeploymentBucketName": "amplify-vacationtracker-dev-225120-deployment",
9 | "UnauthRoleName": "amplify-vacationtracker-dev-225120-unauthRole",
10 | "StackName": "amplify-vacationtracker-dev-225120",
11 | "StackId": "arn:aws:cloudformation:eu-west-1:414275540131:stack/amplify-vacationtracker-dev-225120/0f148800-1fdd-11ec-8d94-0a397a0898a1",
12 | "AmplifyAppId": "d1lhx9dm0heri0"
13 | },
14 | "categories": {
15 | "auth": {
16 | "vacationtrackerd86ca2cd": {}
17 | },
18 | "hosting": {
19 | "amplifyhosting": {
20 | "appId": "d1lhx9dm0heri0",
21 | "type": "manual"
22 | }
23 | }
24 | }
25 | }
26 | }
--------------------------------------------------------------------------------
/event-driven-developers-tale/functions/createVacationRequest/.aws-sam/build.toml:
--------------------------------------------------------------------------------
1 | # This file is auto generated by SAM CLI build command
2 |
3 | [function_build_definitions]
4 | [function_build_definitions.e6005a19-85bf-47ca-8891-6d16e4eb1a21]
5 | codeuri = "/Users/tiagobar/Documents/GitHub/vacation-tracker/functions/createVacationRequest"
6 | runtime = "nodejs14.x"
7 | source_md5 = ""
8 | packagetype = "Zip"
9 | functions = ["CreateVacationRequestFunction"]
10 |
11 | [layer_build_definitions]
12 |
--------------------------------------------------------------------------------
/event-driven-developers-tale/functions/createVacationRequest/.npmignore:
--------------------------------------------------------------------------------
1 | tests/*
2 |
--------------------------------------------------------------------------------
/event-driven-developers-tale/functions/createVacationRequest/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hello_world",
3 | "version": "1.0.0",
4 | "description": "hello world sample for NodeJS",
5 | "main": "app.js",
6 | "repository": "https://github.com/awslabs/aws-sam-cli/tree/develop/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs",
7 | "author": "SAM CLI",
8 | "license": "MIT",
9 | "dependencies": {
10 | "aws-sdk": "^2.1009.0",
11 | "aws-xray-sdk": "^3.3.3"
12 | },
13 | "scripts": {
14 | "build": "npm install"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/event-driven-developers-tale/functions/createVacationRequest/template.yaml:
--------------------------------------------------------------------------------
1 | AWSTemplateFormatVersion: '2010-09-09'
2 | Transform: AWS::Serverless-2016-10-31
3 | Description: >
4 | createVacationRequest
5 |
6 | Sample SAM Template for createVacationRequest
7 |
8 | # More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
9 | Globals:
10 | Function:
11 | Timeout: 3
12 | Environment:
13 | Variables:
14 | TABLE_NAME: VacationRequest-tthjzp4lebavxlmqbniom57coi-dev
15 | EVENT_BUS_NAME: VacationTrackerEvents
16 |
17 | Resources:
18 | CreateVacationRequestFunction:
19 | Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
20 | Properties:
21 | CodeUri: .
22 | Handler: app.handler
23 | Runtime: nodejs14.x
24 |
25 | Outputs:
26 | # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function
27 | # Find out more about other implicit resources you can reference within SAM
28 | # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api
29 | CreateVacationRequestFunction:
30 | Description: "Create Vacation Request Lambda Function ARN"
31 | Value: !GetAtt CreateVacationRequestFunction.Arn
32 | CreateVacationRequestFunctionIamRole:
33 | Description: "Implicit IAM Role created for Create Vacation Request function"
34 | Value: !GetAtt CreateVacationRequestFunction.Arn
35 |
--------------------------------------------------------------------------------
/event-driven-developers-tale/functions/updateVacationRequest/.npmignore:
--------------------------------------------------------------------------------
1 | tests/*
2 |
--------------------------------------------------------------------------------
/event-driven-developers-tale/functions/updateVacationRequest/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hello_world",
3 | "version": "1.0.0",
4 | "description": "hello world sample for NodeJS",
5 | "main": "app.js",
6 | "repository": "https://github.com/awslabs/aws-sam-cli/tree/develop/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs",
7 | "author": "SAM CLI",
8 | "license": "MIT",
9 | "dependencies": {
10 | "aws-sdk": "^2.1006.0",
11 | "aws-xray-sdk": "^3.3.3"
12 | },
13 | "scripts": {
14 | "test": "mocha tests/unit/"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/event-driven-developers-tale/functions/updateVacationRequest/template.yaml:
--------------------------------------------------------------------------------
1 | AWSTemplateFormatVersion: '2010-09-09'
2 | Transform: AWS::Serverless-2016-10-31
3 | Description: >
4 | createVacationRequest
5 |
6 | Sample SAM Template for createVacationRequest
7 |
8 | # More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
9 | Globals:
10 | Function:
11 | Timeout: 3
12 | Environment:
13 | Variables:
14 | TABLE_NAME: VacationRequest-tthjzp4lebavxlmqbniom57coi-dev
15 | EVENT_BUS_NAME: VacationTrackerEvents
16 |
17 | Resources:
18 | UpdateVacationRequestFunction:
19 | Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
20 | Properties:
21 | CodeUri: .
22 | Handler: app.handler
23 | Runtime: nodejs14.x
24 |
25 | Outputs:
26 | # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function
27 | # Find out more about other implicit resources you can reference within SAM
28 | # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api
29 | UpdateVacationRequestFunction:
30 | Description: "Update Vacation Request Lambda Function ARN"
31 | Value: !GetAtt UpdateVacationRequestFunction.Arn
32 | UpdateVacationRequestFunctionIamRole:
33 | Description: "Implicit IAM Role created for Update Vacation Request function"
34 | Value: !GetAtt UpdateVacationRequestFunction.Arn
35 |
--------------------------------------------------------------------------------
/event-driven-developers-tale/functions/vacationRequestValidation/.npmignore:
--------------------------------------------------------------------------------
1 | tests/*
2 |
--------------------------------------------------------------------------------
/event-driven-developers-tale/functions/vacationRequestValidation/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hello_world",
3 | "version": "1.0.0",
4 | "description": "hello world sample for NodeJS",
5 | "main": "app.js",
6 | "repository": "https://github.com/awslabs/aws-sam-cli/tree/develop/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs",
7 | "author": "SAM CLI",
8 | "license": "MIT",
9 | "dependencies": {
10 | "@aws-sdk/client-appsync": "^3.36.0",
11 | "apollo-link-http": "^1.5.17",
12 | "aws-appsync": "^4.1.2",
13 | "aws-sdk": "^2.1005.0",
14 | "aws-xray-sdk": "^3.3.3",
15 | "es6-promise": "^4.2.8",
16 | "graphql-tag": "^2.12.5",
17 | "isomorphic-fetch": "^3.0.0",
18 | "node-fetch": "^3.1.1"
19 | },
20 | "scripts": {
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/event-driven-developers-tale/functions/vacationRequestValidation/template.yaml:
--------------------------------------------------------------------------------
1 | AWSTemplateFormatVersion: '2010-09-09'
2 | Transform: AWS::Serverless-2016-10-31
3 | Description: >
4 | createVacationRequest
5 |
6 | Sample SAM Template for createVacationRequest
7 |
8 | # More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
9 | Globals:
10 | Function:
11 | Timeout: 3
12 | Environment:
13 | Variables:
14 | APP_SYNC_API_URL: https://yeo5h7yxp5gulf7wpl5hq6dvsa.appsync-api.eu-west-1.amazonaws.com/graphql
15 |
16 | Resources:
17 | ValidateVacationRequestFunction:
18 | Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
19 | Properties:
20 | CodeUri: .
21 | Handler: app.handler
22 | Runtime: nodejs14.x
23 |
24 | Outputs:
25 | # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function
26 | # Find out more about other implicit resources you can reference within SAM
27 | # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api
28 | ValidateVacationRequestFunction:
29 | Description: "Validate Vacation Request Function ARN"
30 | Value: !GetAtt ValidateVacationRequestFunction.Arn
31 | ValidateVacationRequestFunctionIamRole:
32 | Description: "Implicit IAM Role created for Validate Vacation Request function"
33 | Value: !GetAtt ValidateVacationRequestFunctionRole.Arn
34 |
--------------------------------------------------------------------------------
/event-driven-developers-tale/images/addtogroup.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/talk-dev-to-me-twitch/ed06f3b0695b4e2b9e17cb0dda17b6d88ecbcd46/event-driven-developers-tale/images/addtogroup.png
--------------------------------------------------------------------------------
/event-driven-developers-tale/images/eventbridgeatlas.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/talk-dev-to-me-twitch/ed06f3b0695b4e2b9e17cb0dda17b6d88ecbcd46/event-driven-developers-tale/images/eventbridgeatlas.png
--------------------------------------------------------------------------------
/event-driven-developers-tale/images/eventbridgeregistry.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/talk-dev-to-me-twitch/ed06f3b0695b4e2b9e17cb0dda17b6d88ecbcd46/event-driven-developers-tale/images/eventbridgeregistry.png
--------------------------------------------------------------------------------
/event-driven-developers-tale/images/eventstormingsessionexample.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/talk-dev-to-me-twitch/ed06f3b0695b4e2b9e17cb0dda17b6d88ecbcd46/event-driven-developers-tale/images/eventstormingsessionexample.png
--------------------------------------------------------------------------------
/event-driven-developers-tale/images/register.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/talk-dev-to-me-twitch/ed06f3b0695b4e2b9e17cb0dda17b6d88ecbcd46/event-driven-developers-tale/images/register.png
--------------------------------------------------------------------------------
/event-driven-developers-tale/images/submitVacationRequestFlow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/talk-dev-to-me-twitch/ed06f3b0695b4e2b9e17cb0dda17b6d88ecbcd46/event-driven-developers-tale/images/submitVacationRequestFlow.png
--------------------------------------------------------------------------------
/event-driven-developers-tale/images/xray.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/talk-dev-to-me-twitch/ed06f3b0695b4e2b9e17cb0dda17b6d88ecbcd46/event-driven-developers-tale/images/xray.png
--------------------------------------------------------------------------------
/event-driven-developers-tale/infrastructure/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
3 | # CDK asset staging directory
4 | .cdk.staging
5 | cdk.out
6 |
--------------------------------------------------------------------------------
/event-driven-developers-tale/infrastructure/.npmignore:
--------------------------------------------------------------------------------
1 | # CDK asset staging directory
2 | .cdk.staging
3 | cdk.out
4 |
--------------------------------------------------------------------------------
/event-driven-developers-tale/infrastructure/README.md:
--------------------------------------------------------------------------------
1 | # Welcome to your CDK JavaScript project!
2 |
3 | This is a blank project for JavaScript development with CDK.
4 |
5 | The `cdk.json` file tells the CDK Toolkit how to execute your app. The build step is not required when using JavaScript.
6 |
7 | ## Useful commands
8 |
9 | * `npm run test` perform the jest unit tests
10 | * `cdk deploy` deploy this stack to your default AWS account/region
11 | * `cdk diff` compare deployed stack with current state
12 | * `cdk synth` emits the synthesized CloudFormation template
13 |
--------------------------------------------------------------------------------
/event-driven-developers-tale/infrastructure/bin/infrastructure.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const cdk = require('@aws-cdk/core');
4 | const { InfrastructureStack } = require('../lib/infrastructure-stack');
5 |
6 | const app = new cdk.App();
7 | new InfrastructureStack(app, 'InfrastructureStack', {
8 | /* If you don't specify 'env', this stack will be environment-agnostic.
9 | * Account/Region-dependent features and context lookups will not work,
10 | * but a single synthesized template can be deployed anywhere. */
11 |
12 | /* Uncomment the next line to specialize this stack for the AWS Account
13 | * and Region that are implied by the current CLI configuration. */
14 | // env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION },
15 |
16 | /* Uncomment the next line if you know exactly what Account and Region you
17 | * want to deploy the stack to. */
18 | // env: { account: '123456789012', region: 'us-east-1' },
19 |
20 | /* For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html */
21 | });
--------------------------------------------------------------------------------
/event-driven-developers-tale/infrastructure/cdk.json:
--------------------------------------------------------------------------------
1 | {
2 | "app": "node bin/infrastructure.js",
3 | "context": {
4 | "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true,
5 | "@aws-cdk/core:enableStackNameDuplicates": "true",
6 | "aws-cdk:enableDiffNoFail": "true",
7 | "@aws-cdk/core:stackRelativeExports": "true",
8 | "@aws-cdk/aws-ecr-assets:dockerIgnoreSupport": true,
9 | "@aws-cdk/aws-secretsmanager:parseOwnedSecretName": true,
10 | "@aws-cdk/aws-kms:defaultKeyPolicies": true,
11 | "@aws-cdk/aws-s3:grantWriteWithoutAcl": true,
12 | "@aws-cdk/aws-ecs-patterns:removeDefaultDesiredCount": true,
13 | "@aws-cdk/aws-rds:lowercaseDbIdentifier": true,
14 | "@aws-cdk/aws-efs:defaultEncryptionAtRest": true,
15 | "@aws-cdk/aws-lambda:recognizeVersionProps": true,
16 | "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/event-driven-developers-tale/infrastructure/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "infrastructure",
3 | "version": "0.1.0",
4 | "bin": {
5 | "infrastructure": "bin/infrastructure.js"
6 | },
7 | "scripts": {
8 | "build": "echo \"The build step is not required when using JavaScript!\" && exit 0",
9 | "cdk": "cdk",
10 | "test": "jest"
11 | },
12 | "devDependencies": {
13 | "@aws-cdk/assert": "^2.68.0",
14 | "aws-cdk": "^1.148.0",
15 | "jest": "^29.7.0"
16 | },
17 | "dependencies": {
18 | "@aws-cdk/aws-events": "^1.19.0",
19 | "@aws-cdk/aws-events-targets": "^1.19.0",
20 | "@aws-cdk/aws-iam": "^1.148.0",
21 | "@aws-cdk/aws-lambda": "^1.19.0",
22 | "@aws-cdk/aws-s3-assets": "^1.19.0",
23 | "@aws-cdk/core": "^1.32.2",
24 | "esbuild": "^0.25.0"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/event-driven-developers-tale/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vacation-tracker",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@aws-amplify/ui-react": "^6.10.0",
7 | "@aws-sdk/client-dynamodb": "^3.782.0",
8 | "@emotion/react": "^11.14.0",
9 | "@emotion/styled": "^11.14.0",
10 | "@heroicons/react": "^2.2.0",
11 | "@mui/material": "^6.4.8",
12 | "@testing-library/jest-dom": "^6.6.3",
13 | "@testing-library/react": "^16.2.0",
14 | "@testing-library/user-event": "^14.6.1",
15 | "aws-amplify": "^6.13.6",
16 | "aws-sdk": "^2.1165.0",
17 | "aws-xray-sdk": "^3.3.6",
18 | "hello_world": "file:functions/createVacationRequest",
19 | "moment": "^2.29.3",
20 | "nanoevents": "^9.1.0",
21 | "react": "^18.2.0",
22 | "react-dom": "^18.2.0",
23 | "react-moment": "^1.1.2",
24 | "react-router-dom": "^6.28.0",
25 | "react-scripts": "^5.0.1",
26 | "react-uuid": "^2.0.0",
27 | "web-vitals": "^4.2.4"
28 | },
29 | "scripts": {
30 | "start": "react-scripts start",
31 | "build": "react-scripts build",
32 | "test": "react-scripts test",
33 | "eject": "react-scripts eject"
34 | },
35 | "eslintConfig": {
36 | "extends": [
37 | "react-app",
38 | "react-app/jest"
39 | ]
40 | },
41 | "browserslist": {
42 | "production": [
43 | ">0.2%",
44 | "not dead",
45 | "not op_mini all"
46 | ],
47 | "development": [
48 | "last 1 chrome version",
49 | "last 1 firefox version",
50 | "last 1 safari version"
51 | ]
52 | },
53 | "devDependencies": {
54 | "autoprefixer": "^10.4.21",
55 | "postcss": "^8.5.3",
56 | "tailwindcss": "^3.4.17"
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/event-driven-developers-tale/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/talk-dev-to-me-twitch/ed06f3b0695b4e2b9e17cb0dda17b6d88ecbcd46/event-driven-developers-tale/public/favicon.ico
--------------------------------------------------------------------------------
/event-driven-developers-tale/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/event-driven-developers-tale/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/talk-dev-to-me-twitch/ed06f3b0695b4e2b9e17cb0dda17b6d88ecbcd46/event-driven-developers-tale/public/logo192.png
--------------------------------------------------------------------------------
/event-driven-developers-tale/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/event-driven-developers-tale/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/event-driven-developers-tale/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | height: 40vmin;
7 | pointer-events: none;
8 | }
9 |
10 | @media (prefers-reduced-motion: no-preference) {
11 | .App-logo {
12 | animation: App-logo-spin infinite 20s linear;
13 | }
14 | }
15 |
16 | .App-header {
17 | background-color: #282c34;
18 | min-height: 100vh;
19 | display: flex;
20 | flex-direction: column;
21 | align-items: center;
22 | justify-content: center;
23 | font-size: calc(10px + 2vmin);
24 | color: white;
25 | }
26 |
27 | .App-link {
28 | color: #61dafb;
29 | }
30 |
31 | @keyframes App-logo-spin {
32 | from {
33 | transform: rotate(0deg);
34 | }
35 | to {
36 | transform: rotate(360deg);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/event-driven-developers-tale/src/components/AddCategory.js:
--------------------------------------------------------------------------------
1 | import { useState } from 'react'
2 | import { API } from 'aws-amplify'
3 | import { createCategory } from '../graphql/mutations'
4 |
5 | function AddCategory({ emmiter }) {
6 |
7 | const [name, setName] = useState("")
8 |
9 | async function addCategory() {
10 | const newCategory = {
11 | name: name
12 | }
13 |
14 | await API.graphql({
15 | query: createCategory,
16 | variables: {
17 | input: newCategory
18 | },
19 | authMode: 'AMAZON_COGNITO_USER_POOLS'
20 | })
21 |
22 | emmiter.emit("showModal", false, "", "", null)
23 | }
24 |
25 | function cancel(){
26 | emmiter.emit("showModal", false, "", "", null)
27 | }
28 |
29 | return (
30 |
31 |
32 |
33 |
34 | setName(event.target.value)} />
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | )
45 | }
46 |
47 | export default AddCategory
48 |
--------------------------------------------------------------------------------
/event-driven-developers-tale/src/components/CategoryItem.js:
--------------------------------------------------------------------------------
1 | function CategoryItem({category}) {
2 | return (
3 |
4 | {category.name}
5 |
6 | )
7 | }
8 |
9 | export default CategoryItem
10 |
--------------------------------------------------------------------------------
/event-driven-developers-tale/src/components/Modal.js:
--------------------------------------------------------------------------------
1 | function Modal({ showModal, setShowModal, header, subtitle, content }) {
2 | return (
3 | <>
4 | {showModal &&
5 |
6 |
7 |
8 |
{header}
9 | {subtitle}
10 |
11 |
12 |
13 | {content}
14 |
15 |
16 |
17 | }
18 | >
19 | )
20 | }
21 |
22 | export default Modal
23 |
--------------------------------------------------------------------------------
/event-driven-developers-tale/src/graphql/mutations.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | // this is an auto generated file. This will be overwritten
3 |
4 | export const submitVacationRequest = /* GraphQL */ `
5 | mutation SubmitVacationRequest($input: CreateVacationRequestInput!) {
6 | submitVacationRequest(input: $input) {
7 | id
8 | category
9 | description
10 | approvalStatus
11 | startDate
12 | endDate
13 | approvedBy
14 | owner
15 | rejectionReason
16 | createdAt
17 | updatedAt
18 | }
19 | }
20 | `;
21 | export const updateVacationRequest = /* GraphQL */ `
22 | mutation UpdateVacationRequest($input: UpdateVacationRequestInput!) {
23 | updateVacationRequest(input: $input) {
24 | id
25 | category
26 | description
27 | approvalStatus
28 | startDate
29 | endDate
30 | approvedBy
31 | owner
32 | rejectionReason
33 | createdAt
34 | updatedAt
35 | }
36 | }
37 | `;
38 | export const deleteVacationRequest = /* GraphQL */ `
39 | mutation DeleteVacationRequest(
40 | $input: DeleteVacationRequestInput!
41 | $condition: ModelVacationRequestConditionInput
42 | ) {
43 | deleteVacationRequest(input: $input, condition: $condition) {
44 | id
45 | category
46 | description
47 | approvalStatus
48 | startDate
49 | endDate
50 | approvedBy
51 | owner
52 | rejectionReason
53 | createdAt
54 | updatedAt
55 | }
56 | }
57 | `;
58 |
--------------------------------------------------------------------------------
/event-driven-developers-tale/src/graphql/queries.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | // this is an auto generated file. This will be overwritten
3 |
4 | export const getVacationRequest = /* GraphQL */ `
5 | query GetVacationRequest($id: ID!) {
6 | getVacationRequest(id: $id) {
7 | id
8 | category
9 | description
10 | approvalStatus
11 | startDate
12 | endDate
13 | approvedBy
14 | owner
15 | rejectionReason
16 | createdAt
17 | updatedAt
18 | }
19 | }
20 | `;
21 | export const listVacationRequests = /* GraphQL */ `
22 | query ListVacationRequests(
23 | $filter: ModelVacationRequestFilterInput
24 | $limit: Int
25 | $nextToken: String
26 | ) {
27 | listVacationRequests(
28 | filter: $filter
29 | limit: $limit
30 | nextToken: $nextToken
31 | ) {
32 | items {
33 | id
34 | category
35 | description
36 | approvalStatus
37 | startDate
38 | endDate
39 | approvedBy
40 | owner
41 | rejectionReason
42 | createdAt
43 | updatedAt
44 | }
45 | nextToken
46 | }
47 | }
48 | `;
49 |
--------------------------------------------------------------------------------
/event-driven-developers-tale/src/graphql/subscriptions.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | // this is an auto generated file. This will be overwritten
3 |
4 | export const onVacationRequestNotification = /* GraphQL */ `
5 | subscription OnVacationRequestNotification($owner: String) {
6 | onVacationRequestNotification(owner: $owner) {
7 | id
8 | category
9 | description
10 | approvalStatus
11 | startDate
12 | endDate
13 | approvedBy
14 | owner
15 | rejectionReason
16 | createdAt
17 | updatedAt
18 | }
19 | }
20 | `;
21 | export const onDeleteVacationRequest = /* GraphQL */ `
22 | subscription OnDeleteVacationRequest {
23 | onDeleteVacationRequest {
24 | id
25 | category
26 | description
27 | approvalStatus
28 | startDate
29 | endDate
30 | approvedBy
31 | owner
32 | rejectionReason
33 | createdAt
34 | updatedAt
35 | }
36 | }
37 | `;
38 |
--------------------------------------------------------------------------------
/event-driven-developers-tale/src/images/profile-pic.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/talk-dev-to-me-twitch/ed06f3b0695b4e2b9e17cb0dda17b6d88ecbcd46/event-driven-developers-tale/src/images/profile-pic.jpg
--------------------------------------------------------------------------------
/event-driven-developers-tale/src/index.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 |
--------------------------------------------------------------------------------
/event-driven-developers-tale/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import Amplify from 'aws-amplify';
6 | import awsExports from './aws-exports';
7 | Amplify.configure(awsExports);
8 |
9 | ReactDOM.render(
10 |
11 |
12 | ,
13 | document.getElementById('root')
14 | );
15 |
--------------------------------------------------------------------------------
/event-driven-developers-tale/src/pages/People.js:
--------------------------------------------------------------------------------
1 | function People() {
2 | return (
3 |
4 |
PEOPLE
5 |
Work in progress
6 |
7 | )
8 | }
9 |
10 | export default People
11 |
--------------------------------------------------------------------------------
/event-driven-developers-tale/src/pages/Profile.js:
--------------------------------------------------------------------------------
1 | function Profile() {
2 | return (
3 |
4 |
PROFILE
5 |
Work in progress
6 |
7 | )
8 | }
9 |
10 | export default Profile
11 |
--------------------------------------------------------------------------------
/event-driven-developers-tale/src/pages/Settings.js:
--------------------------------------------------------------------------------
1 | // import { useState, useEffect } from 'react'
2 | // import { listCategories } from '../graphql/queries'
3 | // import { API } from 'aws-amplify'
4 | // import CategoryItem from '../components/CategoryItem'
5 | // import AddCategory from '../components/AddCategory'
6 |
7 | function Settings({emmiter}) {
8 | // const [categories, setCategories] = useState([])
9 |
10 | // useEffect(()=> {
11 | // getCategories()
12 | // }, [])
13 |
14 | // async function getCategories(){
15 | // const result = await API.graphql({query: listCategories, authMode: 'AMAZON_COGNITO_USER_POOLS'})
16 | // setCategories(result.data.listCategories.items)
17 | // }
18 |
19 | // function showAddCategory(){
20 | // emmiter.emit("showModal", true, "Create Category", "Create a new category to help employees classify their vacations.", )
21 | // }
22 |
23 |
24 |
25 | return (
26 |
27 |
SETTINGS
28 | {/* CATEGORIES */}
29 | {/* Header */}
30 | {/*
31 |
Categories
32 |
33 |
34 |
35 |
*/}
36 |
37 | {/* List Categories */}
38 | {/* { categories.map((category, index) => {
39 | return (
40 |
41 | )
42 | })
43 |
44 | } */}
45 |
46 |
47 | )
48 | }
49 |
50 | export default Settings
51 |
--------------------------------------------------------------------------------
/event-driven-developers-tale/src/pages/Teams.js:
--------------------------------------------------------------------------------
1 | function Teams() {
2 | return (
3 |
4 |
TEAMS
5 |
Work in progress
6 |
7 | )
8 | }
9 |
10 | export default Teams
11 |
--------------------------------------------------------------------------------
/event-driven-developers-tale/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | mode: 'jit',
3 | content: [
4 | './public/**/*.html',
5 | './src/**/*.{js,jsx,ts,tsx,vue}',
6 | ],
7 | theme: {
8 | extend: {
9 | margin: {
10 | '-120': '-500px',
11 | }
12 | },
13 | },
14 | variants: {
15 | extend: {
16 | opacity: ['disabled']
17 | },
18 | },
19 | plugins: [],
20 | }
21 |
--------------------------------------------------------------------------------
/hexagonal-architectures/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .aws-sam/
3 | samconfig.toml
4 | env.json
5 | .DS_Store
6 | src/.DS_Store
7 | .vscode
--------------------------------------------------------------------------------
/hexagonal-architectures/README.md:
--------------------------------------------------------------------------------
1 | # Serverless Typescript Demo
2 |
3 | This is a simple serverless application built in Typescript and uses Node.js runtime. It consists of an [Amazon API Gateway](https://aws.amazon.com/api-gateway/) backed by four [AWS Lambda](https://aws.amazon.com/lambda/) functions and an [Amazon DynamoDB](https://aws.amazon.com/dynamodb/) table for storage.
4 |
5 |
6 |
7 |
8 |
9 | We will explore the usage of [hexagonal architecture](https://www.youtube.com/watch?v=kRFg6fkVChQ) pattern to decouple the entry points, from the main domain logic and the storage logic.
10 | As an example we will see how to implement an adapter for Amazon API Gateway and another adapter for Amazon EventBridge and how to easily switch between them with minor code changes.
11 |
12 | 
13 |
14 | ## Requirements
15 |
16 | - [AWS CLI](https://aws.amazon.com/cli/)
17 | - [SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html)
18 | - [Node.js 14](https://nodejs.org/)
19 |
20 | ### Deployment
21 |
22 | Deploy the demo to your AWS account using [AWS SAM](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-getting-started.html).
23 |
24 | ```bash
25 | npm install
26 | npm run build
27 | # sam deploy --guided # if running for the first time.
28 | sam deploy
29 | ```
30 |
31 | The `npm run build` commmand will first build the products TypeScript project. Then the command `sam deploy` use the SAM Template to deploy the resources to your account.
32 |
33 | SAM will create an output of the API Gateway endpoint URL for future use in our load tests.
34 |
35 | ## License
36 |
37 | This library is licensed under the MIT-0 License. See the LICENSE file.
38 |
--------------------------------------------------------------------------------
/hexagonal-architectures/events/create-product-evb-wrong-payload.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "cdc73f9d-aea9-11e3-9d5a-835b769c0d9c",
3 | "detail-type": "ProductCreationRequested",
4 | "source": "serverless.demo",
5 | "account": "123456789012",
6 | "time": "1970-01-01T00:00:00Z",
7 | "region": "us-east-1",
8 | "resources": ["arn:aws:events:us-east-1:123456789012:rule/ExampleRule"],
9 | "detail": {
10 | "id": "cdc73f9d-aea9-11e3-9d5a-835b769c0d9c",
11 | "name": "product cdc73f9d-aea9-11e3-9d5a-835b769c0d9c"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/hexagonal-architectures/events/create-product-evb.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "cdc73f9d-aea9-11e3-9d5a-835b769c0d9c",
3 | "detail-type": "ProductCreationRequested",
4 | "source": "serverless.demo",
5 | "account": "123456789012",
6 | "time": "1970-01-01T00:00:00Z",
7 | "region": "us-east-1",
8 | "resources": ["arn:aws:events:us-east-1:123456789012:rule/ExampleRule"],
9 | "detail": {
10 | "id": "cdc73f9d-aea9-11e3-9d5a-835b769c0d9c",
11 | "name": "product cdc73f9d-aea9-11e3-9d5a-835b769c0d9c",
12 | "price": 100.20
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/hexagonal-architectures/events/create-product-wrong-payload.json:
--------------------------------------------------------------------------------
1 | {
2 | "body": "{\"id\":\"100\",\"name\":\"product 100\"}",
3 | "resource": "/{proxy+}",
4 | "path": "/product",
5 | "httpMethod": "PUT",
6 | "headers": {
7 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
8 | "Accept-Encoding": "gzip, deflate, sdch",
9 | "Accept-Language": "en-US,en;q=0.8",
10 | "Cache-Control": "max-age=0",
11 | "CloudFront-Forwarded-Proto": "https",
12 | "CloudFront-Is-Desktop-Viewer": "true",
13 | "CloudFront-Is-Mobile-Viewer": "false",
14 | "CloudFront-Is-SmartTV-Viewer": "false",
15 | "CloudFront-Is-Tablet-Viewer": "false",
16 | "CloudFront-Viewer-Country": "US",
17 | "Host": "1234567890.execute-api.{dns_suffix}",
18 | "Upgrade-Insecure-Requests": "1",
19 | "User-Agent": "Custom User Agent String",
20 | "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)",
21 | "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==",
22 | "X-Forwarded-For": "127.0.0.1, 127.0.0.2",
23 | "X-Forwarded-Port": "443",
24 | "X-Forwarded-Proto": "https"
25 | },
26 | "requestContext": {
27 | "accountId": "123456789012",
28 | "resourceId": "123456",
29 | "stage": "prod",
30 | "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef",
31 | "identity": {
32 | "cognitoIdentityPoolId": null,
33 | "accountId": null,
34 | "cognitoIdentityId": null,
35 | "caller": null,
36 | "apiKey": null,
37 | "sourceIp": "127.0.0.1",
38 | "cognitoAuthenticationType": null,
39 | "cognitoAuthenticationProvider": null,
40 | "userArn": null,
41 | "userAgent": "Custom User Agent String",
42 | "user": null
43 | },
44 | "resourcePath": "/{proxy+}",
45 | "httpMethod": "PUT",
46 | "apiId": "1234567890"
47 | }
48 | }
--------------------------------------------------------------------------------
/hexagonal-architectures/events/create-product.json:
--------------------------------------------------------------------------------
1 | {
2 | "body": "{\"id\":\"cdc73f9d-aea9-11e3-9d5a-835b769c0d9c\",\"name\":\"product cdc73f9d-aea9-11e3-9d5a-835b769c0d9c\",\"price\":100.20}",
3 | "resource": "/{proxy+}",
4 | "path": "/product",
5 | "httpMethod": "PUT",
6 | "headers": {
7 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
8 | "Accept-Encoding": "gzip, deflate, sdch",
9 | "Accept-Language": "en-US,en;q=0.8",
10 | "Cache-Control": "max-age=0",
11 | "CloudFront-Forwarded-Proto": "https",
12 | "CloudFront-Is-Desktop-Viewer": "true",
13 | "CloudFront-Is-Mobile-Viewer": "false",
14 | "CloudFront-Is-SmartTV-Viewer": "false",
15 | "CloudFront-Is-Tablet-Viewer": "false",
16 | "CloudFront-Viewer-Country": "US",
17 | "Host": "1234567890.execute-api.{dns_suffix}",
18 | "Upgrade-Insecure-Requests": "1",
19 | "User-Agent": "Custom User Agent String",
20 | "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)",
21 | "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==",
22 | "X-Forwarded-For": "127.0.0.1, 127.0.0.2",
23 | "X-Forwarded-Port": "443",
24 | "X-Forwarded-Proto": "https"
25 | },
26 | "requestContext": {
27 | "accountId": "123456789012",
28 | "resourceId": "123456",
29 | "stage": "prod",
30 | "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef",
31 | "identity": {
32 | "cognitoIdentityPoolId": null,
33 | "accountId": null,
34 | "cognitoIdentityId": null,
35 | "caller": null,
36 | "apiKey": null,
37 | "sourceIp": "127.0.0.1",
38 | "cognitoAuthenticationType": null,
39 | "cognitoAuthenticationProvider": null,
40 | "userArn": null,
41 | "userAgent": "Custom User Agent String",
42 | "user": null
43 | },
44 | "resourcePath": "/{proxy+}",
45 | "httpMethod": "PUT",
46 | "apiId": "1234567890"
47 | }
48 | }
--------------------------------------------------------------------------------
/hexagonal-architectures/events/delete-product.json:
--------------------------------------------------------------------------------
1 | {
2 | "body": "{\"test\":\"body\"}",
3 | "resource": "/{proxy+}",
4 | "path": "/product",
5 | "httpMethod": "DELETE",
6 | "pathParameters": {
7 | "id": "100"
8 | },
9 | "headers": {
10 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
11 | "Accept-Encoding": "gzip, deflate, sdch",
12 | "Accept-Language": "en-US,en;q=0.8",
13 | "Cache-Control": "max-age=0",
14 | "CloudFront-Forwarded-Proto": "https",
15 | "CloudFront-Is-Desktop-Viewer": "true",
16 | "CloudFront-Is-Mobile-Viewer": "false",
17 | "CloudFront-Is-SmartTV-Viewer": "false",
18 | "CloudFront-Is-Tablet-Viewer": "false",
19 | "CloudFront-Viewer-Country": "US",
20 | "Host": "1234567890.execute-api.{dns_suffix}",
21 | "Upgrade-Insecure-Requests": "1",
22 | "User-Agent": "Custom User Agent String",
23 | "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)",
24 | "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==",
25 | "X-Forwarded-For": "127.0.0.1, 127.0.0.2",
26 | "X-Forwarded-Port": "443",
27 | "X-Forwarded-Proto": "https"
28 | },
29 | "requestContext": {
30 | "accountId": "123456789012",
31 | "resourceId": "123456",
32 | "stage": "prod",
33 | "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef",
34 | "identity": {
35 | "cognitoIdentityPoolId": null,
36 | "accountId": null,
37 | "cognitoIdentityId": null,
38 | "caller": null,
39 | "apiKey": null,
40 | "sourceIp": "127.0.0.1",
41 | "cognitoAuthenticationType": null,
42 | "cognitoAuthenticationProvider": null,
43 | "userArn": null,
44 | "userAgent": "Custom User Agent String",
45 | "user": null
46 | },
47 | "resourcePath": "/{proxy+}",
48 | "httpMethod": "DELETE",
49 | "apiId": "1234567890"
50 | }
51 | }
--------------------------------------------------------------------------------
/hexagonal-architectures/events/get-product.json:
--------------------------------------------------------------------------------
1 | {
2 | "resource": "/{proxy+}",
3 | "path": "/product",
4 | "httpMethod": "GET",
5 | "pathParameters": {
6 | "id": "5"
7 | },
8 | "headers": {
9 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
10 | "Accept-Encoding": "gzip, deflate, sdch",
11 | "Accept-Language": "en-US,en;q=0.8",
12 | "Cache-Control": "max-age=0",
13 | "CloudFront-Forwarded-Proto": "https",
14 | "CloudFront-Is-Desktop-Viewer": "true",
15 | "CloudFront-Is-Mobile-Viewer": "false",
16 | "CloudFront-Is-SmartTV-Viewer": "false",
17 | "CloudFront-Is-Tablet-Viewer": "false",
18 | "CloudFront-Viewer-Country": "US",
19 | "Host": "1234567890.execute-api.{dns_suffix}",
20 | "Upgrade-Insecure-Requests": "1",
21 | "User-Agent": "Custom User Agent String",
22 | "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)",
23 | "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==",
24 | "X-Forwarded-For": "127.0.0.1, 127.0.0.2",
25 | "X-Forwarded-Port": "443",
26 | "X-Forwarded-Proto": "https"
27 | },
28 | "requestContext": {
29 | "accountId": "123456789012",
30 | "resourceId": "123456",
31 | "stage": "prod",
32 | "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef",
33 | "identity": {
34 | "cognitoIdentityPoolId": null,
35 | "accountId": null,
36 | "cognitoIdentityId": null,
37 | "caller": null,
38 | "apiKey": null,
39 | "sourceIp": "127.0.0.1",
40 | "cognitoAuthenticationType": null,
41 | "cognitoAuthenticationProvider": null,
42 | "userArn": null,
43 | "userAgent": "Custom User Agent String",
44 | "user": null
45 | },
46 | "resourcePath": "/{proxy+}",
47 | "httpMethod": "GET",
48 | "apiId": "1234567890"
49 | }
50 | }
--------------------------------------------------------------------------------
/hexagonal-architectures/events/get-products.json:
--------------------------------------------------------------------------------
1 | {
2 | "resource": "/{proxy+}",
3 | "path": "/product",
4 | "httpMethod": "GET",
5 | "headers": {
6 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
7 | "Accept-Encoding": "gzip, deflate, sdch",
8 | "Accept-Language": "en-US,en;q=0.8",
9 | "Cache-Control": "max-age=0",
10 | "CloudFront-Forwarded-Proto": "https",
11 | "CloudFront-Is-Desktop-Viewer": "true",
12 | "CloudFront-Is-Mobile-Viewer": "false",
13 | "CloudFront-Is-SmartTV-Viewer": "false",
14 | "CloudFront-Is-Tablet-Viewer": "false",
15 | "CloudFront-Viewer-Country": "US",
16 | "Host": "1234567890.execute-api.{dns_suffix}",
17 | "Upgrade-Insecure-Requests": "1",
18 | "User-Agent": "Custom User Agent String",
19 | "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)",
20 | "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==",
21 | "X-Forwarded-For": "127.0.0.1, 127.0.0.2",
22 | "X-Forwarded-Port": "443",
23 | "X-Forwarded-Proto": "https"
24 | },
25 | "requestContext": {
26 | "accountId": "123456789012",
27 | "resourceId": "123456",
28 | "stage": "prod",
29 | "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef",
30 | "identity": {
31 | "cognitoIdentityPoolId": null,
32 | "accountId": null,
33 | "cognitoIdentityId": null,
34 | "caller": null,
35 | "apiKey": null,
36 | "sourceIp": "127.0.0.1",
37 | "cognitoAuthenticationType": null,
38 | "cognitoAuthenticationProvider": null,
39 | "userArn": null,
40 | "userAgent": "Custom User Agent String",
41 | "user": null
42 | },
43 | "resourcePath": "/{proxy+}",
44 | "httpMethod": "GET",
45 | "apiId": "1234567890"
46 | }
47 | }
--------------------------------------------------------------------------------
/hexagonal-architectures/images/diagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/talk-dev-to-me-twitch/ed06f3b0695b4e2b9e17cb0dda17b6d88ecbcd46/hexagonal-architectures/images/diagram.png
--------------------------------------------------------------------------------
/hexagonal-architectures/images/hex.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/talk-dev-to-me-twitch/ed06f3b0695b4e2b9e17cb0dda17b6d88ecbcd46/hexagonal-architectures/images/hex.png
--------------------------------------------------------------------------------
/hexagonal-architectures/jest.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
2 | module.exports = {
3 | preset: 'ts-jest',
4 | testEnvironment: 'node',
5 | };
--------------------------------------------------------------------------------
/hexagonal-architectures/junit.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/hexagonal-architectures/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "serverless-typescript-demo",
3 | "version": "0.1.0",
4 | "bin": {
5 | "serverless-typescript-demo": "bin/serverless-typescript-demo.js"
6 | },
7 | "scripts": {
8 | "build": "webpack-cli",
9 | "watch": "webpack-cli -w",
10 | "test": "jest",
11 | "test:coverage": "jest --coverage",
12 | "test:watch": "jest --watch"
13 | },
14 | "devDependencies": {
15 | "@types/aws-lambda": "^8.10.149",
16 | "@types/jest": "^27.5.0",
17 | "@types/node": "^22.15.29",
18 | "@types/uuid": "^9.0.8",
19 | "aws-sam-webpack-plugin": "^0.15.1",
20 | "jest": "^27.5.1",
21 | "ts-jest": "^27.1.4",
22 | "ts-loader": "^9.5.2",
23 | "typescript": "^4.9.5",
24 | "webpack": "^5.99.9",
25 | "webpack-cli": "^5.1.4"
26 | },
27 | "dependencies": {
28 | "@aws-sdk/client-dynamodb": "^3.826.0",
29 | "@aws-sdk/lib-dynamodb": "^3.828.0",
30 | "aws-sdk": "^2.1692.0",
31 | "prettier": "^3.5.3",
32 | "source-map-support": "^0.5.21",
33 | "ts-json-object": "^0.4.1",
34 | "uuid": "^11.1.0"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/hexagonal-architectures/src/adapters/event-bridge.ts:
--------------------------------------------------------------------------------
1 | import { EventBridgeHandler } from "aws-lambda";
2 | import { CreateProduct } from "../domain/Products";
3 | import { Product } from "../model/product";
4 |
5 | enum ProductEventTypes {
6 | CreationRequested = "ProductCreationRequested",
7 | Created = "ProductCreated",
8 | Deleted = "ProductDeleted"
9 | }
10 |
11 | export const CreateProductEBAdapter = (next: CreateProduct): EventBridgeHandler => async (event) => {
12 | try {
13 |
14 | if (event["detail-type"] === ProductEventTypes.CreationRequested) {
15 |
16 | const product = new Product(event.detail);
17 |
18 | try {
19 | await next(product);
20 | } catch (error) {
21 | console.error(error);
22 | }
23 | }
24 | else {
25 | console.warn(`Product not provided`);
26 | }
27 | } catch (error) {
28 | console.error(error);
29 | }
30 | }
--------------------------------------------------------------------------------
/hexagonal-architectures/src/domain/Products.ts:
--------------------------------------------------------------------------------
1 | import { ProductStore } from "../store/product-store";
2 | import { Product } from "../model/product";
3 |
4 | export type GetProduct = (id: string) => Promise;
5 | export type GetProducts = () => Promise;
6 | export type DeleteProduct = (id: string) => Promise;
7 | export type CreateProduct = (product: Product) => Promise;
8 |
9 | export const getProduct = (store: ProductStore): GetProduct => async (id: string): Promise => {
10 | try {
11 | console.info(`Fetching product ${id}`)
12 | const result = await store.getProduct(id);
13 | if (!result) {
14 | console.warn(`No product with id: ${id}`);
15 | return {} as Product;
16 | }
17 |
18 | return result;
19 |
20 | } catch (error) {
21 | console.error(error);
22 | throw error;
23 | }
24 | }
25 |
26 | export const getProducts = (store: ProductStore): GetProducts => async (): Promise => {
27 | try {
28 | const result = await store.getProducts();
29 | if (!result) {
30 | console.warn(`No products available.`);
31 | return [];
32 | }
33 |
34 | return result;
35 |
36 | } catch (error) {
37 | console.error(error);
38 | throw error;
39 | }
40 | }
41 |
42 | export const deleteProduct = (store: ProductStore): DeleteProduct => async (id: string): Promise => {
43 | try {
44 | console.info(`Deleting product ${id}`)
45 | await store.deleteProduct(id);
46 | } catch (error) {
47 | console.error(error);
48 | throw error;
49 | }
50 | }
51 |
52 | export const createProduct = (store: ProductStore): CreateProduct => async (product: Product): Promise => {
53 | try {
54 | console.info(`Adding product with Id: ${product.id}`)
55 | await store.putProduct(product);
56 | console.info(`Product '${product.id}' successfully added`);
57 | } catch (error) {
58 | console.error(error);
59 | throw error;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/hexagonal-architectures/src/functions/delete-product/delete-product.ts:
--------------------------------------------------------------------------------
1 | import { DynamoDbStore } from "../../store/dynamodb/dynamodb-store";
2 | import { deleteProduct } from "../../domain/Products";
3 | import { DeleteProductAPIGWAdapter } from "../../adapters/api-gateway";
4 |
5 | const tableName = process.env.TABLE;
6 |
7 | const store = new DynamoDbStore(tableName!);
8 | const domain = deleteProduct(store);
9 |
10 | export const handler = DeleteProductAPIGWAdapter(domain);
--------------------------------------------------------------------------------
/hexagonal-architectures/src/functions/get-product/get-product.ts:
--------------------------------------------------------------------------------
1 | import { DynamoDbStore } from "../../store/dynamodb/dynamodb-store";
2 | import { getProduct } from "../../domain/Products";
3 | import { GetProductAPIGWAdapter } from "../../adapters/api-gateway";
4 |
5 | const tableName = process.env.TABLE;
6 |
7 | const store = new DynamoDbStore(tableName!);
8 | const domain = getProduct(store);
9 |
10 | export const handler = GetProductAPIGWAdapter(domain);
11 |
12 |
--------------------------------------------------------------------------------
/hexagonal-architectures/src/functions/get-products/get-products.ts:
--------------------------------------------------------------------------------
1 | import { DynamoDbStore } from "../../store/dynamodb/dynamodb-store";
2 | import { getProducts } from "../../domain/Products";
3 | import { GetProductsAPIGWAdapter } from "../../adapters/api-gateway";
4 |
5 | const tableName = process.env.TABLE;
6 |
7 | const store = new DynamoDbStore(tableName!);
8 | const domain = getProducts(store);
9 |
10 | export const handler = GetProductsAPIGWAdapter(domain);
--------------------------------------------------------------------------------
/hexagonal-architectures/src/functions/put-product/put-product.ts:
--------------------------------------------------------------------------------
1 | import { DynamoDbStore } from "../../store/dynamodb/dynamodb-store";
2 | import { createProduct } from "../../domain/Products";
3 | import { CreateProductAPIGWAdapter } from "../../adapters/api-gateway";
4 | // import { CreateProductEBAdapter } from "../../adapters/event-bridge";
5 |
6 | const tableName = process.env.TABLE;
7 |
8 | const store = new DynamoDbStore(tableName!);
9 | const domain = createProduct(store);
10 |
11 | export const handler = CreateProductAPIGWAdapter(domain);
12 | // export const handler = CreateProductEBAdapter(domain);
--------------------------------------------------------------------------------
/hexagonal-architectures/src/model/product.ts:
--------------------------------------------------------------------------------
1 | import { JSONObject, required } from "ts-json-object"
2 |
3 | export class Product extends JSONObject {
4 |
5 | @required
6 | // @ts-ignore
7 | id:string
8 | @required
9 | // @ts-ignore
10 | name:string
11 | @required
12 | // @ts-ignore
13 | price:number
14 | }
--------------------------------------------------------------------------------
/hexagonal-architectures/src/store/product-store.ts:
--------------------------------------------------------------------------------
1 | import { Product } from "../model/product";
2 |
3 | export interface ProductStore {
4 | getProduct: (id: string) => Promise;
5 | putProduct: (product: Product) => Promise;
6 | deleteProduct: (id: string) => Promise;
7 | getProducts: () => Promise;
8 | }
--------------------------------------------------------------------------------
/hexagonal-architectures/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2015",
4 | "module": "commonjs",
5 | "sourceMap": true,
6 | "rootDir": "./src",
7 | "strict": true,
8 | "esModuleInterop": true,
9 | "skipLibCheck": true,
10 | "forceConsistentCasingInFileNames": true,
11 | "experimentalDecorators": true,
12 | "emitDecoratorMetadata": true,
13 | }
14 | }
--------------------------------------------------------------------------------
/hexagonal-architectures/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 | const AwsSamPlugin = require("aws-sam-webpack-plugin");
3 |
4 | const awsSamPlugin = new AwsSamPlugin();
5 |
6 | module.exports = {
7 | // Loads the entry object from the AWS::Serverless::Function resources in your
8 | // SAM config. Setting this to a function will
9 | entry: () => awsSamPlugin.entry(),
10 |
11 | // Write the output to the .aws-sam/build folder
12 | output: {
13 | filename: (chunkData) => awsSamPlugin.filename(chunkData),
14 | libraryTarget: "commonjs2",
15 | path: path.resolve(".")
16 | },
17 |
18 | // Create source maps
19 | devtool: "source-map",
20 |
21 | // Resolve .ts and .js extensions
22 | resolve: {
23 | extensions: [".ts", ".js"]
24 | },
25 |
26 | // Target node
27 | target: "node",
28 |
29 | // AWS recommends always including the aws-sdk in your Lambda package but excluding can significantly reduce
30 | // the size of your deployment package. If you want to always include it then comment out this line. It has
31 | // been included conditionally because the node10.x docker image used by SAM local doesn't include it.
32 | externals: process.env.NODE_ENV === "development" ? [] : ["aws-sdk"],
33 |
34 | // Set the webpack mode
35 | mode: process.env.NODE_ENV || "production",
36 |
37 | // Add the TypeScript loader
38 | module: {
39 | rules: [{ test: /\.tsx?$/, loader: "ts-loader" }]
40 | },
41 |
42 | // Add the AWS SAM Webpack plugin
43 | plugins: [awsSamPlugin]
44 | };
--------------------------------------------------------------------------------
/lambda-powertools-feature-flags/app-config-store/cdk/.gitignore:
--------------------------------------------------------------------------------
1 | *.js
2 | !jest.config.js
3 | *.d.ts
4 | node_modules
5 |
6 | # CDK asset staging directory
7 | .cdk.staging
8 | cdk.out
9 |
--------------------------------------------------------------------------------
/lambda-powertools-feature-flags/app-config-store/cdk/.npmignore:
--------------------------------------------------------------------------------
1 | *.ts
2 | !*.d.ts
3 |
4 | # CDK asset staging directory
5 | .cdk.staging
6 | cdk.out
7 |
--------------------------------------------------------------------------------
/lambda-powertools-feature-flags/app-config-store/cdk/README.md:
--------------------------------------------------------------------------------
1 | # Welcome to your CDK TypeScript project!
2 |
3 | This is a blank project for TypeScript development with CDK.
4 |
5 | The `cdk.json` file tells the CDK Toolkit how to execute your app.
6 |
7 | ## Useful commands
8 |
9 | * `npm run build` compile typescript to js
10 | * `npm run watch` watch for changes and compile
11 | * `npm run test` perform the jest unit tests
12 | * `cdk deploy` deploy this stack to your default AWS account/region
13 | * `cdk diff` compare deployed stack with current state
14 | * `cdk synth` emits the synthesized CloudFormation template
15 |
--------------------------------------------------------------------------------
/lambda-powertools-feature-flags/app-config-store/cdk/bin/cdk.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | import 'source-map-support/register';
3 | import * as cdk from '@aws-cdk/core';
4 | import { CdkStack } from '../lib/cdk-stack';
5 |
6 | const app = new cdk.App();
7 | new CdkStack(app, 'CdkStack', {
8 | /* If you don't specify 'env', this stack will be environment-agnostic.
9 | * Account/Region-dependent features and context lookups will not work,
10 | * but a single synthesized template can be deployed anywhere. */
11 |
12 | /* Uncomment the next line to specialize this stack for the AWS Account
13 | * and Region that are implied by the current CLI configuration. */
14 | // env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION },
15 |
16 | /* Uncomment the next line if you know exactly what Account and Region you
17 | * want to deploy the stack to. */
18 | // env: { account: '123456789012', region: 'us-east-1' },
19 |
20 | /* For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html */
21 | });
22 |
--------------------------------------------------------------------------------
/lambda-powertools-feature-flags/app-config-store/cdk/cdk.json:
--------------------------------------------------------------------------------
1 | {
2 | "app": "npx ts-node --prefer-ts-exts bin/cdk.ts",
3 | "context": {
4 | "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true,
5 | "@aws-cdk/core:enableStackNameDuplicates": "true",
6 | "aws-cdk:enableDiffNoFail": "true",
7 | "@aws-cdk/core:stackRelativeExports": "true",
8 | "@aws-cdk/aws-ecr-assets:dockerIgnoreSupport": true,
9 | "@aws-cdk/aws-secretsmanager:parseOwnedSecretName": true,
10 | "@aws-cdk/aws-kms:defaultKeyPolicies": true,
11 | "@aws-cdk/aws-s3:grantWriteWithoutAcl": true,
12 | "@aws-cdk/aws-ecs-patterns:removeDefaultDesiredCount": true,
13 | "@aws-cdk/aws-rds:lowercaseDbIdentifier": true,
14 | "@aws-cdk/aws-efs:defaultEncryptionAtRest": true,
15 | "@aws-cdk/aws-lambda:recognizeVersionProps": true
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/lambda-powertools-feature-flags/app-config-store/cdk/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | testEnvironment: 'node',
3 | roots: ['/test'],
4 | testMatch: ['**/*.test.ts'],
5 | transform: {
6 | '^.+\\.tsx?$': 'ts-jest'
7 | }
8 | };
9 |
--------------------------------------------------------------------------------
/lambda-powertools-feature-flags/app-config-store/cdk/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cdk",
3 | "version": "0.1.0",
4 | "bin": {
5 | "cdk": "bin/cdk.js"
6 | },
7 | "scripts": {
8 | "build": "tsc",
9 | "watch": "tsc -w",
10 | "test": "jest",
11 | "cdk": "cdk"
12 | },
13 | "devDependencies": {
14 | "@aws-cdk/assert": "1.203.0",
15 | "@types/jest": "^26.0.10",
16 | "@types/node": "10.17.27",
17 | "aws-cdk": "^1.148.0",
18 | "jest": "^29.7.0",
19 | "ts-jest": "^29.1.5",
20 | "ts-node": "^9.0.0",
21 | "typescript": "~3.9.7"
22 | },
23 | "dependencies": {
24 | "@aws-cdk/aws-appconfig": "^1.117.0",
25 | "@aws-cdk/aws-s3-assets": "^1.117.0",
26 | "@aws-cdk/core": "1.117.0",
27 | "source-map-support": "^0.5.16"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/lambda-powertools-feature-flags/app-config-store/cdk/test/cdk.test.ts:
--------------------------------------------------------------------------------
1 | import { expect as expectCDK, matchTemplate, MatchStyle } from '@aws-cdk/assert';
2 | import * as cdk from '@aws-cdk/core';
3 | import * as Cdk from '../lib/cdk-stack';
4 |
5 | test('Empty Stack', () => {
6 | const app = new cdk.App();
7 | // WHEN
8 | const stack = new Cdk.CdkStack(app, 'MyTestStack');
9 | // THEN
10 | expectCDK(stack).to(matchTemplate({
11 | "Resources": {}
12 | }, MatchStyle.EXACT))
13 | });
14 |
--------------------------------------------------------------------------------
/lambda-powertools-feature-flags/app-config-store/cdk/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2018",
4 | "module": "commonjs",
5 | "lib": [
6 | "es2018"
7 | ],
8 | "declaration": true,
9 | "strict": true,
10 | "noImplicitAny": true,
11 | "strictNullChecks": true,
12 | "noImplicitThis": true,
13 | "alwaysStrict": true,
14 | "noUnusedLocals": false,
15 | "noUnusedParameters": false,
16 | "noImplicitReturns": true,
17 | "noFallthroughCasesInSwitch": false,
18 | "inlineSourceMap": true,
19 | "inlineSources": true,
20 | "experimentalDecorators": true,
21 | "strictPropertyInitialization": false,
22 | "typeRoots": [
23 | "./node_modules/@types"
24 | ]
25 | },
26 | "exclude": [
27 | "node_modules",
28 | "cdk.out"
29 | ]
30 | }
31 |
--------------------------------------------------------------------------------
/lambda-powertools-feature-flags/app-config-store/cfn/feature-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "premium_features": {
3 | "default": false,
4 | "rules": {
5 | "customer tier equals premium": {
6 | "when_match": true,
7 | "conditions": [
8 | {
9 | "action": "EQUALS",
10 | "key": "tier",
11 | "value": "premium"
12 | }
13 | ]
14 | }
15 | }
16 | },
17 | "feature2": {
18 | "default": true
19 | }
20 | }
--------------------------------------------------------------------------------
/lambda-powertools-feature-flags/app-config-store/cfn/template.yaml:
--------------------------------------------------------------------------------
1 | AWSTemplateFormatVersion: "2010-09-09"
2 | Description: A sample template
3 | Resources:
4 | FeatureStoreApp:
5 | Type: AWS::AppConfig::Application
6 | Properties:
7 | Description: "AppConfig Appliction for feature toggles"
8 | Name: my-app
9 |
10 | FeatureStoreDevEnv:
11 | Type: AWS::AppConfig::Environment
12 | Properties:
13 | ApplicationId: !Ref FeatureStoreApp
14 | Description: "Development Environment for the App Config Store"
15 | Name: "development"
16 |
17 | FeatureStoreConfigProfile:
18 | Type: AWS::AppConfig::ConfigurationProfile
19 | Properties:
20 | ApplicationId: !Ref FeatureStoreApp
21 | Name: "MyTestProfile"
22 | LocationUri: "hosted"
23 |
24 | HostedConfigVersion:
25 | Type: AWS::AppConfig::HostedConfigurationVersion
26 | Properties:
27 | ApplicationId: !Ref FeatureStoreApp
28 | ConfigurationProfileId: !Ref FeatureStoreConfigProfile
29 | Description: 'A sample hosted configuration version'
30 | Content: |
31 | {
32 | "premium_features": {
33 | "default": false,
34 | "rules": {
35 | "customer tier equals premium": {
36 | "when_match": true,
37 | "conditions": [
38 | {
39 | "action": "EQUALS",
40 | "key": "tier",
41 | "value": "premium"
42 | }
43 | ]
44 | }
45 | }
46 | },
47 | "feature2": {
48 | "default": true
49 | }
50 | }
51 | ContentType: 'application/json'
52 |
53 | ConfigDeployment:
54 | Type: AWS::AppConfig::Deployment
55 | Properties:
56 | ApplicationId: !Ref FeatureStoreApp
57 | ConfigurationProfileId: !Ref FeatureStoreConfigProfile
58 | ConfigurationVersion: !Ref HostedConfigVersion
59 | DeploymentStrategyId: "AppConfig.AllAtOnce"
60 | EnvironmentId: !Ref FeatureStoreDevEnv
--------------------------------------------------------------------------------
/lambda-powertools-feature-flags/app/.coveragerc:
--------------------------------------------------------------------------------
1 | [run]
2 | branch = True
3 | omit = */build/*,tests/*
4 | [report]
5 | exclude_lines =
6 | pragma: no cover
7 | raise NotImplementedError.*
8 |
--------------------------------------------------------------------------------
/lambda-powertools-feature-flags/app/Pipfile:
--------------------------------------------------------------------------------
1 | [[source]]
2 | url = "https://pypi.python.org/simple"
3 | verify_ssl = true
4 | name = "pypi"
5 |
6 |
7 | [packages]
8 | boto3 = "*"
9 | aws-lambda-powertools = "*"
10 |
11 | [dev-packages]
12 | boto3 = "*"
13 | pytest = "*"
14 | pytest-cov = "*"
15 | requests = "*"
16 |
17 | [requires]
18 | python_version = "3.8"
19 |
--------------------------------------------------------------------------------
/lambda-powertools-feature-flags/app/products/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/talk-dev-to-me-twitch/ed06f3b0695b4e2b9e17cb0dda17b6d88ecbcd46/lambda-powertools-feature-flags/app/products/__init__.py
--------------------------------------------------------------------------------
/lambda-powertools-feature-flags/app/products/requirements.txt:
--------------------------------------------------------------------------------
1 | boto3
2 | aws-lambda-powertools
--------------------------------------------------------------------------------
/lambda-powertools-feature-flags/app/samconfig.toml:
--------------------------------------------------------------------------------
1 | version = 0.1
2 | [default]
3 | [default.deploy]
4 | [default.deploy.parameters]
5 | stack_name = "sam-product-app"
6 | s3_bucket = "aws-sam-cli-managed-default-samclisourcebucket-18j4nbwfp8srq"
7 | s3_prefix = "sam-product-app"
8 | region = "eu-central-1"
9 | capabilities = "CAPABILITY_IAM"
10 |
--------------------------------------------------------------------------------
/lambda-powertools-feature-flags/app/tests/conftest.py:
--------------------------------------------------------------------------------
1 | import json
2 | from uuid import uuid4
3 |
4 | import pytest
5 |
6 |
7 | class MockContext(object):
8 |
9 | def __init__(self, function_name):
10 | self.function_name = function_name
11 | self.function_version = "v$LATEST"
12 | self.memory_limit_in_mb = 512
13 | self.invoked_function_arn = f"arn:aws:lambda:us-east-1:ACCOUNT:function:{self.function_name}"
14 | self.aws_request_id = str(uuid4)
15 |
16 |
17 | @pytest.fixture
18 | def lambda_context():
19 | return MockContext("dummy_function")
20 |
21 |
22 | @pytest.fixture()
23 | def apigw_event():
24 | """ Generates API GW Event"""
25 | with open("./events/hello_world_event.json", "r") as fp:
26 | return json.load(fp)
27 |
--------------------------------------------------------------------------------
/lambda-powertools-feature-flags/app/tests/sytem/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/talk-dev-to-me-twitch/ed06f3b0695b4e2b9e17cb0dda17b6d88ecbcd46/lambda-powertools-feature-flags/app/tests/sytem/__init__.py
--------------------------------------------------------------------------------
/lambda-powertools-feature-flags/app/tests/sytem/test_app.py:
--------------------------------------------------------------------------------
1 | import boto3
2 | import requests
3 |
4 | boto3.setup_default_session(profile_name='amelnyk-Admin')
5 |
6 | REGION = 'eu-central-1'
7 | STACK_NAME = 'sam-product-app'
8 |
9 |
10 | def get_api_gw_url():
11 | cfn = boto3.resource('cloudformation')
12 | stack = cfn.Stack(STACK_NAME)
13 | return next(x["OutputValue"] for x in stack.outputs if x["OutputKey"] == "ProductsApigwURL")
14 |
15 |
16 | def test_call_api_returns_products():
17 | api_url = get_api_gw_url()
18 | products = requests.get(f"{api_url}/products").json()
19 | assert len(products) == 2
20 |
21 |
22 | def test_call_api_return_product_when_given_id():
23 | api_url = get_api_gw_url()
24 | product = requests.get(f"{api_url}/products/1").json()
25 | assert product == [{"productId": "1", "name": "Mechanical Keyboard 9000", "price": 3549, "discount": "10%"}]
26 |
27 |
28 | def test_call_api_return_empty_when_no_id_given():
29 | api_url = get_api_gw_url()
30 | resp = requests.get(f"{api_url}/products/3").json()
31 | assert resp == []
32 |
33 |
--------------------------------------------------------------------------------
/lambda-powertools-feature-flags/app/tests/unit/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/talk-dev-to-me-twitch/ed06f3b0695b4e2b9e17cb0dda17b6d88ecbcd46/lambda-powertools-feature-flags/app/tests/unit/__init__.py
--------------------------------------------------------------------------------
/lambda-powertools-feature-flags/docs/feature_toggles.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/talk-dev-to-me-twitch/ed06f3b0695b4e2b9e17cb0dda17b6d88ecbcd46/lambda-powertools-feature-flags/docs/feature_toggles.png
--------------------------------------------------------------------------------
/latest_episode.json:
--------------------------------------------------------------------------------
1 | {
2 | "latest_episode": "https://www.twitch.tv/videos/1519229030"
3 | }
4 |
--------------------------------------------------------------------------------
/micro-frontends-module-federation/.gitignore:
--------------------------------------------------------------------------------
1 | accountdetails/dist/
2 | accountdetails/node_modules
3 |
4 | appshell/dist/
5 | appshell/node_modules
6 |
7 | catalogue/dist/
8 | catalogue/node_modules
9 |
10 | myaccount/dist/
11 | myaccount/node_modules
12 |
13 | paymentdetails/dist/
14 | paymentdetails/node_modules
15 |
16 | signin/dist/
17 | signin/node_modules
--------------------------------------------------------------------------------
/micro-frontends-module-federation/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of
4 | this software and associated documentation files (the "Software"), to deal in
5 | the Software without restriction, including without limitation the rights to
6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
7 | the Software, and to permit persons to whom the Software is furnished to do so.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
10 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
11 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
12 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
13 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
14 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
15 |
16 |
--------------------------------------------------------------------------------
/micro-frontends-module-federation/README.md:
--------------------------------------------------------------------------------
1 | # Micro-Frontends with Module Federation
2 |
3 | ## Micro-Frontends definition
4 |
5 | Micro-Frontends are the **technical representation of a business subdomain**, they allow **independent implementations** with the **same or different technology**.
6 |
7 | Finally, they should **minimize the code shared** with other subdomains and they are **own by a single team**
8 |
9 | ### Module Federation
10 |
11 | [Module federation](https://webpack.js.org/concepts/module-federation/) is a webpack plugin. It allows a JavaScript application to dynamically run code from another bundle/build, on both client and server.
12 |
13 | 
14 |
15 | ### UI example
16 | In this example we are going to create:
17 |
18 | - an application shell for hosting all the micro-frontends
19 | - a sign in micro-frontend
20 | - a catalogue micro-frontend with multiple views
21 | - a my account micro-frontend that is loading 2 micro-frontends: account details and payments details
22 |
23 | 
--------------------------------------------------------------------------------
/micro-frontends-module-federation/accountdetails/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "catalogue",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "webpack-cli serve",
8 | "build": "webpack --mode production",
9 | "serve": "serve dist -p 3005",
10 | "clean": "rm -rf dist"
11 | },
12 | "keywords": [],
13 | "author": "",
14 | "license": "ISC",
15 | "dependencies": {
16 | "react": "^18.0.2",
17 | "react-dom": "^18.0.2"
18 | },
19 | "devDependencies": {
20 | "@babel/core": "^7.13.15",
21 | "@babel/preset-react": "^7.13.13",
22 | "babel-loader": "^9.1.0",
23 | "html-webpack-plugin": "^5.3.1",
24 | "serve": "^14.2.3",
25 | "webpack": "^5.33.2",
26 | "webpack-cli": "^5.1.4",
27 | "webpack-dev-server": "^5.1.0"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/micro-frontends-module-federation/accountdetails/src/AccountDetails.js:
--------------------------------------------------------------------------------
1 | import React, {useState} from "react";
2 |
3 | const AccountDetails = (props) => {
4 |
5 | const [lastPaymentDate, setPaymentChanged] = useState("Jan 2021")
6 |
7 | props.emitter.on("paymentChanged", date => setPaymentChanged(date))
8 |
9 | return (
10 |
11 |
Account Details
12 |
13 | - name: Luca
14 | - surname: Mezzalira
15 | - email: guesswho@lm.com
16 | - member since: Jan 2021
17 | - last payment changed: {lastPaymentDate}
18 | - Change account details
19 |
20 |
21 |
22 | );
23 | }
24 |
25 | export default AccountDetails;
26 |
--------------------------------------------------------------------------------
/micro-frontends-module-federation/accountdetails/webpack.config.js:
--------------------------------------------------------------------------------
1 | const { ModuleFederationPlugin } = require("webpack").container;
2 | const path = require("path");
3 |
4 | module.exports = {
5 | entry: "./src/AccountDetails",
6 | mode: "development",
7 | devServer: {
8 | static: {
9 | directory: path.join(__dirname, "dist"),
10 | },
11 | port: 3005,
12 | },
13 | output: {
14 | publicPath: "auto",
15 | },
16 | module: {
17 | rules: [
18 | {
19 | test: /\.jsx?$/,
20 | loader: "babel-loader",
21 | exclude: /node_modules/,
22 | options: {
23 | presets: ["@babel/preset-react"],
24 | },
25 | },
26 | ],
27 | },
28 | plugins: [
29 | new ModuleFederationPlugin({
30 | name: "AccountDetails",
31 | filename: "remoteEntry.js",
32 | exposes:{
33 | "./AccountDetails": "./src/AccountDetails"
34 | },
35 | shared: {
36 | "@material-ui/core": {
37 | singleton: true,
38 | },
39 | "@material-ui/styles": {
40 | singleton: true
41 | },
42 | "react-dom": {
43 | singleton: true,
44 | },
45 | react: {
46 | singleton: true,
47 | },
48 | },
49 | })
50 | ],
51 | };
--------------------------------------------------------------------------------
/micro-frontends-module-federation/appshell/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "appshell",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "webpack-cli serve",
8 | "build": "webpack --mode production",
9 | "serve": "serve dist -p 3001",
10 | "clean": "rm -rf dist"
11 | },
12 | "keywords": [],
13 | "author": "",
14 | "license": "ISC",
15 | "dependencies": {
16 | "@material-ui/core": "^4.12.4",
17 | "@material-ui/icons": "^4.11.3",
18 | "clsx": "^2.1.1",
19 | "react": "^18.3.1",
20 | "react-dom": "^18.3.1",
21 | "react-router-dom": "^6.28.0"
22 | },
23 | "devDependencies": {
24 | "@babel/core": "^7.26.9",
25 | "@babel/preset-react": "^7.26.3",
26 | "atriom-plugin": "^1.1.2",
27 | "babel-loader": "^9.2.1",
28 | "html-webpack-plugin": "^5.6.3",
29 | "serve": "^13.0.4",
30 | "webpack": "^5.98.0",
31 | "webpack-bundle-analyzer": "^4.10.2",
32 | "webpack-cli": "^5.1.4",
33 | "webpack-dev-server": "^5.2.1"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/micro-frontends-module-federation/appshell/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/micro-frontends-module-federation/appshell/src/App.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {BrowserRouter as Router} from "react-router-dom";
3 | import Main from "./Main";
4 |
5 | const App = () => {
6 | return(
7 |
8 |
9 |
10 | )
11 | }
12 |
13 | export default App;
14 |
--------------------------------------------------------------------------------
/micro-frontends-module-federation/appshell/src/bootstrap.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import App from "./App";
4 | ReactDOM.render(, document.getElementById("root"));
--------------------------------------------------------------------------------
/micro-frontends-module-federation/appshell/src/index.js:
--------------------------------------------------------------------------------
1 | import("./bootstrap");
--------------------------------------------------------------------------------
/micro-frontends-module-federation/catalogue/imgs/black1.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/talk-dev-to-me-twitch/ed06f3b0695b4e2b9e17cb0dda17b6d88ecbcd46/micro-frontends-module-federation/catalogue/imgs/black1.jpeg
--------------------------------------------------------------------------------
/micro-frontends-module-federation/catalogue/imgs/black2.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/talk-dev-to-me-twitch/ed06f3b0695b4e2b9e17cb0dda17b6d88ecbcd46/micro-frontends-module-federation/catalogue/imgs/black2.jpeg
--------------------------------------------------------------------------------
/micro-frontends-module-federation/catalogue/imgs/white.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/talk-dev-to-me-twitch/ed06f3b0695b4e2b9e17cb0dda17b6d88ecbcd46/micro-frontends-module-federation/catalogue/imgs/white.jpeg
--------------------------------------------------------------------------------
/micro-frontends-module-federation/catalogue/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "catalogue",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "webpack-cli serve",
8 | "build": "webpack --mode production",
9 | "serve": "serve dist -p 3002",
10 | "clean": "rm -rf dist"
11 | },
12 | "keywords": [],
13 | "author": "",
14 | "license": "ISC",
15 | "dependencies": {
16 | "@material-ui/core": "^4.11.3",
17 | "@material-ui/icons": "^4.11.2",
18 | "react": "^18.2.0",
19 | "react-dom": "^18.2.0",
20 | "react-router-dom": "^6.3.0"
21 | },
22 | "devDependencies": {
23 | "@babel/core": "^7.13.15",
24 | "@babel/preset-react": "^7.13.13",
25 | "babel-loader": "^9.1.0",
26 | "copy-webpack-plugin": "^12.0.2",
27 | "html-webpack-plugin": "^5.3.1",
28 | "serve": "^14.2.3",
29 | "webpack": "^5.33.2",
30 | "webpack-cli": "^5.1.4",
31 | "webpack-dev-server": "^5.0.4"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/micro-frontends-module-federation/catalogue/src/Catalogue.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {Switch, Route, useRouteMatch} from "react-router-dom";
3 | import Home from "./Home";
4 | import Details from "./Details";
5 |
6 |
7 | const Catalogue = () => {
8 | let { path } = useRouteMatch();
9 |
10 | return(
11 |
12 |
13 | Shop
14 |
15 |
16 |
17 |
18 |
19 |
20 | )
21 | }
22 |
23 | export default Catalogue;
--------------------------------------------------------------------------------
/micro-frontends-module-federation/catalogue/src/Details.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {Link, useParams} from "react-router-dom";
3 |
4 | const Details = () => {
5 |
6 | const {productId} = useParams()
7 |
8 | return(
9 |
10 | {`Details page, product id: ${productId}`}
11 |
12 | All products
13 |
14 | )
15 | }
16 |
17 | export default Details;
--------------------------------------------------------------------------------
/micro-frontends-module-federation/catalogue/src/Home.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {useRouteMatch} from "react-router-dom";
3 | import { StylesProvider, createGenerateClassName } from '@material-ui/core/styles';
4 | import Product from "./Product";
5 |
6 | const catalogueData = [
7 | {
8 | id: 123,
9 | productName: "Black no.1 T-Shirt",
10 | image: "http://localhost:3002/imgs/black1.jpeg",
11 | cost: "$3.99"
12 | },
13 | {
14 | id: 234,
15 | productName: "Black no.2 T-Shirt",
16 | image: "http://localhost:3002/imgs/black2.jpeg",
17 | cost: "$4.99"
18 | },
19 | {
20 | id: 456,
21 | productName: "White T-Shirt",
22 | image: "http://localhost:3002/imgs/white.jpeg",
23 | cost: "$5.99"
24 | }
25 | ]
26 |
27 | const generateClassName = createGenerateClassName({
28 | seed:'catalogue'
29 | });
30 |
31 | const Home = () => {
32 | let { path } = useRouteMatch();
33 |
34 | return(
35 |
36 |
37 | {
38 | catalogueData.map(item => {
39 | return (
40 |
41 | )
42 | })
43 | }
44 |
45 |
46 | )
47 | }
48 |
49 | export default Home;
--------------------------------------------------------------------------------
/micro-frontends-module-federation/catalogue/src/Product.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useHistory } from "react-router-dom";
3 | import Paper from '@material-ui/core/Paper';
4 | import IconButton from '@material-ui/core/IconButton';
5 | import InfoIcon from '@material-ui/icons/Info';
6 | import { makeStyles } from '@material-ui/core/styles';
7 |
8 | const useStyles = makeStyles((theme) => ({
9 | paper: {
10 | margin: 10,
11 | float: "left",
12 | padding: 8,
13 | textAlign: "center",
14 | width: 330,
15 | height: 350
16 | }
17 | }));
18 |
19 | const Product = (props) => {
20 | const classes = useStyles();
21 | let history = useHistory();
22 |
23 | const productDetails = () => {
24 | history.push(props.url);
25 | }
26 |
27 | return(
28 |
29 |
30 |
31 |
32 | {props.data.productName}
33 |
34 | {props.data.cost}
35 |
36 |
37 |
38 |
39 |
40 | )
41 | }
42 |
43 | export default Product;
--------------------------------------------------------------------------------
/micro-frontends-module-federation/catalogue/webpack.config.js:
--------------------------------------------------------------------------------
1 | const { ModuleFederationPlugin } = require("webpack").container;
2 | const CopyPlugin = require("copy-webpack-plugin");
3 | const path = require("path");
4 |
5 | module.exports = {
6 | entry: "./src/Catalogue",
7 | mode: "development",
8 | devServer: {
9 | static: {
10 | directory: path.join(__dirname, "dist"),
11 | },
12 | port: 3002,
13 | },
14 | output: {
15 | publicPath: "auto",
16 | },
17 | module: {
18 | rules: [
19 | {
20 | test: /\.jsx?$/,
21 | loader: "babel-loader",
22 | exclude: /node_modules/,
23 | options: {
24 | presets: ["@babel/preset-react"],
25 | },
26 | },
27 | ],
28 | },
29 | plugins: [
30 | new CopyPlugin({
31 | patterns: [
32 | { from: "imgs", to: "imgs" }
33 | ],
34 | }),
35 | new ModuleFederationPlugin({
36 | name: "Catalogue",
37 | filename: "remoteEntry.js",
38 | exposes:{
39 | "./Catalogue": "./src/Catalogue"
40 | },
41 | shared: {
42 | "@material-ui/core": {
43 | singleton: true,
44 | },
45 | "@material-ui/styles": {
46 | singleton: true
47 | },
48 | "react-router-dom": {
49 | singleton: true,
50 | },
51 | "react-dom": {
52 | singleton: true,
53 | },
54 | react: {
55 | singleton: true,
56 | },
57 | },
58 | })
59 | ],
60 | };
--------------------------------------------------------------------------------
/micro-frontends-module-federation/modFedDiagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/talk-dev-to-me-twitch/ed06f3b0695b4e2b9e17cb0dda17b6d88ecbcd46/micro-frontends-module-federation/modFedDiagram.png
--------------------------------------------------------------------------------
/micro-frontends-module-federation/modFedExample.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/talk-dev-to-me-twitch/ed06f3b0695b4e2b9e17cb0dda17b6d88ecbcd46/micro-frontends-module-federation/modFedExample.png
--------------------------------------------------------------------------------
/micro-frontends-module-federation/myaccount/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "myaccount",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "webpack-cli serve",
8 | "build": "webpack --mode production",
9 | "serve": "serve dist -p 3004",
10 | "clean": "rm -rf dist"
11 | },
12 | "keywords": [],
13 | "author": "",
14 | "license": "ISC",
15 | "dependencies": {
16 | "nanoevents": "^7.0.1",
17 | "react": "^18.3.1",
18 | "react-dom": "^18.3.1",
19 | "react-router-dom": "^6.28.0"
20 | },
21 | "devDependencies": {
22 | "@babel/core": "^7.26.9",
23 | "@babel/preset-react": "^7.26.3",
24 | "babel-loader": "^9.2.1",
25 | "html-webpack-plugin": "^5.6.3",
26 | "serve": "^14.2.4",
27 | "webpack": "^5.98.0",
28 | "webpack-cli": "^5.1.4",
29 | "webpack-dev-server": "^5.2.2"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/micro-frontends-module-federation/myaccount/src/MyAccount.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {Link} from "react-router-dom";
3 | import {createNanoEvents} from "nanoevents";
4 | const AccountDetails = React.lazy(() => import("AccountDetails/AccountDetails"));
5 | const PaymentDetails = React.lazy(() => import("PaymentDetails/PaymentDetails"));
6 |
7 | const AuthenticatedView = (props) => {
8 |
9 | return(
10 |
11 |
12 |
13 |
14 |
15 | )
16 | }
17 |
18 | const MyAccount = () => {
19 | const token = window.sessionStorage.getItem("token");
20 | let view;
21 | if(token){
22 | const emitter = createNanoEvents();
23 | view =
24 | } else {
25 | view = Please sign in before accessing this section
26 | }
27 |
28 | return(
29 |
30 |
31 | My Account
32 |
33 |
34 | {view}
35 |
36 |
37 | )
38 | }
39 |
40 | export default MyAccount;
--------------------------------------------------------------------------------
/micro-frontends-module-federation/myaccount/webpack.config.js:
--------------------------------------------------------------------------------
1 | const { ModuleFederationPlugin } = require("webpack").container;
2 | const path = require("path");
3 |
4 | module.exports = {
5 | entry: "./src/MyAccount",
6 | mode: "development",
7 | devServer: {
8 | static: {
9 | directory: path.join(__dirname, "dist"),
10 | },
11 | port: 3004,
12 | },
13 | output: {
14 | publicPath: "auto",
15 | },
16 | module: {
17 | rules: [
18 | {
19 | test: /\.jsx?$/,
20 | loader: "babel-loader",
21 | exclude: /node_modules/,
22 | options: {
23 | presets: ["@babel/preset-react"],
24 | },
25 | },
26 | ],
27 | },
28 | plugins: [
29 | new ModuleFederationPlugin({
30 | name: "MyAccount",
31 | filename: "remoteEntry.js",
32 | exposes:{
33 | "./MyAccount": "./src/MyAccount"
34 | },
35 | remotes: {
36 | AccountDetails: "AccountDetails@http://localhost:3005/remoteEntry.js",
37 | PaymentDetails: "PaymentDetails@http://localhost:3006/remoteEntry.js"
38 | },
39 | shared: {
40 | "@material-ui/core": {
41 | singleton: true,
42 | },
43 | "@material-ui/styles": {
44 | singleton: true
45 | },
46 | "react-router-dom": {
47 | singleton: true,
48 | },
49 | "react-dom": {
50 | singleton: true,
51 | },
52 | react: {
53 | singleton: true,
54 | }
55 | }
56 | })
57 | ],
58 | };
--------------------------------------------------------------------------------
/micro-frontends-module-federation/paymentdetails/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "subscriptiondetails",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "webpack-cli serve",
8 | "build": "webpack --mode production",
9 | "serve": "serve dist -p 3006",
10 | "clean": "rm -rf dist"
11 | },
12 | "keywords": [],
13 | "author": "",
14 | "license": "ISC",
15 | "dependencies": {
16 | "react": "^18.3.0",
17 | "react-dom": "^18.3.1"
18 | },
19 | "devDependencies": {
20 | "@babel/core": "^7.26.10",
21 | "@babel/preset-react": "^7.26.3",
22 | "babel-loader": "^9.2.1",
23 | "html-webpack-plugin": "^5.6.3",
24 | "serve": "^13.0.4",
25 | "webpack": "^5.98.0",
26 | "webpack-cli": "^5.1.4",
27 | "webpack-dev-server": "^5.2.1"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/micro-frontends-module-federation/paymentdetails/src/PaymentDetails.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const PaymentDetails = (props) => {
4 |
5 | const onPaymentChanged = () => {
6 | props.emitter.emit("paymentChanged", "May 2021");
7 | }
8 |
9 | return (
10 |
11 |
Payment Details
12 |
17 |
18 | );
19 | }
20 |
21 | export default PaymentDetails;
22 |
--------------------------------------------------------------------------------
/micro-frontends-module-federation/paymentdetails/webpack.config.js:
--------------------------------------------------------------------------------
1 | const { ModuleFederationPlugin } = require("webpack").container;
2 | const path = require("path");
3 |
4 | module.exports = {
5 | entry: "./src/PaymentDetails",
6 | mode: "development",
7 | devServer: {
8 | static: {
9 | directory: path.join(__dirname, "dist"),
10 | },
11 | port: 3006,
12 | },
13 | output: {
14 | publicPath: "auto",
15 | },
16 | module: {
17 | rules: [
18 | {
19 | test: /\.jsx?$/,
20 | loader: "babel-loader",
21 | exclude: /node_modules/,
22 | options: {
23 | presets: ["@babel/preset-react"],
24 | },
25 | },
26 | ],
27 | },
28 | plugins: [
29 | new ModuleFederationPlugin({
30 | name: "PaymentDetails",
31 | filename: "remoteEntry.js",
32 | exposes:{
33 | "./PaymentDetails": "./src/PaymentDetails"
34 | },
35 | shared: {
36 | "@material-ui/core": {
37 | singleton: true,
38 | },
39 | "@material-ui/styles": {
40 | singleton: true
41 | },
42 | "react-dom": {
43 | singleton: true,
44 | },
45 | react: {
46 | singleton: true,
47 | },
48 | },
49 | })
50 | ],
51 | };
--------------------------------------------------------------------------------
/micro-frontends-module-federation/signin/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "signin",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "webpack-cli serve",
8 | "build": "webpack --mode production",
9 | "serve": "serve dist -p 3003",
10 | "clean": "rm -rf dist"
11 | },
12 | "keywords": [],
13 | "author": "",
14 | "license": "ISC",
15 | "dependencies": {
16 | "@material-ui/core": "^4.11.3",
17 | "react": "^18.1.0",
18 | "react-dom": "^18.1.0",
19 | "react-router-dom": "^6.3.0"
20 | },
21 | "devDependencies": {
22 | "@babel/core": "^7.13.15",
23 | "@babel/preset-react": "^7.13.13",
24 | "babel-loader": "^9.1.0",
25 | "html-webpack-plugin": "^5.3.1",
26 | "serve": "^14.2.3",
27 | "webpack": "^5.33.2",
28 | "webpack-cli": "^5.1.4",
29 | "webpack-dev-server": "^5.0.4"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/micro-frontends-module-federation/signin/src/SignIn.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useHistory } from "react-router-dom";
3 | import Typography from '@material-ui/core/Typography';
4 | import TextField from '@material-ui/core/TextField';
5 | import Button from '@material-ui/core/Button';
6 | import { StylesProvider, createGenerateClassName } from '@material-ui/core/styles';
7 |
8 | const faketoken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
9 |
10 | const generateClassName = createGenerateClassName({
11 | seed:'signin'
12 | });
13 |
14 | const SignIn = () => {
15 | let history = useHistory();
16 |
17 | const onSignIn = () => {
18 | window.sessionStorage.setItem("token", faketoken);
19 | history.push("/shop");
20 | }
21 |
22 | return (
23 |
24 |
42 |
43 | );
44 | }
45 |
46 | export default SignIn;
47 |
--------------------------------------------------------------------------------
/micro-frontends-module-federation/signin/webpack.config.js:
--------------------------------------------------------------------------------
1 | const { ModuleFederationPlugin } = require("webpack").container;
2 | const path = require("path");
3 |
4 | module.exports = {
5 | entry: "./src/SignIn",
6 | mode: "development",
7 | devServer: {
8 | static: {
9 | directory: path.join(__dirname, "dist"),
10 | },
11 | port: 3003,
12 | },
13 | output: {
14 | publicPath: "auto",
15 | },
16 | module: {
17 | rules: [
18 | {
19 | test: /\.jsx?$/,
20 | loader: "babel-loader",
21 | exclude: /node_modules/,
22 | options: {
23 | presets: ["@babel/preset-react"],
24 | },
25 | },
26 | ],
27 | },
28 | plugins: [
29 | new ModuleFederationPlugin({
30 | name: "SignIn",
31 | filename: "remoteEntry.js",
32 | exposes:{
33 | "./SignIn": "./src/SignIn"
34 | },
35 | shared: {
36 | "@material-ui/core": {
37 | singleton: true,
38 | },
39 | "@material-ui/styles": {
40 | singleton: true
41 | },
42 | "react-router-dom": {
43 | singleton: true,
44 | },
45 | "react-dom": {
46 | singleton: true,
47 | },
48 | react: {
49 | singleton: true,
50 | },
51 | },
52 | })
53 | ],
54 | };
--------------------------------------------------------------------------------
/serverless-lambda-java/README.md:
--------------------------------------------------------------------------------
1 | # Serverless AWS Lambda with Java
2 |
3 | In this session Melina Schweizer, Senior Specialist Solutions Architect at AWS, will walk us through Java development on AWS Lambda.
4 |
5 | ## AWS Lambda and Java best pratices
6 |
7 | - reduce deployment and coldstarts by avoidigin heavy frameworks like spring an hibernate
8 | - reuse resources during multiple invocations by moving the initiaisation outside of the main handler
9 | - make your code more testable by moving the business logic outside of the handler
10 |
11 | ## Resources and Links
12 |
13 | - [https://medium.com/i-love-my-local-farmer-engineering-blog](https://medium.com/i-love-my-local-farmer-engineering-blog)
14 | - [https://github.com/aws-samples/i-love-my-local-farmer](https://github.com/aws-samples/i-love-my-local-farmer)
15 | - [https://medium.com/i-love-my-local-farmer-engineering-blog/how-to-use-java-in-your-db-connected-aws-lambdas-211c1f9c53aa](https://medium.com/i-love-my-local-farmer-engineering-blog/how-to-use-java-in-your-db-connected-aws-lambdas-211c1f9c53aa)
16 | - [https://docs.aws.amazon.com/lambda/latest/dg/lambda-java.html](https://docs.aws.amazon.com/lambda/latest/dg/lambda-java.html)
17 | - [https://github.com/awslabs/aws-lambda-powertools-java](https://github.com/awslabs/aws-lambda-powertools-java)
18 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/README.md:
--------------------------------------------------------------------------------
1 | # SPA blue/green deployment on AWS
2 |
3 | This repository has a demo on how to deploy a SPA (Angular) using CodePipeline, CodeBuild and
4 | a custom Step Functions that does Blue/Green Deployment.
5 |
6 | The IaC was written using [AWS CDK for Go](https://aws.amazon.com/blogs/developer/getting-started-with-the-aws-cloud-development-kit-and-go/).
7 |
8 | **NOTICE**: Go support is still in Developer Preview. This implies that APIs may
9 | change while we address early feedback from the community. We would love to hear
10 | about your experience through GitHub issues.
11 |
12 | ## Structure
13 |
14 | - `app`: the sample Angular application
15 | - `cdk`: the IaC project written in Go
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/.browserslistrc:
--------------------------------------------------------------------------------
1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
2 | # For additional information regarding the format and rule options, please see:
3 | # https://github.com/browserslist/browserslist#queries
4 |
5 | # You can see what browsers were selected by your queries by running:
6 | # npx browserslist
7 |
8 | # Googlebot uses an older version of Chrome
9 | # For additional information see: https://developers.google.com/search/docs/guides/rendering
10 |
11 | > 0.5%
12 | last 2 versions
13 | Firefox ESR
14 | not dead
15 | not IE 9-11 # For IE 9-11 support, remove 'not'.
16 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.md]
12 | max_line_length = off
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "ignorePatterns": [
4 | "projects/**/*"
5 | ],
6 | "overrides": [
7 | {
8 | "files": [
9 | "*.ts"
10 | ],
11 | "parserOptions": {
12 | "project": [
13 | "tsconfig.json",
14 | "e2e/tsconfig.json"
15 | ],
16 | "createDefaultProgram": true
17 | },
18 | "extends": [
19 | "plugin:@angular-eslint/recommended",
20 | "plugin:@angular-eslint/template/process-inline-templates"
21 | ],
22 | "rules": {
23 | "@angular-eslint/component-selector": [
24 | "error",
25 | {
26 | "prefix": "app",
27 | "style": "kebab-case",
28 | "type": "element"
29 | }
30 | ],
31 | "@angular-eslint/directive-selector": [
32 | "error",
33 | {
34 | "prefix": "app",
35 | "style": "camelCase",
36 | "type": "attribute"
37 | }
38 | ]
39 | }
40 | },
41 | {
42 | "files": [
43 | "*.html"
44 | ],
45 | "extends": [
46 | "plugin:@angular-eslint/template/recommended"
47 | ],
48 | "rules": {}
49 | }
50 | ]
51 | }
52 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /dist
5 | /tmp
6 | /out-tsc
7 |
8 | # dependencies
9 | /node_modules
10 |
11 | # IDEs and editors
12 | .vscode/
13 | /.idea
14 | .project
15 | .classpath
16 | .c9/
17 | *.launch
18 | .settings/
19 | *.sublime-workspace
20 |
21 | # IDE - VSCode
22 | .vscode/*
23 | !.vscode/settings.json
24 | !.vscode/tasks.json
25 | !.vscode/launch.json
26 | !.vscode/extensions.json
27 |
28 | # misc
29 | /.angular/cache
30 | /.sass-cache
31 | /connect.lock
32 | /coverage
33 | /libpeerconnection.log
34 | npm-debug.log
35 | testem.log
36 | /typings
37 | yarn-error.log
38 |
39 | # e2e
40 | /e2e/*.js
41 | /e2e/*.map
42 |
43 | # System Files
44 | .DS_Store
45 | Thumbs.db
46 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "12.3.1"
4 |
5 | branches:
6 | only:
7 | - master
8 |
9 | before_script:
10 | - yarn install --frozen-lockfile
11 |
12 | env:
13 | - NG_CLI_ANALYTICS=ci
14 |
15 | script:
16 | - yarn build
17 |
18 | deploy:
19 | provider: pages
20 | skip-cleanup: true
21 | github-token: $GITHUB_TOKEN # Set in travis-ci.org dashboard, marked secure
22 | keep-history: true
23 | on:
24 | branch: master
25 | local_dir: dist
26 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/CNAME:
--------------------------------------------------------------------------------
1 | angular.realworld.io
2 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/LICENSE.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) [2022] [Khaled Osman]
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.
22 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/e2e/app.e2e-spec.ts:
--------------------------------------------------------------------------------
1 | import { Ng2RealApp } from './app.po';
2 |
3 | describe('ng-demo App', () => {
4 | let page: Ng2RealApp;
5 |
6 | beforeEach(() => {
7 | page = new Ng2RealApp();
8 | });
9 |
10 | it('should display message saying app works', () => {
11 | page.navigateTo();
12 | expect(page.getParagraphText()).toContain('conduit');
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/e2e/app.po.ts:
--------------------------------------------------------------------------------
1 | import { browser, element, by } from 'protractor';
2 |
3 | export class Ng2RealApp {
4 | navigateTo() {
5 | return browser.get('/');
6 | }
7 |
8 | getParagraphText() {
9 | return element(by.css('.logo-font')).getText();
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/e2e/tsconfig.e2e.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../out-tsc/e2e",
5 | "baseUrl": "./",
6 | "module": "commonjs",
7 | "target": "es5",
8 | "types": [
9 | "jasmine",
10 | "jasminewd2",
11 | "node"
12 | ]
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration file, see link for more information
2 | // https://karma-runner.github.io/1.0/config/configuration-file.html
3 |
4 | module.exports = function(config) {
5 | config.set({
6 | basePath: '',
7 | frameworks: ['jasmine', '@angular-devkit/build-angular'],
8 | plugins: [
9 | require('karma-jasmine'),
10 | require('karma-chrome-launcher'),
11 | require('karma-jasmine-html-reporter'),
12 | require('karma-coverage-istanbul-reporter'),
13 | require('@angular-devkit/build-angular/plugins/karma')
14 | ],
15 | client: {
16 | clearContext: false // leave Jasmine Spec Runner output visible in browser
17 | },
18 | coverageIstanbulReporter: {
19 | dir: require('path').join(__dirname, 'coverage'), reports: ['html', 'lcovonly'],
20 | fixWebpackSourcePaths: true
21 | },
22 |
23 | reporters:
24 | config.angularCli && config.angularCli.codeCoverage
25 | ? ['progress', 'coverage-istanbul']
26 | : ['progress', 'kjhtml'],
27 | port: 9876,
28 | colors: true,
29 | logLevel: config.LOG_INFO,
30 | autoWatch: true,
31 | browsers: ['Chrome'],
32 | singleRun: false
33 | });
34 | };
35 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/talk-dev-to-me-twitch/ed06f3b0695b4e2b9e17cb0dda17b6d88ecbcd46/spa-blue-green-deployments/app/logo.png
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/ngsw-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@angular/service-worker/config/schema.json",
3 | "index": "/index.html",
4 | "assetGroups": [
5 | {
6 | "name": "app",
7 | "installMode": "prefetch",
8 | "resources": {
9 | "files": [
10 | "/favicon.ico",
11 | "/index.html",
12 | "/*.css",
13 | "/*.js",
14 | "/manifest.webmanifest"
15 | ]
16 | }
17 | }, {
18 | "name": "assets",
19 | "installMode": "lazy",
20 | "updateMode": "prefetch",
21 | "resources": {
22 | "files": [
23 | "/assets/**",
24 | "/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)"
25 | ]
26 | }
27 | }
28 | ]
29 | }
30 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/protractor.conf.js:
--------------------------------------------------------------------------------
1 | // Protractor configuration file, see link for more information
2 | // https://github.com/angular/protractor/blob/master/lib/config.ts
3 |
4 | const { SpecReporter } = require('jasmine-spec-reporter');
5 |
6 | exports.config = {
7 | allScriptsTimeout: 11000,
8 | specs: [
9 | './e2e/**/*.e2e-spec.ts'
10 | ],
11 | capabilities: {
12 | 'browserName': 'chrome'
13 | },
14 | directConnect: true,
15 | baseUrl: 'http://localhost:4200/',
16 | framework: 'jasmine',
17 | jasmineNodeOpts: {
18 | showColors: true,
19 | defaultTimeoutInterval: 30000,
20 | print: function() {}
21 | },
22 | onPrepare() {
23 | require('ts-node').register({
24 | project: 'e2e/tsconfig.e2e.json'
25 | });
26 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
27 | }
28 | };
29 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/app-routing.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { Routes, RouterModule } from '@angular/router';
3 | import { QuicklinkModule, QuicklinkStrategy } from 'ngx-quicklink';
4 |
5 | const routes: Routes = [
6 | {
7 | path: 'settings',
8 | loadChildren: () => import('./settings/settings.module').then(m => m.SettingsModule)
9 | },
10 | {
11 | path: 'profile',
12 | loadChildren: () => import('./profile/profile.module').then(m => m.ProfileModule)
13 | },
14 | {
15 | path: 'editor',
16 | loadChildren: () => import('./editor/editor.module').then(m => m.EditorModule)
17 | },
18 | {
19 | path: 'article',
20 | loadChildren: () => import('./article/article.module').then(m => m.ArticleModule)
21 | }
22 | ];
23 |
24 | @NgModule({
25 | imports: [
26 | QuicklinkModule,
27 | RouterModule.forRoot(routes, {
28 | // preload all modules; optionally we could
29 | // implement a custom preloading strategy for just some
30 | // of the modules (PRs welcome 😉)
31 | preloadingStrategy: QuicklinkStrategy,
32 | relativeLinkResolution: 'legacy'
33 | })],
34 | exports: [RouterModule]
35 | })
36 | export class AppRoutingModule {}
37 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';
2 |
3 | import { UserService } from "./core";
4 |
5 | @Component({
6 | selector: 'app-root',
7 | templateUrl: './app.component.html',
8 | changeDetection: ChangeDetectionStrategy.OnPush
9 | })
10 | export class AppComponent implements OnInit {
11 | constructor(private userService: UserService) {}
12 |
13 | ngOnInit() {
14 | this.userService.populate();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { BrowserModule } from '@angular/platform-browser';
3 |
4 | import { AppComponent } from './app.component';
5 | import { AuthModule } from './auth/auth.module';
6 | import { HomeModule } from './home/home.module';
7 | import {
8 | FooterComponent,
9 | HeaderComponent,
10 | SharedModule
11 | } from './shared';
12 | import { AppRoutingModule } from './app-routing.module';
13 | import { CoreModule } from './core/core.module';
14 | import { ServiceWorkerModule } from '@angular/service-worker';
15 | import { environment } from '../environments/environment';
16 |
17 | @NgModule({
18 | declarations: [AppComponent, FooterComponent, HeaderComponent],
19 | imports: [
20 | BrowserModule,
21 | CoreModule,
22 | SharedModule,
23 | HomeModule,
24 | AuthModule,
25 | AppRoutingModule,
26 | ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production })
27 | ],
28 | providers: [],
29 | bootstrap: [AppComponent]
30 | })
31 | export class AppModule {}
32 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/article/article-comment.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ comment.body }}
5 |
6 |
7 |
22 |
23 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/article/article-comment.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, EventEmitter, Input, Output, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
2 |
3 | import { Comment, User, UserService } from '../core';
4 | import { Subscription } from 'rxjs';
5 |
6 | @Component({
7 | selector: 'app-article-comment',
8 | templateUrl: './article-comment.component.html',
9 | changeDetection: ChangeDetectionStrategy.OnPush
10 | })
11 | export class ArticleCommentComponent implements OnInit, OnDestroy {
12 | constructor(
13 | private userService: UserService,
14 | private cd: ChangeDetectorRef
15 | ) {}
16 |
17 | private subscription: Subscription;
18 |
19 | @Input() comment: Comment;
20 | @Output() deleteComment = new EventEmitter();
21 |
22 | canModify: boolean;
23 |
24 | ngOnInit() {
25 | // Load the current user's data
26 | this.subscription = this.userService.currentUser.subscribe(
27 | (userData: User) => {
28 | this.canModify = (userData.username === this.comment.author.username);
29 | this.cd.markForCheck();
30 | }
31 | );
32 | }
33 |
34 | ngOnDestroy() {
35 | this.subscription.unsubscribe();
36 | }
37 |
38 | deleteClicked() {
39 | this.deleteComment.emit(true);
40 | }
41 |
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/article/article-resolver.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable, } from '@angular/core';
2 | import { ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router';
3 | import { Observable } from 'rxjs';
4 |
5 | import { Article, ArticlesService, UserService } from '../core';
6 | import { catchError } from 'rxjs/operators';
7 |
8 | @Injectable({
9 | providedIn: 'root'
10 | })
11 | export class ArticleResolver implements Resolve {
12 | constructor(
13 | private articlesService: ArticlesService,
14 | private router: Router,
15 | private userService: UserService
16 | ) {}
17 |
18 | resolve(
19 | route: ActivatedRouteSnapshot,
20 | state: RouterStateSnapshot
21 | ): Observable {
22 |
23 | return this.articlesService.get(route.params['slug'])
24 | .pipe(catchError((err) => this.router.navigateByUrl('/')));
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/article/article-routing.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { Routes, RouterModule } from '@angular/router';
3 | import { ArticleComponent } from './article.component';
4 | import { ArticleResolver } from './article-resolver.service';
5 |
6 | const routes: Routes = [
7 | {
8 | path: ':slug',
9 | component: ArticleComponent,
10 | resolve: {
11 | article: ArticleResolver
12 | }
13 | }
14 | ];
15 |
16 | @NgModule({
17 | imports: [RouterModule.forChild(routes)],
18 | exports: [RouterModule]
19 | })
20 | export class ArticleRoutingModule {}
21 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/article/article.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 |
3 | import { ArticleComponent } from './article.component';
4 | import { ArticleCommentComponent } from './article-comment.component';
5 | import { MarkdownPipe } from './markdown.pipe';
6 | import { SharedModule } from '../shared';
7 | import { ArticleRoutingModule } from './article-routing.module';
8 |
9 | @NgModule({
10 | imports: [
11 | SharedModule,
12 | ArticleRoutingModule
13 | ],
14 | declarations: [
15 | ArticleComponent,
16 | ArticleCommentComponent,
17 | MarkdownPipe
18 | ],
19 |
20 | providers: [
21 | ]
22 | })
23 | export class ArticleModule {}
24 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/article/markdown.pipe.ts:
--------------------------------------------------------------------------------
1 | import { Pipe, PipeTransform } from '@angular/core';
2 | import * as marked from 'marked';
3 |
4 | @Pipe({name: 'markdown'})
5 | export class MarkdownPipe implements PipeTransform {
6 | transform(content: string): string {
7 | return marked(content, { sanitize: true });
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/auth/auth-routing.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { Routes, RouterModule } from '@angular/router';
3 | import { AuthComponent } from './auth.component';
4 | import { NoAuthGuard } from './no-auth-guard.service';
5 |
6 | const routes: Routes = [
7 | {
8 | path: 'login',
9 | component: AuthComponent,
10 | canActivate: [NoAuthGuard]
11 | },
12 | {
13 | path: 'register',
14 | component: AuthComponent,
15 | canActivate: [NoAuthGuard]
16 | }
17 | ];
18 |
19 | @NgModule({
20 | imports: [RouterModule.forChild(routes)],
21 | exports: [RouterModule]
22 | })
23 | export class AuthRoutingModule {}
24 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/auth/auth.component.html:
--------------------------------------------------------------------------------
1 |
46 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/auth/auth.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
2 | import { FormBuilder, FormGroup, FormControl, Validators } from '@angular/forms';
3 | import { ActivatedRoute, Router } from '@angular/router';
4 |
5 | import { Errors, UserService } from '../core';
6 |
7 | @Component({
8 | selector: 'app-auth-page',
9 | templateUrl: './auth.component.html',
10 | changeDetection: ChangeDetectionStrategy.OnPush
11 | })
12 | export class AuthComponent implements OnInit {
13 | authType: String = '';
14 | title: String = '';
15 | errors: Errors = {errors: {}};
16 | isSubmitting = false;
17 | authForm: FormGroup;
18 |
19 | constructor(
20 | private route: ActivatedRoute,
21 | private router: Router,
22 | private userService: UserService,
23 | private fb: FormBuilder,
24 | private cd: ChangeDetectorRef
25 | ) {
26 | // use FormBuilder to create a form group
27 | this.authForm = this.fb.group({
28 | 'email': ['', Validators.required],
29 | 'password': ['', Validators.required]
30 | });
31 | }
32 |
33 | ngOnInit() {
34 | this.route.url.subscribe(data => {
35 | // Get the last piece of the URL (it's either 'login' or 'register')
36 | this.authType = data[data.length - 1].path;
37 | // Set a title for the page accordingly
38 | this.title = (this.authType === 'login') ? 'Sign in' : 'Sign up';
39 | // add form control for username if this is the register page
40 | if (this.authType === 'register') {
41 | this.authForm.addControl('username', new FormControl());
42 | }
43 | this.cd.markForCheck();
44 | });
45 | }
46 |
47 | submitForm() {
48 | this.isSubmitting = true;
49 | this.errors = {errors: {}};
50 |
51 | const credentials = this.authForm.value;
52 | this.userService
53 | .attemptAuth(this.authType, credentials)
54 | .subscribe(
55 | data => this.router.navigateByUrl('/'),
56 | err => {
57 | this.errors = err;
58 | this.isSubmitting = false;
59 | this.cd.markForCheck();
60 | }
61 | );
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/auth/auth.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 |
3 | import { AuthComponent } from './auth.component';
4 | import { NoAuthGuard } from './no-auth-guard.service';
5 | import { SharedModule } from '../shared';
6 | import { AuthRoutingModule } from './auth-routing.module';
7 |
8 | @NgModule({
9 | imports: [
10 | SharedModule,
11 | AuthRoutingModule
12 | ],
13 | declarations: [
14 | AuthComponent
15 | ],
16 | providers: [
17 | NoAuthGuard
18 | ]
19 | })
20 | export class AuthModule {}
21 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/auth/no-auth-guard.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
3 | import { Observable } from 'rxjs';
4 |
5 | import { UserService } from '../core';
6 | import { map , take } from 'rxjs/operators';
7 |
8 | @Injectable()
9 | export class NoAuthGuard implements CanActivate {
10 | constructor(
11 | private router: Router,
12 | private userService: UserService
13 | ) {}
14 |
15 | canActivate(
16 | route: ActivatedRouteSnapshot,
17 | state: RouterStateSnapshot
18 | ): Observable {
19 |
20 | return this.userService.isAuthenticated.pipe(take(1), map(isAuth => !isAuth));
21 |
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/core/core.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { CommonModule } from '@angular/common';
3 | import { HTTP_INTERCEPTORS } from '@angular/common/http';
4 | import { HttpTokenInterceptor } from './interceptors/http.token.interceptor';
5 |
6 | @NgModule({
7 | imports: [
8 | CommonModule
9 | ],
10 | providers: [
11 | { provide: HTTP_INTERCEPTORS, useClass: HttpTokenInterceptor, multi: true }
12 | ],
13 | declarations: []
14 | })
15 | export class CoreModule { }
16 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/core/index.ts:
--------------------------------------------------------------------------------
1 | export * from './core.module';
2 | export * from './services';
3 | export * from './models';
4 | export * from './interceptors';
5 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/core/interceptors/http.token.interceptor.ts:
--------------------------------------------------------------------------------
1 | import { Injectable, Injector } from '@angular/core';
2 | import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest } from '@angular/common/http';
3 | import { Observable } from 'rxjs';
4 |
5 | import { JwtService } from '../services';
6 |
7 | @Injectable()
8 | export class HttpTokenInterceptor implements HttpInterceptor {
9 | constructor(private jwtService: JwtService) {}
10 |
11 | intercept(req: HttpRequest, next: HttpHandler): Observable> {
12 | const headersConfig = {
13 | 'Content-Type': 'application/json',
14 | 'Accept': 'application/json'
15 | };
16 |
17 | const token = this.jwtService.getToken();
18 |
19 | if (token) {
20 | headersConfig['Authorization'] = `Token ${token}`;
21 | }
22 |
23 | const request = req.clone({ setHeaders: headersConfig });
24 | return next.handle(request);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/core/interceptors/index.ts:
--------------------------------------------------------------------------------
1 | export * from './http.token.interceptor';
2 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/core/models/article-list-config.model.ts:
--------------------------------------------------------------------------------
1 | export interface ArticleListConfig {
2 | type: string;
3 |
4 | filters: {
5 | tag?: string,
6 | author?: string,
7 | favorited?: string,
8 | limit?: number,
9 | offset?: number
10 | };
11 | }
12 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/core/models/article.model.ts:
--------------------------------------------------------------------------------
1 | import { Profile } from './profile.model';
2 |
3 | export interface Article {
4 | slug: string;
5 | title: string;
6 | description: string;
7 | body: string;
8 | tagList: string[];
9 | createdAt: string;
10 | updatedAt: string;
11 | favorited: boolean;
12 | favoritesCount: number;
13 | author: Profile;
14 | }
15 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/core/models/comment.model.ts:
--------------------------------------------------------------------------------
1 | import { Profile } from './profile.model';
2 |
3 | export interface Comment {
4 | id: number;
5 | body: string;
6 | createdAt: string;
7 | author: Profile;
8 | }
9 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/core/models/errors.model.ts:
--------------------------------------------------------------------------------
1 | export interface Errors {
2 | errors: {[key: string]: string};
3 | }
4 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/core/models/index.ts:
--------------------------------------------------------------------------------
1 | export * from './article.model';
2 | export * from './article-list-config.model';
3 | export * from './comment.model';
4 | export * from './errors.model';
5 | export * from './profile.model';
6 | export * from './user.model';
7 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/core/models/profile.model.ts:
--------------------------------------------------------------------------------
1 | export interface Profile {
2 | username: string;
3 | bio: string;
4 | image: string;
5 | following: boolean;
6 | }
7 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/core/models/user.model.ts:
--------------------------------------------------------------------------------
1 | export interface User {
2 | email: string;
3 | token: string;
4 | username: string;
5 | bio: string;
6 | image: string;
7 | }
8 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/core/services/api.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { environment } from '../../../environments/environment';
3 | import { HttpHeaders, HttpClient, HttpParams } from '@angular/common/http';
4 | import { Observable , throwError } from 'rxjs';
5 |
6 | import { catchError } from 'rxjs/operators';
7 |
8 | @Injectable({
9 | providedIn: 'root'
10 | })
11 | export class ApiService {
12 | constructor(
13 | private http: HttpClient
14 | ) {}
15 |
16 | private formatErrors(error: any) {
17 | return throwError(error.error);
18 | }
19 |
20 | get(path: string, params: HttpParams = new HttpParams()): Observable {
21 | return this.http.get(`${environment.api_url}${path}`, { params })
22 | .pipe(catchError(this.formatErrors));
23 | }
24 |
25 | put(path: string, body: Object = {}): Observable {
26 | return this.http.put(
27 | `${environment.api_url}${path}`,
28 | JSON.stringify(body)
29 | ).pipe(catchError(this.formatErrors));
30 | }
31 |
32 | post(path: string, body: Object = {}): Observable {
33 | return this.http.post(
34 | `${environment.api_url}${path}`,
35 | JSON.stringify(body)
36 | ).pipe(catchError(this.formatErrors));
37 | }
38 |
39 | delete(path): Observable {
40 | return this.http.delete(
41 | `${environment.api_url}${path}`
42 | ).pipe(catchError(this.formatErrors));
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/core/services/articles.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { HttpParams } from '@angular/common/http';
3 | import { Observable } from 'rxjs';
4 |
5 | import { ApiService } from './api.service';
6 | import { Article, ArticleListConfig } from '../models';
7 | import { map } from 'rxjs/operators';
8 |
9 | @Injectable({
10 | providedIn: 'root'
11 | })
12 | export class ArticlesService {
13 | constructor (
14 | private apiService: ApiService
15 | ) {}
16 |
17 | query(config: ArticleListConfig): Observable<{articles: Article[], articlesCount: number}> {
18 | // Convert any filters over to Angular's URLSearchParams
19 | const params = {};
20 |
21 | Object.keys(config.filters)
22 | .forEach((key) => {
23 | params[key] = config.filters[key];
24 | });
25 |
26 | return this.apiService
27 | .get(
28 | '/articles' + ((config.type === 'feed') ? '/feed' : ''),
29 | new HttpParams({ fromObject: params })
30 | );
31 | }
32 |
33 | get(slug): Observable {
34 | return this.apiService.get('/articles/' + slug)
35 | .pipe(map(data => data.article));
36 | }
37 |
38 | destroy(slug) {
39 | return this.apiService.delete('/articles/' + slug);
40 | }
41 |
42 | save(article): Observable {
43 | // If we're updating an existing article
44 | if (article.slug) {
45 | return this.apiService.put('/articles/' + article.slug, {article: article})
46 | .pipe(map(data => data.article));
47 |
48 | // Otherwise, create a new article
49 | } else {
50 | return this.apiService.post('/articles/', {article: article})
51 | .pipe(map(data => data.article));
52 | }
53 | }
54 |
55 | favorite(slug): Observable {
56 | return this.apiService.post('/articles/' + slug + '/favorite');
57 | }
58 |
59 | unfavorite(slug): Observable {
60 | return this.apiService.delete('/articles/' + slug + '/favorite');
61 | }
62 |
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/core/services/auth-guard.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
3 | import { Observable } from 'rxjs';
4 |
5 | import { UserService } from './user.service';
6 | import { take } from 'rxjs/operators';
7 |
8 | @Injectable({
9 | providedIn: 'root'
10 | })
11 | export class AuthGuard implements CanActivate {
12 | constructor(
13 | private router: Router,
14 | private userService: UserService
15 | ) {}
16 |
17 | canActivate(
18 | route: ActivatedRouteSnapshot,
19 | state: RouterStateSnapshot
20 | ): Observable {
21 |
22 | return this.userService.isAuthenticated.pipe(take(1));
23 |
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/core/services/comments.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Observable } from 'rxjs';
3 |
4 | import { ApiService } from './api.service';
5 | import { Comment } from '../models';
6 | import { map } from 'rxjs/operators';
7 |
8 |
9 | @Injectable({
10 | providedIn: 'root'
11 | })
12 | export class CommentsService {
13 | constructor (
14 | private apiService: ApiService
15 | ) {}
16 |
17 | add(slug, payload): Observable {
18 | return this.apiService
19 | .post(
20 | `/articles/${slug}/comments`,
21 | { comment: { body: payload } }
22 | ).pipe(map(data => data.comment));
23 | }
24 |
25 | getAll(slug): Observable {
26 | return this.apiService.get(`/articles/${slug}/comments`)
27 | .pipe(map(data => data.comments));
28 | }
29 |
30 | destroy(commentId, articleSlug) {
31 | return this.apiService
32 | .delete(`/articles/${articleSlug}/comments/${commentId}`);
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/core/services/index.ts:
--------------------------------------------------------------------------------
1 | export * from './api.service';
2 | export * from './articles.service';
3 | export * from './auth-guard.service';
4 | export * from './comments.service';
5 | export * from './jwt.service';
6 | export * from './profiles.service';
7 | export * from './tags.service';
8 | export * from './user.service';
9 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/core/services/jwt.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 |
3 |
4 | @Injectable({
5 | providedIn: 'root'
6 | })
7 | export class JwtService {
8 |
9 | getToken(): String {
10 | return window.localStorage['jwtToken'];
11 | }
12 |
13 | saveToken(token: String) {
14 | window.localStorage['jwtToken'] = token;
15 | }
16 |
17 | destroyToken() {
18 | window.localStorage.removeItem('jwtToken');
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/core/services/profiles.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Observable } from 'rxjs';
3 |
4 | import { ApiService } from './api.service';
5 | import { Profile } from '../models';
6 | import { map } from 'rxjs/operators';
7 |
8 | @Injectable({
9 | providedIn: 'root'
10 | })
11 | export class ProfilesService {
12 | constructor (
13 | private apiService: ApiService
14 | ) {}
15 |
16 | get(username: string): Observable {
17 | return this.apiService.get('/profiles/' + username)
18 | .pipe(map((data: {profile: Profile}) => data.profile));
19 | }
20 |
21 | follow(username: string): Observable {
22 | return this.apiService.post('/profiles/' + username + '/follow');
23 | }
24 |
25 | unfollow(username: string): Observable {
26 | return this.apiService.delete('/profiles/' + username + '/follow');
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/core/services/tags.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Observable } from 'rxjs';
3 |
4 | import { ApiService } from './api.service';
5 | import { map } from 'rxjs/operators';
6 |
7 | @Injectable({
8 | providedIn: 'root'
9 | })
10 | export class TagsService {
11 | constructor (
12 | private apiService: ApiService
13 | ) {}
14 |
15 | getAll(): Observable<[string]> {
16 | return this.apiService.get('/tags')
17 | .pipe(map(data => data.tags));
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/editor/editable-article-resolver.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router';
3 | import { Observable } from 'rxjs';
4 |
5 | import { Article, ArticlesService, UserService } from '../core';
6 | import { catchError , map } from 'rxjs/operators';
7 |
8 | @Injectable({
9 | providedIn: 'root'
10 | })
11 | export class EditableArticleResolver implements Resolve {
12 | constructor(
13 | private articlesService: ArticlesService,
14 | private router: Router,
15 | private userService: UserService
16 | ) { }
17 |
18 | resolve(
19 | route: ActivatedRouteSnapshot,
20 | state: RouterStateSnapshot
21 | ): Observable {
22 |
23 | return this.articlesService.get(route.params['slug'])
24 | .pipe(
25 | map(
26 | article => {
27 | if (this.userService.getCurrentUser().username === article.author.username) {
28 | return article;
29 | } else {
30 | this.router.navigateByUrl('/');
31 | }
32 | }
33 | ),
34 | catchError((err) => this.router.navigateByUrl('/'))
35 | );
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/editor/editor-routing.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { Routes, RouterModule } from '@angular/router';
3 | import { EditorComponent } from './editor.component';
4 | import { EditableArticleResolver } from './editable-article-resolver.service';
5 | import { AuthGuard } from '../core';
6 | import { SharedModule } from '../shared';
7 |
8 | const routes: Routes = [
9 | {
10 | path: '',
11 | component: EditorComponent,
12 | canActivate: [AuthGuard]
13 | },
14 | {
15 | path: ':slug',
16 | component: EditorComponent,
17 | canActivate: [AuthGuard],
18 | resolve: {
19 | article: EditableArticleResolver
20 | }
21 | }
22 | ];
23 |
24 | @NgModule({
25 | imports: [RouterModule.forChild(routes)],
26 | exports: [RouterModule]
27 | })
28 | export class EditorRoutingModule {}
29 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/editor/editor.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
55 |
56 |
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/editor/editor.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 |
3 |
4 | import { EditorComponent } from './editor.component';
5 |
6 | import { SharedModule } from '../shared';
7 | import { EditorRoutingModule } from './editor-routing.module';
8 |
9 | @NgModule({
10 | imports: [SharedModule, EditorRoutingModule],
11 | declarations: [EditorComponent],
12 | providers: []
13 | })
14 | export class EditorModule {}
15 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/home/home-auth-resolver.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router';
3 | import { Observable } from 'rxjs';
4 |
5 | import { UserService } from '../core';
6 | import { take } from 'rxjs/operators';
7 |
8 | @Injectable({
9 | providedIn: 'root'
10 | })
11 | export class HomeAuthResolver implements Resolve {
12 | constructor(
13 | private router: Router,
14 | private userService: UserService
15 | ) {}
16 |
17 | resolve(
18 | route: ActivatedRouteSnapshot,
19 | state: RouterStateSnapshot
20 | ): Observable {
21 |
22 | return this.userService.isAuthenticated.pipe(take(1));
23 |
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/home/home-routing.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { Routes, RouterModule } from '@angular/router';
3 | import { HomeComponent } from './home.component';
4 | import { HomeAuthResolver } from './home-auth-resolver.service';
5 |
6 | const routes: Routes = [
7 | {
8 | path: '',
9 | component: HomeComponent,
10 | resolve: {
11 | isAuthenticated: HomeAuthResolver
12 | }
13 | }
14 | ];
15 |
16 | @NgModule({
17 | imports: [RouterModule.forChild(routes)],
18 | exports: [RouterModule]
19 | })
20 | export class HomeRoutingModule {}
21 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/home/home.component.css:
--------------------------------------------------------------------------------
1 | .nav-link {
2 | cursor:pointer;
3 | }
4 |
5 | .tag-pill{
6 | cursor:pointer;
7 | }
8 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/home/home.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
conduit
6 |
A place to share your Angular knowledge.
7 |
8 |
9 |
10 |
11 |
12 |
13 |
40 |
41 |
42 |
61 |
62 |
63 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/home/home.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
2 | import { Router } from '@angular/router';
3 |
4 | import { ArticleListConfig, TagsService, UserService } from '../core';
5 |
6 | @Component({
7 | selector: 'app-home-page',
8 | templateUrl: './home.component.html',
9 | styleUrls: ['./home.component.css'],
10 | changeDetection: ChangeDetectionStrategy.OnPush
11 | })
12 | export class HomeComponent implements OnInit {
13 | constructor(
14 | private router: Router,
15 | private tagsService: TagsService,
16 | private userService: UserService,
17 | private cd: ChangeDetectorRef
18 | ) {}
19 |
20 | isAuthenticated: boolean;
21 | listConfig: ArticleListConfig = {
22 | type: 'all',
23 | filters: {}
24 | };
25 | tags: Array = [];
26 | tagsLoaded = false;
27 |
28 | ngOnInit() {
29 | this.userService.isAuthenticated.subscribe(
30 | (authenticated) => {
31 | this.isAuthenticated = authenticated;
32 |
33 | // set the article list accordingly
34 | if (authenticated) {
35 | this.setListTo('feed');
36 | } else {
37 | this.setListTo('all');
38 | }
39 | this.cd.markForCheck();
40 | }
41 | );
42 |
43 | this.tagsService.getAll()
44 | .subscribe(tags => {
45 | this.tags = tags;
46 | this.tagsLoaded = true;
47 | this.cd.markForCheck();
48 | });
49 | }
50 |
51 | trackByFn(index, item) {
52 | return index;
53 | }
54 |
55 | setListTo(type: string = '', filters: Object = {}) {
56 | // If feed is requested but user is not authenticated, redirect to login
57 | if (type === 'feed' && !this.isAuthenticated) {
58 | this.router.navigateByUrl('/login');
59 | return;
60 | }
61 |
62 | // Otherwise, set the list object
63 | this.listConfig = {type: type, filters: filters};
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/home/home.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 |
3 | import { HomeComponent } from './home.component';
4 | import { SharedModule } from '../shared';
5 | import { HomeRoutingModule } from './home-routing.module';
6 |
7 | @NgModule({
8 | imports: [
9 | SharedModule,
10 | HomeRoutingModule
11 | ],
12 | declarations: [
13 | HomeComponent
14 | ],
15 | providers: [
16 | ]
17 | })
18 | export class HomeModule {}
19 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/index.ts:
--------------------------------------------------------------------------------
1 | export * from './app.component';
2 | export * from './app.module';
3 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/profile/profile-articles.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/profile/profile-articles.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
2 | import { ActivatedRoute, Router } from '@angular/router';
3 |
4 | import { ArticleListConfig, Profile } from '../core';
5 |
6 | @Component({
7 | selector: 'app-profile-articles',
8 | templateUrl: './profile-articles.component.html',
9 | changeDetection: ChangeDetectionStrategy.OnPush
10 | })
11 | export class ProfileArticlesComponent implements OnInit {
12 | constructor(
13 | private route: ActivatedRoute,
14 | private router: Router,
15 | private cd: ChangeDetectorRef
16 | ) {}
17 |
18 | profile: Profile;
19 | articlesConfig: ArticleListConfig = {
20 | type: 'all',
21 | filters: {}
22 | };
23 |
24 | ngOnInit() {
25 | this.route.parent.data.subscribe(
26 | (data: {profile: Profile}) => {
27 | this.profile = data.profile;
28 | this.articlesConfig = {
29 | type: 'all',
30 | filters: {}
31 | }; // Only method I found to refresh article load on swap
32 | this.articlesConfig.filters.author = this.profile.username;
33 | this.cd.markForCheck();
34 | }
35 | );
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/profile/profile-favorites.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/profile/profile-favorites.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
2 | import { ActivatedRoute, Router } from '@angular/router';
3 |
4 | import { ArticleListConfig, Profile } from '../core';
5 |
6 | @Component({
7 | selector: 'app-profile-favorites',
8 | templateUrl: './profile-favorites.component.html',
9 | changeDetection: ChangeDetectionStrategy.OnPush
10 | })
11 | export class ProfileFavoritesComponent implements OnInit {
12 | constructor(
13 | private route: ActivatedRoute,
14 | private cd: ChangeDetectorRef
15 | ) {}
16 |
17 | profile: Profile;
18 | favoritesConfig: ArticleListConfig = {
19 | type: 'all',
20 | filters: {}
21 | };
22 |
23 | ngOnInit() {
24 | this.route.parent.data.subscribe(
25 | (data: {profile: Profile}) => {
26 | this.profile = data.profile;
27 | this.favoritesConfig = {...this.favoritesConfig};
28 | this.favoritesConfig.filters.favorited = this.profile.username;
29 | this.cd.markForCheck();
30 | }
31 | );
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/profile/profile-resolver.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable, } from '@angular/core';
2 | import { ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router';
3 | import { Observable } from 'rxjs';
4 |
5 | import { Profile, ProfilesService } from '../core';
6 | import { catchError } from 'rxjs/operators';
7 |
8 | @Injectable({
9 | providedIn: 'root'
10 | })
11 | export class ProfileResolver implements Resolve {
12 | constructor(
13 | private profilesService: ProfilesService,
14 | private router: Router
15 | ) {}
16 |
17 | resolve(
18 | route: ActivatedRouteSnapshot,
19 | state: RouterStateSnapshot
20 | ): Observable {
21 |
22 | return this.profilesService.get(route.params['username'])
23 | .pipe(catchError((err) => this.router.navigateByUrl('/')));
24 |
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/profile/profile-routing.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { RouterModule, Routes } from '@angular/router';
3 | import { ProfileArticlesComponent } from './profile-articles.component';
4 | import { ProfileFavoritesComponent } from './profile-favorites.component';
5 | import { ProfileResolver } from './profile-resolver.service';
6 | import { ProfileComponent } from './profile.component';
7 |
8 |
9 | const routes: Routes = [
10 | {
11 | path: ':username',
12 | component: ProfileComponent,
13 | resolve: {
14 | profile: ProfileResolver
15 | },
16 | children: [
17 | {
18 | path: '',
19 | component: ProfileArticlesComponent
20 | },
21 | {
22 | path: 'favorites',
23 | component: ProfileFavoritesComponent
24 | }
25 | ]
26 | }
27 | ];
28 |
29 | @NgModule({
30 | imports: [RouterModule.forChild(routes)],
31 | exports: [RouterModule]
32 | })
33 | export class ProfileRoutingModule {}
34 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/profile/profile.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
26 |
27 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/profile/profile.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
2 | import { ActivatedRoute } from '@angular/router';
3 |
4 | import { User, UserService, Profile } from '../core';
5 | import { concatMap , tap } from 'rxjs/operators';
6 |
7 | @Component({
8 | selector: 'app-profile-page',
9 | templateUrl: './profile.component.html',
10 | changeDetection: ChangeDetectionStrategy.OnPush
11 | })
12 | export class ProfileComponent implements OnInit {
13 | constructor(
14 | private route: ActivatedRoute,
15 | private userService: UserService,
16 | private cd: ChangeDetectorRef
17 | ) { }
18 |
19 | profile: Profile;
20 | currentUser: User;
21 | isUser: boolean;
22 |
23 | ngOnInit() {
24 | this.route.data.pipe(
25 | concatMap((data: { profile: Profile }) => {
26 | this.profile = data.profile;
27 | // Load the current user's data.
28 | return this.userService.currentUser.pipe(tap(
29 | (userData: User) => {
30 | this.currentUser = userData;
31 | this.isUser = (this.currentUser.username === this.profile.username);
32 | }
33 | ));
34 | })
35 | ).subscribe((() => {
36 | this.cd.markForCheck();
37 | }));
38 | }
39 |
40 | onToggleFollowing(following: boolean) {
41 | this.profile.following = following;
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/profile/profile.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 |
3 | import { ProfileArticlesComponent } from './profile-articles.component';
4 | import { ProfileComponent } from './profile.component';
5 | import { ProfileFavoritesComponent } from './profile-favorites.component';
6 | import { SharedModule } from '../shared';
7 | import { ProfileRoutingModule } from './profile-routing.module';
8 |
9 | @NgModule({
10 | imports: [
11 | SharedModule,
12 | ProfileRoutingModule
13 | ],
14 | declarations: [
15 | ProfileArticlesComponent,
16 | ProfileComponent,
17 | ProfileFavoritesComponent
18 | ],
19 | providers: [
20 | ]
21 | })
22 | export class ProfileModule {}
23 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/settings/settings-routing.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { RouterModule, Routes } from '@angular/router';
3 | import { AuthGuard } from '../core';
4 | import { SettingsComponent } from './settings.component';
5 |
6 | const routes: Routes = [
7 | {
8 | path: '',
9 | component: SettingsComponent,
10 | canActivate: [AuthGuard]
11 | }
12 | ];
13 |
14 | @NgModule({
15 | imports: [RouterModule.forChild(routes)],
16 | exports: [RouterModule]
17 | })
18 | export class SettingsRoutingModule {}
19 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/settings/settings.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
2 | import { FormBuilder, FormGroup } from '@angular/forms';
3 | import { Router } from '@angular/router';
4 |
5 | import { User, UserService } from '../core';
6 |
7 | @Component({
8 | selector: 'app-settings-page',
9 | templateUrl: './settings.component.html',
10 | changeDetection: ChangeDetectionStrategy.OnPush
11 | })
12 | export class SettingsComponent implements OnInit {
13 | user: User = {} as User;
14 | settingsForm: FormGroup;
15 | errors: Object = {};
16 | isSubmitting = false;
17 |
18 | constructor(
19 | private router: Router,
20 | private userService: UserService,
21 | private fb: FormBuilder,
22 | private cd: ChangeDetectorRef
23 | ) {
24 | // create form group using the form builder
25 | this.settingsForm = this.fb.group({
26 | image: '',
27 | username: '',
28 | bio: '',
29 | email: '',
30 | password: ''
31 | });
32 | // Optional: subscribe to changes on the form
33 | // this.settingsForm.valueChanges.subscribe(values => this.updateUser(values));
34 | }
35 |
36 | ngOnInit() {
37 | // Make a fresh copy of the current user's object to place in editable form fields
38 | Object.assign(this.user, this.userService.getCurrentUser());
39 | // Fill the form
40 | this.settingsForm.patchValue(this.user);
41 | }
42 |
43 | logout() {
44 | this.userService.purgeAuth();
45 | this.router.navigateByUrl('/');
46 | }
47 |
48 | submitForm() {
49 | this.isSubmitting = true;
50 |
51 | // update the model
52 | this.updateUser(this.settingsForm.value);
53 |
54 | this.userService
55 | .update(this.user)
56 | .subscribe(
57 | updatedUser => this.router.navigateByUrl('/profile/' + updatedUser.username),
58 | err => {
59 | this.errors = err;
60 | this.isSubmitting = false;
61 | this.cd.markForCheck();
62 | }
63 | );
64 | }
65 |
66 | updateUser(values: Object) {
67 | Object.assign(this.user, values);
68 | }
69 |
70 | }
71 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/settings/settings.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 |
3 | import { SettingsComponent } from './settings.component';
4 | import { SharedModule } from '../shared';
5 | import { SettingsRoutingModule } from './settings-routing.module';
6 |
7 | @NgModule({
8 | imports: [
9 | SharedModule,
10 | SettingsRoutingModule
11 | ],
12 | declarations: [
13 | SettingsComponent
14 | ]
15 | })
16 | export class SettingsModule {}
17 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/shared/article-helpers/article-list.component.css:
--------------------------------------------------------------------------------
1 | .page-link {
2 | cursor: pointer;
3 | }
4 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/shared/article-helpers/article-list.component.html:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
8 | Loading articles...
9 |
10 |
11 |
13 | No articles are here... yet.
14 |
15 |
16 |
30 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/shared/article-helpers/article-list.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input, ChangeDetectorRef, ChangeDetectionStrategy } from '@angular/core';
2 |
3 | import { Article, ArticleListConfig, ArticlesService } from '../../core';
4 | @Component({
5 | selector: 'app-article-list',
6 | styleUrls: ['article-list.component.css'],
7 | templateUrl: './article-list.component.html',
8 | changeDetection: ChangeDetectionStrategy.OnPush
9 | })
10 | export class ArticleListComponent {
11 | constructor (
12 | private articlesService: ArticlesService,
13 | private cd: ChangeDetectorRef
14 | ) {}
15 |
16 | @Input() limit: number;
17 | @Input()
18 | set config(config: ArticleListConfig) {
19 | if (config) {
20 | this.query = config;
21 | this.currentPage = 1;
22 | this.runQuery();
23 | }
24 | }
25 |
26 | query: ArticleListConfig;
27 | results: Article[];
28 | loading = false;
29 | currentPage = 1;
30 | totalPages: Array = [1];
31 |
32 | setPageTo(pageNumber) {
33 | this.currentPage = pageNumber;
34 | this.runQuery();
35 | }
36 |
37 | trackByFn(index, item) {
38 | return index;
39 | }
40 |
41 | runQuery() {
42 | this.loading = true;
43 | this.results = [];
44 |
45 | // Create limit and offset filter (if necessary)
46 | if (this.limit) {
47 | this.query.filters.limit = this.limit;
48 | this.query.filters.offset = (this.limit * (this.currentPage - 1));
49 | }
50 |
51 | this.articlesService.query(this.query)
52 | .subscribe(data => {
53 | this.loading = false;
54 | this.results = data.articles;
55 |
56 | // Used from http://www.jstips.co/en/create-range-0...n-easily-using-one-line/
57 | this.totalPages = Array.from(new Array(Math.ceil(data.articlesCount / this.limit)), (val, index) => index + 1);
58 | this.cd.markForCheck();
59 | });
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/shared/article-helpers/article-meta.component.html:
--------------------------------------------------------------------------------
1 |
18 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/shared/article-helpers/article-meta.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input, ChangeDetectionStrategy } from '@angular/core';
2 |
3 | import { Article } from '../../core';
4 |
5 | @Component({
6 | selector: 'app-article-meta',
7 | templateUrl: './article-meta.component.html',
8 | changeDetection: ChangeDetectionStrategy.OnPush
9 | })
10 | export class ArticleMetaComponent {
11 | @Input() article: Article;
12 | }
13 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/shared/article-helpers/article-preview.component.html:
--------------------------------------------------------------------------------
1 |
23 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/shared/article-helpers/article-preview.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input, ChangeDetectionStrategy } from '@angular/core';
2 |
3 | import { Article } from '../../core';
4 |
5 | @Component({
6 | selector: 'app-article-preview',
7 | templateUrl: './article-preview.component.html',
8 | changeDetection: ChangeDetectionStrategy.OnPush
9 | })
10 | export class ArticlePreviewComponent {
11 | @Input() article: Article;
12 |
13 | trackByFn(index, item) {
14 | return index;
15 | }
16 |
17 | onToggleFavorite(favorited: boolean) {
18 | this.article['favorited'] = favorited;
19 |
20 | if (favorited) {
21 | this.article['favoritesCount']++;
22 | } else {
23 | this.article['favoritesCount']--;
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/shared/article-helpers/index.ts:
--------------------------------------------------------------------------------
1 | export * from './article-list.component';
2 | export * from './article-meta.component';
3 | export * from './article-preview.component';
4 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/shared/buttons/favorite-button.component.html:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/shared/buttons/favorite-button.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, EventEmitter, Input, Output, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
2 | import { Router } from '@angular/router';
3 |
4 | import { Article, ArticlesService, UserService } from '../../core';
5 | import { of } from 'rxjs';
6 | import { concatMap , tap } from 'rxjs/operators';
7 |
8 | @Component({
9 | selector: 'app-favorite-button',
10 | templateUrl: './favorite-button.component.html',
11 | changeDetection: ChangeDetectionStrategy.OnPush
12 | })
13 | export class FavoriteButtonComponent {
14 | constructor(
15 | private articlesService: ArticlesService,
16 | private router: Router,
17 | private userService: UserService,
18 | private cd: ChangeDetectorRef
19 | ) {}
20 |
21 | @Input() article: Article;
22 | @Output() toggle = new EventEmitter();
23 | isSubmitting = false;
24 |
25 | toggleFavorite() {
26 | this.isSubmitting = true;
27 |
28 | this.userService.isAuthenticated.pipe(concatMap(
29 | (authenticated) => {
30 | // Not authenticated? Push to login screen
31 | if (!authenticated) {
32 | this.router.navigateByUrl('/login');
33 | return of(null);
34 | }
35 |
36 | // Favorite the article if it isn't favorited yet
37 | if (!this.article.favorited) {
38 | return this.articlesService.favorite(this.article.slug)
39 | .pipe(tap(
40 | data => {
41 | this.isSubmitting = false;
42 | this.toggle.emit(true);
43 | },
44 | err => this.isSubmitting = false
45 | ));
46 |
47 | // Otherwise, unfavorite the article
48 | } else {
49 | return this.articlesService.unfavorite(this.article.slug)
50 | .pipe(tap(
51 | data => {
52 | this.isSubmitting = false;
53 | this.toggle.emit(false);
54 | },
55 | err => this.isSubmitting = false
56 | ));
57 | }
58 |
59 | }
60 | )).subscribe(() => {
61 | this.cd.markForCheck();
62 | });
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/shared/buttons/follow-button.component.html:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/shared/buttons/index.ts:
--------------------------------------------------------------------------------
1 | export * from './favorite-button.component';
2 | export * from './follow-button.component';
3 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/shared/index.ts:
--------------------------------------------------------------------------------
1 | export * from './article-helpers';
2 | export * from './buttons';
3 | export * from './layout';
4 | export * from './list-errors.component';
5 | export * from './shared.module';
6 | export * from './show-authed.directive';
7 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/shared/layout/footer.component.html:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/shared/layout/footer.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, ChangeDetectionStrategy } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-layout-footer',
5 | templateUrl: './footer.component.html',
6 | changeDetection: ChangeDetectionStrategy.OnPush
7 | })
8 | export class FooterComponent {
9 | today: number = Date.now();
10 | }
11 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/shared/layout/header.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
2 |
3 | import { User, UserService } from '../../core';
4 |
5 | @Component({
6 | selector: 'app-layout-header',
7 | templateUrl: './header.component.html',
8 | changeDetection: ChangeDetectionStrategy.OnPush
9 | })
10 | export class HeaderComponent implements OnInit {
11 | constructor(
12 | private userService: UserService,
13 | private cd: ChangeDetectorRef
14 | ) {}
15 |
16 | currentUser: User;
17 |
18 | ngOnInit() {
19 | this.userService.currentUser.subscribe(
20 | (userData) => {
21 | this.currentUser = userData;
22 | this.cd.markForCheck();
23 | }
24 | );
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/shared/layout/index.ts:
--------------------------------------------------------------------------------
1 | export * from './footer.component';
2 | export * from './header.component';
3 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/shared/list-errors.component.html:
--------------------------------------------------------------------------------
1 |
2 | -
3 | {{ error }}
4 |
5 |
6 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/shared/list-errors.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input, ChangeDetectionStrategy } from '@angular/core';
2 |
3 | import { Errors } from '../core';
4 |
5 | @Component({
6 | selector: 'app-list-errors',
7 | templateUrl: './list-errors.component.html',
8 | changeDetection: ChangeDetectionStrategy.OnPush
9 | })
10 | export class ListErrorsComponent {
11 | formattedErrors: Array = [];
12 |
13 | @Input()
14 | set errors(errorList: Errors) {
15 | this.formattedErrors = Object.keys(errorList.errors || {})
16 | .map(key => `${key} ${errorList.errors[key]}`);
17 | }
18 |
19 | get errorList() { return this.formattedErrors; }
20 |
21 | trackByFn(index, item) {
22 | return index;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/shared/shared.module.ts:
--------------------------------------------------------------------------------
1 | import { CommonModule } from '@angular/common';
2 | import { NgModule } from '@angular/core';
3 | import { FormsModule, ReactiveFormsModule } from '@angular/forms';
4 | import { HttpClientModule } from '@angular/common/http';
5 | import { RouterModule } from '@angular/router';
6 |
7 | import { ArticleListComponent, ArticleMetaComponent, ArticlePreviewComponent } from './article-helpers';
8 | import { FavoriteButtonComponent, FollowButtonComponent } from './buttons';
9 | import { ListErrorsComponent } from './list-errors.component';
10 | import { ShowAuthedDirective } from './show-authed.directive';
11 |
12 | @NgModule({
13 | imports: [
14 | CommonModule,
15 | FormsModule,
16 | ReactiveFormsModule,
17 | HttpClientModule,
18 | RouterModule
19 | ],
20 | declarations: [
21 | ArticleListComponent,
22 | ArticleMetaComponent,
23 | ArticlePreviewComponent,
24 | FavoriteButtonComponent,
25 | FollowButtonComponent,
26 | ListErrorsComponent,
27 | ShowAuthedDirective
28 | ],
29 | exports: [
30 | ArticleListComponent,
31 | ArticleMetaComponent,
32 | ArticlePreviewComponent,
33 | CommonModule,
34 | FavoriteButtonComponent,
35 | FollowButtonComponent,
36 | FormsModule,
37 | ReactiveFormsModule,
38 | HttpClientModule,
39 | ListErrorsComponent,
40 | RouterModule,
41 | ShowAuthedDirective
42 | ]
43 | })
44 | export class SharedModule {}
45 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/app/shared/show-authed.directive.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Directive,
3 | Input,
4 | OnInit,
5 | TemplateRef,
6 | ViewContainerRef
7 | } from '@angular/core';
8 |
9 | import { UserService } from '../core';
10 |
11 | @Directive({ selector: '[appShowAuthed]' })
12 | export class ShowAuthedDirective implements OnInit {
13 | constructor(
14 | private templateRef: TemplateRef,
15 | private userService: UserService,
16 | private viewContainer: ViewContainerRef
17 | ) {}
18 |
19 | condition: boolean;
20 |
21 | ngOnInit() {
22 | this.userService.isAuthenticated.subscribe(
23 | (isAuthenticated) => {
24 | if (isAuthenticated && this.condition || !isAuthenticated && !this.condition) {
25 | this.viewContainer.createEmbeddedView(this.templateRef);
26 | } else {
27 | this.viewContainer.clear();
28 | }
29 | }
30 | );
31 | }
32 |
33 | @Input() set appShowAuthed(condition: boolean) {
34 | this.condition = condition;
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/talk-dev-to-me-twitch/ed06f3b0695b4e2b9e17cb0dda17b6d88ecbcd46/spa-blue-green-deployments/app/src/assets/.gitkeep
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/assets/.npmignore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/talk-dev-to-me-twitch/ed06f3b0695b4e2b9e17cb0dda17b6d88ecbcd46/spa-blue-green-deployments/app/src/assets/.npmignore
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/assets/icons/icon-128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/talk-dev-to-me-twitch/ed06f3b0695b4e2b9e17cb0dda17b6d88ecbcd46/spa-blue-green-deployments/app/src/assets/icons/icon-128x128.png
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/assets/icons/icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/talk-dev-to-me-twitch/ed06f3b0695b4e2b9e17cb0dda17b6d88ecbcd46/spa-blue-green-deployments/app/src/assets/icons/icon-144x144.png
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/assets/icons/icon-152x152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/talk-dev-to-me-twitch/ed06f3b0695b4e2b9e17cb0dda17b6d88ecbcd46/spa-blue-green-deployments/app/src/assets/icons/icon-152x152.png
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/assets/icons/icon-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/talk-dev-to-me-twitch/ed06f3b0695b4e2b9e17cb0dda17b6d88ecbcd46/spa-blue-green-deployments/app/src/assets/icons/icon-192x192.png
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/assets/icons/icon-384x384.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/talk-dev-to-me-twitch/ed06f3b0695b4e2b9e17cb0dda17b6d88ecbcd46/spa-blue-green-deployments/app/src/assets/icons/icon-384x384.png
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/assets/icons/icon-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/talk-dev-to-me-twitch/ed06f3b0695b4e2b9e17cb0dda17b6d88ecbcd46/spa-blue-green-deployments/app/src/assets/icons/icon-512x512.png
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/assets/icons/icon-72x72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/talk-dev-to-me-twitch/ed06f3b0695b4e2b9e17cb0dda17b6d88ecbcd46/spa-blue-green-deployments/app/src/assets/icons/icon-72x72.png
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/assets/icons/icon-96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/talk-dev-to-me-twitch/ed06f3b0695b4e2b9e17cb0dda17b6d88ecbcd46/spa-blue-green-deployments/app/src/assets/icons/icon-96x96.png
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/environments/environment.prod.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: true,
3 | api_url: 'https://api.realworld.io/api'
4 | };
5 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/environments/environment.ts:
--------------------------------------------------------------------------------
1 | // The file contents for the current environment will overwrite these during build.
2 | // The build system defaults to the dev environment which uses `environment.ts`, but if you do
3 | // `ng build --env=prod` then `environment.prod.ts` will be used instead.
4 | // The list of which env maps to which file can be found in `angular-cli.json`.
5 |
6 | export const environment = {
7 | production: false,
8 | api_url: 'https://api.realworld.io/api'
9 | };
10 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/talk-dev-to-me-twitch/ed06f3b0695b4e2b9e17cb0dda17b6d88ecbcd46/spa-blue-green-deployments/app/src/favicon.ico
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Conduit
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | Loading...
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/main.ts:
--------------------------------------------------------------------------------
1 | import { enableProdMode } from '@angular/core';
2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
3 |
4 | import { AppModule } from './app/app.module';
5 | import { environment } from './environments/environment';
6 |
7 | if (environment.production) {
8 | enableProdMode();
9 | }
10 |
11 | const bootstrapPromise = platformBrowserDynamic().bootstrapModule(AppModule);
12 |
13 | // Logging bootstrap information
14 | bootstrapPromise.then(success => console.log(`Bootstrap success`))
15 | .catch(err => console.error(err));
16 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/manifest.webmanifest:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ang2-conduit",
3 | "short_name": "ang2-conduit",
4 | "theme_color": "#1976d2",
5 | "background_color": "#fafafa",
6 | "display": "standalone",
7 | "scope": "/",
8 | "start_url": "/",
9 | "icons": [
10 | {
11 | "src": "assets/icons/icon-72x72.png",
12 | "sizes": "72x72",
13 | "type": "image/png"
14 | },
15 | {
16 | "src": "assets/icons/icon-96x96.png",
17 | "sizes": "96x96",
18 | "type": "image/png"
19 | },
20 | {
21 | "src": "assets/icons/icon-128x128.png",
22 | "sizes": "128x128",
23 | "type": "image/png"
24 | },
25 | {
26 | "src": "assets/icons/icon-144x144.png",
27 | "sizes": "144x144",
28 | "type": "image/png"
29 | },
30 | {
31 | "src": "assets/icons/icon-152x152.png",
32 | "sizes": "152x152",
33 | "type": "image/png"
34 | },
35 | {
36 | "src": "assets/icons/icon-192x192.png",
37 | "sizes": "192x192",
38 | "type": "image/png"
39 | },
40 | {
41 | "src": "assets/icons/icon-384x384.png",
42 | "sizes": "384x384",
43 | "type": "image/png"
44 | },
45 | {
46 | "src": "assets/icons/icon-512x512.png",
47 | "sizes": "512x512",
48 | "type": "image/png"
49 | }
50 | ]
51 | }
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/polyfills.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This file includes polyfills needed by Angular and is loaded before the app.
3 | * You can add your own extra polyfills to this file.
4 | *
5 | * This file is divided into 2 sections:
6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main
8 | * file.
9 | *
10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
13 | *
14 | * Learn more in https://angular.io/guide/browser-support
15 | */
16 |
17 | /***************************************************************************************************
18 | * BROWSER POLYFILLS
19 | */
20 |
21 | /** IE10 and IE11 requires the following for the Reflect API. */
22 | // import 'core-js/es6/reflect';
23 |
24 | /** Evergreen browsers require these. **/
25 | // Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove.
26 | // import 'core-js/es7/reflect';
27 |
28 | /***************************************************************************************************
29 | * Zone JS is required by default for Angular itself.
30 | */
31 | import 'zone.js'; // Included with Angular CLI.
32 |
33 | /***************************************************************************************************
34 | * APPLICATION IMPORTS
35 | */
36 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/styles.css:
--------------------------------------------------------------------------------
1 | /* You can add global styles to this file, and also import other style files */
2 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/test.ts:
--------------------------------------------------------------------------------
1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files
2 |
3 | import 'zone.js/dist/long-stack-trace-zone';
4 | import 'zone.js/dist/proxy.js';
5 | import 'zone.js/dist/sync-test';
6 | import 'zone.js/dist/jasmine-patch';
7 | import 'zone.js/dist/async-test';
8 | import 'zone.js/dist/fake-async-test';
9 | import { getTestBed } from '@angular/core/testing';
10 | import {
11 | BrowserDynamicTestingModule,
12 | platformBrowserDynamicTesting
13 | } from '@angular/platform-browser-dynamic/testing';
14 |
15 | // Unfortunately there's no typing for the `__karma__` variable. Just declare it as any.
16 | declare const __karma__: any;
17 | declare const require: any;
18 |
19 | // Prevent Karma from running prematurely.
20 | __karma__.loaded = function () {};
21 |
22 | // First, initialize the Angular testing environment.
23 | getTestBed().initTestEnvironment(
24 | BrowserDynamicTestingModule,
25 | platformBrowserDynamicTesting(), {
26 | teardown: { destroyAfterEach: false }
27 | }
28 | );
29 | // Then we find all the tests.
30 | const context = require.context('./', true, /\.spec\.ts$/);
31 | // And load the modules.
32 | context.keys().map(context);
33 | // Finally, start Karma to run the tests.
34 | __karma__.start();
35 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../out-tsc/app",
5 | "baseUrl": "./",
6 | "types": []
7 | },
8 | "files": [
9 | "main.ts",
10 | "polyfills.ts"
11 | ],
12 | "include": [
13 | "src/**/*.d.ts"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../out-tsc/spec",
5 | "baseUrl": "./",
6 | "types": [
7 | "jasmine",
8 | "node"
9 | ]
10 | },
11 | "files": [
12 | "test.ts",
13 | "polyfills.ts"
14 | ],
15 | "include": [
16 | "**/*.spec.ts",
17 | "**/*.d.ts"
18 | ]
19 | }
20 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/src/typings.d.ts:
--------------------------------------------------------------------------------
1 | /* SystemJS module definition */
2 | declare var module: NodeModule;
3 | interface NodeModule {
4 | id: string;
5 | }
6 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/app/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": false,
3 | "compilerOptions": {
4 | "importHelpers": true,
5 | "downlevelIteration": true,
6 | "outDir": "./dist/out-tsc",
7 | "sourceMap": true,
8 | "declaration": false,
9 | "module": "es2020",
10 | "moduleResolution": "node",
11 | "emitDecoratorMetadata": true,
12 | "experimentalDecorators": true,
13 | "target": "ES2015",
14 | "typeRoots": [
15 | "node_modules/@types"
16 | ],
17 | "lib": [
18 | "es2018",
19 | "dom"
20 | ]
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/cdk/.gitignore:
--------------------------------------------------------------------------------
1 | # Binaries for programs and plugins
2 | *.exe
3 | *.exe~
4 | *.dll
5 | *.so
6 | *.dylib
7 |
8 | # Test binary, built with `go test -c`
9 | *.test
10 |
11 | # Output of the go coverage tool, specifically when used with LiteIDE
12 | *.out
13 |
14 | # go.sum should be committed
15 | !go.sum
16 |
17 | # CDK asset staging directory
18 | .cdk.staging
19 | cdk.out
20 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/cdk/README.md:
--------------------------------------------------------------------------------
1 | # Getting started
2 |
3 | ```
4 | npm install -g aws-cdk
5 | cdk deploy
6 | ```
7 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/cdk/cdk.json:
--------------------------------------------------------------------------------
1 | {
2 | "app": "go mod download && go run cdk.go",
3 | "watch": {
4 | "include": [
5 | "**"
6 | ],
7 | "exclude": [
8 | "README.md",
9 | "cdk*.json",
10 | "go.mod",
11 | "go.sum",
12 | "**/*test.go"
13 | ]
14 | },
15 | "context": {
16 | "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true,
17 | "@aws-cdk/core:stackRelativeExports": true,
18 | "@aws-cdk/aws-rds:lowercaseDbIdentifier": true,
19 | "@aws-cdk/aws-lambda:recognizeVersionProps": true,
20 | "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true,
21 | "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
22 | "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true,
23 | "@aws-cdk/aws-iam:minimizePolicies": true,
24 | "@aws-cdk/core:target-partitions": [
25 | "aws",
26 | "aws-cn"
27 | ]
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/cdk/go.mod:
--------------------------------------------------------------------------------
1 | module cdk
2 |
3 | go 1.16
4 |
5 | require (
6 | github.com/aws/aws-cdk-go/awscdk/v2 v2.18.0
7 | github.com/aws/constructs-go/constructs/v10 v10.0.9
8 | github.com/aws/jsii-runtime-go v1.55.1
9 | )
10 |
--------------------------------------------------------------------------------
/spa-blue-green-deployments/cdk/go.sum:
--------------------------------------------------------------------------------
1 | github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
2 | github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
3 | github.com/aws/aws-cdk-go/awscdk/v2 v2.18.0 h1:GHb8GVCFCZTwf2daSv5TbGO9xP2GJlU1WXs7DoEt07c=
4 | github.com/aws/aws-cdk-go/awscdk/v2 v2.18.0/go.mod h1:GAUjiaBL3+MR0J8UyJGdVnWrHG9k4+io+K+kTLHydYg=
5 | github.com/aws/constructs-go/constructs/v10 v10.0.9 h1:YGk+deTAD3rgyANybjOtaoVrjC7HZZtALeC872avWFQ=
6 | github.com/aws/constructs-go/constructs/v10 v10.0.9/go.mod h1:RC6w8bOwxLmPX7Jfo9dkEZ9iVfgH4QnaVnfWvaNOHy0=
7 | github.com/aws/jsii-runtime-go v1.37.0/go.mod h1:6tZnlstx8bAB3vnLFF9n8bbkI//LDblAek9zFyMXV3E=
8 | github.com/aws/jsii-runtime-go v1.55.1 h1:naQzHp7NMVpQ6lElj5tn5HcnBvVjiP+g6XmNzs22Bvs=
9 | github.com/aws/jsii-runtime-go v1.55.1/go.mod h1:9htokR2a9XpRcbNf3fwqPaTs0CpC3KJCOUDMMJvEgZQ=
10 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
11 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
12 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
13 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
14 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
15 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
16 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
17 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
18 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
19 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
20 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
21 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
22 |
--------------------------------------------------------------------------------