├── .github └── workflows │ ├── build.yml │ └── deploy.yml ├── .gitignore ├── .idea ├── .gitignore ├── aws.xml ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── compiler.xml ├── encodings.xml ├── jarRepositories.xml ├── misc.xml └── vcs.xml ├── .mvn └── wrapper │ └── maven-wrapper.properties ├── HELP.md ├── README.md ├── elasticbeanstalk └── docker-compose.yaml ├── mvnw ├── mvnw.cmd ├── package-lock.json ├── package.json ├── pom.xml ├── resources ├── architecture.jpg ├── aws-configure-database.png ├── aws-deployed-application.png ├── aws-environments.png ├── aws-rds-add-inbounded-rules.png ├── aws-rds-config.png ├── aws-rds-external-connection.png ├── aws-rds-screen.png ├── aws-rds-vcp-screen.png ├── aws-rds.png ├── cicd-message1.png ├── cicd-message2.png ├── cicd-message3.png ├── cicd-message4.png ├── deploy-to-dockerhub.png ├── github-actions.png └── unit_testing_uml_diagram.png ├── src ├── frontend │ ├── .gitignore │ ├── README.md │ ├── node │ │ ├── node │ │ ├── npm │ │ ├── npm.cmd │ │ ├── npx │ │ └── npx.cmd │ ├── package-lock.json │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── logo192.png │ │ ├── logo512.png │ │ ├── manifest.json │ │ └── robots.txt │ └── src │ │ ├── App.css │ │ ├── App.js │ │ ├── App.test.js │ │ ├── Client.js │ │ ├── Notification.js │ │ ├── StudentDrawerForm.js │ │ ├── index.css │ │ ├── index.js │ │ ├── logo.svg │ │ ├── reportWebVitals.js │ │ └── setupTests.js ├── main │ ├── java │ │ └── com │ │ │ └── syscomz │ │ │ └── springbootfullstackprofessional │ │ │ ├── SpringBootFullStackProfessionalApplication.java │ │ │ └── student │ │ │ ├── Gender.java │ │ │ ├── Student.java │ │ │ ├── StudentController.java │ │ │ ├── StudentRepository.java │ │ │ ├── StudentService.java │ │ │ └── exception │ │ │ ├── BadRequestException.java │ │ │ └── StudentNotFoundException.java │ └── resources │ │ ├── application-dev.properties │ │ └── application.properties └── test │ ├── java │ └── com │ │ └── syscomz │ │ └── springbootfullstackprofessional │ │ ├── SpringBootFullStackProfessionalApplicationTests.java │ │ ├── integration │ │ └── StudentIT.java │ │ └── student │ │ ├── StudentRepositoryTest.java │ │ └── StudentServiceTest.java │ └── resources │ ├── application-it.properties │ └── application.properties └── student.sql /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | # Start CI only on pull request against the main branch 4 | on: 5 | pull_request: 6 | branches: [ main ] 7 | 8 | # This workflow dispatch, allows us to execute this workflow manually 9 | workflow_dispatch: 10 | 11 | # Postgresql data is coming from application.properties file, because of CI execution, which means we are not deploy this data to the server 12 | env: 13 | POSTGRESQL_VERSION: 13.7 14 | POSTGRESQL_DB: syscomz 15 | POSTGRESQL_USER: postgres 16 | POSTGRESQL_PASSWORD: password 17 | JAVA_VERSION: 11 18 | 19 | jobs: 20 | # build means sequence of steps, it contains all the steps on the workflow 21 | build: 22 | runs-on: ubuntu-latest 23 | services: 24 | postgres: 25 | image: postgres:13.7 26 | env: 27 | POSTGRES_DB: ${{ env.POSTGRESQL_DB }} 28 | POSTGRES_USER: ${{ env.POSTGRESQL_USER }} 29 | POSTGRES_PASSWORD: ${{ env.POSTGRESQL_PASSWORD }} 30 | ports: 31 | - 5432:5432 32 | options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 33 | steps: 34 | - uses: actions/checkout@v2 35 | - uses: actions/setup-java@v1.4.3 36 | with: 37 | java-version: ${{ env.JAVA_VERSION }} 38 | - name: Maven Clean Package 39 | run: | 40 | ./mvnw --no-transfer-progress clean package -P bundle-backend-and-frontend -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: CI/CD 2 | 3 | # Start CI/CD only on pull request against the main branch 4 | on: 5 | push: 6 | branches: [ main ] 7 | 8 | # This workflow dispatch, allows us to execute this workflow manually 9 | workflow_dispatch: 10 | 11 | # Postgresql data is coming from application.properties file, because of CI/CD execution, which means we are not deploy this data to the server 12 | env: 13 | # Postgres 14 | POSTGRESQL_VERSION: 13.7 15 | POSTGRESQL_DB: syscomz 16 | POSTGRESQL_USER: syscomz 17 | POSTGRESQL_PASSWORD: password 18 | # Java 19 | JAVA_VERSION: 11 20 | # DockerHub 21 | DOCKER_HUB_USERNAME: bdostumski 22 | # AWS & Elastic Bean Stalk 23 | AWS_REGION: eu-west-2 24 | # Get Environment and Application names from AWS 25 | EB_ENVIRONMENT_NAME: Fullstackapplication-env 26 | EB_APPLICATION_NAME: fullstack-application 27 | # This is my local docker-compose.yaml 28 | EB_DEPLOYMENT_PACKAGE: elasticbeanstalk/docker-compose.yaml 29 | 30 | jobs: 31 | deploy: 32 | runs-on: ubuntu-latest 33 | services: 34 | postgres: 35 | image: postgres:13.7 36 | env: 37 | POSTGRES_DB: ${{ env.POSTGRESQL_DB }} 38 | POSTGRES_USER: ${{ env.POSTGRESQL_USER }} 39 | POSTGRES_PASSWORD: ${{ env.POSTGRESQL_PASSWORD }} 40 | ports: 41 | - 5432:5432 42 | options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 43 | steps: 44 | - uses: 8398a7/action-slack@v3 # Allow us to send Slack notifications 45 | with: 46 | status: ${{ job.status }} 47 | fields: repo,message,commit,author,action,eventName,ref,workflow,job,took # this is the tabs that will be showed into slack message 48 | text: 'CICD ongoing... :eyes:' # This is the title of the message 49 | env: 50 | SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} # comes from GitHub secrets 51 | 52 | - uses: actions/checkout@v2 # Checkout the code 53 | 54 | - name: Setup Java JDK 55 | uses: actions/setup-java@v1.4.3 56 | with: 57 | java-version: ${{ env.JAVA_VERSION }} 58 | 59 | - name: Build Number 60 | id: build-number 61 | run: echo "::set-output name=BUILD_NUMBER::$(date '+%-d.%-m.%Y.%-H.%-M.%-S')" # this is the build number of our image as date 62 | 63 | - name: Docker Login 64 | uses: docker/login-action@f3364599c6aa293cdc2b8391b1b56d0c30e45c8a # Docker login action 65 | with: 66 | username: ${{ env.DOCKER_HUB_USERNAME }} 67 | password: ${{ secrets.DOCKER_HUB_PASSWORD }} # comes from GitHub secrets created from me 68 | 69 | - name: Maven Clean Package and Push to Dockerhub 70 | # this is maven profiles to bundle FE and BE applications and to build and deploy docker image 71 | # line 75 come from code in line 59 72 | run: | 73 | ./mvnw --no-transfer-progress clean package \ 74 | -P bundle-backend-and-frontend \ 75 | -P jib-build-docker-image-and-push-it-to-docker-hub \ 76 | -Dapp.image.tag=${{steps.build-number.outputs.BUILD_NUMBER}} 77 | - uses: 8398a7/action-slack@v3 # trigger Slack message 78 | with: 79 | status: ${{ job.status }} # with status about the task 80 | fields: repo,message,commit,author,action,eventName,ref,workflow,job,took # tags into our message 81 | text: ':white_check_mark: pushed bdostumski/springboot-react-fullstack:${{steps.build-number.outputs.BUILD_NUMBER}} to docker hub... https://hub.docker.com/repository/docker/bdostumski/springboot-react-fullstack' 82 | env: 83 | SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} # comes from GitHub secrets 84 | - name: Update and commit app version in docker-compose.yaml 85 | run: | 86 | BUILD_NUMBER=${{steps.build-number.outputs.BUILD_NUMBER}} 87 | echo -e "Current elasticbeanstalk/docker-compose.yaml\n$(cat elasticbeanstalk/docker-compose.yaml)" 88 | sed -i -E 's_(bdostumski/springboot-react-fullstack:)([^"]*)_\1'${BUILD_NUMBER}'_' elasticbeanstalk/docker-compose.yaml 89 | echo -e "Current elasticbeanstalk/docker-compose.yaml\n$(cat elasticbeanstalk/docker-compose.yaml)" 90 | git config user.name github-actions 91 | git config user.email github-actions@github.com 92 | git add elasticbeanstalk/docker-compose.yaml 93 | git commit -m "new app version: ${BUILD_NUMBER}" 94 | git push 95 | 96 | - uses: 8398a7/action-slack@v3 # Another Slack message 97 | with: 98 | status: ${{ job.status }} 99 | fields: repo,message,commit,author,action,eventName,ref,workflow,job,took 100 | text: ':elasticbeanstalk: :aws: Deployment started... :grimacing: :crossed_fingers:' 101 | env: 102 | SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} # comes from GitHub secrets 103 | - name: Beanstalk Deploy 104 | uses: einaregilsson/beanstalk-deploy@ebe3476a4ce991d54336935e75e78dd9d86f9408 # beanstalk deploy 105 | with: 106 | aws_access_key: ${{ secrets.AWS_ACCESS_KEY_ID }} # comes from GitHub secrets 107 | aws_secret_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} # comes from GitHub secrets 108 | region: ${{ env.AWS_REGION }} 109 | environment_name: ${{ env.EB_ENVIRONMENT_NAME }} 110 | application_name: ${{ env.EB_APPLICATION_NAME }} 111 | deployment_package: ${{ env.EB_DEPLOYMENT_PACKAGE }} 112 | version_label: ${{ steps.build-number.outputs.BUILD_NUMBER }} 113 | version_description: Version ${{steps.build-number.outputs.BUILD_NUMBER}} deployed via github actions ${{ github.sha }} 114 | wait_for_deployment: 60 115 | - uses: 8398a7/action-slack@v3 # End of the job Slack message 116 | with: 117 | status: ${{ job.status }} 118 | fields: repo,message,commit,author,action,eventName,ref,workflow,job,took 119 | text: ':tada: :arrow_right: Springbootreactfullstack-env.eba-bvvehcpg.eu-west-2.elasticbeanstalk.com' 120 | env: 121 | SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} # comes from GitHub secrets 122 | if: always() # if the build fails the line from line 115 to the end will always triggered -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | *.ipr 3 | *.iws 4 | *.jar 5 | *.sw? 6 | *~ 7 | .#* 8 | .*.md.html 9 | .DS_Store 10 | .attach_pid* 11 | .classpath 12 | .factorypath 13 | .gradle 14 | .idea 15 | .metadata 16 | .project 17 | .recommenders 18 | .settings 19 | .springBeans 20 | .vscode 21 | /code 22 | MANIFEST.MF 23 | _site/ 24 | activemq-data 25 | bin 26 | build 27 | !/**/src/**/bin 28 | !/**/src/**/build 29 | build.log 30 | dependency-reduced-pom.xml 31 | dump.rdb 32 | interpolated*.xml 33 | lib/ 34 | manifest.yml 35 | out 36 | overridedb.* 37 | target 38 | .flattened-pom.xml 39 | secrets.yml 40 | .gradletasknamecache 41 | .sts4-cache 42 | .idea/ 43 | .vscode/ 44 | node_modules/ 45 | build/ 46 | .DS_Store 47 | *.tgz 48 | my-app* 49 | template/src/__tests__/__snapshots__/ 50 | lerna-debug.log 51 | npm-debug.log* 52 | yarn-debug.log* 53 | yarn-error.log* 54 | /.changelog 55 | .npm/ 56 | 57 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/aws.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip 18 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar 19 | -------------------------------------------------------------------------------- /HELP.md: -------------------------------------------------------------------------------- 1 | # Read Me First 2 | The following was discovered as part of building this project: 3 | 4 | * The original package name 'com.syscomz.spring-boot-full-stack-professional' is invalid and this project uses 'com.syscomz.springbootfullstackprofessional' instead. 5 | 6 | # Getting Started 7 | 8 | ### Reference Documentation 9 | For further reference, please consider the following sections: 10 | 11 | * [Official Apache Maven documentation](https://maven.apache.org/guides/index.html) 12 | * [Spring Boot Maven Plugin Reference Guide](https://docs.spring.io/spring-boot/docs/2.7.6/maven-plugin/reference/html/) 13 | * [Create an OCI image](https://docs.spring.io/spring-boot/docs/2.7.6/maven-plugin/reference/html/#build-image) 14 | * [Spring Web](https://docs.spring.io/spring-boot/docs/2.7.6/reference/htmlsingle/#web) 15 | 16 | ### Guides 17 | The following guides illustrate how to use some features concretely: 18 | 19 | * [Building a RESTful Web Service](https://spring.io/guides/gs/rest-service/) 20 | * [Serving Web Content with Spring MVC](https://spring.io/guides/gs/serving-web-content/) 21 | * [Building REST services with Spring](https://spring.io/guides/tutorials/rest/) 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Full Stack Spring Boot & React (PROFESSIONAL) 2 | 3 | ## Software Description 4 | - Describe the software main aim 5 | 6 | ## Software Architecture Description 7 | - Spring Boot Backend API 8 | - Frontend with React.js Hooks and Functions Components 9 | - Maven Build Tool 10 | - Databases using Postgres on Docker 11 | - Spring Data JPA 12 | - Server and Client Side Error Handling 13 | - Packaging applications for deployment using Docker and Jib 14 | - AWS RDS & Elastic Beanstalk 15 | - Software Deployment Automation with GitHub Actions 16 | - Software Deployment Monitoring with Slack 17 | - Unit and Integration Testing 18 | 19 | ## Software Architecture 20 | !["Software Architecture Image"](./resources/architecture.jpg) 21 | 22 | ## Links 23 | - [Spring.io](https://spring.io/projects/spring-framework) | The Spring Framework is an application framework and inversion of control container for the Java platform 24 | - [Start.Spring.io](https://start.spring.io/) | Spring Initializer 25 | - [React.js](https://reactjs.org/) | A JavaScript library for building user interfaces 26 | - [Node.js](https://nodejs.org/en/) | Node.js is an open-source, cross-platform JavaScript runtime environment. 27 | - [Create React App with npm](https://www.npmjs.com/package/create-react-app) 28 | - [Create React App](https://github.com/facebook/create-react-app) 29 | - [Ant Design](https://ant.design/) | A design system for enterprise-level products. Create an efficient and enjoyable work experience. 30 | - [Ant Design Use in create-react-app](https://ant.design/docs/react/use-with-create-react-app) 31 | - [React Bootstrap](https://react-bootstrap.github.io/) | By relying entirely on the Bootstrap stylesheet for UI design, React-Bootstrap just works with the thousands of Bootstrap themes you already love. 32 | - [Unfetch](https://github.com/developit/unfetch) | Bare minimum 500b fetch polyfill. 33 | - [Axios](https://github.com/axios/axios) | Promise based HTTP client for the browser and node.js 34 | - [Cross-Origin Resource Sharing (CORS)](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) 35 | - [Draw.io](https://app.diagrams.net/) 36 | - [frontend-maven-plugin](https://github.com/eirslett/frontend-maven-plugin) | is used to automate the bundle process between FE and BE projects int one project. 37 | - [maven-resources-plugin](https://maven.apache.org/plugins/maven-resources-plugin/dependency-info.html) | is used to copy **./built** directory from our FE project into **./resources/static** directory from our BE project 38 | - [Docker Hub](https://hub.docker.com/) | Docker containers repository 39 | - [Jib](https://github.com/GoogleContainerTools/jib) | Containerize your Java application 40 | - [Jib FAQs in case of issues](https://github.com/GoogleContainerTools/jib/blob/master/docs/faq.md#what-should-i-do-when-the-registry-responds-with-unauthorized) | If you have any issues with jib refer to this link 41 | - [AWS Registration Page](https://aws.amazon.com/free/?all-free-tier.sort-by=item.additionalFields.SortRank&all-free-tier.sort-order=asc&awsf.Free%20Tier%20Types=*all&awsf.Free%20Tier%20Categories=*all) | Create AWS Account 42 | - [Docker Compose](https://docs.docker.com/compose/) | Compose is a tool for defining and running multi-container Docker application. 43 | - [Docker Compose Versions](https://docs.docker.com/compose/compose-file/compose-file-v3/) | Compose file version 3 reference 44 | - [Terminate Elastic Beanstalk Environment](https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/using-features.terminating.html) 45 | - [Restore Elastic Beanstalk Environment](https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/environment-management-rebuild.html) 46 | - [Docker image Postgres](https://hub.docker.com/_/postgres) | Postgres Image from docker hub repository 47 | - [Mockaroo](https://www.mockaroo.com/) | Generate fake data based on your production data 48 | - [GitHub](https://github.com/features/actions) | Automate your workflow from idea to production 49 | - [Slack](https://slack.com/) | Slack team messaging system 50 | - [Slack build own apps](https://api.slack.com/apps) | Slack build own app that will automate messaging notifications in CI/CD process 51 | - [Junit 5](https://junit.org/junit5/docs/current/user-guide/#writing-tests-parameterized-tests) | testing framework for Java and the JVM 52 | - [AssertJ](https://assertj.github.io/doc/) | AssertJ 53 | - [H2 Database](https://www.h2database.com/html/main.html) | H2 In-memory Database 54 | - [Failsafe Plugin](https://maven.apache.org/surefire/maven-failsafe-plugin/) | The Failsafe Plugin is designed to run integration tests. 55 | - [Faker](https://github.com/DiUS/java-faker) | Randomly generate fake data, which will be used into integration tests 56 | 57 | ## Cheat Sheet 58 | - npm -g i npx | global install npx 59 | - npx create-react-app@4.0.3 frontend | choose react version 60 | - npx create-react-app frontend 61 | - npm start | run react application 62 | - HOST=0.0.0.0 npm start | run react application on specific host 63 | - npm install --save antd@4.13.0 | install dependency with specific version 64 | - npm i -S unfetch | install dependency 65 | - npm install --save @ant-design/icons 66 | - npm run build | build FE project 67 | - ./mvnw | in the root directory of the BE project this command will run **maven** the acronym of **mvnw is maven wrapper** 68 | - ./mvnw clean | remove the **target** folder int our BE application 69 | - ./mvnw clean install | it will clean the project and then at the end will build the JAR file 70 | - docker image ls | show docker images 71 | - docker ps | show running docker images 72 | - ./mvnw jib:dockerBuild -Djib.to.image=fullstack:v1 | Jib create docker image with name **fullstack** and version **v1** 73 | - ./mvnw clean install jib:dockerBuild -Djib.to.image=fullstack:v1 | in case if the **./target** folder is empty and Jar file was not created 74 | - docker run --name fullstack -p 8080:8080 fullstack:v1 | this command says to run docker image with process name fullstack to use port 8080 exposed from port 8080 from the image and name of the image packet is fullstack:v1 75 | - docker ps -a | show all containers (without -a shows just running) 76 | - docker rm -f fullstack | delete docker process with name fullstack 77 | - docker image rm image_name:tag_version | delete docker image 78 | - docker login | login into docker hub account from your terminal 79 | - ./mvnw clean install jib:build -Djib.to.image=bdostumski/spring-react-fullstack:v1 | maven create **./target** folder use **Jib** to create docker image **bdostumski** is my username into docker hub repository after that is the name of the actual tag. 80 | - ./mvnw clean install jib:build -Djib.to.image=bdostumski/spring-react-fullstack:lates -Djib.to.auth.username=bdostumski -Djib.to.auth.password=my-secret-password | this is same as above but also pass the authentication data 81 | - docker pull bdostumski/spring-react-fullstack | without the version it will pull the latest version of the iamge 82 | - ./mvnw help:active-profiles | shows all active profile 83 | - ./mvnw clean install -P build-frontend -P jib-push-to-dockerhub -Dapp.image.tag=1 | used the two custom created -P profiles (1. bundle FE and BE and build JAR file, 2. create docker images one with tag identity 1 and another **latest** version and push them to docker hub repository) 84 | - ./mvnw clean install -P build-frontend -P jib-push-to-dockerhub -Dapp.image.tag=2 | push to docker hub 85 | - ./mvnw clean install -P build-frontend -P jib-push-to-local -Dapp.image.tag=latest | create local docker image 86 | - docker run --rm -p 8080:8080 bdostumski/springboot-react-fullstack:latest | run docker image and when it is close will be removed from ```docker ps -a``` registers 87 | - docker network create db | create network with name **db** on which will communicate docker containers 88 | - docker network rm db | delete docker network with name **db** 89 | - docker run --name db -p 5432:5432 --network=db -v "$PWD:/var/lib/postgresql/data" -e POSTGRES_PASSWORD=password -d postgres:alpine | run docker container with name db expose it on port 5423 use network with name db and volume $PWD (current directory) map it to the /var/lib/postgresql/data which is linux directory on the container use environment variable POSTGRES_PASSWORD start it in detached mode and use alpine version of postgres docker container 90 | - docker run -it --rm --network=db postgres:alpine psql -h db -U postgres | run docker container in **it** interactive mode, **rm** kill the process when the application is closed, use network with name db, use postgres:alpine image, **-h** the host is db this is the name of the other package, **-U** use default user postgres, 91 | 92 | #### NPM 93 | npm install --> install all dependencies described from package.json file, into node_modules directory
94 | npm install --save \[dependency@version\] --> install new dependency
95 | npm run build --> build FE application, create build directory
96 | HOST=0.0.0.0 npm start --> run FE project
97 | 98 | #### Java 99 | java -jar file.jar --> running jar file, from target folder
100 | 101 | #### Maven .mvnw (maven wrapper) 102 | ./mvnw clean --> delete target folder
103 | ./mvnw install --> create target folder, maven will use the active profile to build the project, even if the profile includes to build the FE project too.
104 | ./mvnw clean install --> combine above commands, maven will use the active profile to build the project, even if the profile includes to build the FE project too.
105 | 106 | #### Maven, Profiles and Automate FE and BE bundling 107 | https://github.com/eirslett/frontend-maven-plugin --> this plugin use npm to install and build FE project, it is started when maven run install command into active maven profile described into pom.xml file
108 | https://maven.apache.org/plugins/maven-resources-plugin/plugin-info.html --> automate copy FE build folder into static folder in our BE project, as part of the maven profile and above maven plugin
109 | 110 | #### Docker and Jib 111 | https://github.com/GoogleContainerTools/jib --> Jib builds optimized Docker and OCI (Open Container Initiative) images for our Java applications without Docker daemon.
112 |
113 | ./mvnw jib:dockerBuild -Djib.to.image=image-name:version --> Jib build LOCAL DOCKER IMAGE from our application
114 | ./mvnw clean install jib:dockerBuild -Djib.to.image=image-name:version --> If the jar file is missing, first create jar file and then build LOCAL DOCKER IMAGE
115 |
116 | ./mvnw clean install jib:build -Djib.to.image=bdostumski/spring-react-fullstack:v1 --> build jar file, containerized it into docker image and push it to the docker hub repository
117 | ./mvnw clean install jib:build -Djib.to.image=bdostumski/spring-react-fullstack:latest -Djib.to.auth.username=bdostumski -Djib.to.auth.password=**** --> same as above but with login credentials
118 |
119 | ./mvnw help:active-profiles --> show all active profiles
120 |
121 | ./mvnw clean install -P bundle-backend-and-frontend -P jib-build-docker-image-and-push-it-to-docker-hub -Dapp.image.tag=2.3 --> build jar file, dockerized it and push it to docker hub repository
122 | ./mvnw clean install -P bundle-backend-and-frontend -P jib-build-local-docker-image -Dapp.image.tag=2.3 --> build jar file, and create docker image
123 |
124 | #### Bundle FE and BE locally and build docker image with Jib and push it to the DockerHub repository 125 | ./mvnw clean install -P bundle-backend-and-frontend -P jib-build-docker-image-and-push-it-to-docker-hub -Dapp.image.tag=3 -- use -P (for profile) bundle-backend-and-frontend to bundle FE and BE locally, and then run -P (for profile) bundle-backend-and-frontend to use Jib to create docker image and to push it into DockerHub repository, and then set up the version of the docker image -Dapp.image.tag=3 (where app.image.tag is environment variable)
126 |
127 | docker login --> login into docker repository
128 | docker image ls | docker images --> show images in our local machine
129 | docker ps --> show running containers
130 | docker ps -a --> show all containers
131 | docker rm -f --> force the removal of a running containers (uses SIGKILL)
132 | docker run --name image-name -p 8080:8080 image:version
133 | docker run --rm --name image-name -p 8080:8080 image:version
134 | docker pull image:version --> pull image from remove repository
135 |
136 | #### Docker and databases 137 | docker network create db
138 | docker network rm db
139 | docker run --name db -p 5555:5432 --network=db -v "/path/to/database-dir:/var/lib/postgresql/data" -e POSTGRES_PASSWORD=password -d postgres:alpine // when use $PWD you must be in the directory, that you want to be used for database
140 | docker run -it --rm --network=db postgres:alpine psql -h db -U postgres
141 | docker run -it --rm postgres:alpine psql -h aa9320n4muma7h.celswdmxhcr1.eu-west-1.rds.amazonaws.com -U amigoscode -d postgres // connect to AWS RDS database, after setup AWS Security Group to allow external login to it. -h this is the path to the database that can be found into Elastic Beanstalk, log into environment and click configuration, find database tab copy Endpoint without the port
142 | 143 | ## Notes 144 | 1. React & Functional Components 145 | 1. React project structure: 146 | 1. Main folder 147 | - ./node_modules | (Folder) Contains all the dependencies that are needed for an initial working react app 148 | - ./gitignore | (File) This file specifies intentionally untracked files that Git should ignore 149 | - ./package.json | (File) This file contains various metadata that is relevant to our project. It specifies the dependencies being used in the project which helps npm setup same environment on different machine for our project. 150 | - ./README.md | (File) This file can be used to define usage, build instructions, summary of project, etc. It uses markdown markup language to create content. 151 | - ./yarn.log | (File) This file has same purpose as package-lock.json, it ensures that your package is consistent across various machines by storing the versions of which dependencies are installed with your package. 152 | 2. Public folder 153 | - ./public | (Folder) Root folder that gets served up as our react app. 154 | - ./public/favicon.icon | (File) It’s an icon file that is used in index.html as favicon. 155 | - ./public/index.html | (File) It is the template file which is served up when we run start script to launch our app. It is considered best practice not to create multiple html file in public folder instead use this file and inject react components in this file’s root div container. Other css libraries, etc can be defined in this files. 156 | - ./public/logo192.png & logo512.png | (Files) These are react logo which is of dimension 192\*192 px and 512\*512 px and is used in the template file (index.html) by default. \[can be removed once you are using your own component\] 157 | - ./public/manifest.json | (File) It’s used to define our app, mostly contains metadata. Used by mobile phones to add web app to the home-screen of a device. Part of PWA. 158 | - ./robots.txt | (File) Defines rules for spiders, crawlers and scrapers for accessing your app. 159 | 3. Src folder 160 | - ./src | (Folder) In simplest form it’s our React app folder i.e. containing components, tests, css files etc. It’s the mind of our app. 161 | - ./src/App.css | (File) Contains styles of our React component(App.js) 162 | - ./src/App.js | (File) This file has very basic react component defined which can be replaced by our own root component 163 | - ./src/App.test.js | (File) A very basic test(for the default app) is defined in this file which can be replaced by our own tests. \[make use of Jest\] 164 | - ./src/index.css | (File) Contains styles for general setup of our app. 165 | - ./src/index.js | (File) This files renders our component and registers service workers(unregistered by default) 166 | - ./src/logo.svg | (File) Svg file of react logo, being used in component(App.js) by default. 167 | - ./src/serviceWorker.js | (File) Service worker for pre-caching the scripts files of our react app thereby improving performance. 168 | - ./src/setupTests.js | (File) As the name suggest this files setups tests and runs them. This file in directly invoked when we run tests from cli(npm run test).[make use of Jest] 169 | 2. Fetching data from BE API with React Unfetch component (alternative is Axios which also is very popular HTTP client) 170 | 3. The React **useState** Hook allows us to track state in a function component. State generally refers to data or properties that need to be tracking in an application. 171 | 4. The **useEffect** Hook allows you to perform side effects in your components. Some examples of side effects are: fetching data, directly updating the DOM, and timers. 172 | 2. Bundle FE and BE project into single project. Instead, to run BE and FE separately as single project each other. We can bundle them into a single JAR file with Maven, and start the project from single JAR file. 173 | 1. First we need to map FE application for example localhost:3000 to the home page of the BE application for example localhost:8080, also we need still to have APIs available for example localhost:8080/api/v1/students 174 | - **Manual approach (build FE project with ```npm run build``` and copy the files from FE ./build directory into BE ./resources/static directory:** First we need to build FE project ```npm run build``` it will create **build** folder. Copy everything inside **./build** directory and paste it into **./resources/static** directory in our BE project 175 | - **Automation approach (to build BE and FE projects into one write at the root directory of our BE project the command ```./mvnw clean install``` which means use maven wrapper, clean the project actually it will delete the target folder, and after that rebuild the project by using the POM.xml configuration file, where actually is our maven automation script) is done by **maven** and **frontend-maven-plugin** plugin into our POM.xml file** with all the necessary configurations. Check the implementation and comments into POM.xml file 176 | - Generated JAR file from the two approaches mentioned above is placed into **./target** directory ```name-of-the-project.jar``` 177 | - To run the JAR file go to the **./target** directory and write ```java -jar name-of-the-project.jar``` it will run the project itself not from the IDEA. 178 | 3. Docker and Jib (Jib is used to bundle JAR file into Docker image) 179 | 1. Jib plugin is used to build docker image from our JAR file (which contains FE and BE together) 180 | 2. Maven Profiles (gives us more flexibility - you can choose which custom profile to start and for example one can bundle FE and BE, another can build local docker image, another ca build docker image and push it to docker hub) this is excellent example of automation build 181 | 4. AWS & Elastic Beanstalk (Deploy our application into AWS) 182 | 1. Elastic Beanstalk (End-to-end web application management service. Which is used for deploying and scaling web applications) 183 | 2. Create docker-compose.yaml file and upload it into Elastic Beanstalk (Elastic Beanstalk Environment will create: load balancer, auto-scaling, into ECS cluster we have EC2 this is the actual virtual server on the cloud, RDS database system) 184 | 3. Elastic Beanstalk (The idea is when the client create request to our application it will go through **load balancer** which will care to load balancing the request into free EC2 virtual server which actually expose our application, the amount of the EC2 servers is configured by auto-scaling set up amount, all the EC2 servers will make requests to our database which is placed into RDS service) 185 | 4. When we want to deploy our application we should choose the file ```./elasticbeanstalk/docker-compose.yaml``` from our local machine 186 | 5. In EC2 tab which is my virtual server will see all the configurations for my application for example (instances, images, EBS (Elastic Block Store), Network & Security, Load Balancing, Auto Scaling) 187 | 6. One approach of deployment is fore example to have BE and FE bundled into single Jar file and to be deployed, another approach for bigger applications is to decouple BE, FE, and other services as separate applications and to be managed by kubernetes to handle the incremental traffic. 188 | 7. The idea of Elastic Beanstalk is to handle simple applications, if there is a need to have more complex application than we need to check for different approach. 189 | 8. To not be charged from AWS (it is good idea to **terminate** the environment and when it is needed can be **restored** again) links are added above into **Links** section 190 | ![AWS Environments](./resources/aws-environments.png) 191 | 5. Database and Spring Data JPA 192 | 1. First create docker network with name **db** | docker network create db 193 | 2. On our local machine create folder to store data from our docker container with name **db-data** 194 | 3. Run Postgres docker container in **db-data** folder | docker run --name db -p 5432:5432 --network=db -v "$PWD:/var/lib/postgresql/data" -e POSTGRES_PASSWORD=password -d postgres:alpine 195 | 4. Actually we will have to containers one will be (docker-container-database-name: db) <-> (network-name: db) <-> (docker-container: psql), psql is used to connect to database with name db through network with name db | docker run -it --rm --network=db postgres:alpine psql -h db -U postgres 196 | 5. This docker containers will be used only for testing purposes, in our AWS will use **RDS** database 197 | 6. Check the application.properties for database configurations 198 | ![Deploy to Docker Hub](./resources/deploy-to-dockerhub.png)) 199 | 6. Database in ElasticBeanstalk -> AWS RDS 200 | 1. Got to the application environment, click configuration tab and choose database 201 | 2. On the open windows setup these settings (postgres, version: db.t2.micro this is the smalls database in the AWS) 202 | ![AWS ElasticBean Stalk Config DB](./resources/aws-configure-database.png) 203 | 3. Create new application-dev.properties file, and make your IDE to use it by adding Environment variables: SPRING_PROFILES_ACTIVE=dev , where dev is equals to dev in application-dev.properties 204 | 4. Databases in AWS are protected by SecurityGroup which means only the instances behind the Load balancer can connect to them, or we can specify specific ip address which also can externally log into our database which is bad practice. (What it is happened, actually is that our clients will log into our application through load balancer, where load balancer will check for free instance of our application will connect it to the client, and the instance of our application will be connected to AWS RDS database which is protected by AWS Firewall Security Group) 205 | 5. Allow external applications to rich our AWS RDS database, find RDS (relational database) into AWS window, click DB instances, after that click into database id, to log into database configurations for the specific database instance. Choose VCP Security Group, log-in, click into Inbound Rules, copy first part of the source code for example: sg-0b8d993d27127ea78. Same Security Group can be seen into EC2 where is the environment instance of our application, check the tab Security and the Security Group id should be the same as that of the RDS database: sg-0b8d993d27127ea78, which means this EC2 instance of the application can communicate with this instance of RDS database. 206 | ![AWS RDS](./resources/aws-rds.png) 207 | ![AWS RDS Screen](./resources/aws-rds-screen.png) 208 | ![AWS RDS VCP Screen](./resources/aws-rds-vcp-screen.png) 209 | 6. Setup AWS RDS (relational databases) to allow external applications to rich it. Go to RDS tab, choose DB instance, open the database instance that you want to change, on the Security section click on the link under VPC security groups. In Inbound Rules click Edit Inbound Rules -> click Add Rule setup settings (PostgreSQL, after that choose from where you can connect to your database: from anyware, or from IP) and click Save Rules 210 | ![AWS RDS add Inbounded Rules](./resources/aws-rds-add-inbounded-rules.png) 211 | 7. Connect to my AWS RDS trough docker example: 212 | - docker run -it --rm postgres:alpine psql -h aa9320n4muma7h.celswdmxhcr1.eu-west-1.rds.amazonaws.com -U syscomz -d postgres | -h it is the host path to the AWS RDS database without the port, can be found into Environment -> Configuration 213 | ![AWS RDS external connection](./resources/aws-rds-external-connection.png) 214 | 7. Deploy new version of the application (first should create new image and push it to the DockerHub, after that should upload docker-compose.yaml into AWS to get the new version from DockerHub) 215 | 0. Remove environment variable from the IDEA SPRING_PROFILES_ACTIVE=dev 216 | 1. First write: docker login, to login into docker 217 | 2. mvn clean install -P bundle-backend-and-frontend -P jib-build-docker-image-and-push-it-to-docker-hub -Dapp.image.tag=3 -- use -P (for profile) bundle-backend-and-frontend to bundle FE and BE locally, and then run -P (for profile) bundle-backend-and-frontend to use Jib to create docker image and to push it into DockerHub repository, and then set up the version of the docker image -Dapp.image.tag=3 (where app.image.tag is environment variable) 218 | 3. To deploy the application into AWS upload docker-compose.yaml file 219 | ![AWS Deployed Application](./resources/aws-deployed-application.png) 220 | 221 | 222 | 8. CI/CD Pipelines (Continues Integration Continues Delivery) 223 | 1. CI/CD Steps: 224 | 1. Create code 225 | 2. Push code to GitHub 226 | 3. Execute GitHub Actions (First is for PULL REQUEST, Second is for MERGE TO MAIN) 227 | 1. PULL REQUEST Action (If we push some code and if we raze PULL REQUEST then it will be executed PULL REQUEST Action) 228 | 1. The BUILD WORKFLOW will be triggered which can be run in different VMs like Ubuntu, Windows etc. \*workflow is sequence of steps that allows us to achieve our main goal, it runs on a runner which are VMs 229 | 2. Sequence of steps in BUILD WORKFLOW that will be executed in the runner most likely Ubuntu: 230 | 1. CHECKOUT CODE into our Ubuntu runner - the code is in our GitHub repository 231 | 2. SETUP JAVA 232 | 3. SETUP POSTGRESQL 233 | 4. MVN CLEAN PACKAGE (which will build, compile and run all the tests) 234 | 5. and if everything is fine will receive message "OK TO MERGE", which will allow us to merge into our main/master branch, if reviewer of the code is OK with it. When we merge into main/master branch it will be executed second step which is MERGE TO MAIN Action 235 | 2. MERGE TO MAIN Action (If we want to merge to main/master branch then it will be executed MERGE REQUEST Action) 236 | 1. The DEPLOYMENT WORKFLOW will be triggered which can be run in different VMs like Ubuntu, Windows etc. \*workflow is sequence of steps that allows us to achieve our main goal, it runs on a runner which are VMs 237 | 2. Sequence of steps into DEPLOYMENT WORKFLOW that will be executed in the runner most likely Ubuntu: 238 | 1. SEND SLACK NOTIFICATION message to the team in this case slack (with message that CI/CD is on going) 239 | 2. CHECKOUT CODE into our Ubuntu runner - the code is in our GitHub repository 240 | 3. SETUP JAVA 241 | 4. GENERATE BUILD NUMBER (version of the application) 242 | 5. DOCKER LOGIN (into DockerHub repository) 243 | 6. MVN CLEAN PACKAGE (which will build, compile and run all the tests), and then BUILD IMAGE (using Jib) 244 | 7. SEND SLACK NOTIFICATION message to the team in this case slack (with message that the images was PUSHED with syntax's bdostumski/image-name:1.0.1) 245 | 8. UPDATE DOCKER-COMPOSE.YAML file into Elastic Beanstalk in AWS 246 | 9. SEND SLACK NOTIFICATION message to the team in this case slack (with message that DEPLOYMENT STARTED this is the deployment for Elastic Beanstalk in AWS) 247 | 10. DEPLOY TO AWS (deployment process is started) 248 | 11. SEND SLACK NOTIFICATION message to the team in this case slack (with message that THE NEW APP IS UP AND RUNNING) 249 | 12. THE APPLICATION IS ALREADY UP 250 | 3. IN CASE OF ERROR INTO DEPLOYMENT WORKFLOW (it will stop to the step where is the error, and will not continue to the next steps) 251 | 3. BUILD WORKFLOW this step is CI (because we merge our new code but do not deploy the new code to the application, we do not create a new build of our application) 252 | 4. DEPLOYMENT FLOW this step is CI/CD (because we merge our new code and deploy the new code to the application, we create a new build of our application) 253 | ![Github Actions](./resources/github-actions.png) 254 | 4. Slack messaging system 255 | 1. Build own app that will message on deployment process 256 | 2. Click on Settings & Administration -> Manage apps -> Build -> Create App -> From scratch -> Give Name and choose app workspace -> click Create App 257 | 3. Choose button Incoming Webhooks -> Activate Incoming Webhooks -> Add New Webhook to Workspace \(choose workspace and chanel for messaging\) -> choose Allow 258 | 4. Copy automatic generated Simple curl request to post to a chanel -> past the code into IntelliJ terminal and will receive message into Slack Hello, World! 259 | 5. Copy Webhook URL and go to your GitHub project go to Settings of the project -> go to Secrets -> Codespaces -> click New Repository Secret -> give random name, and for the value past the copied URL -> Add the secret 260 | 6. Create new Repository Secret and for DockerHub repository to automate the login process -> click agan into New Repository Secret 261 | 7. Create AWS user to perform automated deployments into Elastic Beanstalk 262 | 1. Go to AWS Management Console -> go to your Username -> Security credentials 263 | 1. Into UserGroups create -> Group \[Give group name, choose policy AdministratorAccess-AWSElasticBeanStalk, click Create Group\] 264 | 2. Into Users create -> create new user for the new group -> click Add User -> give username and click Access key Programmatic access -> click Next Permissions -> into Add user to group choose the group that will be added to this user -> click Next Tags -> do not add anything just click Next:Review -> click Create User 265 | 3. Copy generated Secret Access ID and Key and paste them into GitHub repository create again into Codespace New Secret give random name for both for example AWS_ACCESS_KEY paste only access key, after that create new secret for the password give name for example AWS_SECRET_ACCESS_KEY and past the secret access key 266 | 4. [Slack messages fix](https://www.svix.com/resources/guides/how-to-get-slack-webhook-url/) Read this to fix Slack messages 267 | 2. Create DEPLOY Workflow Yaml 268 | 5. CI/CD Messages on Slack 269 | ![CICD ongoing](./resources/cicd-message1.png) 270 | ![CICD pushed bdostumski/springboot-react-fullstack:12.3.2023.8.17.50 to docker hub..](./resources/cicd-message2.png) 271 | ![elasticbeanstalk aws Deployment started...](./resources/cicd-message3.png) 272 | ![Deployed to AWS](./resources/cicd-message4.png) 273 | 274 | 9. Unit / Integration Testing (testing should start from the repository to the services. Every unit that was tested before should be mocked from the other test. For example, if we test the repository unit, we should mock it in the service tests unit. Except we make integration testing in this case we want the request flow through the system, and make sour that everything is working fine.) 275 | !["Testing UML Diagram Image"](./resources/unit_testing_uml_diagram.png) 276 | 1. JUnit5 = JUnit Platform + JUnit Jupiter + JUnit Vintage 277 | 1. The JUnit Platform serves as a foundation for launching testing framework on the JVM. 278 | 2. JUnit Jupiter is the combination of the new programming model and extension model for writing test and extensions in JUnit5. 279 | 3. JUnit Vintage provides a TestingEngine for running JUnit3 and JUnit4 based tests on the platform. 280 | 2. AssertJ - is a java library providing a rich set of assertions, truly helpful error messages, improves test code readability. THIS IS BETTER than assertions of JUnit5 281 | 3. Using H2 In-memory Database for Testing purposes 282 | 4. Integration Tests 283 | 1. Using Failsafe Plugin (which is part of Plugin Management, in spring-boot-starter-parent), it is designed to run integration tests while the Surefire Plugin is designed to run unit tests. 284 | 2. Integration tests should be tested only positive paths, because all other negative tests are already tested with unit tests -------------------------------------------------------------------------------- /elasticbeanstalk/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3.3" 2 | services: 3 | backend: # service name 4 | image: "bdostumski/springboot-react-fullstack:12.3.2023.9.6.7" # docker image from our docker hub repository 5 | ports: # array of ports 6 | - "80:8080" # map port 80 to port 8080 which is spring boot port 7 | restart: "always" # if the application have issues and fails it will try to bring the application up, so we want to restart alwaysroot@syscomz:/home/dostumski/Programming/spring-boot-full-stack-professional/elasticbeanstalk 8 | environment: # environment variables 9 | SPRING_PROFILES_ACTIVE: dev # setup to use application-dev.properties -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # https://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /usr/local/etc/mavenrc ] ; then 40 | . /usr/local/etc/mavenrc 41 | fi 42 | 43 | if [ -f /etc/mavenrc ] ; then 44 | . /etc/mavenrc 45 | fi 46 | 47 | if [ -f "$HOME/.mavenrc" ] ; then 48 | . "$HOME/.mavenrc" 49 | fi 50 | 51 | fi 52 | 53 | # OS specific support. $var _must_ be set to either true or false. 54 | cygwin=false; 55 | darwin=false; 56 | mingw=false 57 | case "`uname`" in 58 | CYGWIN*) cygwin=true ;; 59 | MINGW*) mingw=true;; 60 | Darwin*) darwin=true 61 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 62 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 63 | if [ -z "$JAVA_HOME" ]; then 64 | if [ -x "/usr/libexec/java_home" ]; then 65 | export JAVA_HOME="`/usr/libexec/java_home`" 66 | else 67 | export JAVA_HOME="/Library/Java/Home" 68 | fi 69 | fi 70 | ;; 71 | esac 72 | 73 | if [ -z "$JAVA_HOME" ] ; then 74 | if [ -r /etc/gentoo-release ] ; then 75 | JAVA_HOME=`java-config --jre-home` 76 | fi 77 | fi 78 | 79 | if [ -z "$M2_HOME" ] ; then 80 | ## resolve links - $0 may be a link to maven's home 81 | PRG="$0" 82 | 83 | # need this for relative symlinks 84 | while [ -h "$PRG" ] ; do 85 | ls=`ls -ld "$PRG"` 86 | link=`expr "$ls" : '.*-> \(.*\)$'` 87 | if expr "$link" : '/.*' > /dev/null; then 88 | PRG="$link" 89 | else 90 | PRG="`dirname "$PRG"`/$link" 91 | fi 92 | done 93 | 94 | saveddir=`pwd` 95 | 96 | M2_HOME=`dirname "$PRG"`/.. 97 | 98 | # make it fully qualified 99 | M2_HOME=`cd "$M2_HOME" && pwd` 100 | 101 | cd "$saveddir" 102 | # echo Using m2 at $M2_HOME 103 | fi 104 | 105 | # For Cygwin, ensure paths are in UNIX format before anything is touched 106 | if $cygwin ; then 107 | [ -n "$M2_HOME" ] && 108 | M2_HOME=`cygpath --unix "$M2_HOME"` 109 | [ -n "$JAVA_HOME" ] && 110 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 111 | [ -n "$CLASSPATH" ] && 112 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 113 | fi 114 | 115 | # For Mingw, ensure paths are in UNIX format before anything is touched 116 | if $mingw ; then 117 | [ -n "$M2_HOME" ] && 118 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 119 | [ -n "$JAVA_HOME" ] && 120 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 121 | fi 122 | 123 | if [ -z "$JAVA_HOME" ]; then 124 | javaExecutable="`which javac`" 125 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 126 | # readlink(1) is not available as standard on Solaris 10. 127 | readLink=`which readlink` 128 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 129 | if $darwin ; then 130 | javaHome="`dirname \"$javaExecutable\"`" 131 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 132 | else 133 | javaExecutable="`readlink -f \"$javaExecutable\"`" 134 | fi 135 | javaHome="`dirname \"$javaExecutable\"`" 136 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 137 | JAVA_HOME="$javaHome" 138 | export JAVA_HOME 139 | fi 140 | fi 141 | fi 142 | 143 | if [ -z "$JAVACMD" ] ; then 144 | if [ -n "$JAVA_HOME" ] ; then 145 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 146 | # IBM's JDK on AIX uses strange locations for the executables 147 | JAVACMD="$JAVA_HOME/jre/sh/java" 148 | else 149 | JAVACMD="$JAVA_HOME/bin/java" 150 | fi 151 | else 152 | JAVACMD="`\\unset -f command; \\command -v java`" 153 | fi 154 | fi 155 | 156 | if [ ! -x "$JAVACMD" ] ; then 157 | echo "Error: JAVA_HOME is not defined correctly." >&2 158 | echo " We cannot execute $JAVACMD" >&2 159 | exit 1 160 | fi 161 | 162 | if [ -z "$JAVA_HOME" ] ; then 163 | echo "Warning: JAVA_HOME environment variable is not set." 164 | fi 165 | 166 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 167 | 168 | # traverses directory structure from process work directory to filesystem root 169 | # first directory with .mvn subdirectory is considered project base directory 170 | find_maven_basedir() { 171 | 172 | if [ -z "$1" ] 173 | then 174 | echo "Path not specified to find_maven_basedir" 175 | return 1 176 | fi 177 | 178 | basedir="$1" 179 | wdir="$1" 180 | while [ "$wdir" != '/' ] ; do 181 | if [ -d "$wdir"/.mvn ] ; then 182 | basedir=$wdir 183 | break 184 | fi 185 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 186 | if [ -d "${wdir}" ]; then 187 | wdir=`cd "$wdir/.."; pwd` 188 | fi 189 | # end of workaround 190 | done 191 | echo "${basedir}" 192 | } 193 | 194 | # concatenates all lines of a file 195 | concat_lines() { 196 | if [ -f "$1" ]; then 197 | echo "$(tr -s '\n' ' ' < "$1")" 198 | fi 199 | } 200 | 201 | BASE_DIR=`find_maven_basedir "$(pwd)"` 202 | if [ -z "$BASE_DIR" ]; then 203 | exit 1; 204 | fi 205 | 206 | ########################################################################################## 207 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 208 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 209 | ########################################################################################## 210 | if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then 211 | if [ "$MVNW_VERBOSE" = true ]; then 212 | echo "Found .mvn/wrapper/maven-wrapper.jar" 213 | fi 214 | else 215 | if [ "$MVNW_VERBOSE" = true ]; then 216 | echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." 217 | fi 218 | if [ -n "$MVNW_REPOURL" ]; then 219 | jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 220 | else 221 | jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 222 | fi 223 | while IFS="=" read key value; do 224 | case "$key" in (wrapperUrl) jarUrl="$value"; break ;; 225 | esac 226 | done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" 227 | if [ "$MVNW_VERBOSE" = true ]; then 228 | echo "Downloading from: $jarUrl" 229 | fi 230 | wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" 231 | if $cygwin; then 232 | wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` 233 | fi 234 | 235 | if command -v wget > /dev/null; then 236 | if [ "$MVNW_VERBOSE" = true ]; then 237 | echo "Found wget ... using wget" 238 | fi 239 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 240 | wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 241 | else 242 | wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 243 | fi 244 | elif command -v curl > /dev/null; then 245 | if [ "$MVNW_VERBOSE" = true ]; then 246 | echo "Found curl ... using curl" 247 | fi 248 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 249 | curl -o "$wrapperJarPath" "$jarUrl" -f 250 | else 251 | curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f 252 | fi 253 | 254 | else 255 | if [ "$MVNW_VERBOSE" = true ]; then 256 | echo "Falling back to using Java to download" 257 | fi 258 | javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" 259 | # For Cygwin, switch paths to Windows format before running javac 260 | if $cygwin; then 261 | javaClass=`cygpath --path --windows "$javaClass"` 262 | fi 263 | if [ -e "$javaClass" ]; then 264 | if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 265 | if [ "$MVNW_VERBOSE" = true ]; then 266 | echo " - Compiling MavenWrapperDownloader.java ..." 267 | fi 268 | # Compiling the Java class 269 | ("$JAVA_HOME/bin/javac" "$javaClass") 270 | fi 271 | if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 272 | # Running the downloader 273 | if [ "$MVNW_VERBOSE" = true ]; then 274 | echo " - Running MavenWrapperDownloader.java ..." 275 | fi 276 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") 277 | fi 278 | fi 279 | fi 280 | fi 281 | ########################################################################################## 282 | # End of extension 283 | ########################################################################################## 284 | 285 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 286 | if [ "$MVNW_VERBOSE" = true ]; then 287 | echo $MAVEN_PROJECTBASEDIR 288 | fi 289 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 290 | 291 | # For Cygwin, switch paths to Windows format before running java 292 | if $cygwin; then 293 | [ -n "$M2_HOME" ] && 294 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 295 | [ -n "$JAVA_HOME" ] && 296 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 297 | [ -n "$CLASSPATH" ] && 298 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 299 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 300 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 301 | fi 302 | 303 | # Provide a "standardized" way to retrieve the CLI args that will 304 | # work with both Windows and non-Windows executions. 305 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" 306 | export MAVEN_CMD_LINE_ARGS 307 | 308 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 309 | 310 | exec "$JAVACMD" \ 311 | $MAVEN_OPTS \ 312 | $MAVEN_DEBUG_OPTS \ 313 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 314 | "-Dmaven.home=${M2_HOME}" \ 315 | "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 316 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 317 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM https://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM set title of command window 39 | title %0 40 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' 41 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 42 | 43 | @REM set %HOME% to equivalent of $HOME 44 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 45 | 46 | @REM Execute a user defined script before this one 47 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 48 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 49 | if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* 50 | if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* 51 | :skipRcPre 52 | 53 | @setlocal 54 | 55 | set ERROR_CODE=0 56 | 57 | @REM To isolate internal variables from possible post scripts, we use another setlocal 58 | @setlocal 59 | 60 | @REM ==== START VALIDATION ==== 61 | if not "%JAVA_HOME%" == "" goto OkJHome 62 | 63 | echo. 64 | echo Error: JAVA_HOME not found in your environment. >&2 65 | echo Please set the JAVA_HOME variable in your environment to match the >&2 66 | echo location of your Java installation. >&2 67 | echo. 68 | goto error 69 | 70 | :OkJHome 71 | if exist "%JAVA_HOME%\bin\java.exe" goto init 72 | 73 | echo. 74 | echo Error: JAVA_HOME is set to an invalid directory. >&2 75 | echo JAVA_HOME = "%JAVA_HOME%" >&2 76 | echo Please set the JAVA_HOME variable in your environment to match the >&2 77 | echo location of your Java installation. >&2 78 | echo. 79 | goto error 80 | 81 | @REM ==== END VALIDATION ==== 82 | 83 | :init 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 121 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 122 | 123 | set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 124 | 125 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 126 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 127 | ) 128 | 129 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 130 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 131 | if exist %WRAPPER_JAR% ( 132 | if "%MVNW_VERBOSE%" == "true" ( 133 | echo Found %WRAPPER_JAR% 134 | ) 135 | ) else ( 136 | if not "%MVNW_REPOURL%" == "" ( 137 | SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 138 | ) 139 | if "%MVNW_VERBOSE%" == "true" ( 140 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 141 | echo Downloading from: %DOWNLOAD_URL% 142 | ) 143 | 144 | powershell -Command "&{"^ 145 | "$webclient = new-object System.Net.WebClient;"^ 146 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ 147 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ 148 | "}"^ 149 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ 150 | "}" 151 | if "%MVNW_VERBOSE%" == "true" ( 152 | echo Finished downloading %WRAPPER_JAR% 153 | ) 154 | ) 155 | @REM End of extension 156 | 157 | @REM Provide a "standardized" way to retrieve the CLI args that will 158 | @REM work with both Windows and non-Windows executions. 159 | set MAVEN_CMD_LINE_ARGS=%* 160 | 161 | %MAVEN_JAVA_EXE% ^ 162 | %JVM_CONFIG_MAVEN_PROPS% ^ 163 | %MAVEN_OPTS% ^ 164 | %MAVEN_DEBUG_OPTS% ^ 165 | -classpath %WRAPPER_JAR% ^ 166 | "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ 167 | %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 168 | if ERRORLEVEL 1 goto error 169 | goto end 170 | 171 | :error 172 | set ERROR_CODE=1 173 | 174 | :end 175 | @endlocal & set ERROR_CODE=%ERROR_CODE% 176 | 177 | if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost 178 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 179 | if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" 180 | if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" 181 | :skipRcPost 182 | 183 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 184 | if "%MAVEN_BATCH_PAUSE%"=="on" pause 185 | 186 | if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% 187 | 188 | cmd /C exit /B %ERROR_CODE% 189 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "latest": "^0.2.0" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.7.6 9 | 10 | 11 | com.syscomz 12 | spring-boot-full-stack-professional 13 | 0.0.1-SNAPSHOT 14 | spring-boot-full-stack-professional 15 | Spring boot | React | AWS 16 | 17 | 11 18 | 19 | springboot-react-fullstack 20 | 21 | 22 | 23 | 24 | 25 | org.springframework.boot 26 | spring-boot-starter-web 27 | 28 | 29 | 30 | org.projectlombok 31 | lombok 32 | true 33 | 34 | 35 | 36 | org.springframework.boot 37 | spring-boot-starter-test 38 | test 39 | 40 | 41 | 42 | 43 | com.h2database 44 | h2 45 | test 46 | 47 | 48 | 49 | 50 | com.github.javafaker 51 | javafaker 52 | 1.0.2 53 | 54 | 55 | 56 | org.springframework.boot 57 | spring-boot-starter-data-jpa 58 | 59 | 60 | 61 | org.postgresql 62 | postgresql 63 | runtime 64 | 65 | 66 | 67 | 68 | org.springframework.boot 69 | spring-boot-starter-validation 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | org.apache.maven.plugins 78 | maven-failsafe-plugin 79 | 80 | 81 | org.springframework.boot 82 | spring-boot-maven-plugin 83 | 84 | 85 | 86 | org.projectlombok 87 | lombok 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | bundle-backend-and-frontend 114 | 115 | true 116 | 117 | 118 | 119 | 120 | 121 | 122 | com.github.eirslett 123 | frontend-maven-plugin 124 | 1.11.2 125 | 126 | v4.6.0 127 | src/frontend 128 | 129 | 130 | 131 | install node and npm 132 | 133 | install-node-and-npm 134 | 135 | 136 | v14.21.1 137 | 6.14.17 138 | 139 | https://nodejs.org/dist/ 140 | https://registry.npmjs.org/npm/-/ 141 | 142 | 143 | 144 | npm install 145 | 146 | npm 147 | 148 | 149 | install 150 | 151 | 152 | 153 | 154 | npm run build 155 | 156 | npm 157 | 158 | 159 | run build 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | maven-resources-plugin 171 | 172 | 173 | copy-build-folder 174 | process-classes 175 | 176 | copy-resources 177 | 178 | 179 | 180 | 181 | src/frontend/build 182 | 183 | 184 | ${project.basedir}/target/classes/static 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | com.google.cloud.tools 194 | jib-maven-plugin 195 | 3.3.1 196 | 197 | 198 | gcr.io/distroless/java:11 199 | 200 | 201 | 202 | ${project.name} 203 | ${project.version} 204 | 205 | 206 | 8080 207 | 208 | OCI 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | jib-build-docker-image-and-push-it-to-docker-hub 222 | 223 | false 224 | 225 | 226 | 227 | 228 | 229 | 230 | com.google.cloud.tools 231 | jib-maven-plugin 232 | 3.3.1 233 | 234 | 235 | gcr.io/distroless/java:11 236 | 237 | 238 | 239 | 8080 240 | 241 | OCI 242 | 243 | 244 | 245 | 246 | 247 | push-image-with-custom-tag 248 | package 249 | 250 | 251 | 252 | 253 | docker.io/bdostumski/${app.image.name}:${app.image.tag} 254 | 255 | 256 | 257 | 258 | build 259 | 260 | 261 | 262 | 263 | push-image-with-latest-tag 264 | package 265 | 266 | 267 | 268 | 269 | docker.io/bdostumski/${app.image.name}:latest 270 | 271 | 272 | 273 | 274 | build 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | jib-build-local-docker-image 289 | 290 | false 291 | 292 | 293 | 294 | 295 | 296 | 297 | com.google.cloud.tools 298 | jib-maven-plugin 299 | 3.3.1 300 | 301 | 302 | gcr.io/distroless/java:11 303 | 304 | 305 | 306 | 8080 307 | 308 | OCI 309 | 310 | 311 | 312 | 313 | 314 | push-image-with-custom-tag 315 | package 316 | 317 | 318 | 319 | 320 | bdostumski/${app.image.name}:${app.image.tag} 321 | 322 | 323 | 324 | 325 | dockerBuild 326 | 327 | 328 | 329 | 330 | push-image-with-latest-tag 331 | package 332 | 333 | 334 | 335 | 336 | bdostumski/${app.image.name}:latest 337 | 338 | 339 | 340 | 341 | dockerBuild 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | -------------------------------------------------------------------------------- /resources/architecture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdostumski/learning-full-stack/342ebc4d8c82271e907fbc79898be4506254ce01/resources/architecture.jpg -------------------------------------------------------------------------------- /resources/aws-configure-database.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdostumski/learning-full-stack/342ebc4d8c82271e907fbc79898be4506254ce01/resources/aws-configure-database.png -------------------------------------------------------------------------------- /resources/aws-deployed-application.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdostumski/learning-full-stack/342ebc4d8c82271e907fbc79898be4506254ce01/resources/aws-deployed-application.png -------------------------------------------------------------------------------- /resources/aws-environments.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdostumski/learning-full-stack/342ebc4d8c82271e907fbc79898be4506254ce01/resources/aws-environments.png -------------------------------------------------------------------------------- /resources/aws-rds-add-inbounded-rules.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdostumski/learning-full-stack/342ebc4d8c82271e907fbc79898be4506254ce01/resources/aws-rds-add-inbounded-rules.png -------------------------------------------------------------------------------- /resources/aws-rds-config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdostumski/learning-full-stack/342ebc4d8c82271e907fbc79898be4506254ce01/resources/aws-rds-config.png -------------------------------------------------------------------------------- /resources/aws-rds-external-connection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdostumski/learning-full-stack/342ebc4d8c82271e907fbc79898be4506254ce01/resources/aws-rds-external-connection.png -------------------------------------------------------------------------------- /resources/aws-rds-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdostumski/learning-full-stack/342ebc4d8c82271e907fbc79898be4506254ce01/resources/aws-rds-screen.png -------------------------------------------------------------------------------- /resources/aws-rds-vcp-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdostumski/learning-full-stack/342ebc4d8c82271e907fbc79898be4506254ce01/resources/aws-rds-vcp-screen.png -------------------------------------------------------------------------------- /resources/aws-rds.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdostumski/learning-full-stack/342ebc4d8c82271e907fbc79898be4506254ce01/resources/aws-rds.png -------------------------------------------------------------------------------- /resources/cicd-message1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdostumski/learning-full-stack/342ebc4d8c82271e907fbc79898be4506254ce01/resources/cicd-message1.png -------------------------------------------------------------------------------- /resources/cicd-message2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdostumski/learning-full-stack/342ebc4d8c82271e907fbc79898be4506254ce01/resources/cicd-message2.png -------------------------------------------------------------------------------- /resources/cicd-message3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdostumski/learning-full-stack/342ebc4d8c82271e907fbc79898be4506254ce01/resources/cicd-message3.png -------------------------------------------------------------------------------- /resources/cicd-message4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdostumski/learning-full-stack/342ebc4d8c82271e907fbc79898be4506254ce01/resources/cicd-message4.png -------------------------------------------------------------------------------- /resources/deploy-to-dockerhub.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdostumski/learning-full-stack/342ebc4d8c82271e907fbc79898be4506254ce01/resources/deploy-to-dockerhub.png -------------------------------------------------------------------------------- /resources/github-actions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdostumski/learning-full-stack/342ebc4d8c82271e907fbc79898be4506254ce01/resources/github-actions.png -------------------------------------------------------------------------------- /resources/unit_testing_uml_diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdostumski/learning-full-stack/342ebc4d8c82271e907fbc79898be4506254ce01/resources/unit_testing_uml_diagram.png -------------------------------------------------------------------------------- /src/frontend/.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 | -------------------------------------------------------------------------------- /src/frontend/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Create React App 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm start` 10 | 11 | Runs the app in the development mode.\ 12 | Open [http://localhost:3000](http://localhost:3000) to view it in your browser. 13 | 14 | The page will reload when you make changes.\ 15 | You may also see any lint errors in the console. 16 | 17 | ### `npm test` 18 | 19 | Launches the test runner in the interactive watch mode.\ 20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 21 | 22 | ### `npm run build` 23 | 24 | Builds the app for production to the `build` folder.\ 25 | It correctly bundles React in production mode and optimizes the build for the best performance. 26 | 27 | The build is minified and the filenames include the hashes.\ 28 | Your app is ready to be deployed! 29 | 30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 31 | 32 | ### `npm run eject` 33 | 34 | **Note: this is a one-way operation. Once you `eject`, you can't go back!** 35 | 36 | 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. 37 | 38 | 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. 39 | 40 | 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. 41 | 42 | ## Learn More 43 | 44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 45 | 46 | To learn React, check out the [React documentation](https://reactjs.org/). 47 | 48 | ### Code Splitting 49 | 50 | This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting) 51 | 52 | ### Analyzing the Bundle Size 53 | 54 | This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size) 55 | 56 | ### Making a Progressive Web App 57 | 58 | This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app) 59 | 60 | ### Advanced Configuration 61 | 62 | This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration) 63 | 64 | ### Deployment 65 | 66 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment) 67 | 68 | ### `npm run build` fails to minify 69 | 70 | This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify) 71 | -------------------------------------------------------------------------------- /src/frontend/node/node: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdostumski/learning-full-stack/342ebc4d8c82271e907fbc79898be4506254ce01/src/frontend/node/node -------------------------------------------------------------------------------- /src/frontend/node/npm: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | (set -o igncr) 2>/dev/null && set -o igncr; # cygwin encoding fix 3 | 4 | basedir=`dirname "$0"` 5 | 6 | case `uname` in 7 | *CYGWIN*) basedir=`cygpath -w "$basedir"`;; 8 | esac 9 | 10 | NODE_EXE="$basedir/node.exe" 11 | if ! [ -x "$NODE_EXE" ]; then 12 | NODE_EXE="$basedir/node" 13 | fi 14 | if ! [ -x "$NODE_EXE" ]; then 15 | NODE_EXE=node 16 | fi 17 | 18 | # this path is passed to node.exe, so it needs to match whatever 19 | # kind of paths Node.js thinks it's using, typically win32 paths. 20 | CLI_BASEDIR="$("$NODE_EXE" -p 'require("path").dirname(process.execPath)')" 21 | NPM_CLI_JS="$CLI_BASEDIR/node_modules/npm/bin/npm-cli.js" 22 | 23 | NPM_PREFIX=`"$NODE_EXE" "$NPM_CLI_JS" prefix -g` 24 | if [ $? -ne 0 ]; then 25 | # if this didn't work, then everything else below will fail 26 | echo "Could not determine Node.js install directory" >&2 27 | exit 1 28 | fi 29 | NPM_PREFIX_NPM_CLI_JS="$NPM_PREFIX/node_modules/npm/bin/npm-cli.js" 30 | 31 | # a path that will fail -f test on any posix bash 32 | NPM_WSL_PATH="/.." 33 | 34 | # WSL can run Windows binaries, so we have to give it the win32 path 35 | # however, WSL bash tests against posix paths, so we need to construct that 36 | # to know if npm is installed globally. 37 | if [ `uname` = 'Linux' ] && type wslpath &>/dev/null ; then 38 | NPM_WSL_PATH=`wslpath "$NPM_PREFIX_NPM_CLI_JS"` 39 | fi 40 | if [ -f "$NPM_PREFIX_NPM_CLI_JS" ] || [ -f "$NPM_WSL_PATH" ]; then 41 | NPM_CLI_JS="$NPM_PREFIX_NPM_CLI_JS" 42 | fi 43 | 44 | "$NODE_EXE" "$NPM_CLI_JS" "$@" 45 | -------------------------------------------------------------------------------- /src/frontend/node/npm.cmd: -------------------------------------------------------------------------------- 1 | :: Created by npm, please don't edit manually. 2 | @ECHO OFF 3 | 4 | SETLOCAL 5 | 6 | SET "NODE_EXE=%~dp0\node.exe" 7 | IF NOT EXIST "%NODE_EXE%" ( 8 | SET "NODE_EXE=node" 9 | ) 10 | 11 | SET "NPM_CLI_JS=%~dp0\node_modules\npm\bin\npm-cli.js" 12 | FOR /F "delims=" %%F IN ('CALL "%NODE_EXE%" "%NPM_CLI_JS%" prefix -g') DO ( 13 | SET "NPM_PREFIX_NPM_CLI_JS=%%F\node_modules\npm\bin\npm-cli.js" 14 | ) 15 | IF EXIST "%NPM_PREFIX_NPM_CLI_JS%" ( 16 | SET "NPM_CLI_JS=%NPM_PREFIX_NPM_CLI_JS%" 17 | ) 18 | 19 | "%NODE_EXE%" "%NPM_CLI_JS%" %* 20 | -------------------------------------------------------------------------------- /src/frontend/node/npx: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This is used by the Node.js installer, which expects the cygwin/mingw 4 | # shell script to already be present in the npm dependency folder. 5 | 6 | (set -o igncr) 2>/dev/null && set -o igncr; # cygwin encoding fix 7 | 8 | basedir=`dirname "$0"` 9 | 10 | case `uname` in 11 | *CYGWIN*) basedir=`cygpath -w "$basedir"`;; 12 | esac 13 | 14 | NODE_EXE="$basedir/node.exe" 15 | if ! [ -x "$NODE_EXE" ]; then 16 | NODE_EXE="$basedir/node" 17 | fi 18 | if ! [ -x "$NODE_EXE" ]; then 19 | NODE_EXE=node 20 | fi 21 | 22 | # these paths are passed to node.exe, so they need to match whatever 23 | # kind of paths Node.js thinks it's using, typically win32 paths. 24 | CLI_BASEDIR="$("$NODE_EXE" -p 'require("path").dirname(process.execPath)')" 25 | if [ $? -ne 0 ]; then 26 | # if this didn't work, then everything else below will fail 27 | echo "Could not determine Node.js install directory" >&2 28 | exit 1 29 | fi 30 | NPM_CLI_JS="$CLI_BASEDIR/node_modules/npm/bin/npm-cli.js" 31 | NPX_CLI_JS="$CLI_BASEDIR/node_modules/npm/bin/npx-cli.js" 32 | NPM_PREFIX=`"$NODE_EXE" "$NPM_CLI_JS" prefix -g` 33 | NPM_PREFIX_NPX_CLI_JS="$NPM_PREFIX/node_modules/npm/bin/npx-cli.js" 34 | 35 | # a path that will fail -f test on any posix bash 36 | NPX_WSL_PATH="/.." 37 | 38 | # WSL can run Windows binaries, so we have to give it the win32 path 39 | # however, WSL bash tests against posix paths, so we need to construct that 40 | # to know if npm is installed globally. 41 | if [ `uname` = 'Linux' ] && type wslpath &>/dev/null ; then 42 | NPX_WSL_PATH=`wslpath "$NPM_PREFIX_NPX_CLI_JS"` 43 | fi 44 | if [ -f "$NPM_PREFIX_NPX_CLI_JS" ] || [ -f "$NPX_WSL_PATH" ]; then 45 | NPX_CLI_JS="$NPM_PREFIX_NPX_CLI_JS" 46 | fi 47 | 48 | "$NODE_EXE" "$NPX_CLI_JS" "$@" 49 | -------------------------------------------------------------------------------- /src/frontend/node/npx.cmd: -------------------------------------------------------------------------------- 1 | :: Created by npm, please don't edit manually. 2 | @ECHO OFF 3 | 4 | SETLOCAL 5 | 6 | SET "NODE_EXE=%~dp0\node.exe" 7 | IF NOT EXIST "%NODE_EXE%" ( 8 | SET "NODE_EXE=node" 9 | ) 10 | 11 | SET "NPM_CLI_JS=%~dp0\node_modules\npm\bin\npm-cli.js" 12 | SET "NPX_CLI_JS=%~dp0\node_modules\npm\bin\npx-cli.js" 13 | FOR /F "delims=" %%F IN ('CALL "%NODE_EXE%" "%NPM_CLI_JS%" prefix -g') DO ( 14 | SET "NPM_PREFIX_NPX_CLI_JS=%%F\node_modules\npm\bin\npx-cli.js" 15 | ) 16 | IF EXIST "%NPM_PREFIX_NPX_CLI_JS%" ( 17 | SET "NPX_CLI_JS=%NPM_PREFIX_NPX_CLI_JS%" 18 | ) 19 | 20 | "%NODE_EXE%" "%NPX_CLI_JS%" %* 21 | -------------------------------------------------------------------------------- /src/frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@ant-design/icons": "^4.8.0", 7 | "@testing-library/jest-dom": "^5.16.5", 8 | "@testing-library/react": "^13.4.0", 9 | "@testing-library/user-event": "^13.5.0", 10 | "antd": "^5.0.1", 11 | "react": "^18.2.0", 12 | "react-dom": "^18.2.0", 13 | "react-scripts": "^5.0.1", 14 | "unfetch": "^4.2.0", 15 | "web-vitals": "^2.1.4" 16 | }, 17 | "scripts": { 18 | "start": "react-scripts start", 19 | "build": "react-scripts build", 20 | "test": "react-scripts test", 21 | "eject": "react-scripts eject" 22 | }, 23 | "eslintConfig": { 24 | "extends": [ 25 | "react-app", 26 | "react-app/jest" 27 | ] 28 | }, 29 | "browserslist": { 30 | "production": [ 31 | ">0.2%", 32 | "not dead", 33 | "not op_mini all" 34 | ], 35 | "development": [ 36 | "last 1 chrome version", 37 | "last 1 firefox version", 38 | "last 1 safari version" 39 | ] 40 | }, 41 | "proxy": "http://localhost:8080" 42 | } 43 | -------------------------------------------------------------------------------- /src/frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdostumski/learning-full-stack/342ebc4d8c82271e907fbc79898be4506254ce01/src/frontend/public/favicon.ico -------------------------------------------------------------------------------- /src/frontend/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 | -------------------------------------------------------------------------------- /src/frontend/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdostumski/learning-full-stack/342ebc4d8c82271e907fbc79898be4506254ce01/src/frontend/public/logo192.png -------------------------------------------------------------------------------- /src/frontend/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdostumski/learning-full-stack/342ebc4d8c82271e907fbc79898be4506254ce01/src/frontend/public/logo512.png -------------------------------------------------------------------------------- /src/frontend/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 | -------------------------------------------------------------------------------- /src/frontend/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/frontend/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 | .site-badge-count-4 .ant-badge-count { 32 | margin-left: 10px; 33 | color: #999; 34 | background-color: #fff; 35 | box-shadow: 0 0 0 1px #d9d9d9 inset; 36 | } 37 | 38 | @keyframes App-logo-spin { 39 | from { 40 | transform: rotate(0deg); 41 | } 42 | to { 43 | transform: rotate(360deg); 44 | } 45 | } -------------------------------------------------------------------------------- /src/frontend/src/App.js: -------------------------------------------------------------------------------- 1 | // Custom functional componsents 2 | import { deleteStudent, getAllStudents } from "./Client"; 3 | import StudentDrawerForm from "./StudentDrawerForm"; 4 | import { errorNotification, successNotification } from "./Notification"; 5 | 6 | // Styles 7 | import './App.css'; 8 | 9 | // Functional componsents 10 | import { 11 | useState, 12 | useEffect 13 | } from "react"; 14 | 15 | import { 16 | Layout, 17 | Menu, 18 | Breadcrumb, 19 | Table, 20 | Spin, 21 | Empty, 22 | Button, 23 | Badge, 24 | Tag, 25 | Avatar, 26 | Radio, 27 | Popconfirm, 28 | Image, 29 | Divider 30 | } from "antd"; 31 | 32 | 33 | // Import Icons 34 | import { 35 | DesktopOutlined, 36 | FileOutlined, 37 | PieChartOutlined, 38 | TeamOutlined, 39 | UserOutlined, 40 | LoadingOutlined, 41 | PlusOutlined 42 | } from '@ant-design/icons'; 43 | 44 | 45 | 46 | // Setup Main Layout 47 | const { Header, Content, Footer, Sider } = Layout; 48 | 49 | const items = [ 50 | getItem('Option 1', '1', ), 51 | getItem('Option 2', '2', ), 52 | getItem('User', 'sub1', , [ 53 | getItem('Tom', '3'), 54 | getItem('Bill', '4'), 55 | getItem('Alex', '5'), 56 | ]), 57 | getItem('Team', 'sub2', , [getItem('Team 1', '6'), getItem('Team 2', '8')]), 58 | getItem('Files', '9', ), 59 | ]; 60 | 61 | function getItem(label, key, icon, children) { 62 | return { 63 | key, 64 | icon, 65 | children, 66 | label, 67 | }; 68 | } 69 | 70 | // Add avatar in the table column 71 | const TheAvatar = ({ name }) => { 72 | if (name === null) { 73 | if (name === null) { 74 | return } /> 75 | } 76 | 77 | let trim = name.trim(); 78 | const split = trim.split(" "); 79 | if (split.length === 1) { 80 | return {name.charAt(0)} 81 | } 82 | } 83 | 84 | return 85 | {`${name.charAt(0)}${name.charAt(name.length - 1)}`} 86 | 87 | } 88 | 89 | // Remove student request 90 | const removeStudent = (studentId, callback) => { 91 | deleteStudent(studentId).then(() => { 92 | successNotification("Student deleted", `Student with ${studentId} was deleted`); 93 | callback(); 94 | }).catch(err => { 95 | console.log(err.response); 96 | // get error response as an object 97 | err.response.json().then(res => { 98 | console.log(res); 99 | errorNotification( 100 | "There was an issue", 101 | `${res.message} [statusCode ${res.status}] [${res.statusText}]` 102 | ) 103 | }) 104 | }); 105 | } 106 | 107 | // Setup table columns 108 | const columns = fetchStudents => [ 109 | { 110 | title: '', 111 | dataIndex: 'avatar', 112 | key: 'avatar', 113 | render: (text, student) => 114 | }, 115 | { 116 | title: 'Id', 117 | dataIndex: 'id', 118 | key: 'id', 119 | }, 120 | { 121 | title: 'Name', 122 | dataIndex: 'name', 123 | key: 'name', 124 | }, 125 | { 126 | title: 'Email', 127 | dataIndex: 'email', 128 | key: 'email', 129 | }, 130 | { 131 | title: 'Gender', 132 | dataIndex: 'gender', 133 | key: 'gender', 134 | }, 135 | { 136 | title: 'Actions', 137 | key: 'actions', 138 | render: (text, student) => 139 | 140 | removeStudent(student.id, fetchStudents)} 144 | okText='Yes' 145 | cancelText='No'> 146 | Delete 147 | 148 | Edit 149 | 150 | } 151 | ]; 152 | 153 | 154 | // Setup spin loader 155 | const antIcon = ; 156 | 157 | 158 | function App() { 159 | 160 | // Fetch students only once when the component is loaded 161 | useEffect(() => { 162 | console.log("Component is mounted"); 163 | fetchStudents(); 164 | }, []); 165 | 166 | // Setup Main Layout left vertical sidebar/menu 167 | const [collapsed, setCollapsed] = useState(false); 168 | 169 | // Student Drawer Form vertical right sidebar/menu 170 | const [showDrawer, setShowDrawer] = useState(false); 171 | 172 | // Setup students data 173 | const [students, setStudents] = useState([]); 174 | // Setup spin indicator, while fetching data or loading something 175 | const [fetching, setFetching] = useState(true); 176 | 177 | const fetchStudents = () => getAllStudents() 178 | .then(response => response.json()) 179 | .then(data => { 180 | console.log(data); 181 | // fetch students data 182 | setStudents(data); 183 | }).catch(err => { 184 | console.log(err.response); 185 | // get the error response as object from BE 186 | err.response.json().then(res => { 187 | console.log(res); 188 | errorNotification( 189 | "There was an issues", 190 | `${res.message} [statusCode ${res.status}] [${res.error}]`); 191 | }); 192 | }).finally(() => { 193 | // stop the spin when the data is fetched 194 | setFetching(false); 195 | }); 196 | 197 | // Setup Table Students data 198 | const renderStudentsTable = () => { 199 | // Run spin while the data is fetching 200 | if (fetching) { return ; } 201 | 202 | // Check for data if is empty show add new student button hide students table and show empty bucket icon 203 | if (students.length <= 0) { 204 | return <> 205 | 210 | 215 | 216 | 217 | } 218 | 219 | return <> 220 | 221 | 226 | 227 | 232 | // <> Wrap more than one component 233 | <> 234 | Number of students 235 | 236 |

