├── .gitignore ├── PART01 └── SETUP.md ├── PART02 ├── CertManager.md ├── CloudFront.md ├── Route53.md ├── Route53Part2.md ├── S3.md └── index.html ├── PART03 ├── Cognito.md ├── Make.md ├── React01.md └── front-end │ ├── .gitignore │ ├── Makefile │ ├── README.md │ ├── package.json │ ├── public │ ├── images │ │ └── logo.png │ ├── index.html │ ├── manifest.json │ └── robots.txt │ ├── src │ ├── App.css │ ├── App.js │ ├── App.test.js │ ├── components │ │ ├── Confirm.js │ │ ├── Error.js │ │ ├── Footer.js │ │ ├── Home.js │ │ ├── Navi.js │ │ ├── NotFound.js │ │ ├── S3Image.js │ │ └── SignInForm.js │ ├── config-example.js │ ├── containers │ │ ├── Auth │ │ │ └── SignUp.js │ │ ├── Photos.js │ │ └── Welcome.js │ ├── index.js │ ├── main.css │ ├── redux │ │ ├── actions.js │ │ ├── auth │ │ │ ├── actions.js │ │ │ ├── api.js │ │ │ ├── constants.js │ │ │ ├── reducers.js │ │ │ └── saga.js │ │ ├── photos │ │ │ ├── actions.js │ │ │ ├── api.js │ │ │ ├── constants.js │ │ │ ├── reducers.js │ │ │ └── saga.js │ │ ├── reducers.js │ │ ├── sagas.js │ │ └── store.js │ ├── routes │ │ └── index.js │ ├── serviceWorker.js │ └── setupTests.js │ └── yarn.lock ├── PART04 ├── Connect.md ├── DynamoDB.md ├── Serverless.md └── main.tf ├── PART05 ├── Rek.md └── TheEnd.md ├── README.md ├── TECH.md ├── images ├── api01.png ├── api02.png ├── api03.png ├── api04.png ├── arch01.png ├── arch02.png ├── arch03.png ├── arch04.png ├── certmanager01.png ├── certmanager02.png ├── cf01.png ├── cf02.png ├── cf03.png ├── cf04.png ├── cognito01.png ├── cognito02.png ├── cognito03.png ├── cognito04.png ├── cognito05.png ├── cognito06.png ├── cognito07.png ├── cognito08.png ├── dynamo01.png ├── dynamo02.png ├── foo.png ├── iamDown.png ├── r5301.png ├── r5302.png ├── r5303.png ├── react01.png ├── s301.png ├── s302.png ├── s303.png ├── test01.png └── test02.png ├── infra └── main.tf └── serverless ├── lib.py ├── package.json ├── photos.py ├── resources └── api-gateway-errors.yml ├── serverless.yml └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # Distribution / packaging 2 | .Python 3 | *.pyc 4 | env/ 5 | build/ 6 | develop-eggs/ 7 | dist/ 8 | downloads/ 9 | eggs/ 10 | .eggs/ 11 | lib/ 12 | lib64/ 13 | parts/ 14 | sdist/ 15 | var/ 16 | *.egg-info/ 17 | .installed.cfg 18 | *.egg 19 | 20 | # Serverless directories 21 | .serverless 22 | -------------------------------------------------------------------------------- /PART01/SETUP.md: -------------------------------------------------------------------------------- 1 | # Set Up 2 | 3 | 4 | ## AWS Account 5 | 6 | To get started with this course, you will need to have an AWS account that gives enough permissions to do the actions here. 7 | 8 | We don't recommend using the root account as this creates security issues. In addition AWS strongly discourages creating API keys for the root user. As such we create another user and use that user account to login and create AWS resources. 9 | 10 | ## AWS CLI 11 | 12 | The [AWS CLI](https://aws.amazon.com/cli/) can be downloaded [here](https://aws.amazon.com/cli/). We do recommend installing the optional autocomplete if it is available for your operating system as it allows you to type commands faster. 13 | 14 | ## Git Clone 15 | 16 | As you'll want to use the sample code for this course, you should clone this repo: 17 | 18 | ``` 19 | git clone git@github.com:vallard/MicroServicesOnAWS.git 20 | ``` 21 | 22 | Then you can make changes as you go along. 23 | 24 | 25 | -------------------------------------------------------------------------------- /PART02/CertManager.md: -------------------------------------------------------------------------------- 1 | # Certificate Manager 2 | 3 | We'll now open up the certificate service to create a TLS certificate for our app. That way we can secure our connection. 4 | 5 | ## Important: Do this in the us-east-1 region 6 | You'll need to do this in the `us-east-1` region so it shows up for later. Even if you do everything else in a different region, do this portion in the `us-east-1` region. 7 | 8 | ## Request a Certificate 9 | 10 | From the certificate service in AWS's huge menu of services, click 'Request a certificate' 11 | 12 | Request a public certificate and add 4 domains to it: 13 | 14 | ![cert manager](../images/certmanager01.png) 15 | 16 | * `pics.castlerock.ai` - the main page. 17 | * `www.pics.castlerock.ai` - alias if people still do www. Do people do that still? 18 | 19 | 20 | * `dev.pics.castlerock.ai` - this is the staging page. 21 | * `api.pics.castlerock.ai` - this is the API we will create later. 22 | * `dev.api.pics.castlerock.ai` - this is the development API we will use. 23 | 24 | You'll of course want to change these to match your own domain names. 25 | 26 | To verify the certificates, we use DNS validation and automatically `Create record in Route 53` with the push of the button. 27 | 28 | ![cert validation](../images/certmanager02.png) 29 | 30 | Once done, we are now set to create a cloud front service. -------------------------------------------------------------------------------- /PART02/CloudFront.md: -------------------------------------------------------------------------------- 1 | # Cloud Front 2 | 3 | After creating our certificates, we need to create our front end CDN that will allow caching and terminate secure requests. 4 | 5 | CloudFront AWS free tier allows up to 50GB Transfer out, 2,000,000 HTTP/HTTPS requests. Using this with the certificate we just created comes with no extra cost. 6 | 7 | We will create our distribution only for the US as for now, that is all we are operating in, but you can choose differently depending on your needs. 8 | 9 | ## 01 Create New Distribution 10 | 11 | Let's create a new distribution: 12 | 13 | ![cf get started](../images/cf01.png) 14 | 15 | Next select Web as your delivery method. From there we get a nice big form to fill out. 16 | 17 | ![cf form](../images/cf02.png) 18 | 19 | Important fields on this form: 20 | 21 | * Select the S3 delivery bucket. (pics.castlerock.ai.s3.amazonaws.com for our example) 22 | * Restrict bucket access. 23 | * Create a new origin access identity 24 | * Update the bucket policy 25 | * Redirect HTTP to HTTPS 26 | 27 | The next fields also make use of our TLS certificate we generated previously. 28 | 29 | ![cf form2](../images/cf03.png) 30 | * Select the US region unless you are ready to go world wide. 31 | * Enter your domain names. 32 | * You should be able to select your own certificate that you created in the previous section. 33 | * Your default object will be the index.html you created already. 34 | 35 | Saving this will create your cloud front distribution. 36 | -------------------------------------------------------------------------------- /PART02/Route53.md: -------------------------------------------------------------------------------- 1 | # Route 53 2 | 3 | Route 53 is AWS's DNS service. You can buy domain names from them or manage DNS from other domain providers. 4 | 5 | I usually buy my domains elsewhere, but AWS is probably a good place to buy. Then I point the domains DNS to be managed by Route 53 where applicable. 6 | 7 | ## 01 - Create Hosted Zone 8 | 9 | Let's create a new hosted zone: 10 | 11 | ``` 12 | aws route53 create-hosted-zone --name pics2.castlerock.ai \ 13 | --caller-reference '2020-09-15:09:25' 14 | ``` 15 | 16 | The caller-reference is just a unique string to make sure the request doesn't happen twice. We just put in the date. 17 | 18 | The results is our DNS info we need to put into our other DNS service: 19 | 20 | ``` 21 | https://route53.amazonaws.com/2013-04-01/hostedzone/Z01374762ZFP1521DBRNE 22 | CHANGEINFO /change/C09383741NM5URADU2YFH PENDING 2020-09-15T16:24:20.412Z 23 | NAMESERVERS ns-227.awsdns-28.com 24 | NAMESERVERS ns-1119.awsdns-11.org 25 | NAMESERVERS ns-692.awsdns-22.net 26 | NAMESERVERS ns-1993.awsdns-57.co.uk 27 | HOSTEDZONE 2020-09-15:09:25 /hostedzone/Z01374762ZFP1521DBRNE pics2.castlerock.ai. 2 28 | CONFIG False 29 | ``` 30 | 31 | ## 02 - Update DNS Name Servers 32 | 33 | Going to our DNS service provider (Namecheap.com shown here) we update our `NS` records with the records generated from the previous call: 34 | 35 | ![update ns record](../images/r5301.png) 36 | 37 | ** NB: the image may say `pics2` or `pics`, or `photos`. As I tested different things I used different subdomains. Whichever you choose to use, make sure its just consistent and don't blindly copy and paste. 38 | 39 | ## 03 - Create R53 to S3 Record 40 | 41 | Let's now test what happens when we point our Route 53 record to the static site in S3. 42 | 43 | ![create policy](../images/r5302.png) 44 | 45 | 46 | 47 | ## 04 - Test your website 48 | 49 | Now lets make sure it comes up. It might take up to 20 before it shows up. 50 | 51 | ![s3](../images/s302.png) 52 | 53 | This is better in that we now have a real domain, hiding the Amazon domian. But we still don't have security. 54 | 55 | Let's add that next. -------------------------------------------------------------------------------- /PART02/Route53Part2.md: -------------------------------------------------------------------------------- 1 | # Route 53 - Part 2 2 | 3 | Now that we have a cloud front distribution we need to update our DNS record to point to this distribution. 4 | 5 | Click on Route 53 and edit the record `pics.castlerock.ai`. Instead of an S3 origin, we are going to point to a Cloud Front Distribution. 6 | 7 | ![change to distro](../images/r5303.png) 8 | 9 | We should now be able to see our website securely! 10 | 11 | ![website](../images/s303.png) 12 | 13 | -------------------------------------------------------------------------------- /PART02/S3.md: -------------------------------------------------------------------------------- 1 | # Serving Web Pages on S3 2 | 3 | In this lab we will create a web page that will be served on S3. 4 | 5 | ## 01. Create `index.html` 6 | 7 | Open your favorite text editor and type something up that can be saved as `index.html`. If you know HTML, then you can go nuts. Or, you can simply copy the sample [bootstrap](https://getbootstrap.com) template code below: 8 | 9 | ```html 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | Hello, from S3 21 | 22 | 23 |