237 | 242 | 243 | 244 | } 245 | pagination={{ pageSize: 50 }} 246 | scroll={{ y: 500 }} 247 | rowKey={(student) => student.id} 248 | /> 249 | 250 | 251 | } 252 | 253 | // Main Layout 254 | return ( 255 | 260 | setCollapsed(value)}> 261 |
262 | 263 | 264 | 265 |
271 | 276 | 281 | User 282 | Bill 283 | 284 |
291 | 292 | { 293 | // Render Student Table 294 | renderStudentsTable() 295 | } 296 | 297 |
298 |
299 | 318 | 319 | 320 | ) 321 | } 322 | 323 | export default App; 324 | -------------------------------------------------------------------------------- /src/frontend/src/App.test.js: -------------------------------------------------------------------------------- 1 | import { render, screen } from '@testing-library/react'; 2 | import App from './App'; 3 | 4 | test('renders learn react link', () => {}); 5 | -------------------------------------------------------------------------------- /src/frontend/src/Client.js: -------------------------------------------------------------------------------- 1 | import fetch from 'unfetch'; 2 | 3 | const checkStatus = response => { 4 | if (response.ok) { 5 | return response; 6 | } 7 | 8 | // convert non-2xx HTTP responses into errors: 9 | const error = new Error(response.statusText); 10 | error.response = response; 11 | return Promise.reject(error); 12 | } 13 | 14 | export const getAllStudents = () => 15 | // To handle the CORS probelem, add proxy into package.json file 16 | fetch("api/v1/students") 17 | .then(response => checkStatus(response)); 18 | 19 | export const addNewStudent = student => 20 | fetch("api/v1/students", { 21 | headers: { 22 | 'Content-type': 'application/json' 23 | }, 24 | method: 'POST', 25 | body: JSON.stringify(student) 26 | }).then(checkStatus); 27 | 28 | export const deleteStudent = studentId => 29 | fetch(`api/v1/students/${studentId}`, { 30 | method: 'DELETE' 31 | }).then(checkStatus); -------------------------------------------------------------------------------- /src/frontend/src/Notification.js: -------------------------------------------------------------------------------- 1 | import { notification } from "antd"; 2 | 3 | const openNotificationWithIcon = (type, message, description, placement) => { 4 | placement = placement || "topRight"; 5 | notification[type]({ message, description, placement}); 6 | }; 7 | 8 | export const successNotification = (message, description, placement) => 9 | openNotificationWithIcon('success', message, description, placement); 10 | 11 | export const errorNotification = (message, description, placement) => 12 | openNotificationWithIcon('error', message, description, placement); 13 | 14 | export const infoNotification = (message, description, placement) => 15 | openNotificationWithIcon('info', message, description, placement); 16 | 17 | export const warningNotification = (message, description, placement) => 18 | openNotificationWithIcon('warning', message, description, placement); -------------------------------------------------------------------------------- /src/frontend/src/StudentDrawerForm.js: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { addNewStudent } from './Client'; 3 | import { successNotification, errorNotification } from './Notification'; 4 | 5 | import { 6 | Drawer, 7 | Input, 8 | Col, 9 | Select, 10 | Form, 11 | Row, 12 | Spin, 13 | Button, 14 | } from 'antd'; 15 | 16 | import { 17 | LoadingOutlined 18 | } from '@ant-design/icons'; 19 | 20 | const { Option } = Select; 21 | const antIcon = ; 22 | 23 | // This is the right vertical menu with form to add new students 24 | // Functional component StudnetDrawerForm with showDrawer, setShowDrawer, and fetchStudents properties 25 | function StudentDrawerForm({ showDrawer, setShowDrawer, fetchStudents }) { 26 | 27 | const onCLose = () => setShowDrawer(false); 28 | const [submitting, setSubmitting] = useState(false); 29 | 30 | const onFinish = student => { 31 | setSubmitting(true); 32 | console.log(JSON.stringify(student, null, 2)) 33 | addNewStudent(student) 34 | .then(() => { 35 | console.log("student added"); 36 | onCLose(); 37 | successNotification("Student succesfully added", `${student.name} was added to the system`) 38 | fetchStudents(); 39 | }).catch(err => { 40 | console.log(err.response); 41 | // get error response as an object 42 | err.response.json().then(res => { 43 | errorNotification( 44 | "There was an issue", 45 | `${res.message} [statusCode ${res.status}] [${res.statusText}]`, 46 | "bottomLeft"); 47 | }); 48 | }).finally(() => { 49 | setSubmitting(false); 50 | }) 51 | }; 52 | 53 | const onFinishFailed = errorInfo => { 54 | alert(JSON.stringify(errorInfo, null, 2)); 55 | }; 56 | 57 | return ( 58 | 70 | 73 |
74 | } 75 | > 76 |
80 | 81 |
82 | 87 | 88 | 89 | 90 | 91 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 107 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 121 | 122 | 123 | 124 | 125 | {submitting && } 126 | 127 | 128 | 129 | ) 130 | } 131 | 132 | export default StudentDrawerForm; -------------------------------------------------------------------------------- /src/frontend/src/index.css: -------------------------------------------------------------------------------- 1 | /* Import antd css styles */ 2 | @import 'antd/dist/reset.css'; 3 | 4 | body { 5 | margin: 0; 6 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 7 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 8 | sans-serif; 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | } 12 | 13 | code { 14 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 15 | monospace; 16 | } 17 | -------------------------------------------------------------------------------- /src/frontend/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import './index.css'; 4 | import App from './App'; 5 | import reportWebVitals from './reportWebVitals'; 6 | 7 | const root = ReactDOM.createRoot(document.getElementById('root')); 8 | root.render( 9 | 10 | 11 | 12 | ); 13 | 14 | // If you want to start measuring performance in your app, pass a function 15 | // to log results (for example: reportWebVitals(console.log)) 16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 17 | reportWebVitals(); 18 | -------------------------------------------------------------------------------- /src/frontend/src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/frontend/src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = onPerfEntry => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 4 | getCLS(onPerfEntry); 5 | getFID(onPerfEntry); 6 | getFCP(onPerfEntry); 7 | getLCP(onPerfEntry); 8 | getTTFB(onPerfEntry); 9 | }); 10 | } 11 | }; 12 | 13 | export default reportWebVitals; 14 | -------------------------------------------------------------------------------- /src/frontend/src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /src/main/java/com/syscomz/springbootfullstackprofessional/SpringBootFullStackProfessionalApplication.java: -------------------------------------------------------------------------------- 1 | package com.syscomz.springbootfullstackprofessional; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class SpringBootFullStackProfessionalApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(SpringBootFullStackProfessionalApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/syscomz/springbootfullstackprofessional/student/Gender.java: -------------------------------------------------------------------------------- 1 | package com.syscomz.springbootfullstackprofessional.student; 2 | 3 | public enum Gender { 4 | MALE, 5 | FEMALE, 6 | OTHER 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/syscomz/springbootfullstackprofessional/student/Student.java: -------------------------------------------------------------------------------- 1 | package com.syscomz.springbootfullstackprofessional.student; 2 | 3 | import lombok.*; 4 | 5 | import javax.persistence.*; 6 | import javax.validation.constraints.Email; 7 | import javax.validation.constraints.NotBlank; 8 | import javax.validation.constraints.NotNull; 9 | 10 | @Getter 11 | @Setter 12 | @ToString 13 | @EqualsAndHashCode 14 | @AllArgsConstructor 15 | @NoArgsConstructor 16 | @Entity // it is used by JPQL (Java Persistence Query Language) 17 | @Table // takes the class Student and create table with name students, it's using camelCase naming convention 18 | public class Student { 19 | 20 | @Id 21 | @SequenceGenerator( 22 | name = "student_sequence", 23 | sequenceName = "student_sequence", 24 | allocationSize = 1 25 | ) 26 | @GeneratedValue( 27 | generator = "student_sequence", 28 | strategy = GenerationType.SEQUENCE 29 | ) 30 | private Long id; 31 | @NotBlank // BE validation 32 | @Column(nullable = false) // database validations 33 | private String name; 34 | @Email // BE validation. can have custom regex. 35 | @Column(nullable = false, unique = true) // database validations 36 | private String email; 37 | @NotNull // BE validation 38 | @Enumerated(EnumType.STRING) 39 | @Column(nullable = false) // database validations 40 | private Gender gender; 41 | 42 | public Student(String name, String email, Gender gender) { 43 | this.name = name; 44 | this.email = email; 45 | this.gender = gender; 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/syscomz/springbootfullstackprofessional/student/StudentController.java: -------------------------------------------------------------------------------- 1 | package com.syscomz.springbootfullstackprofessional.student; 2 | 3 | import lombok.AllArgsConstructor; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.web.bind.annotation.*; 6 | 7 | import javax.validation.Valid; 8 | import java.util.List; 9 | 10 | @RestController 11 | @RequestMapping(path = "api/v1/students") 12 | @AllArgsConstructor 13 | public class StudentController { 14 | 15 | @Autowired 16 | private final StudentService studentService; 17 | 18 | @GetMapping 19 | public List getAllStudents() { 20 | return studentService.getAllStudents(); 21 | } 22 | 23 | // @Valid annotation makes sure that the body has valid data in it, which validations are described in Student POJO class 24 | @PostMapping 25 | public void addStudent(@Valid @RequestBody Student student) { 26 | studentService.addStudent(student); 27 | } 28 | 29 | @DeleteMapping(path = "{studentId}") 30 | public void deleteStudent(@PathVariable("studentId") Long studentId) { 31 | studentService.deleteStudent(studentId); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/syscomz/springbootfullstackprofessional/student/StudentRepository.java: -------------------------------------------------------------------------------- 1 | package com.syscomz.springbootfullstackprofessional.student; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | import org.springframework.data.jpa.repository.Query; 5 | import org.springframework.stereotype.Repository; 6 | 7 | @Repository 8 | public interface StudentRepository extends JpaRepository { 9 | 10 | // JPQL request, which is enabled by @Entity in Student object 11 | @Query("" + 12 | "SELECT CASE WHEN COUNT(s) > 0 THEN " + 13 | "TRUE ELSE FALSE END " + 14 | "FROM Student s " + 15 | "WHERE s.email = ?1" 16 | ) 17 | Boolean selectExistsEmail(String email); 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/syscomz/springbootfullstackprofessional/student/StudentService.java: -------------------------------------------------------------------------------- 1 | package com.syscomz.springbootfullstackprofessional.student; 2 | 3 | import com.syscomz.springbootfullstackprofessional.student.exception.BadRequestException; 4 | import com.syscomz.springbootfullstackprofessional.student.exception.StudentNotFoundException; 5 | import lombok.AllArgsConstructor; 6 | import org.springframework.stereotype.Service; 7 | 8 | import java.util.List; 9 | 10 | @AllArgsConstructor 11 | @Service 12 | public class StudentService { 13 | 14 | private final StudentRepository studentRepository; 15 | 16 | public List getAllStudents() { 17 | return studentRepository.findAll(); 18 | } 19 | 20 | public void addStudent(Student student) { 21 | Boolean isEmailTaken = studentRepository.selectExistsEmail(student.getEmail()); 22 | if (isEmailTaken) 23 | throw new BadRequestException(String.format("Student with email %s, already exists!", student.getEmail())); 24 | 25 | studentRepository.save(student); 26 | } 27 | 28 | public void deleteStudent(Long studentId) { 29 | boolean isStudentNotExists = !studentRepository.existsById(studentId); 30 | if (isStudentNotExists) 31 | throw new StudentNotFoundException(String.format("Student with id %d, does not exists!", studentId)); 32 | 33 | studentRepository.deleteById(studentId); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/syscomz/springbootfullstackprofessional/student/exception/BadRequestException.java: -------------------------------------------------------------------------------- 1 | package com.syscomz.springbootfullstackprofessional.student.exception; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.web.bind.annotation.ResponseStatus; 5 | 6 | @ResponseStatus(HttpStatus.BAD_REQUEST) 7 | public class BadRequestException extends RuntimeException{ 8 | public BadRequestException(String msg) { 9 | super(msg); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/syscomz/springbootfullstackprofessional/student/exception/StudentNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.syscomz.springbootfullstackprofessional.student.exception; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.web.bind.annotation.ResponseStatus; 5 | 6 | @ResponseStatus(HttpStatus.NOT_FOUND) 7 | public class StudentNotFoundException extends RuntimeException { 8 | public StudentNotFoundException(String msg) { 9 | super(msg); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/resources/application-dev.properties: -------------------------------------------------------------------------------- 1 | erver.error.include-message=always 2 | server.error.include-binding-errors=always 3 | 4 | spring.datasource.url=jdbc:postgresql://awseb-e-qxrephbvpc-stack-awsebrdsdatabase-gnjcw2rerkkq.ca2pu4hjb4cr.eu-west-2.rds.amazonaws.com:5432/syscomz 5 | spring.datasource.driver-class-name=org.postgresql.Driver 6 | spring.datasource.username=syscomz 7 | spring.datasource.password=password 8 | spring.jpa.hibernate.ddl-auto=update 9 | spring.jpa.show-sql=true 10 | spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect 11 | spring.jpa.properties.hibernate.format_sql=true -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | # JPA configuration 2 | spring.datasource.url=jdbc:postgresql://localhost:5555/syscomz 3 | spring.datasource.driver-class-name=org.postgresql.Driver 4 | spring.datasource.username=postgres 5 | spring.datasource.password=password 6 | spring.jpa.hibernate.ddl-auto=update 7 | spring.jpa.show-sql=true 8 | spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect 9 | spring.jpa.properties.hibernate.format_sql=true 10 | 11 | # Get BE (error, info, and other) messages to the FE 12 | # The server throws the error message to the client 13 | # The message can be viewed in the Network -> Response tab 14 | server.error.include-message=always 15 | server.error.include-binding-errors=always 16 | -------------------------------------------------------------------------------- /src/test/java/com/syscomz/springbootfullstackprofessional/SpringBootFullStackProfessionalApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.syscomz.springbootfullstackprofessional; 2 | 3 | import org.junit.jupiter.api.Disabled; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static org.assertj.core.api.AssertionsForClassTypes.assertThat; 7 | 8 | class SpringBootFullStackProfessionalApplicationTests { 9 | 10 | Calculator underTest = new Calculator(); 11 | 12 | // BDD: behavior-driven development testing style 13 | // User Journey Story and Given, When and Then 14 | // Given - the state of the system that will receive the behavior/action 15 | // When - the behavior/action that happens and causes the result in the end 16 | // Then - the result caused by the behavior in the state 17 | @Test 18 | // @Disabled // Stop this test to be started 19 | void itShouldAddTwoNumbers() { 20 | // given 21 | int numberOne = 20; 22 | int numberTwo = 30; 23 | 24 | // when 25 | int result = underTest.add(numberOne, numberTwo); 26 | 27 | // then 28 | int expected = 50; 29 | assertThat(result).isEqualTo(expected); 30 | } 31 | 32 | class Calculator { 33 | int add(int a, int b) { 34 | return a + b; 35 | } 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/test/java/com/syscomz/springbootfullstackprofessional/integration/StudentIT.java: -------------------------------------------------------------------------------- 1 | package com.syscomz.springbootfullstackprofessional.integration; 2 | 3 | import com.fasterxml.jackson.core.type.TypeReference; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import com.github.javafaker.Faker; 6 | import com.syscomz.springbootfullstackprofessional.student.Gender; 7 | import com.syscomz.springbootfullstackprofessional.student.Student; 8 | import com.syscomz.springbootfullstackprofessional.student.StudentRepository; 9 | import org.junit.jupiter.api.Test; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 12 | import org.springframework.boot.test.context.SpringBootTest; 13 | import org.springframework.http.MediaType; 14 | import org.springframework.test.context.TestPropertySource; 15 | import org.springframework.test.web.servlet.MockMvc; 16 | import org.springframework.test.web.servlet.MvcResult; 17 | import org.springframework.test.web.servlet.ResultActions; 18 | 19 | import java.util.List; 20 | 21 | import static org.assertj.core.api.AssertionsForClassTypes.assertThat; 22 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; 23 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 24 | 25 | // IT means integration test, which is important because the maven Failsafe plugin will look for it. 26 | // We need to run integration tests against the database that we're going to run when we deploy to real users. In our case, it is Postgres. 27 | @SpringBootTest 28 | // When we add @TestPropertiesSource we should make maven clean 29 | // After that we should add spring.datasource.driver-class-name=org.postgresql.Driver in aplication-it.properties file, and it is good practice to add it and into our application.properties file and also into application-dev.properties file 30 | @TestPropertySource(locations = "classpath:application-it.properties") 31 | // setup integration tests to use application-it.properties 32 | @AutoConfigureMockMvc // enable us to @Autowired the MockMvc 33 | public class StudentIT { 34 | 35 | @Autowired 36 | private MockMvc mockMvc; 37 | 38 | @Autowired 39 | private ObjectMapper objectMapper; 40 | 41 | @Autowired 42 | private StudentRepository studentRepository; 43 | 44 | private final Faker faker = new Faker(); 45 | 46 | @Test 47 | void canRegisterNewStudent() throws Exception { 48 | // given 49 | String name = String.format("%s %s", faker.name().firstName(), faker.name().lastName()); 50 | String email = String.format("%s@syscomz.com", name.replace(" ", "").toLowerCase()); 51 | 52 | Student student = new Student(name, email, Gender.MALE); 53 | 54 | // when 55 | ResultActions resultActions = mockMvc.perform(post("/api/v1/students") 56 | .contentType(MediaType.APPLICATION_JSON) 57 | .content(objectMapper.writeValueAsString(student))); 58 | 59 | // then 60 | resultActions.andExpect(status().isOk()); 61 | List students = studentRepository.findAll(); 62 | assertThat(students) 63 | .usingRecursiveComparison() 64 | .ignoringFields("id") // ignore id because it is randomly generated for us 65 | .comparingOnlyFields("name", "email", "gender"); 66 | } 67 | 68 | @Test 69 | void canDeleteStudent() throws Exception { 70 | // given 71 | String name = String.format("%s %s", faker.name().firstName(), faker.name().lastName()); 72 | String email = String.format("%s@syscomz.com", name.replace(" ", "").toLowerCase()); 73 | 74 | Student student = new Student(name, email, Gender.MALE); 75 | 76 | mockMvc.perform(post("/api/v1/students") 77 | .contentType(MediaType.APPLICATION_JSON) 78 | .content(objectMapper.writeValueAsString(student))) 79 | .andExpect(status().isOk()); 80 | 81 | MvcResult getStudentsResult = mockMvc.perform(get("/api/v1/students") 82 | .contentType(MediaType.APPLICATION_JSON)) 83 | .andExpect(status().isOk()) 84 | .andReturn(); 85 | 86 | String contentAsString = getStudentsResult 87 | .getResponse() 88 | .getContentAsString(); 89 | 90 | List students = objectMapper.readValue( 91 | contentAsString, 92 | new TypeReference>() {} 93 | ); 94 | 95 | long id = students.stream() 96 | .filter(s -> s.getEmail().equals(student.getEmail())) 97 | .map(Student::getId) 98 | .findFirst() 99 | .orElseThrow(() -> new IllegalStateException("student with email: " + email + " not found")); 100 | 101 | // when 102 | ResultActions resultActions = mockMvc 103 | .perform(delete("/api/v1/students/" + id)); 104 | 105 | // then 106 | resultActions.andExpect(status().isOk()); 107 | boolean exists = studentRepository.existsById(id); 108 | assertThat(exists).isFalse(); 109 | } 110 | 111 | 112 | } 113 | -------------------------------------------------------------------------------- /src/test/java/com/syscomz/springbootfullstackprofessional/student/StudentRepositoryTest.java: -------------------------------------------------------------------------------- 1 | package com.syscomz.springbootfullstackprofessional.student; 2 | 3 | import org.junit.jupiter.api.AfterEach; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; 7 | 8 | import static org.assertj.core.api.AssertionsForClassTypes.assertThat; 9 | 10 | // Testing Unit StudentRepositoryTest 11 | // @SpringBootTest(classes = StudentRepositoryTest.class) 12 | @DataJpaTest 13 | class StudentRepositoryTest { 14 | // We should test only our own custom methods 15 | // Spring Data JPA methods are already tested for us 16 | 17 | @Autowired 18 | private StudentRepository underTest; 19 | 20 | @AfterEach 21 | void tearDown() { 22 | underTest.deleteAll(); 23 | } 24 | 25 | @Test 26 | void itShouldCheckWhenStudentEmailExists() { 27 | // given 28 | String email = "b.dostumski@gmail.com"; 29 | Student student = new Student("Borislav", email, Gender.MALE); 30 | underTest.save(student); 31 | 32 | //when 33 | boolean expected = underTest.selectExistsEmail(email); 34 | 35 | // then 36 | assertThat(expected).isTrue(); 37 | } 38 | 39 | @Test 40 | void itShouldCheckWhenStudentEmailDoesNotExists() { 41 | // given 42 | String email = "b.dostumski@gmail.com"; 43 | 44 | //when 45 | boolean expected = underTest.selectExistsEmail(email); 46 | 47 | // then 48 | assertThat(expected).isFalse(); 49 | } 50 | } -------------------------------------------------------------------------------- /src/test/java/com/syscomz/springbootfullstackprofessional/student/StudentServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.syscomz.springbootfullstackprofessional.student; 2 | 3 | import com.syscomz.springbootfullstackprofessional.student.exception.BadRequestException; 4 | import com.syscomz.springbootfullstackprofessional.student.exception.StudentNotFoundException; 5 | import org.junit.jupiter.api.AfterEach; 6 | import org.junit.jupiter.api.BeforeEach; 7 | import org.junit.jupiter.api.Test; 8 | import org.mockito.ArgumentCaptor; 9 | import org.mockito.Mock; 10 | import org.mockito.MockitoAnnotations; 11 | 12 | import static org.assertj.core.api.AssertionsForClassTypes.assertThat; 13 | import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; 14 | import static org.mockito.ArgumentMatchers.any; 15 | import static org.mockito.BDDMockito.given; 16 | import static org.mockito.Mockito.never; 17 | import static org.mockito.Mockito.verify; 18 | 19 | 20 | // Testing Unit StudentServiceTest 21 | class StudentServiceTest { 22 | 23 | @Mock // Mock already tested unit - we didn't need to test it again, so we mock it 24 | private StudentRepository studentRepository; 25 | private AutoCloseable autoCloseable; 26 | private StudentService underTest; 27 | 28 | @BeforeEach 29 | void setUp() { 30 | autoCloseable = MockitoAnnotations.openMocks(this); // initialize all the @Mock 's in this class 31 | underTest = new StudentService(studentRepository); 32 | } 33 | 34 | @AfterEach 35 | void tearDown() throws Exception { 36 | autoCloseable.close(); // close the resource after each test 37 | } 38 | 39 | @Test 40 | void canGetAllStudents() { 41 | // when 42 | underTest.getAllStudents(); 43 | // then 44 | verify(studentRepository).findAll(); 45 | } 46 | 47 | // @Disabled // disable test 48 | @Test 49 | void canAddStudent() { 50 | // given 51 | Student student = new Student("Borislav", "b.dostumski@gmail.com", Gender.MALE); 52 | 53 | // when 54 | underTest.addStudent(student); 55 | 56 | // then 57 | ArgumentCaptor studentArgumentCaptor = ArgumentCaptor.forClass(Student.class); 58 | verify(studentRepository).save(studentArgumentCaptor.capture()); 59 | Student capturedStudent = studentArgumentCaptor.getValue(); 60 | assertThat(capturedStudent).isEqualTo(student); 61 | } 62 | 63 | @Test 64 | void willThrowWhenEmailIsTaken() { 65 | // given 66 | Student student = new Student("Borislav", "b.dostumski@gmail.com", Gender.MALE); 67 | 68 | // Mock that the email of the student already exists 69 | // to get in the if the statement of the method addStudent(), and throw an exception 70 | given(studentRepository.selectExistsEmail(student.getEmail())).willReturn(Boolean.TRUE); 71 | 72 | // when 73 | // then 74 | assertThatThrownBy(() -> underTest.addStudent(student)) 75 | .isInstanceOf(BadRequestException.class) 76 | .hasMessageContaining(String.format("Student with email %s, already exists!", student.getEmail())); 77 | 78 | // checks that the student repository is never called to save any data in it 79 | verify(studentRepository, never()).save(any()); 80 | } 81 | 82 | // @Disabled // disable test 83 | @Test 84 | void canDeleteStudent() { 85 | // given 86 | long id = 10; 87 | given(studentRepository.existsById(id)) 88 | .willReturn(true); 89 | // when 90 | underTest.deleteStudent(id); 91 | 92 | // then 93 | verify(studentRepository).deleteById(id); 94 | } 95 | 96 | @Test 97 | void willThrowWhenDeleteStudentNotFound() { 98 | // given 99 | long id = 10; 100 | given(studentRepository.existsById(id)) 101 | .willReturn(false); 102 | // when 103 | // then 104 | assertThatThrownBy(() -> underTest.deleteStudent(id)) 105 | .isInstanceOf(StudentNotFoundException.class) 106 | .hasMessageContaining(String.format("Student with id %d, does not exists!", id)); 107 | 108 | verify(studentRepository, never()).deleteById(any()); 109 | } 110 | } -------------------------------------------------------------------------------- /src/test/resources/application-it.properties: -------------------------------------------------------------------------------- 1 | # JPA configuration 2 | spring.datasource.url=jdbc:postgresql://localhost:5555/syscomz 3 | spring.datasource.driver-class-name=org.postgresql.Driver 4 | spring.datasource.username=postgres 5 | spring.datasource.password=password 6 | spring.jpa.hibernate.ddl-auto=update 7 | spring.jpa.show-sql=true 8 | spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect 9 | spring.jpa.properties.hibernate.format_sql=true 10 | 11 | # Get BE (error, info, and other) messages to the FE 12 | # The server throws the error message to the client 13 | # The message can be viewed in the Network -> Response tab 14 | server.error.include-message=always 15 | server.error.include-binding-errors=always 16 | -------------------------------------------------------------------------------- /src/test/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.datasource.url=jdbc:h2://mem:db;DB_CLOSE_DELAY=-1 2 | spring.datasource.username=sa 3 | spring.datasource.password=sa 4 | spring.datasource.driver-class-name=org.h2.Driver 5 | spring.jpa.hibernate.ddl-auto=create-drop 6 | spring.jpa.show-sql=true 7 | spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect 8 | spring.jpa.properties.hibernate.format_sql=true 9 | --------------------------------------------------------------------------------