Hello, from S3!

24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | ``` 33 | 34 | ## 02. Create S3 Bucket 35 | 36 | If you have a domain name registered, think of a subdomain you can do with this. In our case, we have the domain name: [https://castlerock.ai](https://castlerock.ai). We are going to create a new subdomain called [https://pics.castlerock.ai](https://pics.castlerock.ai). This will be the site our users will log into to upload photos. 37 | 38 | Create a bucket that is the name of your complete domain name: 39 | 40 | ``` 41 | aws s3 mb s3://pics.castlerock.ai 42 | ``` 43 | 44 | Now, upload `index.html` to this bucket: 45 | 46 | ``` 47 | aws s3 cp index.html s3://pics.castlerock.ai/ 48 | ``` 49 | 50 | ## 03. Serve Web Page 51 | 52 | Now that we have the `index.html` file in our bucket, let's serve it as a webpage. 53 | 54 | The command line is: 55 | 56 | ``` 57 | aws s3 website s3://pics.castlerock.ai --index-document index.html 58 | ``` 59 | Since my `REGION` is set to us-west-2, then my website is now: 60 | 61 | [http://pics.castlerock.ai.s3-website-us-west-2.amazonaws.com](http://pics.castlerock.ai.s3-website-us-west-2.amazonaws.com) 62 | 63 | Unfortunately, if we open this webpage we see the following error: 64 | 65 | ![s3 error](../images/s301.png) 66 | 67 | This is because when we uploaded the file, we didn't change the permission to public readable. This can be changed by reuploading and changing the permission: 68 | 69 | ``` 70 | aws s3 cp index.html s3://pics.castlerock.ai/ --acl public-read 71 | ``` 72 | 73 | Clicking the link now, shows our webpage: 74 | 75 | ![s3 bucket](../images/s302.png) 76 | 77 | At this point, we've configured an S3 static web page in just a couple of commands! 78 | 79 | At this point, we could point our domain name to this bucket and we would have a nice web page set up! But the problem is, and this is a big no-no, is that our webpage is using HTTP. If we are going to have users enter in their passwords and emails, we need to secure this site. 80 | In addition, since we want this to have low latency all over the world, we are going to front end it with Cloud Front. This will allow us to serve it securely and faster. 81 | 82 | -------------------------------------------------------------------------------- /PART02/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Hello, from S3 13 | 14 | 15 | 52 |
53 |

Hello! We are live on S3!

54 |

This is a sample website showing how you can host really cool stuff in S3. There are lots of benefits:

55 | 60 |

One of the perceived drawbacks is you can't host server side logic. For example, if you like using Python Flask, or server side rendering in Node, that is not something you can do here. 61 |

62 |

However, if you structure your app so the front end is separate from the backend (like React) and use an API like we are in this class, then you most certainly can still get the benefits of a backend. Just because its a static webpage, doesn't mean its a static web site!

63 | 64 |

As an example, even though this is a static site, you can still enter your name in the hello input form in the top right. Javascript still executes. This is because it is being executed on your browser. We can make the browser perform all kinds of calls remotely to our API. 65 |

66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /PART03/Cognito.md: -------------------------------------------------------------------------------- 1 | # Cognito 2 | 3 | [Cognito is AWS's simple sign up, sign in, and access control micro service](https://aws.amazon.com/cognito/). It allows you to easily add Facebook / Google sign in as well as other federated identities. For our purpose, we'll just use the normal email and password features of the service. 4 | 5 | Once we have this, we can add it into our React Application and let our users sign in. 6 | 7 | ## 01 Create User Pool 8 | 9 | Open [Cognito](https://us-west-2.console.aws.amazon.com/cognito/users/) and create a new user pool. You can call it after the name of your app, like "photos" or "pics". 10 | 11 | Once selected review the defaults and we'll make a few changes. 12 | 13 | ### Attributes 14 | 15 | We will make the email the default login since our application is set up to work this way: 16 | 17 | ![cognito](../images/cognito01.png) 18 | 19 | ### App Clients 20 | 21 | Create an App Client for this Cognito User Pool. You can call it `Web`. Be sure to uncheck 'Generate Client Secret'. 22 | 23 | ![cognito create client](../images/cognito02.png) 24 | 25 | Save the changes and the new pool will be created. 26 | 27 | ### Fill in `config.js` values with your pool 28 | 29 | Click on the new pool you created if you are not already there. click on general settings and note down the pool ID 30 | 31 | ![cognito pool id and arn](../images/cognito03.png) 32 | 33 | This should be something like `us-west-2_sL68RzWSl`. Make note of it. 34 | 35 | Next look at the `App Integration` section. Look for the App Client ID 36 | 37 | ![cognito app Id](../images/cognito04.png) 38 | 39 | It should look something like `3rltur6k60chccelkcfti5a1b5` but with other random string characters. 40 | 41 | 42 | Opening up the React code, our `src/config.js` we will add these values in: 43 | 44 | ![cognito integration](../images/cognito05.png) 45 | 46 | ## 02 Create Identity Pool 47 | Identity pools tie to user pools. They allow us to authenticate through other services like Facebook / Google, Amazon, Apple, Twitter, or simple OpenID, etc. 48 | 49 | We will create a basic one. Note the User Pool ID and App client ID you used in the previous section you will place in this section as well. 50 | 51 | ![cognito identity pool](../images/cognito06.png) 52 | 53 | We will then create a new IAM identity. At the time I was writing this IAM went down. 54 | 55 | ![IAM Down](../images/iamDown.png) 56 | 57 | This is one of the drawbacks of relying on a cloud provider, as this is out of our hands to fix. On the other hand, you will have problems if you do your own identities as well. 58 | 59 | 60 | Let Cognito create a new IAM role for you. We will modify it later, so take note of the name. 61 | 62 | 63 | ![cognito iam](../images/cognito08.png) 64 | 65 | Enter the Identity pool id into the app in `config.js` as well: 66 | 67 | ``` 68 | ... 69 | IDENTITY_POOL_ID: us-west-2:cb797dfa-8f0c-4c91-8497-4fa6e10411a4 70 | ... 71 | ``` 72 | 73 | ## 03 Sign in 74 | 75 | At this point you should be able to sign in to the web application on localhost! You'll need to sign up with a real email address so you actually get the confirmation code that is sent. Then you can log in. When you log in you'll see some errors, but we will fix those as we add more microservices on the back end. 76 | 77 | ![cognito iam](../images/cognito07.png) 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /PART03/Make.md: -------------------------------------------------------------------------------- 1 | # Makefiles 2 | 3 | If we have a CI/CD system to update our tooling, a good idea is to create a Makefile that makes pushing easy. You could create a script and there are other ways to do this, but Makefile is tried and true. You just type `make` and it pushes the commands. 4 | 5 | ## 01 Edit Makefile 6 | 7 | In the [./front-end](./front-end) directory there exists a [Makefile](./front-end/Makefile). You can just modify the S3 bucket to match your bucket. 8 | 9 | ``` 10 | .PHONY: all build push 11 | 12 | all: build push 13 | 14 | build: 15 | yarn run build 16 | 17 | push: 18 | # replace s3://photos.castlerock.ai with the name of your s3 bucket. 19 | aws s3 cp build s3://pics.castlerock.ai --recursive --acl public-read 20 | ``` 21 | 22 | This is a pretty simple Makefile. It will build the contents of the webpage and then push it up to the S3 bucket where we have our simple S3 site we created in [Part 2](../PART02). 23 | 24 | Once edited, on Linux and MacOS you can type: 25 | 26 | ``` 27 | make 28 | ``` 29 | 30 | 31 | ## 02 Invalidate CloudFront Distribution 32 | 33 | Once we update the changes we can wait the 8 hours for cloudfront to sync, or we can invalidate everything now. That way, within 5-10 minutes we see the updates instead of 8 hours. 34 | 35 | Navigate to Cloud Front, click on your distribution and select Create Invalidation. From there, just put in the `index.html` and create the invalidation. Make sure your files uploaded to S3 first as you expected. 36 | 37 | ![cf invalidation](../images/cf04.png) 38 | 39 | Invalidating can be expensive. There are other methods that may be better for your organization described [here](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Invalidation.html). -------------------------------------------------------------------------------- /PART03/React01.md: -------------------------------------------------------------------------------- 1 | # React Application 2 | 3 | There are a few ways you can run this next part. You could create your react application from scratch using something like: 4 | 5 | ``` 6 | mkdir front-end 7 | cd front-end 8 | create-react-app . 9 | ``` 10 | 11 | But instead we're going to just use the code we've already developed for this application and change parts we need to make it work with our AWS infrastructure. 12 | 13 | In this directory, you'll see there is a [front-end](./front-end) folder. This is where the code is. After downloading this and changing to this directory, we can run the following commands to start the application: 14 | 15 | ``` 16 | yarn install 17 | yarn start 18 | ``` 19 | 20 | You'll notice that it is missing a file called `config.js`. You can add this with: 21 | 22 | ``` 23 | cd src/ 24 | cp config-example.js config.js 25 | ``` 26 | And the application will start. 27 | 28 | ![application](../images/react01.png) 29 | 30 | We need to let our user log in and log out. To do that we'll have to create a micro service that handles user sign up, login , logout, etc. This is a pretty big task. Luckily, AWS has this micro service for us to use, and its called Cognito. Let's create that now. -------------------------------------------------------------------------------- /PART03/front-end/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /PART03/front-end/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all build push 2 | 3 | all: build push 4 | 5 | build: 6 | yarn run build 7 | 8 | push: 9 | # replace s3://photos.castlerock.ai with the name of your bucket. 10 | aws s3 cp build s3://photos.castlerock.ai --recursive --acl public-read 11 | -------------------------------------------------------------------------------- /PART03/front-end/README.md: -------------------------------------------------------------------------------- 1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 2 | 3 | ## Available Scripts 4 | 5 | In the project directory, you can run: 6 | 7 | ### `yarn start` 8 | 9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 11 | 12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console. 14 | 15 | ### `yarn test` 16 | 17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 19 | 20 | ### `yarn build` 21 | 22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance. 24 | 25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed! 27 | 28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 29 | 30 | ### `yarn eject` 31 | 32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 33 | 34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 35 | 36 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 37 | 38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 39 | 40 | ## Learn More 41 | 42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 43 | 44 | To learn React, check out the [React documentation](https://reactjs.org/). 45 | 46 | ### Code Splitting 47 | 48 | This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting 49 | 50 | ### Analyzing the Bundle Size 51 | 52 | This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size 53 | 54 | ### Making a Progressive Web App 55 | 56 | This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app 57 | 58 | ### Advanced Configuration 59 | 60 | This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration 61 | 62 | ### Deployment 63 | 64 | This section has moved here: https://facebook.github.io/create-react-app/docs/deployment 65 | 66 | ### `yarn build` fails to minify 67 | 68 | This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify 69 | -------------------------------------------------------------------------------- /PART03/front-end/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "front-end", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^4.2.4", 7 | "@testing-library/react": "^9.3.2", 8 | "@testing-library/user-event": "^7.1.2", 9 | "aws-amplify": "^3.0.25", 10 | "bootstrap": "^4.5.0", 11 | "react": "^16.13.1", 12 | "react-bootstrap": "^1.3.0", 13 | "react-dom": "^16.13.1", 14 | "react-redux": "^7.2.1", 15 | "react-router-dom": "^5.2.0", 16 | "react-scripts": "3.4.1", 17 | "redux-saga": "^1.1.3" 18 | }, 19 | "scripts": { 20 | "start": "react-scripts start", 21 | "build": "react-scripts build", 22 | "test": "react-scripts test", 23 | "eject": "react-scripts eject" 24 | }, 25 | "eslintConfig": { 26 | "extends": "react-app" 27 | }, 28 | "browserslist": { 29 | "production": [ 30 | ">0.2%", 31 | "not dead", 32 | "not op_mini all" 33 | ], 34 | "development": [ 35 | "last 1 chrome version", 36 | "last 1 firefox version", 37 | "last 1 safari version" 38 | ] 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /PART03/front-end/public/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vallard/MicroServicesOnAWS/52a1be28f51c4cf759fa21e7acd404b23bca0a34/PART03/front-end/public/images/logo.png -------------------------------------------------------------------------------- /PART03/front-end/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | Castle Rock Photos 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /PART03/front-end/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 | -------------------------------------------------------------------------------- /PART03/front-end/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /PART03/front-end/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 | -------------------------------------------------------------------------------- /PART03/front-end/src/App.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import Routes from './routes'; 3 | import {connect} from 'react-redux'; 4 | import {getCurrentSession} from './redux/actions'; 5 | import './App.css'; 6 | 7 | class App extends Component { 8 | componentDidMount() { 9 | this.props.getCurrentSession(); 10 | } 11 | 12 | render() { 13 | return ( 14 | 15 | ); 16 | } 17 | } 18 | 19 | const mapStateToProps = state => ({ 20 | isAuthenticated: state.Auth.isAuthenticated, 21 | }); 22 | 23 | const mapDispatchToProps = dispatch => ({ 24 | getCurrentSession: () => dispatch(getCurrentSession()), 25 | }); 26 | 27 | export default connect(mapStateToProps, mapDispatchToProps)(App); 28 | -------------------------------------------------------------------------------- /PART03/front-end/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import App from './App'; 4 | 5 | test('renders learn react link', () => { 6 | const { getByText } = render(); 7 | const linkElement = getByText(/learn react/i); 8 | expect(linkElement).toBeInTheDocument(); 9 | }); 10 | -------------------------------------------------------------------------------- /PART03/front-end/src/components/Confirm.js: -------------------------------------------------------------------------------- 1 | import React, {useState} from 'react'; 2 | import Button from 'react-bootstrap/Button'; 3 | import Form from 'react-bootstrap/Form'; 4 | import Spinner from 'react-bootstrap/Spinner'; 5 | 6 | const Confirm = ({action, loading}) => { 7 | const [code, setCode] = useState(''); 8 | const submit = (e) => { 9 | console.log("did submit."); 10 | console.log("code: ", code); 11 | e.preventDefault(); 12 | console.log(action); 13 | action( code ); 14 | } 15 | return ( 16 |
17 | 18 | setCode(e.target.value)}/> 19 | 20 | 27 |
28 | ) 29 | }; 30 | export default Confirm; 31 | -------------------------------------------------------------------------------- /PART03/front-end/src/components/Error.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Alert from 'react-bootstrap/Alert'; 3 | const Error = ({error}) => { 4 | return( 5 | 6 | {error.toString()} 7 | 8 | ) 9 | } 10 | export default Error; 11 | -------------------------------------------------------------------------------- /PART03/front-end/src/components/Footer.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Footer = () => ( 4 |
5 |
6 |

© 2020 Castle Rock Data LLC - PhotoBook

7 |
8 |
9 | ); 10 | 11 | export default Footer 12 | -------------------------------------------------------------------------------- /PART03/front-end/src/components/Home.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Button from 'react-bootstrap/Button'; 3 | import Form from 'react-bootstrap/Form'; 4 | import Spinner from 'react-bootstrap/Spinner'; 5 | import Card from 'react-bootstrap/Card'; 6 | import S3Image from './S3Image'; 7 | 8 | const Home = ({photos, uploadFunc, delFunc, loading}) => { 9 | return ( 10 |
11 | {loading && 12 |
13 | 14 |
15 | } 16 |
17 |
18 | {photos && photos.photos && photos.photos.map( (photo, i) => ( 19 | 20 | 21 | 22 | { photo.objects == null ? 23 |
24 | : 25 |
26 | Detected Objects 27 | { photo.objects.map( (obj, j) => ( 28 |
{obj.item} {parseFloat(obj.score).toFixed(2)}%
29 | ))} 30 |
31 | } 32 |
33 | 36 |
37 |
38 |
39 | ))} 40 |
41 |
42 | 43 | 44 | 45 |
46 |
47 | )}; 48 | 49 | export default Home 50 | -------------------------------------------------------------------------------- /PART03/front-end/src/components/Navi.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Navbar from 'react-bootstrap/Navbar'; 3 | import Nav from 'react-bootstrap/Nav'; 4 | import NavDropdown from 'react-bootstrap/NavDropdown'; 5 | import Error from './Error'; 6 | 7 | const Navi = ({user, signOutFunc, errors}) => ( 8 | <> 9 | 10 |
11 | 12 | photobook 13 | Photos 14 | 15 | 16 | 17 | 24 | 25 |
26 |
27 | {errors && 28 |
29 |
30 | 31 |
32 | } 33 | 34 | ); 35 | 36 | export default Navi 37 | 38 | -------------------------------------------------------------------------------- /PART03/front-end/src/components/NotFound.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const NotFound = () => ( 4 |
5 |

Hmm....

6 |

7 | This page doesn't seem to exist... Or does it? Cause here we are. So it must exist. But really, it doesn't. 8 |

9 | Take Me Home! 10 |
11 | ); 12 | 13 | export default NotFound; 14 | -------------------------------------------------------------------------------- /PART03/front-end/src/components/S3Image.js: -------------------------------------------------------------------------------- 1 | import React, {useState} from 'react'; 2 | import {Storage} from 'aws-amplify'; 3 | import Card from 'react-bootstrap/Card'; 4 | 5 | const S3Image = ({photoName}) => { 6 | const [imageURL, setImageURL] = useState("") 7 | 8 | Storage.get(photoName, {level: 'private'}) 9 | .then(result => setImageURL(result)); 10 | 11 | return ( 12 | <> 13 | { imageURL === "" ? 14 | null 15 | : 16 | <> 17 | {/*{photoName} */} 18 | 19 | 20 | } 21 | 22 | )}; 23 | 24 | export default S3Image 25 | -------------------------------------------------------------------------------- /PART03/front-end/src/components/SignInForm.js: -------------------------------------------------------------------------------- 1 | import React, {useState} from 'react'; 2 | import Button from 'react-bootstrap/Button'; 3 | import Form from 'react-bootstrap/Form'; 4 | import Spinner from 'react-bootstrap/Spinner'; 5 | 6 | const SignInForm = ({action, loading, sub}) => { 7 | const [email, setEmail] = useState(''); 8 | const [password, setPassword] = useState(''); 9 | const submit = (e) => { 10 | e.preventDefault(); 11 | action(email, password); 12 | } 13 | return ( 14 |
15 | 16 | setEmail(e.target.value)}/> 17 | We'll never share your email. 18 | 19 | 20 | setPassword(e.target.value)} /> 21 | 22 | 29 |
30 | ) 31 | }; 32 | export default SignInForm; 33 | -------------------------------------------------------------------------------- /PART03/front-end/src/config-example.js: -------------------------------------------------------------------------------- 1 | const cognitoSettings = { 2 | REGION: "us-west-2", 3 | USER_POOL_ID: "YOUR_POOL", 4 | APP_CLIENT_ID: "YOUR_APP_CLIENT_ID", 5 | IDENTITY_POOL_ID: "IDENTITY_POOL_ID", 6 | } 7 | 8 | const dev = { 9 | cognito: cognitoSettings, 10 | apiGateway: { 11 | REGION: "us-west-2", 12 | URL: "YOUR_DEV_API_GATEWAY" 13 | }, 14 | s3: { 15 | BUCKET: "DEV_PHOTO_S3_BUCKET", 16 | REGION: "us-west-2", 17 | } 18 | } 19 | 20 | const prod = { 21 | cognito: cognitoSettings, 22 | apiGateway: { 23 | REGION: "us-west-2", 24 | URL: "PROD_API_GATEWAY", 25 | }, 26 | s3: { 27 | BUCKET: "PROD_PHOTO_S3_BUCKET", 28 | REGION: "us-west-2", 29 | } 30 | } 31 | 32 | export const APINAME="photos" 33 | 34 | export default process.env.NODE_ENV === "development" ? dev : prod; 35 | -------------------------------------------------------------------------------- /PART03/front-end/src/containers/Auth/SignUp.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { Link, Redirect } from 'react-router-dom'; 4 | import { 5 | signUp, 6 | confirmSignUp, 7 | } from '../../redux/actions'; 8 | 9 | import Error from '../../components/Error'; 10 | import Confirm from '../../components/Confirm'; 11 | import SignInForm from '../../components/SignInForm'; 12 | 13 | const Confirmation = ({loading, action}) => { 14 | 15 | return( 16 | <> 17 |

Please check your email for the confirmation code that was generated. If you don't see an email from us, please check your junk folder or contact support@castlerock.ai 18 |

19 | 23 | 24 | ) 25 | } 26 | 27 | 28 | 29 | 30 | class SignUp extends Component { 31 | 32 | render() { 33 | return ( 34 | <> 35 | { this.props.isAuthenticated && } 36 |
37 |

Castle Rock Photos

38 | { this.props.error && 39 | 40 | } 41 | 42 | { this.props.user ? 43 | { 46 | console.log("code: ", code, this.props.user, this.props.history); 47 | this.props.confirmSignUp(this.props.user, code, this.props.history) 48 | }} 49 | /> 50 | : 51 | <> 52 |

Sign Up to store all your photos.

53 | 58 |
59 | Already have an account? Sign In 60 | 61 | } 62 |
63 | 64 | ) 65 | } 66 | }; 67 | 68 | const mapStateToProps = state => ({ 69 | isAuthenticated: state.Auth.isAuthenticated, 70 | user: state.Auth.user, 71 | loading: state.Auth.loading, 72 | error: state.Auth.error, 73 | }); 74 | 75 | const mapDispatchToProps = dispatch => ({ 76 | signUp: (user, password) => dispatch(signUp(user, password)), 77 | confirmSignUp: (user, code, history) => dispatch(confirmSignUp(user, code, history)), 78 | }); 79 | 80 | 81 | export default connect(mapStateToProps, mapDispatchToProps)(SignUp); 82 | -------------------------------------------------------------------------------- /PART03/front-end/src/containers/Photos.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import {connect} from 'react-redux'; 3 | import Home from '../components/Home'; 4 | import Navi from '../components/Navi'; 5 | import Footer from '../components/Footer'; 6 | import { 7 | delPhoto, 8 | getPhotos, 9 | signOut, 10 | upPhoto, 11 | } from '../redux/actions'; 12 | 13 | class Photos extends Component { 14 | 15 | componentDidMount() { 16 | this.props.getPhotos() 17 | } 18 | 19 | uploadPhoto = (e) => { 20 | var f = e.target; 21 | if (f.files[0]) { 22 | const file = f.files[0] 23 | this.props.upPhoto(file); 24 | } 25 | } 26 | 27 | deletePhoto = (e) => { 28 | const photoId = e.target.id 29 | this.props.delPhotos(photoId); 30 | } 31 | 32 | render() { 33 | return ( 34 | <> 35 | {/*console.log(this.props.photos)*/} 36 | 37 | 42 |