├── .docker ├── .dockerignore ├── docker-compose.yaml ├── dockerfile ├── localstack.sh ├── logstash.conf └── postgres-admin.json ├── .editorconfig ├── .gitattributes ├── .github └── workflows │ └── build.yaml ├── .gitignore ├── license.md ├── readme.md └── source ├── .run ├── Application.run.xml └── Tests.run.xml ├── lombok.config ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── company │ │ └── architecture │ │ ├── Application.java │ │ ├── animal │ │ ├── CreateAnimalInput.java │ │ ├── CreateAnimalOutput.java │ │ └── CreateAnimalUseCase.java │ │ ├── api │ │ ├── JsonPlaceholderClient.java │ │ ├── JsonPlaceholderClientFallback.java │ │ ├── JsonPlaceholderService.java │ │ └── Todo.java │ │ ├── auth │ │ ├── Auth.java │ │ ├── AuthConfiguration.java │ │ ├── AuthController.java │ │ ├── AuthFilter.java │ │ ├── AuthService.java │ │ ├── Authority.java │ │ ├── JwtConfiguration.java │ │ └── JwtService.java │ │ ├── aws │ │ ├── AwsConfiguration.java │ │ ├── AwsController.java │ │ ├── AwsS3Service.java │ │ ├── AwsService.java │ │ └── AwsSqsService.java │ │ ├── book │ │ ├── BookController.java │ │ └── create │ │ │ ├── CreateBookHandler.java │ │ │ └── CreateBookRequest.java │ │ ├── car │ │ ├── Car.java │ │ ├── CarController.java │ │ └── CarService.java │ │ ├── category │ │ ├── Category.java │ │ ├── CategoryCacheService.java │ │ ├── CategoryRepository.java │ │ └── CategoryService.java │ │ ├── game │ │ ├── Game.java │ │ ├── GameRepository.java │ │ └── GameService.java │ │ ├── group │ │ ├── GroupService.java │ │ └── Person.java │ │ ├── invoice │ │ ├── InvoiceController.java │ │ ├── InvoiceRepository.java │ │ ├── InvoiceService.java │ │ ├── InvoiceStatus.java │ │ ├── dtos │ │ │ ├── AddInvoiceDto.java │ │ │ ├── AddInvoiceItemDto.java │ │ │ ├── InvoiceDto.java │ │ │ └── InvoiceItemDto.java │ │ └── entities │ │ │ ├── BaseEntity.java │ │ │ ├── Invoice.java │ │ │ └── InvoiceItem.java │ │ ├── location │ │ ├── City.java │ │ ├── Country.java │ │ ├── FlatLocation.java │ │ ├── Location.java │ │ ├── LocationService.java │ │ └── State.java │ │ ├── movie │ │ ├── Movie.java │ │ ├── MovieConfiguration.java │ │ ├── MovieController.java │ │ ├── MovieOutbox.java │ │ ├── MovieOutboxRepository.java │ │ ├── MovieOutboxService.java │ │ ├── MovieRepository.java │ │ └── MovieService.java │ │ ├── notification │ │ ├── Notification.java │ │ ├── NotificationController.java │ │ └── NotificationService.java │ │ ├── payment │ │ ├── CreditCardPaymentStrategy.java │ │ ├── DebitCardPaymentStrategy.java │ │ ├── PaymentMethod.java │ │ ├── PaymentService.java │ │ └── PaymentStrategy.java │ │ ├── product │ │ ├── Product.java │ │ ├── ProductController.java │ │ ├── ProductRepository.java │ │ ├── ProductService.java │ │ └── dtos │ │ │ ├── AddProductDto.java │ │ │ ├── GetProductDto.java │ │ │ ├── ProductDto.java │ │ │ ├── UpdatePriceProductDto.java │ │ │ └── UpdateProductDto.java │ │ ├── shared │ │ ├── configurations │ │ │ ├── ExceptionConfiguration.java │ │ │ └── JacksonConfiguration.java │ │ ├── dtos │ │ │ └── PageableDto.java │ │ ├── mediator │ │ │ ├── Handler.java │ │ │ └── Mediator.java │ │ ├── runners │ │ │ └── MongoRunner.java │ │ ├── services │ │ │ ├── MapperService.java │ │ │ ├── MessageService.java │ │ │ └── ValidatorService.java │ │ ├── swagger │ │ │ ├── BaseApiResponses.java │ │ │ ├── DefaultApiResponses.java │ │ │ ├── GetApiResponses.java │ │ │ ├── PostApiResponses.java │ │ │ └── SwaggerConfiguration.java │ │ └── usecase │ │ │ ├── Input.java │ │ │ ├── Output.java │ │ │ └── UseCase.java │ │ └── user │ │ ├── User.java │ │ ├── UserController.java │ │ ├── UserRepository.java │ │ ├── UserService.java │ │ └── dtos │ │ ├── AddUserDto.java │ │ ├── GetUserDto.java │ │ ├── UpdateUserDto.java │ │ └── UserDto.java └── resources │ ├── application.yml │ ├── logback-spring.xml │ ├── messages.properties │ └── messages_pt_BR.properties └── test └── java └── com └── company └── architecture ├── ControllerTest.java ├── IntegrationTest.java ├── KafkaTest.java ├── LocalStackTest.java ├── MongoTest.java ├── PostgreTest.java ├── animal └── CreateAnimalUseCaseTest.java ├── api └── JsonPlaceholderServiceTest.java ├── auth ├── AuthIntegrationTest.java └── JwtServiceTest.java ├── aws ├── AwsIntegrationTest.java ├── AwsS3ServiceTest.java ├── AwsServiceTest.java └── AwsSqsServiceTest.java ├── book ├── BookControllerTest.java ├── BookIntegrationTest.java ├── CreateBookHandlerMediatorTest.java └── CreateBookHandlerTest.java ├── car └── CarServiceTest.java ├── category └── CategoryServiceTest.java ├── game ├── GameServiceTest.java └── MockGameServiceTest.java ├── group └── GroupServiceTest.java ├── invoice ├── InvoiceIntegrationTest.java └── InvoiceRepositoryTest.java ├── location └── LocationServiceTest.java ├── movie └── MovieIntegrationTest.java ├── notification └── NotificationTest.java ├── payment └── PaymentServiceTest.java ├── product ├── ProductControllerTest.java ├── ProductIntegrationTest.java └── ProductRepositoryTest.java ├── shared ├── Color.java ├── Data.java ├── Dto.java ├── Entity.java └── services │ ├── MapperServiceTest.java │ ├── MessageServiceTest.java │ └── ValidatorServiceTest.java └── user ├── UserControllerTest.java └── UserIntegrationTest.java /.docker/.dockerignore: -------------------------------------------------------------------------------- 1 | *.bat 2 | *.dockerignore 3 | *.editorconfig 4 | *.gitattributes 5 | *.gitignore 6 | *.iml 7 | *.md 8 | *.yml 9 | .git/ 10 | .github/ 11 | .idea/ 12 | .vscode/ 13 | target/ 14 | -------------------------------------------------------------------------------- /.docker/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | # docker compose up --detach --build --force-recreate --remove-orphans 2 | 3 | name: java 4 | services: 5 | application: 6 | image: application 7 | container_name: application 8 | depends_on: 9 | - kafka 10 | - localstack 11 | - mongo 12 | - postgres 13 | - elk-logstash 14 | build: 15 | context: .. 16 | dockerfile: .docker/dockerfile 17 | ports: 18 | - "8090:8080" 19 | environment: 20 | #SPRING_PROFILES_ACTIVE: production 21 | SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/database 22 | SPRING_DATASOURCE_USERNAME: admin 23 | SPRING_DATASOURCE_PASSWORD: password 24 | SPRING_DATA_MONGODB_URI: mongodb://admin:password@mongo:27017/database?authSource=admin 25 | SPRING_KAFKA_BOOTSTRAP_SERVERS: kafka:9094 26 | AWS_ENDPOINT: http://localstack:4566 27 | AWS_REGION: us-east-1 28 | AWS_ACCESS_KEY_ID: test 29 | AWS_SECRET_ACCESS_KEY: test 30 | ELK_LOGSTASH: elk-logstash:5000 31 | FEIGN_CLIENTS_JSONPLACEHOLDER_URL: https://jsonplaceholder.typicode.com 32 | elk-elasticsearch: 33 | image: docker.elastic.co/elasticsearch/elasticsearch:9.0.0 34 | container_name: elk-elasticsearch 35 | ports: 36 | - "9200:9200" 37 | environment: 38 | - discovery.type=single-node 39 | - xpack.security.enabled=false 40 | - ES_JAVA_OPTS=-Xms512m -Xmx512m 41 | elk-logstash: 42 | image: docker.elastic.co/logstash/logstash:9.0.0 43 | container_name: elk-logstash 44 | depends_on: 45 | - elk-elasticsearch 46 | ports: 47 | - "5000:5000" 48 | volumes: 49 | - ./logstash.conf:/usr/share/logstash/pipeline/logstash.conf:ro 50 | elk-kibana: 51 | image: docker.elastic.co/kibana/kibana:9.0.0 52 | container_name: elk-kibana 53 | depends_on: 54 | - elk-elasticsearch 55 | ports: 56 | - "5601:5601" 57 | environment: 58 | - ELASTICSEARCH_HOSTS=http://elk-elasticsearch:9200 59 | elk-apm-server: 60 | image: docker.elastic.co/apm/apm-server:9.0.0 61 | container_name: elk-apm 62 | depends_on: 63 | - elk-elasticsearch 64 | - elk-kibana 65 | ports: 66 | - "8200:8200" 67 | environment: 68 | - output.elasticsearch.hosts=["http://elk-elasticsearch:9200"] 69 | - apm-server.host=0.0.0.0:8200 70 | - apm-server.auth.anonymous.enabled=true 71 | - apm-server.kibana.enabled=true 72 | - apm-server.kibana.host=http://elk-kibana:5601 73 | kafka: 74 | image: apache/kafka 75 | container_name: kafka 76 | ports: 77 | - "9092:9092" 78 | environment: 79 | KAFKA_NODE_ID: 1 80 | KAFKA_PROCESS_ROLES: broker,controller 81 | KAFKA_LISTENERS: CONTROLLER://:9093,PLAINTEXT://:9094,EXTERNAL://:9092 82 | KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9094,EXTERNAL://localhost:9092 83 | KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER 84 | KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT,EXTERNAL:PLAINTEXT 85 | KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka:9093 86 | KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 87 | KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1 88 | KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1 89 | kafka-admin: 90 | image: obsidiandynamics/kafdrop 91 | container_name: kafka-admin 92 | depends_on: 93 | - kafka 94 | ports: 95 | - "9000:9000" 96 | environment: 97 | KAFKA_BROKERCONNECT: kafka:9094 98 | localstack: 99 | image: localstack/localstack 100 | container_name: localstack 101 | ports: 102 | - "4566:4566" 103 | volumes: 104 | - /var/run/docker.sock:/var/run/docker.sock 105 | - ./localstack.sh:/etc/localstack/init/ready.d/localstack.sh 106 | environment: 107 | - SERVICES=sqs,sqs-query,s3 108 | mongo: 109 | image: mongo 110 | container_name: mongo 111 | ports: 112 | - "27017:27017" 113 | volumes: 114 | - mongo:/data/db 115 | environment: 116 | MONGO_INITDB_ROOT_USERNAME: admin 117 | MONGO_INITDB_ROOT_PASSWORD: password 118 | mongo-admin: 119 | image: mongo-express 120 | container_name: mongo-admin 121 | depends_on: 122 | - mongo 123 | ports: 124 | - "27018:8081" 125 | environment: 126 | ME_CONFIG_MONGODB_URL: mongodb://admin:password@mongo:27017 127 | ME_CONFIG_MONGODB_ADMINUSERNAME: admin 128 | ME_CONFIG_MONGODB_ADMINPASSWORD: password 129 | ME_CONFIG_BASICAUTH: false 130 | postgres: 131 | image: postgres 132 | container_name: postgres 133 | ports: 134 | - "5432:5432" 135 | volumes: 136 | - postgres:/var/lib/postgresql/data 137 | environment: 138 | POSTGRES_DB: database 139 | POSTGRES_USER: admin 140 | POSTGRES_PASSWORD: password 141 | postgres-admin: 142 | image: dpage/pgadmin4 143 | container_name: postgres-admin 144 | depends_on: 145 | - postgres 146 | ports: 147 | - "5433:80" 148 | volumes: 149 | - ./postgres-admin.json:/pgadmin4/servers.json 150 | - postgres-admin:/var/lib/pgadmin 151 | environment: 152 | PGADMIN_DEFAULT_EMAIL: admin@admin.com 153 | PGADMIN_DEFAULT_PASSWORD: password 154 | PGADMIN_CONFIG_SERVER_MODE: "False" 155 | PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED: "False" 156 | volumes: 157 | mongo: 158 | postgres: 159 | postgres-admin: 160 | -------------------------------------------------------------------------------- /.docker/dockerfile: -------------------------------------------------------------------------------- 1 | FROM eclipse-temurin:24-jdk-alpine AS build 2 | RUN apk add --no-cache maven 3 | WORKDIR /source 4 | COPY source/pom.xml . 5 | RUN mvn dependency:go-offline -X 6 | COPY source . 7 | RUN mvn clean package -DskipTests -X 8 | 9 | FROM eclipse-temurin:24-jre-alpine 10 | WORKDIR /app 11 | COPY --from=build /source/target/*.jar app.jar 12 | EXPOSE 8090 13 | ENTRYPOINT ["java", "-jar", "app.jar"] 14 | -------------------------------------------------------------------------------- /.docker/localstack.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | awslocal sqs create-queue --queue-name queue 3 | awslocal s3 mb s3://bucket 4 | -------------------------------------------------------------------------------- /.docker/logstash.conf: -------------------------------------------------------------------------------- 1 | input { 2 | tcp { 3 | port => 5000 4 | codec => json 5 | } 6 | } 7 | 8 | output { 9 | elasticsearch { 10 | hosts => ["http://elk-elasticsearch:9200"] 11 | index => "application-%{+YYYY-MM-dd}" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.docker/postgres-admin.json: -------------------------------------------------------------------------------- 1 | { 2 | "Servers": { 3 | "Database": { 4 | "Group": "Servers", 5 | "Name": "Docker", 6 | "Host": "postgres", 7 | "Port": 5432, 8 | "MaintenanceDB": "postgres", 9 | "Username": "admin", 10 | "Password": "password", 11 | "SSLMode": "prefer", 12 | "Favorite": true 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | charset = utf-8 3 | end_of_line = lf 4 | indent_size = 4 5 | indent_style = space 6 | insert_final_newline = true 7 | max_line_length = 500 8 | tab_width = 4 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.java diff=java 3 | -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: build 2 | on: 3 | push: 4 | branches: [main] 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Checkout 10 | uses: actions/checkout@v4 11 | 12 | - name: Java Setup 13 | uses: actions/setup-java@v4 14 | with: 15 | java-version: 24 16 | distribution: temurin 17 | cache: maven 18 | 19 | - name: Java Publish 20 | run: mvn -B clean package --file source/pom.xml 21 | 22 | - name: Artifact Upload 23 | uses: actions/upload-artifact@v4 24 | with: 25 | name: app 26 | path: source/target/*.jar 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.bat 2 | *.iml 3 | .idea 4 | .vscode 5 | target -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 2 | 3 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 4 | 5 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 6 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # JAVA 2 | 3 | ![](https://github.com/rafaelfgx/Java/actions/workflows/build.yaml/badge.svg) 4 | 5 | Java, Spring Boot, Docker, Testcontainers, PostgreSQL, MongoDB, Kafka, LocalStack, AWS (SQS, S3), JWT, Swagger, Patterns (Mediator, Observer, Outbox, Strategy). 6 | 7 | ## TECHNOLOGIES 8 | 9 | - [Java](https://dev.java) 10 | - [Spring Boot](https://spring.io/projects/spring-boot) 11 | - [Docker](https://www.docker.com/get-started) 12 | - [Testcontainers](https://java.testcontainers.org) 13 | - [PostgreSQL](https://www.postgresql.org/) 14 | - [MongoDB](https://www.mongodb.com/docs/manual) 15 | - [Kafka](https://kafka.apache.org) 16 | - [LocalStack](https://localstack.cloud) 17 | - [AWS SQS](https://aws.amazon.com/sqs) 18 | - [AWS S3](https://aws.amazon.com/s3) 19 | - [JWT](https://jwt.io) 20 | - [Swagger](https://swagger.io) 21 | 22 | ## RUN 23 | 24 |
25 | IntelliJ IDEA 26 | 27 | #### Prerequisites 28 | 29 | * [Docker](https://www.docker.com/get-started) 30 | * [Java JDK](https://www.oracle.com/java/technologies/downloads) 31 | * [IntelliJ IDEA](https://www.jetbrains.com/idea/download) 32 | 33 | #### Steps 34 | 35 | 1. Execute **docker compose up --detach --build --force-recreate --remove-orphans** in **docker** directory. 36 | 2. Open **source** directory in **IntelliJ IDEA**. 37 | 3. Select **Application.java** class. 38 | 4. Click **Run** or **Debug**. 39 | 5. Open . 40 | 41 |
42 | 43 |
44 | Docker 45 | 46 | #### Prerequisites 47 | 48 | * [Docker](https://www.docker.com/get-started) 49 | 50 | #### Steps 51 | 52 | 1. Execute **docker compose up --detach --build --force-recreate --remove-orphans** in **docker** directory. 53 | 2. Open . 54 | 55 |
56 | 57 | ## EXAMPLES 58 | 59 | - **Authentication and Authorization:** [Main](https://github.com/rafaelfgx/Java/tree/main/source/src/main/java/com/company/architecture/auth) | [Tests](https://github.com/rafaelfgx/Java/tree/main/source/src/test/java/com/company/architecture/auth) 60 | - **Cache:** [Main](https://github.com/rafaelfgx/Java/tree/main/source/src/main/java/com/company/architecture/category) | [Tests](https://github.com/rafaelfgx/Java/tree/main/source/src/test/java/com/company/architecture/category) 61 | - **Feign:** [Main](https://github.com/rafaelfgx/Java/tree/main/source/src/main/java/com/company/architecture/api) | [Tests](https://github.com/rafaelfgx/Java/tree/main/source/src/test/java/com/company/architecture/api) 62 | - **Kafka:** [Main](https://github.com/rafaelfgx/Java/tree/main/source/src/main/java/com/company/architecture/notification) | [Tests](https://github.com/rafaelfgx/Java/tree/main/source/src/test/java/com/company/architecture/notification) 63 | - **Mocks:** [Main](https://github.com/rafaelfgx/Java/tree/main/source/src/main/java/com/company/architecture/game) | [Tests](https://github.com/rafaelfgx/Java/tree/main/source/src/test/java/com/company/architecture/game) 64 | - **Amazon Web Services:** [Main](https://github.com/rafaelfgx/Java/tree/main/source/src/main/java/com/company/architecture/aws) | [Tests](https://github.com/rafaelfgx/Java/tree/main/source/src/test/java/com/company/architecture/aws) 65 | - **Databases - MongoDB:** [Main](https://github.com/rafaelfgx/Java/tree/main/source/src/main/java/com/company/architecture/product) | [Tests](https://github.com/rafaelfgx/Java/tree/main/source/src/test/java/com/company/architecture/product) 66 | - **Databases - PostgreSQL:** [Main](https://github.com/rafaelfgx/Java/tree/main/source/src/main/java/com/company/architecture/invoice) | [Tests](https://github.com/rafaelfgx/Java/tree/main/source/src/test/java/com/company/architecture/invoice) 67 | - **Patterns - Mediator:** [Main](https://github.com/rafaelfgx/Java/tree/main/source/src/main/java/com/company/architecture/book) | [Tests](https://github.com/rafaelfgx/Java/tree/main/source/src/test/java/com/company/architecture/book) 68 | - **Patterns - Observer:** [Main](https://github.com/rafaelfgx/Java/tree/main/source/src/main/java/com/company/architecture/car) | [Tests](https://github.com/rafaelfgx/Java/tree/main/source/src/test/java/com/company/architecture/car) 69 | - **Patterns - Outbox:** [Main](https://github.com/rafaelfgx/Java/tree/main/source/src/main/java/com/company/architecture/movie) | [Tests](https://github.com/rafaelfgx/Java/tree/main/source/src/test/java/com/company/architecture/movie) 70 | - **Patterns - Strategy:** [Main](https://github.com/rafaelfgx/Java/tree/main/source/src/main/java/com/company/architecture/payment) | [Tests](https://github.com/rafaelfgx/Java/tree/main/source/src/test/java/com/company/architecture/payment) 71 | - **Patterns - UseCase:** [Main](https://github.com/rafaelfgx/Java/tree/main/source/src/main/java/com/company/architecture/animal) | [Tests](https://github.com/rafaelfgx/Java/tree/main/source/src/test/java/com/company/architecture/animal) 72 | - **Logic - Business Rules:** [Main](https://github.com/rafaelfgx/Java/tree/main/source/src/main/java/com/company/architecture/user) | [Tests](https://github.com/rafaelfgx/Java/tree/main/source/src/test/java/com/company/architecture/user) 73 | - **Logic - Flat Object to Nested Object:** [Main](https://github.com/rafaelfgx/Java/tree/main/source/src/main/java/com/company/architecture/location) | [Tests](https://github.com/rafaelfgx/Java/tree/main/source/src/test/java/com/company/architecture/location) 74 | - **Logic - Groups:** [Main](https://github.com/rafaelfgx/Java/tree/main/source/src/main/java/com/company/architecture/group) | [Tests](https://github.com/rafaelfgx/Java/tree/main/source/src/test/java/com/company/architecture/group) 75 | -------------------------------------------------------------------------------- /source/.run/Application.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 17 | -------------------------------------------------------------------------------- /source/.run/Tests.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 13 | -------------------------------------------------------------------------------- /source/lombok.config: -------------------------------------------------------------------------------- 1 | lombok.addLombokGeneratedAnnotation = true 2 | -------------------------------------------------------------------------------- /source/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | com.company 5 | architecture 6 | architecture 7 | 1.0.0 8 | 9 | 24 10 | 24 11 | 24 12 | full 13 | UTF-8 14 | UTF-8 15 | 16 | 17 | org.springframework.boot 18 | spring-boot-starter-parent 19 | 3.5.0 20 | 21 | 22 | 23 | 24 | org.springframework.cloud 25 | spring-cloud-dependencies 26 | 2025.0.0 27 | pom 28 | import 29 | 30 | 31 | io.awspring.cloud 32 | spring-cloud-aws-dependencies 33 | 3.3.1 34 | pom 35 | import 36 | 37 | 38 | 39 | 40 | 41 | org.springframework.boot 42 | spring-boot-starter-actuator 43 | 44 | 45 | org.springframework.boot 46 | spring-boot-starter-data-jpa 47 | 48 | 49 | org.springframework.boot 50 | spring-boot-starter-data-mongodb 51 | 52 | 53 | org.springframework.boot 54 | spring-boot-starter-oauth2-authorization-server 55 | 56 | 57 | org.springframework.boot 58 | spring-boot-starter-security 59 | 60 | 61 | org.springframework.boot 62 | spring-boot-starter-test 63 | test 64 | 65 | 66 | org.springframework.boot 67 | spring-boot-starter-validation 68 | 69 | 70 | org.springframework.boot 71 | spring-boot-starter-web 72 | 73 | 74 | org.springframework.boot 75 | spring-boot-testcontainers 76 | test 77 | 78 | 79 | org.springframework.kafka 80 | spring-kafka 81 | 82 | 83 | org.springframework.cloud 84 | spring-cloud-starter-openfeign 85 | 86 | 87 | io.awspring.cloud 88 | spring-cloud-aws-starter 89 | 90 | 91 | io.awspring.cloud 92 | spring-cloud-aws-starter-s3 93 | 94 | 95 | io.awspring.cloud 96 | spring-cloud-aws-starter-sqs 97 | 98 | 99 | net.logstash.logback 100 | logstash-logback-encoder 101 | 8.1 102 | 103 | 104 | org.postgresql 105 | postgresql 106 | 42.7.6 107 | runtime 108 | 109 | 110 | org.projectlombok 111 | lombok 112 | provided 113 | 114 | 115 | org.springdoc 116 | springdoc-openapi-starter-webmvc-ui 117 | 2.8.8 118 | 119 | 120 | org.testcontainers 121 | junit-jupiter 122 | test 123 | 124 | 125 | org.testcontainers 126 | kafka 127 | test 128 | 129 | 130 | org.testcontainers 131 | localstack 132 | test 133 | 134 | 135 | org.testcontainers 136 | mongodb 137 | test 138 | 139 | 140 | org.testcontainers 141 | postgresql 142 | test 143 | 144 | 145 | 146 | 147 | 148 | org.springframework.boot 149 | spring-boot-maven-plugin 150 | 151 | 152 | 153 | 154 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/Application.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cache.annotation.EnableCaching; 6 | import org.springframework.cloud.openfeign.EnableFeignClients; 7 | import org.springframework.kafka.annotation.EnableKafka; 8 | import org.springframework.scheduling.annotation.EnableScheduling; 9 | 10 | @EnableCaching 11 | @EnableFeignClients 12 | @EnableKafka 13 | @EnableScheduling 14 | @SpringBootApplication 15 | public class Application { 16 | public static void main(final String[] args) { 17 | SpringApplication.run(Application.class, args); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/animal/CreateAnimalInput.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.animal; 2 | 3 | import com.company.architecture.shared.usecase.Input; 4 | 5 | public record CreateAnimalInput(String name) implements Input { 6 | } 7 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/animal/CreateAnimalOutput.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.animal; 2 | 3 | import com.company.architecture.shared.usecase.Output; 4 | 5 | import java.util.UUID; 6 | 7 | public record CreateAnimalOutput(UUID id) implements Output { 8 | } 9 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/animal/CreateAnimalUseCase.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.animal; 2 | 3 | import com.company.architecture.shared.usecase.UseCase; 4 | 5 | import java.util.UUID; 6 | 7 | public class CreateAnimalUseCase implements UseCase { 8 | @Override 9 | public CreateAnimalOutput execute(CreateAnimalInput input) { 10 | return new CreateAnimalOutput(UUID.randomUUID()); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/api/JsonPlaceholderClient.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.api; 2 | 3 | import org.springframework.cloud.openfeign.FeignClient; 4 | import org.springframework.web.bind.annotation.GetMapping; 5 | import org.springframework.web.bind.annotation.PathVariable; 6 | 7 | import java.util.List; 8 | 9 | @FeignClient(name = "jsonplaceholder", url = "${feign.clients.jsonplaceholder.url}", fallback = JsonPlaceholderClientFallback.class) 10 | public interface JsonPlaceholderClient { 11 | @GetMapping("todos") 12 | List getTodos(); 13 | 14 | @GetMapping("todos/{id}") 15 | Todo getTodo(@PathVariable int id); 16 | } 17 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/api/JsonPlaceholderClientFallback.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.api; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.springframework.stereotype.Component; 5 | 6 | import java.util.List; 7 | 8 | @Component 9 | @Slf4j 10 | public class JsonPlaceholderClientFallback implements JsonPlaceholderClient { 11 | @Override 12 | public List getTodos() { 13 | log.info("[JsonPlaceholderClientFallback].[getTodos]"); 14 | return List.of(); 15 | } 16 | 17 | @Override 18 | public Todo getTodo(int id) { 19 | log.info("[JsonPlaceholderClientFallback].[getTodo]"); 20 | return null; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/api/JsonPlaceholderService.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.api; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.springframework.stereotype.Service; 5 | 6 | import java.util.List; 7 | 8 | @Service 9 | @RequiredArgsConstructor 10 | public class JsonPlaceholderService { 11 | private final JsonPlaceholderClient jsonPlaceholderClient; 12 | 13 | public List getTodos() { 14 | return jsonPlaceholderClient.getTodos(); 15 | } 16 | 17 | public Todo getTodo(final int id) { 18 | return jsonPlaceholderClient.getTodo(id); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/api/Todo.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.api; 2 | 3 | public record Todo(int userId, int id, String title, boolean completed) { 4 | } 5 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/auth/Auth.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.auth; 2 | 3 | import jakarta.validation.constraints.NotBlank; 4 | 5 | public record Auth(@NotBlank String username, @NotBlank String password) { 6 | } 7 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/auth/AuthConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.auth; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.http.HttpMethod; 7 | import org.springframework.security.authentication.AuthenticationManager; 8 | import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; 9 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 10 | import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; 11 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 12 | import org.springframework.security.crypto.password.PasswordEncoder; 13 | import org.springframework.security.web.SecurityFilterChain; 14 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; 15 | 16 | @Configuration 17 | @RequiredArgsConstructor 18 | public class AuthConfiguration { 19 | private final AuthFilter authFilter; 20 | 21 | @Bean 22 | AuthenticationManager authenticationManager(final AuthenticationConfiguration configuration) throws Exception { 23 | return configuration.getAuthenticationManager(); 24 | } 25 | 26 | @Bean 27 | PasswordEncoder passwordEncoder() { 28 | return new BCryptPasswordEncoder(); 29 | } 30 | 31 | @Bean 32 | SecurityFilterChain securityFilterChain(final HttpSecurity security) throws Exception { 33 | return security 34 | .csrf(AbstractHttpConfigurer::disable) 35 | .addFilterBefore(authFilter, UsernamePasswordAuthenticationFilter.class) 36 | .authorizeHttpRequests(registry -> registry 37 | .requestMatchers("/", "/actuator/**", "/v3/api-docs/**", "/swagger-ui/**", "/auth").permitAll() 38 | .requestMatchers(HttpMethod.DELETE).hasAuthority(Authority.ADMINISTRATOR.name()) 39 | .anyRequest().authenticated() 40 | ) 41 | .build(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/auth/AuthController.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.auth; 2 | 3 | import com.company.architecture.shared.swagger.PostApiResponses; 4 | import io.swagger.v3.oas.annotations.Operation; 5 | import io.swagger.v3.oas.annotations.tags.Tag; 6 | import jakarta.validation.Valid; 7 | import lombok.RequiredArgsConstructor; 8 | import org.springframework.http.HttpStatus; 9 | import org.springframework.web.bind.annotation.*; 10 | 11 | @Tag(name = "Auth") 12 | @RequiredArgsConstructor 13 | @RestController 14 | @RequestMapping("/auth") 15 | public class AuthController { 16 | private final AuthService authService; 17 | 18 | @Operation(summary = "Auth") 19 | @PostApiResponses 20 | @PostMapping 21 | @ResponseStatus(HttpStatus.OK) 22 | public String auth(@RequestBody @Valid final Auth auth) { 23 | return authService.auth(auth); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/auth/AuthFilter.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.auth; 2 | 3 | import jakarta.annotation.Nonnull; 4 | import jakarta.servlet.FilterChain; 5 | import jakarta.servlet.ServletException; 6 | import jakarta.servlet.http.HttpServletRequest; 7 | import jakarta.servlet.http.HttpServletResponse; 8 | import lombok.RequiredArgsConstructor; 9 | import org.apache.commons.lang3.StringUtils; 10 | import org.springframework.http.HttpHeaders; 11 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 12 | import org.springframework.security.core.context.SecurityContextHolder; 13 | import org.springframework.stereotype.Component; 14 | import org.springframework.web.filter.OncePerRequestFilter; 15 | 16 | import java.io.IOException; 17 | 18 | @Component 19 | @RequiredArgsConstructor 20 | public class AuthFilter extends OncePerRequestFilter { 21 | private final JwtService jwtService; 22 | 23 | @Override 24 | protected void doFilterInternal(final HttpServletRequest request, final @Nonnull HttpServletResponse response, final @Nonnull FilterChain filterChain) throws ServletException, IOException { 25 | final var jwt = StringUtils.removeStart(StringUtils.defaultString(request.getHeader(HttpHeaders.AUTHORIZATION)), "Bearer").trim(); 26 | 27 | if (jwtService.verify(jwt)) { 28 | SecurityContextHolder.getContext().setAuthentication(UsernamePasswordAuthenticationToken.authenticated(jwtService.getSubject(jwt), null, jwtService.getAuthorities(jwt))); 29 | } 30 | 31 | filterChain.doFilter(request, response); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/auth/AuthService.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.auth; 2 | 3 | import com.company.architecture.user.UserRepository; 4 | import lombok.RequiredArgsConstructor; 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.security.crypto.password.PasswordEncoder; 7 | import org.springframework.stereotype.Service; 8 | import org.springframework.web.server.ResponseStatusException; 9 | 10 | @Service 11 | @RequiredArgsConstructor 12 | public class AuthService { 13 | private final PasswordEncoder passwordEncoder; 14 | private final JwtService jwtService; 15 | private final UserRepository userRepository; 16 | 17 | public String auth(final Auth auth) { 18 | return userRepository 19 | .findByUsername(auth.username()) 20 | .filter(user -> passwordEncoder.matches(auth.password(), user.getPassword())) 21 | .map(jwtService::create) 22 | .orElseThrow(() -> new ResponseStatusException(HttpStatus.UNAUTHORIZED, "auth.unauthorized")); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/auth/Authority.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.auth; 2 | 3 | public enum Authority { 4 | DEFAULT, 5 | ADMINISTRATOR 6 | } 7 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/auth/JwtConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.auth; 2 | 3 | import com.nimbusds.jose.jwk.JWKSet; 4 | import com.nimbusds.jose.jwk.RSAKey; 5 | import com.nimbusds.jose.jwk.source.ImmutableJWKSet; 6 | import lombok.RequiredArgsConstructor; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.security.oauth2.jwt.JwtDecoder; 10 | import org.springframework.security.oauth2.jwt.JwtEncoder; 11 | import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; 12 | import org.springframework.security.oauth2.jwt.NimbusJwtEncoder; 13 | 14 | import java.security.KeyPair; 15 | import java.security.KeyPairGenerator; 16 | import java.security.NoSuchAlgorithmException; 17 | import java.security.interfaces.RSAPrivateKey; 18 | import java.security.interfaces.RSAPublicKey; 19 | 20 | @Configuration 21 | @RequiredArgsConstructor 22 | public class JwtConfiguration { 23 | @Bean 24 | KeyPair keyPair() throws NoSuchAlgorithmException { 25 | return KeyPairGenerator.getInstance("RSA").generateKeyPair(); 26 | } 27 | 28 | @Bean 29 | RSAKey key(KeyPair keyPair) { 30 | return new RSAKey.Builder((RSAPublicKey) keyPair.getPublic()).privateKey((RSAPrivateKey) keyPair.getPrivate()).build(); 31 | } 32 | 33 | @Bean 34 | JwtEncoder jwtEncoder(RSAKey key) { 35 | return new NimbusJwtEncoder(new ImmutableJWKSet<>(new JWKSet(key))); 36 | } 37 | 38 | @Bean 39 | JwtDecoder jwtDecoder(KeyPair keyPair) { 40 | return NimbusJwtDecoder.withPublicKey((RSAPublicKey) keyPair.getPublic()).build(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/auth/JwtService.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.auth; 2 | 3 | import com.company.architecture.user.User; 4 | import lombok.RequiredArgsConstructor; 5 | import org.springframework.security.core.GrantedAuthority; 6 | import org.springframework.security.core.authority.AuthorityUtils; 7 | import org.springframework.security.oauth2.jwt.JwtClaimsSet; 8 | import org.springframework.security.oauth2.jwt.JwtDecoder; 9 | import org.springframework.security.oauth2.jwt.JwtEncoder; 10 | import org.springframework.security.oauth2.jwt.JwtEncoderParameters; 11 | import org.springframework.stereotype.Service; 12 | 13 | import java.util.List; 14 | 15 | @Service 16 | @RequiredArgsConstructor 17 | public class JwtService { 18 | private final JwtEncoder encoder; 19 | private final JwtDecoder decoder; 20 | 21 | public String create(final User user) { 22 | final var authorities = user.getAuthorities().stream().map(Enum::name).toArray(String[]::new); 23 | final var claims = JwtClaimsSet.builder().subject(user.getId().toString()).claim("authorities", authorities).build(); 24 | return this.encoder.encode(JwtEncoderParameters.from(claims)).getTokenValue(); 25 | } 26 | 27 | public boolean verify(final String jwt) { 28 | try { 29 | decoder.decode(jwt); 30 | return true; 31 | } catch (Exception exception) { 32 | return false; 33 | } 34 | } 35 | 36 | public String getSubject(final String jwt) { 37 | return decoder.decode(jwt).getSubject(); 38 | } 39 | 40 | public List getAuthorities(final String jwt) { 41 | return AuthorityUtils.createAuthorityList(decoder.decode(jwt).getClaimAsStringList("authorities")); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/aws/AwsConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.aws; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.core.env.Environment; 7 | import software.amazon.awssdk.services.s3.S3Client; 8 | import software.amazon.awssdk.services.s3.S3Configuration; 9 | import software.amazon.awssdk.services.sqs.SqsAsyncClient; 10 | 11 | import java.net.URI; 12 | import java.util.Objects; 13 | 14 | @RequiredArgsConstructor 15 | @Configuration 16 | public class AwsConfiguration { 17 | private final Environment environment; 18 | protected static final String BUCKET = "bucket"; 19 | protected static final String QUEUE = "queue"; 20 | 21 | @Bean 22 | public S3Client s3Client() { 23 | final var client = S3Client.builder().endpointOverride(endpoint()).serviceConfiguration(S3Configuration.builder().pathStyleAccessEnabled(true).build()).build(); 24 | client.createBucket(builder -> builder.bucket(BUCKET)); 25 | return client; 26 | } 27 | 28 | @Bean 29 | public SqsAsyncClient sqsAsyncClient() { 30 | final var client = SqsAsyncClient.builder().endpointOverride(endpoint()).build(); 31 | client.createQueue(builder -> builder.queueName(QUEUE)); 32 | return client; 33 | } 34 | 35 | private URI endpoint() { 36 | return URI.create(Objects.requireNonNullElse(environment.getProperty("aws.endpoint"), environment.getProperty("AWS_ENDPOINT"))); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/aws/AwsController.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.aws; 2 | 3 | import com.company.architecture.shared.swagger.GetApiResponses; 4 | import com.company.architecture.shared.swagger.PostApiResponses; 5 | import io.swagger.v3.oas.annotations.Operation; 6 | import io.swagger.v3.oas.annotations.tags.Tag; 7 | import lombok.RequiredArgsConstructor; 8 | import org.springframework.core.io.Resource; 9 | import org.springframework.http.HttpHeaders; 10 | import org.springframework.http.MediaType; 11 | import org.springframework.http.ResponseEntity; 12 | import org.springframework.web.bind.annotation.*; 13 | import org.springframework.web.multipart.MultipartFile; 14 | 15 | import java.io.IOException; 16 | 17 | @Tag(name = "AWS") 18 | @RequiredArgsConstructor 19 | @RestController 20 | @RequestMapping("/aws") 21 | public class AwsController { 22 | private final AwsService awsService; 23 | 24 | @Operation(summary = "Send") 25 | @PostApiResponses 26 | @PostMapping("queues/send") 27 | public void send(@RequestBody final String message) { 28 | awsService.send(message); 29 | } 30 | 31 | @Operation(summary = "Upload") 32 | @PostApiResponses 33 | @PostMapping(value = "files/upload", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE}) 34 | public String upload(@RequestParam MultipartFile file) throws IOException { 35 | return awsService.upload(file).getFilename(); 36 | } 37 | 38 | @Operation(summary = "Download") 39 | @GetApiResponses 40 | @GetMapping("files/download/{key}") 41 | public ResponseEntity get(@PathVariable final String key) { 42 | final var headers = new HttpHeaders(); 43 | headers.setContentType(MediaType.APPLICATION_OCTET_STREAM); 44 | return ResponseEntity.ok().headers(headers).body(awsService.download(key)); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/aws/AwsS3Service.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.aws; 2 | 3 | import io.awspring.cloud.s3.ObjectMetadata; 4 | import io.awspring.cloud.s3.S3Resource; 5 | import io.awspring.cloud.s3.S3Template; 6 | import lombok.RequiredArgsConstructor; 7 | import org.springframework.stereotype.Service; 8 | import org.springframework.web.multipart.MultipartFile; 9 | 10 | import java.io.ByteArrayInputStream; 11 | import java.io.IOException; 12 | import java.nio.file.Files; 13 | import java.nio.file.Paths; 14 | 15 | @Service 16 | @RequiredArgsConstructor 17 | public class AwsS3Service { 18 | private final S3Template s3Template; 19 | 20 | public S3Resource store(final String bucket, final String key, final Object object) { 21 | return s3Template.store(bucket, key, object); 22 | } 23 | 24 | public T read(final String bucket, final String key, final Class clazz) { 25 | return s3Template.read(bucket, key, clazz); 26 | } 27 | 28 | public S3Resource upload(final String bucket, final MultipartFile file) throws IOException { 29 | return upload(bucket, file.getOriginalFilename(), file.getBytes()); 30 | } 31 | 32 | public S3Resource upload(final String bucket, final String key, final byte[] bytes) throws IOException { 33 | return s3Template.upload(bucket, key, new ByteArrayInputStream(bytes), ObjectMetadata.builder().contentType(Files.probeContentType(Paths.get(key))).build()); 34 | } 35 | 36 | public S3Resource download(final String bucket, final String key) { 37 | return s3Template.download(bucket, key); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/aws/AwsService.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.aws; 2 | 3 | import io.awspring.cloud.s3.S3Resource; 4 | import io.awspring.cloud.sqs.annotation.SqsListener; 5 | import lombok.RequiredArgsConstructor; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.stereotype.Service; 8 | import org.springframework.web.multipart.MultipartFile; 9 | 10 | import java.io.IOException; 11 | 12 | @Slf4j 13 | @Service 14 | @RequiredArgsConstructor 15 | public class AwsService { 16 | private final AwsSqsService awsSqsService; 17 | private final AwsS3Service awsS3Service; 18 | 19 | @SqsListener(AwsConfiguration.QUEUE) 20 | public void listen(final Object object) { 21 | log.info("[AwsSqsService].[listen]: {}", object); 22 | } 23 | 24 | public void send(final Object object) { 25 | awsSqsService.send(AwsConfiguration.QUEUE, object); 26 | } 27 | 28 | public S3Resource upload(final MultipartFile file) throws IOException { 29 | return awsS3Service.upload(AwsConfiguration.BUCKET, file.getOriginalFilename(), file.getBytes()); 30 | } 31 | 32 | public S3Resource download(final String key) { 33 | return awsS3Service.download(AwsConfiguration.BUCKET, key); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/aws/AwsSqsService.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.aws; 2 | 3 | import io.awspring.cloud.sqs.operations.SqsTemplate; 4 | import lombok.RequiredArgsConstructor; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.stereotype.Service; 7 | 8 | @Slf4j 9 | @Service 10 | @RequiredArgsConstructor 11 | public class AwsSqsService { 12 | private final SqsTemplate sqsTemplate; 13 | 14 | public void send(final String queue, final Object object) { 15 | sqsTemplate.send(queue, object); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/book/BookController.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.book; 2 | 3 | import com.company.architecture.book.create.CreateBookRequest; 4 | import com.company.architecture.shared.mediator.Mediator; 5 | import com.company.architecture.shared.swagger.PostApiResponses; 6 | import io.swagger.v3.oas.annotations.Operation; 7 | import io.swagger.v3.oas.annotations.tags.Tag; 8 | import jakarta.validation.Valid; 9 | import lombok.RequiredArgsConstructor; 10 | import org.springframework.http.ResponseEntity; 11 | import org.springframework.web.bind.annotation.PostMapping; 12 | import org.springframework.web.bind.annotation.RequestBody; 13 | import org.springframework.web.bind.annotation.RequestMapping; 14 | import org.springframework.web.bind.annotation.RestController; 15 | 16 | import java.util.UUID; 17 | 18 | @Tag(name = "Books") 19 | @RequiredArgsConstructor 20 | @RestController 21 | @RequestMapping("/books") 22 | public class BookController { 23 | private final Mediator mediator; 24 | 25 | @Operation(summary = "Create") 26 | @PostApiResponses 27 | @PostMapping 28 | public ResponseEntity create(@RequestBody @Valid final CreateBookRequest request) { 29 | return mediator.handle(request, UUID.class); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/book/create/CreateBookHandler.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.book.create; 2 | 3 | import com.company.architecture.shared.mediator.Handler; 4 | import org.springframework.http.HttpStatus; 5 | import org.springframework.http.ResponseEntity; 6 | import org.springframework.stereotype.Component; 7 | 8 | import java.util.UUID; 9 | 10 | @Component 11 | public class CreateBookHandler implements Handler { 12 | @Override 13 | public ResponseEntity handle(CreateBookRequest request) { 14 | return new ResponseEntity<>(UUID.randomUUID(), HttpStatus.CREATED); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/book/create/CreateBookRequest.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.book.create; 2 | 3 | import jakarta.validation.constraints.NotBlank; 4 | 5 | public record CreateBookRequest(@NotBlank String title, @NotBlank String author) { 6 | } 7 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/car/Car.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.car; 2 | 3 | import jakarta.validation.constraints.NotBlank; 4 | 5 | public record Car(@NotBlank String brand, @NotBlank String model) { 6 | } 7 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/car/CarController.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.car; 2 | 3 | import com.company.architecture.shared.swagger.PostApiResponses; 4 | import io.swagger.v3.oas.annotations.Operation; 5 | import io.swagger.v3.oas.annotations.tags.Tag; 6 | import jakarta.validation.Valid; 7 | import lombok.RequiredArgsConstructor; 8 | import org.springframework.http.HttpStatus; 9 | import org.springframework.web.bind.annotation.*; 10 | 11 | @Tag(name = "Cars") 12 | @RequiredArgsConstructor 13 | @RestController 14 | @RequestMapping("/cars") 15 | public class CarController { 16 | private final CarService carService; 17 | 18 | @Operation(summary = "Add") 19 | @PostApiResponses 20 | @PostMapping 21 | @ResponseStatus(HttpStatus.OK) 22 | public void add(@RequestBody @Valid final Car car) { 23 | carService.add(car); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/car/CarService.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.car; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.context.ApplicationEventPublisher; 6 | import org.springframework.context.event.EventListener; 7 | import org.springframework.stereotype.Service; 8 | 9 | @Slf4j 10 | @Service 11 | @RequiredArgsConstructor 12 | public class CarService { 13 | private final ApplicationEventPublisher applicationEventPublisher; 14 | 15 | public void add(Car car) { 16 | log.info("[CarService].[add]: {}", car); 17 | applicationEventPublisher.publishEvent(car); 18 | } 19 | 20 | @EventListener 21 | public void listen(Car car) { 22 | log.info("[CarService].[listen]: {}", car); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/category/Category.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.category; 2 | 3 | import jakarta.validation.constraints.NotBlank; 4 | 5 | public record Category(@NotBlank String name) { 6 | } 7 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/category/CategoryCacheService.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.category; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.cache.annotation.CacheEvict; 6 | import org.springframework.cache.annotation.Cacheable; 7 | import org.springframework.scheduling.annotation.Scheduled; 8 | import org.springframework.stereotype.Service; 9 | 10 | import java.util.List; 11 | import java.util.concurrent.TimeUnit; 12 | 13 | @Slf4j 14 | @Service 15 | @RequiredArgsConstructor 16 | public class CategoryCacheService { 17 | private static final String KEY = "Category"; 18 | private final CategoryRepository categoryRepository; 19 | 20 | @Cacheable(KEY) 21 | public List list() { 22 | log.info("[CategoryCacheService].[list]: {}", KEY); 23 | return categoryRepository.list(); 24 | } 25 | 26 | @CacheEvict(allEntries = true, cacheNames = {KEY}) 27 | @Scheduled(fixedRateString = "1", timeUnit = TimeUnit.HOURS) 28 | public void cacheEvict() { 29 | log.info("[CategoryCacheService].[cacheEvict]: {}", KEY); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/category/CategoryRepository.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.category; 2 | 3 | import org.springframework.stereotype.Repository; 4 | 5 | import java.util.List; 6 | 7 | @Repository 8 | public class CategoryRepository { 9 | public List list() { 10 | return List.of(new Category("Category")); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/category/CategoryService.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.category; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.springframework.stereotype.Service; 5 | 6 | import java.util.List; 7 | 8 | @Service 9 | @RequiredArgsConstructor 10 | public class CategoryService { 11 | private final CategoryCacheService categoryCacheService; 12 | 13 | public List list() { 14 | return categoryCacheService.list(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/game/Game.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.game; 2 | 3 | import jakarta.validation.constraints.NotBlank; 4 | 5 | public record Game(@NotBlank String title) { 6 | } 7 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/game/GameRepository.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.game; 2 | 3 | import org.springframework.stereotype.Repository; 4 | 5 | import java.util.List; 6 | 7 | @Repository 8 | public class GameRepository { 9 | private static final List games = List.of(new Game("Game A"), new Game("Game B")); 10 | 11 | public List list(final Game game) { 12 | return games.stream().filter(item -> item.title().contains(game.title())).toList(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/game/GameService.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.game; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.springframework.stereotype.Service; 5 | 6 | import java.util.List; 7 | 8 | @Service 9 | @RequiredArgsConstructor 10 | public class GameService { 11 | private final GameRepository gameRepository; 12 | 13 | public List list(final Game game) { 14 | return gameRepository.list(game); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/group/GroupService.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.group; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.springframework.beans.BeanWrapperImpl; 5 | import org.springframework.stereotype.Service; 6 | 7 | import java.util.Arrays; 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | import java.util.Objects; 11 | 12 | @Service 13 | @RequiredArgsConstructor 14 | public class GroupService { 15 | public Map> groupPropertiesBy(final Object object, final String contains) { 16 | final var result = new HashMap>(); 17 | 18 | Arrays.stream(object.getClass().getDeclaredFields()).filter(field -> field.getName().contains(contains)).forEach(field -> { 19 | final var wrapper = new BeanWrapperImpl(object); 20 | final var name = field.getName().replace(contains, ""); 21 | result.computeIfAbsent(wrapper.getPropertyValue(field.getName()), _ -> new HashMap<>()).put(name, wrapper.getPropertyValue(name)); 22 | }); 23 | 24 | return result; 25 | } 26 | 27 | public void setProperties(final Object object, final Map map) { 28 | final var wrapper = new BeanWrapperImpl(object); 29 | 30 | map.forEach((property, value) -> { 31 | if (Objects.nonNull(value)) { 32 | wrapper.setPropertyValue(property, value); 33 | } 34 | }); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/group/Person.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.group; 2 | 3 | import lombok.Data; 4 | 5 | import java.time.LocalDate; 6 | 7 | @Data 8 | public class Person { 9 | private String passport; 10 | private LocalDate passportExpirationDate; 11 | private String driverLicense; 12 | private LocalDate driverLicenseExpirationDate; 13 | } 14 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/invoice/InvoiceController.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.invoice; 2 | 3 | import com.company.architecture.invoice.dtos.AddInvoiceDto; 4 | import com.company.architecture.invoice.dtos.InvoiceDto; 5 | import com.company.architecture.shared.swagger.GetApiResponses; 6 | import com.company.architecture.shared.swagger.PostApiResponses; 7 | import io.swagger.v3.oas.annotations.Operation; 8 | import io.swagger.v3.oas.annotations.tags.Tag; 9 | import jakarta.validation.Valid; 10 | import lombok.RequiredArgsConstructor; 11 | import org.springframework.web.bind.annotation.*; 12 | 13 | import java.util.List; 14 | 15 | @Tag(name = "Invoices") 16 | @RequiredArgsConstructor 17 | @RestController 18 | @RequestMapping("/invoices") 19 | public class InvoiceController { 20 | private final InvoiceService invoiceService; 21 | 22 | @Operation(summary = "Get") 23 | @GetApiResponses 24 | @GetMapping 25 | public List get() { 26 | return invoiceService.get(); 27 | } 28 | 29 | @Operation(summary = "Add") 30 | @PostApiResponses 31 | @PostMapping 32 | public InvoiceDto add(@RequestBody @Valid final AddInvoiceDto dto) { 33 | return invoiceService.add(dto); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/invoice/InvoiceRepository.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.invoice; 2 | 3 | import com.company.architecture.invoice.entities.Invoice; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | @Repository 8 | public interface InvoiceRepository extends JpaRepository { 9 | } 10 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/invoice/InvoiceService.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.invoice; 2 | 3 | import com.company.architecture.invoice.dtos.AddInvoiceDto; 4 | import com.company.architecture.invoice.dtos.InvoiceDto; 5 | import com.company.architecture.invoice.entities.Invoice; 6 | import com.company.architecture.shared.services.MapperService; 7 | import lombok.RequiredArgsConstructor; 8 | import org.springframework.stereotype.Service; 9 | import org.springframework.transaction.annotation.Transactional; 10 | 11 | import java.util.List; 12 | 13 | @Service 14 | @RequiredArgsConstructor 15 | public class InvoiceService { 16 | private final MapperService mapperService; 17 | private final InvoiceRepository invoiceRepository; 18 | 19 | public List get() { 20 | return mapperService.mapList(invoiceRepository.findAll(), InvoiceDto.class); 21 | } 22 | 23 | @Transactional 24 | public InvoiceDto add(final AddInvoiceDto dto) { 25 | final var invoice = mapperService.map(dto, Invoice.class); 26 | return mapperService.map(invoiceRepository.save(invoice), InvoiceDto.class); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/invoice/InvoiceStatus.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.invoice; 2 | 3 | public enum InvoiceStatus { 4 | DRAFT, ISSUED, PAID, CANCELED 5 | } 6 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/invoice/dtos/AddInvoiceDto.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.invoice.dtos; 2 | 3 | import com.company.architecture.invoice.InvoiceStatus; 4 | import jakarta.validation.constraints.NotNull; 5 | import jakarta.validation.constraints.Size; 6 | 7 | import java.time.LocalDateTime; 8 | import java.util.List; 9 | 10 | public record AddInvoiceDto( 11 | @NotNull @Size(min = 1, max = 50) String number, 12 | @NotNull LocalDateTime dateTime, 13 | @NotNull InvoiceStatus status, 14 | List items) { 15 | } 16 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/invoice/dtos/AddInvoiceItemDto.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.invoice.dtos; 2 | 3 | import jakarta.validation.constraints.DecimalMin; 4 | import jakarta.validation.constraints.Min; 5 | import jakarta.validation.constraints.NotNull; 6 | import jakarta.validation.constraints.Size; 7 | 8 | import java.math.BigDecimal; 9 | 10 | public record AddInvoiceItemDto( 11 | @NotNull @Size(min = 1, max = 100) String product, 12 | @NotNull @Min(value = 1) Integer quantity, 13 | @NotNull @DecimalMin(value = "0.01") BigDecimal unitPrice) { 14 | } 15 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/invoice/dtos/InvoiceDto.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.invoice.dtos; 2 | 3 | import com.company.architecture.invoice.InvoiceStatus; 4 | 5 | import java.time.LocalDateTime; 6 | import java.util.List; 7 | 8 | public record InvoiceDto( 9 | Long id, 10 | String number, 11 | LocalDateTime dateTime, 12 | InvoiceStatus status, 13 | List items) { 14 | } 15 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/invoice/dtos/InvoiceItemDto.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.invoice.dtos; 2 | 3 | import java.math.BigDecimal; 4 | 5 | public record InvoiceItemDto( 6 | Long id, 7 | String product, 8 | Integer quantity, 9 | BigDecimal unitPrice) { 10 | } 11 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/invoice/entities/BaseEntity.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.invoice.entities; 2 | 3 | import jakarta.persistence.GeneratedValue; 4 | import jakarta.persistence.GenerationType; 5 | import jakarta.persistence.Id; 6 | import jakarta.persistence.MappedSuperclass; 7 | import lombok.Data; 8 | 9 | @MappedSuperclass 10 | @Data 11 | public abstract class BaseEntity { 12 | @Id 13 | @GeneratedValue(strategy = GenerationType.IDENTITY) 14 | private long id; 15 | } 16 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/invoice/entities/Invoice.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.invoice.entities; 2 | 3 | import com.company.architecture.invoice.InvoiceStatus; 4 | import com.fasterxml.jackson.annotation.JsonManagedReference; 5 | import jakarta.persistence.*; 6 | import jakarta.validation.Valid; 7 | import jakarta.validation.constraints.NotNull; 8 | import jakarta.validation.constraints.Size; 9 | import lombok.*; 10 | 11 | import java.time.LocalDateTime; 12 | import java.util.HashSet; 13 | import java.util.Set; 14 | 15 | @NoArgsConstructor 16 | @AllArgsConstructor 17 | @Builder 18 | @Data 19 | @EqualsAndHashCode(callSuper = true, onlyExplicitlyIncluded = true) 20 | @Entity 21 | @Table(indexes = {@Index(columnList = "number")}, uniqueConstraints = {@UniqueConstraint(columnNames = "number")}) 22 | public class Invoice extends BaseEntity { 23 | @NotNull 24 | @Size(min = 1, max = 50) 25 | @EqualsAndHashCode.Include 26 | @Column(nullable = false, length = 50) 27 | private String number; 28 | 29 | @NotNull 30 | @Column(nullable = false) 31 | @Builder.Default 32 | private LocalDateTime dateTime = LocalDateTime.now(); 33 | 34 | @NotNull 35 | @Enumerated(EnumType.STRING) 36 | @Column(nullable = false) 37 | private InvoiceStatus status; 38 | 39 | @Valid 40 | @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY) 41 | @JsonManagedReference 42 | @Builder.Default 43 | private Set items = new HashSet<>(); 44 | 45 | public Invoice addItem(final InvoiceItem item) { 46 | if (item == null) return this; 47 | if (items == null) items = new HashSet<>(); 48 | item.setInvoice(this); 49 | items.add(item); 50 | return this; 51 | } 52 | 53 | public Invoice setItems(Set items) { 54 | this.items = items != null ? items : new HashSet<>(); 55 | this.items.forEach(item -> item.setInvoice(this)); 56 | return this; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/invoice/entities/InvoiceItem.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.invoice.entities; 2 | 3 | import com.fasterxml.jackson.annotation.JsonBackReference; 4 | import jakarta.persistence.*; 5 | import jakarta.validation.constraints.DecimalMin; 6 | import jakarta.validation.constraints.Min; 7 | import jakarta.validation.constraints.NotNull; 8 | import jakarta.validation.constraints.Size; 9 | import lombok.*; 10 | 11 | import java.math.BigDecimal; 12 | 13 | @NoArgsConstructor 14 | @AllArgsConstructor 15 | @Builder 16 | @Data 17 | @EqualsAndHashCode(callSuper = true, onlyExplicitlyIncluded = true) 18 | @ToString(exclude = "invoice") 19 | @Entity 20 | public class InvoiceItem extends BaseEntity { 21 | @NotNull 22 | @Size(min = 1, max = 100) 23 | @Column(nullable = false, length = 100) 24 | private String product; 25 | 26 | @NotNull 27 | @Min(value = 1) 28 | @Column(nullable = false) 29 | private BigDecimal quantity; 30 | 31 | @NotNull 32 | @DecimalMin(value = "0.01") 33 | @Column(nullable = false) 34 | private BigDecimal unitPrice; 35 | 36 | @NotNull 37 | @ManyToOne(fetch = FetchType.LAZY) 38 | @JoinColumn(nullable = false) 39 | @JsonBackReference 40 | private Invoice invoice; 41 | } 42 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/location/City.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.location; 2 | 3 | public class City extends Location { 4 | } 5 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/location/Country.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.location; 2 | 3 | import lombok.Data; 4 | import lombok.EqualsAndHashCode; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | @Data 10 | @EqualsAndHashCode(callSuper = true) 11 | public class Country extends Location { 12 | private List states = new ArrayList<>(); 13 | } 14 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/location/FlatLocation.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.location; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | 6 | @Data 7 | @AllArgsConstructor 8 | public class FlatLocation { 9 | private Location country; 10 | private Location state; 11 | private Location city; 12 | } 13 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/location/Location.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.location; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | @AllArgsConstructor 8 | @Data 9 | @NoArgsConstructor 10 | class Location { 11 | private String code; 12 | private String name; 13 | } 14 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/location/LocationService.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.location; 2 | 3 | import org.springframework.stereotype.Service; 4 | 5 | import java.util.List; 6 | 7 | @Service 8 | public class LocationService { 9 | private static List getStates(final List locations, final Country country) { 10 | return locations.stream().filter(location -> location.getCountry().getCode().equals(country.getCode())).map(FlatLocation::getState).distinct().map(state -> { 11 | final var newState = new State(); 12 | newState.setCode(state.getCode()); 13 | newState.setName(state.getName()); 14 | newState.setCities(getCities(locations, newState)); 15 | return newState; 16 | }).toList(); 17 | } 18 | 19 | private static List getCities(final List locations, final State state) { 20 | return locations.stream().filter(location -> location.getState().getCode().equals(state.getCode())).map(FlatLocation::getCity).distinct().map(city -> { 21 | final var newCity = new City(); 22 | newCity.setCode(city.getCode()); 23 | newCity.setName(city.getName()); 24 | return newCity; 25 | }).toList(); 26 | } 27 | 28 | public List getCountries(final List locations) { 29 | return locations.stream().map(FlatLocation::getCountry).distinct().map(country -> { 30 | final var newCountry = new Country(); 31 | newCountry.setCode(country.getCode()); 32 | newCountry.setName(country.getName()); 33 | newCountry.setStates(getStates(locations, newCountry)); 34 | return newCountry; 35 | }).toList(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/location/State.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.location; 2 | 3 | import lombok.Data; 4 | import lombok.EqualsAndHashCode; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | @Data 10 | @EqualsAndHashCode(callSuper = true) 11 | public class State extends Location { 12 | private List cities = new ArrayList<>(); 13 | } 14 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/movie/Movie.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.movie; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import jakarta.persistence.*; 5 | import jakarta.validation.constraints.NotBlank; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | 10 | @AllArgsConstructor 11 | @NoArgsConstructor 12 | @Data 13 | @Entity 14 | @Table 15 | public class Movie { 16 | @Id 17 | @GeneratedValue(strategy = GenerationType.IDENTITY) 18 | @Schema(hidden = true) 19 | private long id; 20 | 21 | @NotBlank 22 | private String title; 23 | } 24 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/movie/MovieConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.movie; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | 5 | @Configuration 6 | public class MovieConfiguration { 7 | protected static final String MOVIES = "movies"; 8 | } 9 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/movie/MovieController.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.movie; 2 | 3 | import com.company.architecture.shared.swagger.PostApiResponses; 4 | import io.swagger.v3.oas.annotations.Operation; 5 | import io.swagger.v3.oas.annotations.tags.Tag; 6 | import jakarta.validation.Valid; 7 | import lombok.RequiredArgsConstructor; 8 | import org.springframework.http.HttpStatus; 9 | import org.springframework.web.bind.annotation.*; 10 | 11 | @Tag(name = "Movies") 12 | @RequiredArgsConstructor 13 | @RestController 14 | @RequestMapping("/movies") 15 | public class MovieController { 16 | private final MovieService movieService; 17 | 18 | @Operation(summary = "Add") 19 | @PostApiResponses 20 | @PostMapping 21 | @ResponseStatus(HttpStatus.OK) 22 | public void add(@RequestBody @Valid final Movie movie) { 23 | movieService.add(movie); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/movie/MovieOutbox.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.movie; 2 | 3 | import jakarta.persistence.*; 4 | import jakarta.validation.constraints.NotNull; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | import java.util.UUID; 9 | 10 | @NoArgsConstructor 11 | @Data 12 | @Entity 13 | @Table 14 | public class MovieOutbox { 15 | @Id 16 | private UUID id = UUID.randomUUID(); 17 | 18 | @NotNull 19 | @OneToOne 20 | private Movie movie; 21 | 22 | public MovieOutbox(Movie movie) { 23 | this.movie = movie; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/movie/MovieOutboxRepository.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.movie; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | import org.springframework.stereotype.Repository; 5 | 6 | import java.util.UUID; 7 | 8 | @Repository 9 | public interface MovieOutboxRepository extends JpaRepository { 10 | } 11 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/movie/MovieOutboxService.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.movie; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.kafka.core.KafkaTemplate; 6 | import org.springframework.scheduling.annotation.Scheduled; 7 | import org.springframework.stereotype.Component; 8 | 9 | @Component 10 | @RequiredArgsConstructor 11 | @Slf4j 12 | public class MovieOutboxService { 13 | private final MovieOutboxRepository movieOutboxRepository; 14 | private final KafkaTemplate kafkaTemplate; 15 | 16 | @Scheduled(fixedDelay = 5000) 17 | public void process() { 18 | movieOutboxRepository.findAll().forEach(movieOutbox -> { 19 | kafkaTemplate.send(MovieConfiguration.MOVIES, movieOutbox.getMovie()).whenComplete((_, _) -> movieOutboxRepository.delete(movieOutbox)); 20 | log.info("[MovieOutboxService].[process]: {}", movieOutbox.getMovie()); 21 | }); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/movie/MovieRepository.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.movie; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | import org.springframework.stereotype.Repository; 5 | 6 | @Repository 7 | public interface MovieRepository extends JpaRepository { 8 | } 9 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/movie/MovieService.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.movie; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.kafka.annotation.KafkaListener; 6 | import org.springframework.stereotype.Service; 7 | import org.springframework.transaction.annotation.Transactional; 8 | 9 | @Service 10 | @RequiredArgsConstructor 11 | @Slf4j 12 | public class MovieService { 13 | private final MovieRepository movieRepository; 14 | private final MovieOutboxRepository movieOutboxRepository; 15 | 16 | @Transactional 17 | public void add(Movie movie) { 18 | movieOutboxRepository.save(new MovieOutbox(movieRepository.save(movie))); 19 | log.info("[MovieService].[add]: {}", movie); 20 | } 21 | 22 | @KafkaListener(topics = MovieConfiguration.MOVIES, groupId = MovieConfiguration.MOVIES) 23 | public void listen(final Movie movie) { 24 | log.info("[MovieService].[listen]: {}", movie); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/notification/Notification.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.notification; 2 | 3 | import jakarta.validation.constraints.NotBlank; 4 | 5 | public record Notification(@NotBlank String message) { 6 | } 7 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/notification/NotificationController.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.notification; 2 | 3 | import com.company.architecture.shared.swagger.PostApiResponses; 4 | import io.swagger.v3.oas.annotations.Operation; 5 | import io.swagger.v3.oas.annotations.tags.Tag; 6 | import jakarta.validation.Valid; 7 | import lombok.RequiredArgsConstructor; 8 | import org.springframework.http.HttpStatus; 9 | import org.springframework.web.bind.annotation.*; 10 | 11 | @Tag(name = "Notifications") 12 | @RequiredArgsConstructor 13 | @RestController 14 | @RequestMapping("/notifications") 15 | public class NotificationController { 16 | private final NotificationService notificationService; 17 | 18 | @Operation(summary = "Send") 19 | @PostApiResponses 20 | @PostMapping 21 | @ResponseStatus(HttpStatus.OK) 22 | public void send(@RequestBody @Valid final Notification notification) { 23 | notificationService.send(notification); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/notification/NotificationService.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.notification; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.kafka.annotation.KafkaListener; 6 | import org.springframework.kafka.core.KafkaTemplate; 7 | import org.springframework.stereotype.Service; 8 | 9 | @Service 10 | @RequiredArgsConstructor 11 | @Slf4j 12 | public class NotificationService { 13 | private final KafkaTemplate kafkaTemplate; 14 | private static final String NOTIFICATIONS = "notifications"; 15 | 16 | public void send(Notification notification) { 17 | kafkaTemplate.send(NOTIFICATIONS, notification); 18 | log.info("[NotificationService].[send]: {}", notification); 19 | } 20 | 21 | @KafkaListener(topics = NOTIFICATIONS, groupId = NOTIFICATIONS) 22 | public void listen(final Notification notification) { 23 | log.info("[NotificationService].[listen]: {}", notification); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/payment/CreditCardPaymentStrategy.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.payment; 2 | 3 | import org.springframework.stereotype.Service; 4 | 5 | @Service 6 | public class CreditCardPaymentStrategy implements PaymentStrategy { 7 | @Override 8 | public PaymentMethod paymentMethod() { 9 | return PaymentMethod.CREDIT_CARD; 10 | } 11 | 12 | @Override 13 | public String pay() { 14 | return CreditCardPaymentStrategy.class.getSimpleName(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/payment/DebitCardPaymentStrategy.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.payment; 2 | 3 | import org.springframework.stereotype.Service; 4 | 5 | @Service 6 | public class DebitCardPaymentStrategy implements PaymentStrategy { 7 | @Override 8 | public PaymentMethod paymentMethod() { 9 | return PaymentMethod.DEBIT_CARD; 10 | } 11 | 12 | @Override 13 | public String pay() { 14 | return DebitCardPaymentStrategy.class.getSimpleName(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/payment/PaymentMethod.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.payment; 2 | 3 | public enum PaymentMethod { 4 | CREDIT_CARD, DEBIT_CARD 5 | } 6 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/payment/PaymentService.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.payment; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.springframework.stereotype.Service; 5 | 6 | import java.util.List; 7 | 8 | @RequiredArgsConstructor 9 | @Service 10 | public class PaymentService { 11 | private final List strategies; 12 | 13 | public String process(final PaymentMethod paymentMethod) { 14 | return strategies.stream().filter(strategy -> strategy.paymentMethod().equals(paymentMethod)).findFirst().orElseThrow().pay(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/payment/PaymentStrategy.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.payment; 2 | 3 | public interface PaymentStrategy { 4 | PaymentMethod paymentMethod(); 5 | 6 | String pay(); 7 | } 8 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/product/Product.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.product; 2 | 3 | import jakarta.persistence.Id; 4 | import jakarta.validation.constraints.Min; 5 | import jakarta.validation.constraints.NotBlank; 6 | import lombok.*; 7 | import org.springframework.data.mongodb.core.mapping.Document; 8 | 9 | import java.math.BigDecimal; 10 | import java.util.UUID; 11 | 12 | @NoArgsConstructor 13 | @AllArgsConstructor 14 | @Builder 15 | @Data 16 | @Document 17 | @EqualsAndHashCode(onlyExplicitlyIncluded = true) 18 | public class Product { 19 | @Id 20 | @EqualsAndHashCode.Include 21 | private UUID id; 22 | 23 | @NotBlank 24 | private String description; 25 | 26 | @Min(0L) 27 | private BigDecimal price; 28 | } 29 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/product/ProductController.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.product; 2 | 3 | import com.company.architecture.product.dtos.*; 4 | import com.company.architecture.shared.swagger.DefaultApiResponses; 5 | import com.company.architecture.shared.swagger.GetApiResponses; 6 | import com.company.architecture.shared.swagger.PostApiResponses; 7 | import io.swagger.v3.oas.annotations.Operation; 8 | import io.swagger.v3.oas.annotations.tags.Tag; 9 | import jakarta.validation.Valid; 10 | import lombok.RequiredArgsConstructor; 11 | import org.springdoc.core.annotations.ParameterObject; 12 | import org.springframework.data.domain.Page; 13 | import org.springframework.web.bind.annotation.*; 14 | 15 | import java.math.BigDecimal; 16 | import java.util.UUID; 17 | 18 | @Tag(name = "Products") 19 | @RequiredArgsConstructor 20 | @RestController 21 | @RequestMapping("/products") 22 | public class ProductController { 23 | private final ProductService productService; 24 | 25 | @Operation(summary = "Get") 26 | @GetApiResponses 27 | @GetMapping 28 | public Page get(@ParameterObject @ModelAttribute @Valid final GetProductDto dto) { 29 | return productService.get(dto); 30 | } 31 | 32 | @Operation(summary = "Get") 33 | @GetApiResponses 34 | @GetMapping("{id}") 35 | public ProductDto get(@PathVariable final UUID id) { 36 | return productService.get(id); 37 | } 38 | 39 | @Operation(summary = "Add") 40 | @PostApiResponses 41 | @PostMapping 42 | public UUID add(@RequestBody @Valid final AddProductDto dto) { 43 | return productService.add(dto); 44 | } 45 | 46 | @Operation(summary = "Update") 47 | @DefaultApiResponses 48 | @PutMapping("{id}") 49 | public void update(@PathVariable final UUID id, @RequestBody @Valid final UpdateProductDto dto) { 50 | productService.update(dto.withId(id)); 51 | } 52 | 53 | @Operation(summary = "Update Price") 54 | @DefaultApiResponses 55 | @PatchMapping("{id}/price/{price}") 56 | public void update(@PathVariable final UUID id, @PathVariable final BigDecimal price) { 57 | productService.update(new UpdatePriceProductDto(id, price)); 58 | } 59 | 60 | @Operation(summary = "Delete") 61 | @DefaultApiResponses 62 | @DeleteMapping("{id}") 63 | public void delete(@PathVariable final UUID id) { 64 | productService.delete(id); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/product/ProductRepository.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.product; 2 | 3 | import org.springframework.data.mongodb.repository.MongoRepository; 4 | import org.springframework.stereotype.Repository; 5 | 6 | import java.util.UUID; 7 | 8 | @Repository 9 | public interface ProductRepository extends MongoRepository { 10 | } 11 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/product/ProductService.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.product; 2 | 3 | import com.company.architecture.product.dtos.*; 4 | import com.company.architecture.shared.services.MapperService; 5 | import lombok.RequiredArgsConstructor; 6 | import org.springframework.beans.BeanUtils; 7 | import org.springframework.data.domain.Page; 8 | import org.springframework.stereotype.Service; 9 | 10 | import java.util.NoSuchElementException; 11 | import java.util.UUID; 12 | 13 | @Service 14 | @RequiredArgsConstructor 15 | public class ProductService { 16 | private final MapperService mapperService; 17 | private final ProductRepository productRepository; 18 | 19 | public Page get(final GetProductDto dto) { 20 | final var products = productRepository.findAll(dto.getExample(Product.class), dto.getPageable()); 21 | if (products.isEmpty()) throw new NoSuchElementException(); 22 | return mapperService.mapPage(products, ProductDto.class); 23 | } 24 | 25 | public ProductDto get(final UUID id) { 26 | return productRepository.findById(id).map(product -> mapperService.map(product, ProductDto.class)).orElseThrow(); 27 | } 28 | 29 | public UUID add(final AddProductDto dto) { 30 | final var product = mapperService.map(dto, Product.class); 31 | product.setId(UUID.randomUUID()); 32 | return productRepository.insert(product).getId(); 33 | } 34 | 35 | public void update(final UpdateProductDto dto) { 36 | productRepository.save(mapperService.map(dto, Product.class)); 37 | } 38 | 39 | public void update(final UpdatePriceProductDto dto) { 40 | final var product = productRepository.findById(dto.id()).orElseThrow(); 41 | BeanUtils.copyProperties(dto, product); 42 | productRepository.save(product); 43 | } 44 | 45 | public void delete(final UUID id) { 46 | productRepository.deleteById(id); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/product/dtos/AddProductDto.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.product.dtos; 2 | 3 | import jakarta.validation.constraints.Min; 4 | import jakarta.validation.constraints.NotBlank; 5 | 6 | import java.math.BigDecimal; 7 | 8 | public record AddProductDto( 9 | @NotBlank String description, 10 | @Min(0L) BigDecimal price) { 11 | } 12 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/product/dtos/GetProductDto.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.product.dtos; 2 | 3 | import com.company.architecture.shared.dtos.PageableDto; 4 | import lombok.Data; 5 | import lombok.EqualsAndHashCode; 6 | 7 | @Data 8 | @EqualsAndHashCode(callSuper = false) 9 | public final class GetProductDto extends PageableDto { 10 | private String description; 11 | 12 | public GetProductDto() { 13 | setSort("description"); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/product/dtos/ProductDto.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.product.dtos; 2 | 3 | import java.math.BigDecimal; 4 | import java.util.UUID; 5 | 6 | public record ProductDto( 7 | UUID id, 8 | String description, 9 | BigDecimal price) { 10 | } 11 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/product/dtos/UpdatePriceProductDto.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.product.dtos; 2 | 3 | import jakarta.validation.constraints.Min; 4 | import jakarta.validation.constraints.NotNull; 5 | 6 | import java.math.BigDecimal; 7 | import java.util.UUID; 8 | 9 | public record UpdatePriceProductDto( 10 | @NotNull UUID id, 11 | @Min(0L) BigDecimal price) { 12 | } 13 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/product/dtos/UpdateProductDto.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.product.dtos; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import jakarta.validation.constraints.Min; 5 | import jakarta.validation.constraints.NotBlank; 6 | 7 | import java.math.BigDecimal; 8 | import java.util.UUID; 9 | 10 | public record UpdateProductDto( 11 | @Schema(hidden = true) UUID id, 12 | @NotBlank String description, 13 | @Min(0L) BigDecimal price) { 14 | 15 | public UpdateProductDto withId(UUID id) { 16 | return new UpdateProductDto(id, description, price); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/shared/configurations/ExceptionConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.shared.configurations; 2 | 3 | import jakarta.validation.ConstraintViolationException; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.http.ResponseEntity; 7 | import org.springframework.http.converter.HttpMessageNotReadableException; 8 | import org.springframework.security.access.AccessDeniedException; 9 | import org.springframework.web.bind.MethodArgumentNotValidException; 10 | import org.springframework.web.bind.annotation.ExceptionHandler; 11 | import org.springframework.web.bind.annotation.RestControllerAdvice; 12 | import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; 13 | import org.springframework.web.server.ResponseStatusException; 14 | 15 | import java.util.*; 16 | import java.util.stream.Stream; 17 | 18 | import static java.util.Objects.isNull; 19 | 20 | @Slf4j 21 | @RestControllerAdvice 22 | public class ExceptionConfiguration { 23 | @ExceptionHandler(AccessDeniedException.class) 24 | public ResponseEntity handle(final AccessDeniedException exception) { 25 | return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); 26 | } 27 | 28 | @ExceptionHandler(ConstraintViolationException.class) 29 | public ResponseEntity handle(final ConstraintViolationException exception) { 30 | final var error = exception.getConstraintViolations().stream() 31 | .map(violation -> "%s: %s".formatted(violation.getPropertyPath(), violation.getMessage())).toList().toString(); 32 | 33 | return ResponseEntity.badRequest().body(error); 34 | } 35 | 36 | @ExceptionHandler(HttpMessageNotReadableException.class) 37 | public ResponseEntity handle(final HttpMessageNotReadableException exception) { 38 | return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(exception.getMessage()); 39 | } 40 | 41 | @ExceptionHandler(MethodArgumentNotValidException.class) 42 | public ResponseEntity handle(final MethodArgumentNotValidException exception) { 43 | final var fieldErrors = exception.getBindingResult().getFieldErrors().stream() 44 | .map(error -> "%s: %s".formatted(error.getField(), "typeMismatch".equals(error.getCode()) ? "must be valid" : error.getDefaultMessage())); 45 | 46 | final var globalErrors = exception.getBindingResult().getGlobalErrors().stream() 47 | .map(error -> "%s: %s".formatted(error.getObjectName(), error.getDefaultMessage())); 48 | 49 | final var errors = Stream.concat(fieldErrors, globalErrors).sorted().toList(); 50 | 51 | return ResponseEntity.badRequest().body(errors.toString()); 52 | } 53 | 54 | @ExceptionHandler(MethodArgumentTypeMismatchException.class) 55 | public ResponseEntity handle(final MethodArgumentTypeMismatchException exception) { 56 | return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("[%s: must be valid]".formatted(exception.getPropertyName())); 57 | } 58 | 59 | @ExceptionHandler(NoSuchElementException.class) 60 | public ResponseEntity handle(final NoSuchElementException exception) { 61 | return ResponseEntity.status(HttpStatus.NOT_FOUND).build(); 62 | } 63 | 64 | @ExceptionHandler(ResponseStatusException.class) 65 | public ResponseEntity handle(final ResponseStatusException exception) { 66 | if (isNull(exception.getCause())) { 67 | return ResponseEntity.status(HttpStatus.valueOf(exception.getStatusCode().value())).body(exception.getReason()); 68 | } 69 | 70 | log.error(exception.getMessage(), exception); 71 | return ResponseEntity.status(HttpStatus.valueOf(exception.getStatusCode().value())).build(); 72 | } 73 | 74 | @ExceptionHandler(Exception.class) 75 | public ResponseEntity handle(final Exception exception) { 76 | log.error(exception.getMessage(), exception); 77 | return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/shared/configurations/JacksonConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.shared.configurations; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | import com.fasterxml.jackson.databind.DeserializationFeature; 5 | import com.fasterxml.jackson.databind.ObjectMapper; 6 | import com.fasterxml.jackson.databind.SerializationFeature; 7 | import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | import org.springframework.context.annotation.Primary; 11 | import org.springframework.data.web.config.EnableSpringDataWebSupport; 12 | import org.springframework.data.web.config.SpringDataJacksonConfiguration.PageModule; 13 | import org.springframework.data.web.config.SpringDataWebSettings; 14 | 15 | @Configuration 16 | public class JacksonConfiguration { 17 | @Bean 18 | @Primary 19 | public ObjectMapper objectMapper() { 20 | return new ObjectMapper() 21 | .registerModule(new JavaTimeModule()) 22 | .registerModule(new PageModule(new SpringDataWebSettings(EnableSpringDataWebSupport.PageSerializationMode.VIA_DTO))) 23 | .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, SerializationFeature.FAIL_ON_EMPTY_BEANS) 24 | .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) 25 | .enable(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT, DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT) 26 | .setSerializationInclusion(JsonInclude.Include.NON_NULL); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/shared/dtos/PageableDto.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.shared.dtos; 2 | 3 | import jakarta.validation.constraints.Positive; 4 | import jakarta.validation.constraints.PositiveOrZero; 5 | import lombok.Data; 6 | import org.springframework.beans.BeanUtils; 7 | import org.springframework.data.domain.*; 8 | import org.springframework.data.domain.ExampleMatcher.StringMatcher; 9 | import org.springframework.data.domain.Sort.Direction; 10 | 11 | @Data 12 | public class PageableDto { 13 | @PositiveOrZero 14 | private int page = 0; 15 | 16 | @Positive 17 | private int size = 2_000_000_000; 18 | 19 | private String sort = "id"; 20 | 21 | private Direction direction = Direction.ASC; 22 | 23 | public Pageable getPageable() { 24 | return PageRequest.of(page, size, Sort.by(direction, sort)); 25 | } 26 | 27 | public Example getExample(final Class clazz) { 28 | var instance = BeanUtils.instantiateClass(clazz); 29 | BeanUtils.copyProperties(this, instance); 30 | return Example.of(instance, ExampleMatcher.matching().withIgnoreCase().withStringMatcher(StringMatcher.CONTAINING)); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/shared/mediator/Handler.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.shared.mediator; 2 | 3 | import org.springframework.http.ResponseEntity; 4 | 5 | public interface Handler { 6 | ResponseEntity handle(Request request); 7 | } 8 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/shared/mediator/Mediator.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.shared.mediator; 2 | 3 | import jakarta.validation.*; 4 | import org.springframework.context.ApplicationContext; 5 | import org.springframework.http.ResponseEntity; 6 | import org.springframework.stereotype.Service; 7 | 8 | import java.lang.reflect.ParameterizedType; 9 | import java.util.Map; 10 | import java.util.concurrent.ConcurrentHashMap; 11 | 12 | @Service 13 | @SuppressWarnings("unchecked") 14 | public class Mediator { 15 | private record Key(Class request, Class response) {} 16 | private static final Validator validator = Validation.buildDefaultValidatorFactory().getValidator(); 17 | private static final Map> handlers = new ConcurrentHashMap<>(); 18 | 19 | public Mediator(final ApplicationContext context) { 20 | context.getBeansOfType(Handler.class).values().forEach(handler -> { 21 | final var arguments = ((ParameterizedType) handler.getClass().getGenericInterfaces()[0]).getActualTypeArguments(); 22 | handlers.put(new Key((Class) arguments[0], (Class) arguments[1]), handler); 23 | }); 24 | } 25 | 26 | public ResponseEntity handle(Request request, Class responseType) { 27 | final var violations = validator.validate(request); 28 | if (!violations.isEmpty()) throw new ConstraintViolationException(violations); 29 | return ((Handler) handlers.get(new Key(request.getClass(), responseType))).handle(request); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/shared/runners/MongoRunner.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.shared.runners; 2 | 3 | import com.company.architecture.auth.Authority; 4 | import com.company.architecture.user.User; 5 | import lombok.RequiredArgsConstructor; 6 | import org.springframework.boot.ApplicationArguments; 7 | import org.springframework.boot.ApplicationRunner; 8 | import org.springframework.data.mongodb.core.MongoTemplate; 9 | import org.springframework.security.crypto.password.PasswordEncoder; 10 | import org.springframework.stereotype.Component; 11 | 12 | import java.util.List; 13 | import java.util.UUID; 14 | 15 | @Component 16 | @RequiredArgsConstructor 17 | public class MongoRunner implements ApplicationRunner { 18 | private final MongoTemplate mongoTemplate; 19 | private final PasswordEncoder passwordEncoder; 20 | 21 | public void run(final ApplicationArguments args) { 22 | if (!mongoTemplate.collectionExists("user")) { 23 | mongoTemplate.save(new User(UUID.randomUUID(), "Admin", "admin@mail.com", "admin", passwordEncoder.encode("123456"), List.of(Authority.ADMINISTRATOR))); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/shared/services/MapperService.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.shared.services; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import jakarta.annotation.Nullable; 5 | import lombok.RequiredArgsConstructor; 6 | import lombok.SneakyThrows; 7 | import org.springframework.data.domain.Page; 8 | import org.springframework.stereotype.Service; 9 | 10 | import java.util.Collections; 11 | import java.util.List; 12 | 13 | import static java.util.Objects.isNull; 14 | 15 | @RequiredArgsConstructor 16 | @Service 17 | public class MapperService { 18 | private final ObjectMapper objectMapper; 19 | 20 | public T map(Object source, Class target) { 21 | return objectMapper.convertValue(source, target); 22 | } 23 | 24 | public List mapList(@Nullable List source, Class target) { 25 | return isNull(source) ? Collections.emptyList() : source.stream().map(entity -> map(entity, target)).toList(); 26 | } 27 | 28 | public Page mapPage(Page source, Class target) { 29 | return isNull(source) ? Page.empty() : source.map(element -> map(element, target)); 30 | } 31 | 32 | @SneakyThrows 33 | public String toJson(Object source) { 34 | return isNull(source) ? null : objectMapper.writeValueAsString(source); 35 | } 36 | 37 | @SneakyThrows 38 | public T fromJson(String source, Class target) { 39 | return isNull(source) ? null : objectMapper.readValue(source, target); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/shared/services/MessageService.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.shared.services; 2 | 3 | import org.springframework.context.i18n.LocaleContextHolder; 4 | import org.springframework.context.support.ResourceBundleMessageSource; 5 | import org.springframework.stereotype.Service; 6 | 7 | @Service 8 | public class MessageService { 9 | private static final ResourceBundleMessageSource source = new ResourceBundleMessageSource(); 10 | 11 | public MessageService() { 12 | source.setBasename("messages"); 13 | } 14 | 15 | public static String get(final String message, String... args) { 16 | return source.getMessage(message, args, message, LocaleContextHolder.getLocale()); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/shared/services/ValidatorService.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.shared.services; 2 | 3 | import jakarta.validation.ConstraintViolation; 4 | import jakarta.validation.Validation; 5 | import org.springframework.stereotype.Service; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | import java.util.stream.IntStream; 10 | 11 | @Service 12 | public class ValidatorService { 13 | private static boolean cpfVerifier(CharSequence value, int length) { 14 | final var verifier = IntStream.range(0, length).map(i -> Character.getNumericValue(value.charAt(i)) * (length + 1 - i)).sum() * 10 % 11 % 10; 15 | return verifier == Character.getNumericValue(value.charAt(length)); 16 | } 17 | 18 | private static boolean cnpjVerifier(CharSequence value, int index) { 19 | final var weights = new ArrayList<>(List.of(6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2)); 20 | if (index == 12) weights.removeFirst(); 21 | final var sum = IntStream.range(0, index).map(i -> Character.getNumericValue(value.charAt(i)) * weights.get(i)).sum(); 22 | final var verifier = ((sum % 11) < 2) ? 0 : (11 - (sum % 11)); 23 | return verifier == Character.getNumericValue(value.charAt(index)); 24 | } 25 | 26 | public List> validate(Object object) { 27 | try (final var validatorFactory = Validation.buildDefaultValidatorFactory()) { 28 | return validatorFactory.getValidator().validate(object).stream().toList(); 29 | } 30 | } 31 | 32 | public boolean validateCpf(String value) { 33 | return (value = value.replaceAll("\\D", "")).matches("\\d{11}") && !value.matches("(\\d)\\1{10}") && cpfVerifier(value, 9) && cpfVerifier(value, 10); 34 | } 35 | 36 | public boolean validateCnpj(String value) { 37 | return (value = value.replaceAll("\\D", "")).matches("\\d{14}") && !value.matches("(\\d)\\1{13}") && cnpjVerifier(value, 12) && cnpjVerifier(value, 13); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/shared/swagger/BaseApiResponses.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.shared.swagger; 2 | 3 | import io.swagger.v3.oas.annotations.media.Content; 4 | import io.swagger.v3.oas.annotations.responses.ApiResponse; 5 | 6 | import java.lang.annotation.ElementType; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.RetentionPolicy; 9 | import java.lang.annotation.Target; 10 | 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Target(ElementType.ANNOTATION_TYPE) 13 | @ApiResponse(responseCode = "400", content = @Content) 14 | @ApiResponse(responseCode = "401", content = @Content) 15 | @ApiResponse(responseCode = "403", content = @Content) 16 | @ApiResponse(responseCode = "404", content = @Content) 17 | @ApiResponse(responseCode = "409", content = @Content) 18 | @ApiResponse(responseCode = "422", content = @Content) 19 | @ApiResponse(responseCode = "500", content = @Content) 20 | @ApiResponse(responseCode = "503", content = @Content) 21 | public @interface BaseApiResponses { 22 | } 23 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/shared/swagger/DefaultApiResponses.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.shared.swagger; 2 | 3 | import io.swagger.v3.oas.annotations.media.Content; 4 | import io.swagger.v3.oas.annotations.responses.ApiResponse; 5 | 6 | import java.lang.annotation.ElementType; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.RetentionPolicy; 9 | import java.lang.annotation.Target; 10 | 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Target(ElementType.METHOD) 13 | @BaseApiResponses 14 | @ApiResponse(responseCode = "200", content = @Content) 15 | @ApiResponse(responseCode = "202", content = @Content) 16 | @ApiResponse(responseCode = "204", content = @Content) 17 | public @interface DefaultApiResponses { 18 | } 19 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/shared/swagger/GetApiResponses.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.shared.swagger; 2 | 3 | import io.swagger.v3.oas.annotations.responses.ApiResponse; 4 | 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | @Retention(RetentionPolicy.RUNTIME) 11 | @Target(ElementType.METHOD) 12 | @BaseApiResponses 13 | @ApiResponse(responseCode = "200") 14 | public @interface GetApiResponses { 15 | } 16 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/shared/swagger/PostApiResponses.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.shared.swagger; 2 | 3 | import io.swagger.v3.oas.annotations.media.Content; 4 | import io.swagger.v3.oas.annotations.responses.ApiResponse; 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.web.bind.annotation.ResponseStatus; 7 | 8 | import java.lang.annotation.ElementType; 9 | import java.lang.annotation.Retention; 10 | import java.lang.annotation.RetentionPolicy; 11 | import java.lang.annotation.Target; 12 | 13 | @Retention(RetentionPolicy.RUNTIME) 14 | @Target(ElementType.METHOD) 15 | @BaseApiResponses 16 | @ApiResponse(responseCode = "201") 17 | @ApiResponse(responseCode = "202", content = @Content) 18 | @ApiResponse(responseCode = "204", content = @Content) 19 | @ResponseStatus(HttpStatus.CREATED) 20 | public @interface PostApiResponses { 21 | } 22 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/shared/swagger/SwaggerConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.shared.swagger; 2 | 3 | import io.swagger.v3.oas.models.Components; 4 | import io.swagger.v3.oas.models.OpenAPI; 5 | import io.swagger.v3.oas.models.security.SecurityRequirement; 6 | import io.swagger.v3.oas.models.security.SecurityScheme; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | 10 | @Configuration 11 | public class SwaggerConfiguration { 12 | @Bean 13 | OpenAPI openAPI() { 14 | return new OpenAPI() 15 | .components(new Components() 16 | .addSecuritySchemes("JWT", new SecurityScheme() 17 | .type(SecurityScheme.Type.HTTP) 18 | .scheme("bearer") 19 | .bearerFormat("JWT"))) 20 | .addSecurityItem(new SecurityRequirement().addList("JWT")); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/shared/usecase/Input.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.shared.usecase; 2 | 3 | public interface Input { 4 | } 5 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/shared/usecase/Output.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.shared.usecase; 2 | 3 | public interface Output { 4 | } 5 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/shared/usecase/UseCase.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.shared.usecase; 2 | 3 | public interface UseCase { 4 | O execute(I input); 5 | } 6 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/user/User.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.user; 2 | 3 | import com.company.architecture.auth.Authority; 4 | import jakarta.persistence.Id; 5 | import jakarta.validation.constraints.Email; 6 | import jakarta.validation.constraints.NotBlank; 7 | import lombok.*; 8 | import org.springframework.data.mongodb.core.index.Indexed; 9 | import org.springframework.data.mongodb.core.mapping.Document; 10 | 11 | import java.util.List; 12 | import java.util.UUID; 13 | 14 | @NoArgsConstructor 15 | @AllArgsConstructor 16 | @Builder 17 | @Data 18 | @Document 19 | @EqualsAndHashCode(onlyExplicitlyIncluded = true) 20 | public class User { 21 | @Id 22 | @EqualsAndHashCode.Include 23 | private UUID id; 24 | 25 | @NotBlank 26 | private String name; 27 | 28 | @NotBlank 29 | @Email 30 | @Indexed(unique = true) 31 | private String email; 32 | 33 | @NotBlank 34 | @Indexed(unique = true) 35 | private String username; 36 | 37 | @NotBlank 38 | private String password; 39 | 40 | @NotBlank 41 | public List authorities; 42 | } 43 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/user/UserController.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.user; 2 | 3 | import com.company.architecture.shared.swagger.DefaultApiResponses; 4 | import com.company.architecture.shared.swagger.GetApiResponses; 5 | import com.company.architecture.shared.swagger.PostApiResponses; 6 | import com.company.architecture.user.dtos.AddUserDto; 7 | import com.company.architecture.user.dtos.GetUserDto; 8 | import com.company.architecture.user.dtos.UpdateUserDto; 9 | import com.company.architecture.user.dtos.UserDto; 10 | import io.swagger.v3.oas.annotations.Operation; 11 | import io.swagger.v3.oas.annotations.tags.Tag; 12 | import jakarta.validation.Valid; 13 | import lombok.RequiredArgsConstructor; 14 | import org.springdoc.core.annotations.ParameterObject; 15 | import org.springframework.data.domain.Page; 16 | import org.springframework.web.bind.annotation.*; 17 | 18 | import java.util.UUID; 19 | 20 | @Tag(name = "Users") 21 | @RequiredArgsConstructor 22 | @RestController 23 | @RequestMapping("/users") 24 | public class UserController { 25 | private final UserService userService; 26 | 27 | @Operation(summary = "Get") 28 | @GetApiResponses 29 | @GetMapping 30 | public Page get(@ParameterObject @ModelAttribute @Valid final GetUserDto dto) { 31 | return userService.get(dto); 32 | } 33 | 34 | @Operation(summary = "Get") 35 | @GetApiResponses 36 | @GetMapping("{id}") 37 | public UserDto get(@PathVariable final UUID id) { 38 | return userService.get(id); 39 | } 40 | 41 | @Operation(summary = "Add") 42 | @PostApiResponses 43 | @PostMapping 44 | public UUID add(@RequestBody @Valid final AddUserDto dto) { 45 | return userService.add(dto); 46 | } 47 | 48 | @Operation(summary = "Update") 49 | @DefaultApiResponses 50 | @PutMapping("{id}") 51 | public void update(@PathVariable final UUID id, @RequestBody @Valid final UpdateUserDto dto) { 52 | userService.update(dto.withId(id)); 53 | } 54 | 55 | @Operation(summary = "Delete") 56 | @DefaultApiResponses 57 | @DeleteMapping("{id}") 58 | public void delete(@PathVariable final UUID id) { 59 | userService.delete(id); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/user/UserRepository.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.user; 2 | 3 | import org.springframework.data.mongodb.repository.MongoRepository; 4 | import org.springframework.data.mongodb.repository.Query; 5 | import org.springframework.stereotype.Repository; 6 | 7 | import java.util.Optional; 8 | import java.util.UUID; 9 | 10 | @Repository 11 | public interface UserRepository extends MongoRepository { 12 | boolean existsByEmailOrUsername(String email, String username); 13 | 14 | @Query(value = "{$and: [{'_id': {$ne: ?0}}, {$or: [{'email': ?1}, {'username': ?2}]}]}", exists = true) 15 | boolean existsByEmailOrUsername(UUID id, String email, String username); 16 | 17 | Optional findByUsername(String username); 18 | } 19 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/user/UserService.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.user; 2 | 3 | import com.company.architecture.auth.Authority; 4 | import com.company.architecture.shared.services.MapperService; 5 | import com.company.architecture.user.dtos.AddUserDto; 6 | import com.company.architecture.user.dtos.GetUserDto; 7 | import com.company.architecture.user.dtos.UpdateUserDto; 8 | import com.company.architecture.user.dtos.UserDto; 9 | import lombok.RequiredArgsConstructor; 10 | import org.springframework.data.domain.Page; 11 | import org.springframework.http.HttpStatus; 12 | import org.springframework.security.crypto.password.PasswordEncoder; 13 | import org.springframework.stereotype.Service; 14 | import org.springframework.web.server.ResponseStatusException; 15 | 16 | import java.util.List; 17 | import java.util.NoSuchElementException; 18 | import java.util.UUID; 19 | 20 | @Service 21 | @RequiredArgsConstructor 22 | public class UserService { 23 | private final PasswordEncoder passwordEncoder; 24 | private final MapperService mapperService; 25 | private final UserRepository userRepository; 26 | 27 | public Page get(final GetUserDto dto) { 28 | final var users = userRepository.findAll(dto.getExample(User.class), dto.getPageable()); 29 | if (users.isEmpty()) throw new NoSuchElementException(); 30 | return mapperService.mapPage(users, UserDto.class); 31 | } 32 | 33 | public UserDto get(UUID id) { 34 | return userRepository.findById(id).map(user -> mapperService.map(user, UserDto.class)).orElseThrow(); 35 | } 36 | 37 | 38 | public UUID add(final AddUserDto dto) { 39 | final var exists = userRepository.existsByEmailOrUsername(dto.email(), dto.username()); 40 | if (exists) throw new ResponseStatusException(HttpStatus.CONFLICT); 41 | final var user = mapperService.map(dto, User.class); 42 | user.setId(UUID.randomUUID()); 43 | user.setPassword(passwordEncoder.encode(dto.password())); 44 | user.setAuthorities(List.of(Authority.DEFAULT)); 45 | return userRepository.insert(user).getId(); 46 | } 47 | 48 | public void update(final UpdateUserDto dto) { 49 | final var exists = userRepository.existsByEmailOrUsername(dto.id(), dto.email(), dto.username()); 50 | if (exists) throw new ResponseStatusException(HttpStatus.CONFLICT); 51 | final var user = mapperService.map(dto, User.class); 52 | user.setPassword(passwordEncoder.encode(dto.password())); 53 | userRepository.save(user); 54 | } 55 | 56 | public void delete(final UUID id) { 57 | userRepository.deleteById(id); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/user/dtos/AddUserDto.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.user.dtos; 2 | 3 | import jakarta.validation.constraints.Email; 4 | import jakarta.validation.constraints.NotBlank; 5 | 6 | public record AddUserDto( 7 | @NotBlank String name, 8 | @NotBlank @Email String email, 9 | @NotBlank String username, 10 | @NotBlank String password) { 11 | } 12 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/user/dtos/GetUserDto.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.user.dtos; 2 | 3 | import com.company.architecture.shared.dtos.PageableDto; 4 | import lombok.Data; 5 | import lombok.EqualsAndHashCode; 6 | 7 | @Data 8 | @EqualsAndHashCode(callSuper = false) 9 | public final class GetUserDto extends PageableDto { 10 | private String name; 11 | 12 | public GetUserDto() { 13 | setSort("name"); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/user/dtos/UpdateUserDto.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.user.dtos; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import jakarta.validation.constraints.Email; 5 | import jakarta.validation.constraints.NotBlank; 6 | 7 | import java.util.UUID; 8 | 9 | public record UpdateUserDto( 10 | @Schema(hidden = true) UUID id, 11 | @NotBlank String name, 12 | @NotBlank @Email String email, 13 | @NotBlank String username, 14 | @NotBlank String password) { 15 | 16 | public UpdateUserDto withId(UUID id) { 17 | return new UpdateUserDto(id, name, email, username, password); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /source/src/main/java/com/company/architecture/user/dtos/UserDto.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.user.dtos; 2 | 3 | import java.util.UUID; 4 | 5 | public record UserDto( 6 | UUID id, 7 | String name, 8 | String email) { 9 | } 10 | -------------------------------------------------------------------------------- /source/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | jpa: 3 | open-in-view: false 4 | show-sql: false 5 | hibernate: 6 | ddl-auto: create 7 | properties: 8 | hibernate: 9 | enable_lazy_load_no_trans: true 10 | format_sql: true 11 | datasource: 12 | driver-class-name: org.postgresql.Driver 13 | url: jdbc:postgresql://localhost:5432/database 14 | username: admin 15 | password: password 16 | data: 17 | mongodb: 18 | uuid-representation: standard 19 | uri: mongodb://admin:password@localhost:27017/database?authSource=admin 20 | kafka: 21 | bootstrap-servers: localhost:9092 22 | producer: 23 | key-serializer: org.apache.kafka.common.serialization.StringSerializer 24 | value-serializer: org.springframework.kafka.support.serializer.JsonSerializer 25 | acks: all 26 | retries: 3 27 | consumer: 28 | auto-offset-reset: earliest 29 | key-deserializer: org.apache.kafka.common.serialization.StringDeserializer 30 | value-deserializer: org.springframework.kafka.support.serializer.JsonDeserializer 31 | max-poll-records: 100 32 | properties.spring.json.trusted.packages: "*" 33 | springdoc: 34 | show-actuator: true 35 | swagger-ui: 36 | path: / 37 | docExpansion: none 38 | filter: true 39 | tagsSorter: alpha 40 | operationsSorter: alpha 41 | tryItOutEnabled: true 42 | management: 43 | endpoints: 44 | enabled-by-default: false 45 | web: 46 | exposure: 47 | include: "health,metrics" 48 | endpoint: 49 | health: 50 | enabled: true 51 | metrics: 52 | enabled: true 53 | logging: 54 | structured: 55 | format: 56 | console: logstash 57 | feign: 58 | clients: 59 | jsonplaceholder: 60 | url: https://jsonplaceholder.typicode.com 61 | -------------------------------------------------------------------------------- /source/src/main/resources/logback-spring.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | %cyan([%d{yyyy-MM-dd}] [%d{HH:mm:ss}] [%logger]) %highlight([%level]: %msg%n%n) 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | ${ELK_LOGSTASH} 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /source/src/main/resources/messages.properties: -------------------------------------------------------------------------------- 1 | auth.unauthorized=Invalid username and password. 2 | -------------------------------------------------------------------------------- /source/src/main/resources/messages_pt_BR.properties: -------------------------------------------------------------------------------- 1 | auth.unauthorized=Usu\u00e1rio e senha inv\u00e1lidos. 2 | -------------------------------------------------------------------------------- /source/src/test/java/com/company/architecture/ControllerTest.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture; 2 | 3 | import com.company.architecture.auth.Authority; 4 | import com.company.architecture.auth.JwtService; 5 | import com.fasterxml.jackson.databind.ObjectMapper; 6 | import org.junit.jupiter.api.BeforeEach; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.http.HttpMethod; 9 | import org.springframework.http.MediaType; 10 | import org.springframework.mock.web.MockMultipartFile; 11 | import org.springframework.test.context.bean.override.mockito.MockitoBean; 12 | import org.springframework.test.web.servlet.MockMvc; 13 | import org.springframework.test.web.servlet.ResultActions; 14 | import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; 15 | 16 | import java.util.UUID; 17 | 18 | import static org.mockito.ArgumentMatchers.any; 19 | import static org.mockito.ArgumentMatchers.anyString; 20 | import static org.mockito.Mockito.when; 21 | import static org.springframework.security.core.authority.AuthorityUtils.createAuthorityList; 22 | 23 | public abstract class ControllerTest { 24 | @Autowired 25 | MockMvc mockMvc; 26 | 27 | @Autowired 28 | ObjectMapper objectMapper; 29 | 30 | @MockitoBean 31 | JwtService jwtService; 32 | 33 | @BeforeEach 34 | void beforeEach() { 35 | when(jwtService.getSubject(anyString())).thenReturn(UUID.randomUUID().toString()); 36 | when(jwtService.getAuthorities(anyString())).thenReturn(createAuthorityList(Authority.ADMINISTRATOR.toString())); 37 | when(jwtService.create(any())).thenReturn(""); 38 | when(jwtService.verify(anyString())).thenReturn(true); 39 | } 40 | 41 | protected ResultActions perform(HttpMethod method, String uri) throws Exception { 42 | final var builder = MockMvcRequestBuilders.request(method, uri).contentType(MediaType.APPLICATION_JSON); 43 | return mockMvc.perform(builder).andDo(result -> System.out.printf(" Uri: %s%n Method: %s%n Response: %s%n%n", uri, method, result.getResponse().getContentAsString())); 44 | } 45 | 46 | protected ResultActions perform(HttpMethod method, String uri, Object body) throws Exception { 47 | final var builder = MockMvcRequestBuilders.request(method, uri).contentType(MediaType.APPLICATION_JSON).content(objectMapper.writeValueAsString(body)); 48 | return mockMvc.perform(builder).andDo(result -> System.out.printf(" Uri: %s%n Method: %s%n Request: %s%n Response: %s%n%n", uri, method, body, result.getResponse().getContentAsString())); 49 | } 50 | 51 | protected ResultActions multipart(String uri, MockMultipartFile file) throws Exception { 52 | return mockMvc.perform(MockMvcRequestBuilders.multipart(uri).file(file)); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /source/src/test/java/com/company/architecture/IntegrationTest.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture; 2 | 3 | import com.company.architecture.shared.services.MapperService; 4 | import org.junit.jupiter.api.BeforeAll; 5 | import org.junit.jupiter.api.BeforeEach; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | import org.springframework.boot.testcontainers.service.connection.ServiceConnection; 10 | import org.springframework.data.mongodb.core.MongoTemplate; 11 | import org.testcontainers.containers.MongoDBContainer; 12 | import org.testcontainers.containers.PostgreSQLContainer; 13 | import org.testcontainers.containers.localstack.LocalStackContainer; 14 | import org.testcontainers.junit.jupiter.Container; 15 | import org.testcontainers.kafka.KafkaContainer; 16 | 17 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) 18 | @AutoConfigureMockMvc 19 | public abstract class IntegrationTest extends ControllerTest { 20 | @ServiceConnection 21 | static final KafkaContainer kafka = KafkaTest.kafka; 22 | 23 | @Container 24 | static final LocalStackContainer localstack = LocalStackTest.localstack; 25 | 26 | @ServiceConnection 27 | static final MongoDBContainer mongo = MongoTest.mongo; 28 | 29 | @ServiceConnection 30 | static final PostgreSQLContainer postgres = PostgreTest.postgres; 31 | 32 | @Autowired 33 | protected MongoTemplate mongoTemplate; 34 | 35 | @Autowired 36 | protected MapperService mapperService; 37 | 38 | @BeforeAll 39 | static void beforeAll() { 40 | KafkaTest.beforeAll(); 41 | LocalStackTest.beforeAll(); 42 | } 43 | 44 | @BeforeEach 45 | void before() { 46 | mongoTemplate.getDb().drop(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /source/src/test/java/com/company/architecture/KafkaTest.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture; 2 | 3 | import org.junit.jupiter.api.BeforeAll; 4 | import org.springframework.boot.testcontainers.service.connection.ServiceConnection; 5 | import org.testcontainers.kafka.KafkaContainer; 6 | import org.testcontainers.utility.DockerImageName; 7 | 8 | public abstract class KafkaTest { 9 | @ServiceConnection 10 | protected static final KafkaContainer kafka = new KafkaContainer(DockerImageName.parse("apache/kafka")) 11 | .withEnv("KAFKA_LISTENERS", "PLAINTEXT://:9092,BROKER://:9093,CONTROLLER://:9094"); 12 | 13 | @BeforeAll 14 | static void beforeAll() { 15 | kafka.start(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /source/src/test/java/com/company/architecture/LocalStackTest.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture; 2 | 3 | import org.junit.jupiter.api.BeforeAll; 4 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 5 | import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; 6 | import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration; 7 | import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; 8 | import org.testcontainers.containers.localstack.LocalStackContainer; 9 | import org.testcontainers.junit.jupiter.Container; 10 | import org.testcontainers.utility.DockerImageName; 11 | 12 | @EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class, HibernateJpaAutoConfiguration.class}) 13 | public abstract class LocalStackTest { 14 | @Container 15 | protected static final LocalStackContainer localstack = new LocalStackContainer(DockerImageName.parse("localstack/localstack")) 16 | .withServices( 17 | LocalStackContainer.Service.SQS, 18 | LocalStackContainer.EnabledService.named("sqs-query"), 19 | LocalStackContainer.Service.S3 20 | ); 21 | 22 | @BeforeAll 23 | static void beforeAll() { 24 | localstack.start(); 25 | System.setProperty("aws.endpoint", localstack.getEndpoint().toString()); 26 | System.setProperty("aws.region", localstack.getRegion()); 27 | System.setProperty("aws.accessKeyId", localstack.getAccessKey()); 28 | System.setProperty("aws.secretAccessKey", localstack.getSecretKey()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /source/src/test/java/com/company/architecture/MongoTest.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture; 2 | 3 | import org.junit.jupiter.api.BeforeEach; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest; 6 | import org.springframework.boot.testcontainers.service.connection.ServiceConnection; 7 | import org.springframework.data.mongodb.core.MongoTemplate; 8 | import org.testcontainers.containers.MongoDBContainer; 9 | 10 | @DataMongoTest 11 | public abstract class MongoTest { 12 | @ServiceConnection 13 | protected static final MongoDBContainer mongo = new MongoDBContainer("mongo"); 14 | 15 | @Autowired 16 | protected MongoTemplate mongoTemplate; 17 | 18 | @BeforeEach 19 | void beforeEach() { 20 | mongoTemplate.getDb().drop(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /source/src/test/java/com/company/architecture/PostgreTest.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; 5 | import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; 6 | import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager; 7 | import org.springframework.boot.testcontainers.service.connection.ServiceConnection; 8 | import org.springframework.test.annotation.DirtiesContext; 9 | import org.testcontainers.containers.PostgreSQLContainer; 10 | 11 | @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) 12 | @DataJpaTest 13 | @DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD) 14 | public abstract class PostgreTest { 15 | @ServiceConnection 16 | protected static final PostgreSQLContainer postgres = new PostgreSQLContainer<>("postgres"); 17 | 18 | @Autowired 19 | protected TestEntityManager testEntityManager; 20 | } 21 | -------------------------------------------------------------------------------- /source/src/test/java/com/company/architecture/animal/CreateAnimalUseCaseTest.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.animal; 2 | 3 | import org.junit.jupiter.api.Assertions; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.util.UUID; 7 | 8 | class CreateAnimalUseCaseTest { 9 | @Test 10 | void shouldReturnValidOutputWithNonNullId() { 11 | final var useCase = new CreateAnimalUseCase(); 12 | final var input = new CreateAnimalInput("Animal"); 13 | final var output = useCase.execute(input); 14 | 15 | Assertions.assertNotNull(output); 16 | Assertions.assertNotNull(output.id()); 17 | Assertions.assertInstanceOf(UUID.class, output.id()); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /source/src/test/java/com/company/architecture/api/JsonPlaceholderServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.api; 2 | 3 | import feign.FeignException; 4 | import org.junit.jupiter.api.Assertions; 5 | import org.junit.jupiter.api.Test; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; 8 | import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; 9 | import org.springframework.boot.test.context.SpringBootTest; 10 | import org.springframework.cloud.openfeign.EnableFeignClients; 11 | import org.springframework.cloud.openfeign.FeignAutoConfiguration; 12 | 13 | @SpringBootTest(classes = {FeignAutoConfiguration.class, JacksonAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, JsonPlaceholderService.class}) 14 | @EnableFeignClients 15 | class JsonPlaceholderServiceTest { 16 | @Autowired 17 | JsonPlaceholderService jsonPlaceholderService; 18 | 19 | @Test 20 | void shouldReturnNotNullWhenGetTodos() { 21 | final var list = jsonPlaceholderService.getTodos(); 22 | Assertions.assertNotNull(list); 23 | Assertions.assertFalse(list.isEmpty()); 24 | } 25 | 26 | @Test 27 | void shouldReturnNotNullWhenGetTodo() { 28 | Assertions.assertNotNull(jsonPlaceholderService.getTodo(1)); 29 | } 30 | 31 | @Test 32 | void shouldThrowExceptionWhenGetTodo() { 33 | Assertions.assertThrows(FeignException.class, () -> jsonPlaceholderService.getTodo(99999)); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /source/src/test/java/com/company/architecture/auth/AuthIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.auth; 2 | 3 | import com.company.architecture.IntegrationTest; 4 | import com.company.architecture.shared.Data; 5 | import com.company.architecture.user.UserRepository; 6 | import org.junit.jupiter.params.ParameterizedTest; 7 | import org.junit.jupiter.params.provider.Arguments; 8 | import org.junit.jupiter.params.provider.MethodSource; 9 | import org.springframework.http.HttpStatus; 10 | import org.springframework.http.HttpStatusCode; 11 | import org.springframework.test.context.bean.override.mockito.MockitoBean; 12 | 13 | import java.util.Optional; 14 | import java.util.UUID; 15 | import java.util.stream.Stream; 16 | 17 | import static org.junit.jupiter.params.provider.Arguments.arguments; 18 | import static org.mockito.Mockito.when; 19 | import static org.springframework.http.HttpMethod.POST; 20 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 21 | 22 | class AuthIntegrationTest extends IntegrationTest { 23 | @MockitoBean 24 | UserRepository userRepository; 25 | 26 | private static Stream parameters() { 27 | return Stream.of( 28 | arguments(null, HttpStatus.BAD_REQUEST), 29 | arguments(new Auth(null, null), HttpStatus.BAD_REQUEST), 30 | arguments(new Auth("", ""), HttpStatus.BAD_REQUEST), 31 | arguments(new Auth(UUID.randomUUID().toString(), UUID.randomUUID().toString()), HttpStatus.UNAUTHORIZED), 32 | arguments(new Auth(Data.USER.getUsername(), UUID.randomUUID().toString()), HttpStatus.UNAUTHORIZED), 33 | arguments(new Auth(UUID.randomUUID().toString(), Data.PASSWORD), HttpStatus.UNAUTHORIZED), 34 | arguments(new Auth(Data.USER.getUsername(), Data.PASSWORD), HttpStatus.OK) 35 | ); 36 | } 37 | 38 | @ParameterizedTest 39 | @MethodSource("parameters") 40 | void shouldReturnHttpStatus(Auth auth, HttpStatusCode statusCode) throws Exception { 41 | when(userRepository.findByUsername(Data.USER.getUsername())).thenReturn(Optional.of(Data.USER)); 42 | perform(POST, "/auth", auth).andExpectAll(status().is(statusCode.value())); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /source/src/test/java/com/company/architecture/auth/JwtServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.auth; 2 | 3 | import com.company.architecture.shared.Data; 4 | import org.junit.jupiter.api.Assertions; 5 | import org.junit.jupiter.api.Test; 6 | import org.junit.jupiter.params.ParameterizedTest; 7 | import org.junit.jupiter.params.provider.NullAndEmptySource; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.boot.test.context.SpringBootTest; 10 | import org.springframework.security.core.authority.AuthorityUtils; 11 | 12 | import java.util.UUID; 13 | 14 | @SpringBootTest(classes = {JwtConfiguration.class, JwtService.class}) 15 | class JwtServiceTest { 16 | @Autowired 17 | JwtService jwtService; 18 | 19 | @ParameterizedTest 20 | @NullAndEmptySource 21 | void shouldReturnFalseWhenTokenIsNullOrEmpty(String token) { 22 | Assertions.assertFalse(jwtService.verify(token)); 23 | } 24 | 25 | @Test 26 | void shouldReturnFalseWhenTokenIsInvalid() { 27 | Assertions.assertFalse(jwtService.verify(UUID.randomUUID().toString())); 28 | } 29 | 30 | @Test 31 | void shouldReturnTrueWhenTokenIsValid() { 32 | Assertions.assertTrue(jwtService.verify(jwtService.create(Data.USER))); 33 | } 34 | 35 | @Test 36 | void shouldNotReturnInvalidSubjectWhenTokenIsValid() { 37 | Assertions.assertNotEquals("Invalid", jwtService.getSubject(jwtService.create(Data.USER))); 38 | } 39 | 40 | @Test 41 | void shouldReturnNonNullSubjectWhenTokenIsValid() { 42 | Assertions.assertNotNull(jwtService.getSubject(jwtService.create(Data.USER))); 43 | } 44 | 45 | @Test 46 | void shouldNotReturnInvalidAuthoritiesWhenTokenIsValid() { 47 | Assertions.assertNotEquals(AuthorityUtils.createAuthorityList("Invalid"), jwtService.getAuthorities(jwtService.create(Data.USER))); 48 | } 49 | 50 | @Test 51 | void shouldReturnCorrectAuthoritiesWhenTokenIsValid() { 52 | Assertions.assertEquals(AuthorityUtils.createAuthorityList("ADMINISTRATOR"), jwtService.getAuthorities(jwtService.create(Data.USER))); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /source/src/test/java/com/company/architecture/aws/AwsIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.aws; 2 | 3 | import com.company.architecture.IntegrationTest; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.mock.web.MockMultipartFile; 6 | 7 | import java.io.ByteArrayInputStream; 8 | 9 | import static org.springframework.http.HttpMethod.GET; 10 | import static org.springframework.http.HttpMethod.POST; 11 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 12 | 13 | class AwsIntegrationTest extends IntegrationTest { 14 | @Test 15 | void shouldReturnCreatedWhenSendingMessage() throws Exception { 16 | perform(POST, "/aws/queues/send", "Message").andExpectAll(status().isCreated()); 17 | } 18 | 19 | @Test 20 | void shouldReturnOkWhenUploadingAndDownloadingFile() throws Exception { 21 | final var file = new MockMultipartFile("file", "Test.txt", "text/plain", new ByteArrayInputStream("Test".getBytes())); 22 | multipart("/aws/files/upload", file).andExpectAll(status().isCreated()); 23 | perform(GET, "/aws/files/download/Test.txt").andExpectAll(status().isOk()); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /source/src/test/java/com/company/architecture/aws/AwsS3ServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.aws; 2 | 3 | import com.company.architecture.LocalStackTest; 4 | import org.junit.jupiter.api.Assertions; 5 | import org.junit.jupiter.api.Test; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.test.context.SpringBootTest; 8 | import org.springframework.mock.web.MockMultipartFile; 9 | 10 | import java.io.ByteArrayInputStream; 11 | import java.io.IOException; 12 | 13 | @SpringBootTest(classes = {AwsConfiguration.class, AwsS3Service.class}) 14 | class AwsS3ServiceTest extends LocalStackTest { 15 | @Autowired 16 | AwsS3Service awsS3Service; 17 | 18 | @Test 19 | void shouldThrowExceptionWhenStoringInInvalidBucket() { 20 | Assertions.assertThrows(Exception.class, () -> awsS3Service.store("INVALID", "123", "Object")); 21 | } 22 | 23 | @Test 24 | void shouldNotThrowExceptionWhenStoringInValidBucket() { 25 | Assertions.assertDoesNotThrow(() -> awsS3Service.store(AwsConfiguration.BUCKET, "123", "Object")); 26 | } 27 | 28 | @Test 29 | void shouldThrowExceptionWhenReadingFromInvalidBucket() { 30 | Assertions.assertThrows(Exception.class, () -> awsS3Service.read("INVALID", "123", String.class)); 31 | } 32 | 33 | @Test 34 | void shouldThrowExceptionWhenReadingWithInvalidKey() { 35 | Assertions.assertThrows(Exception.class, () -> awsS3Service.read(AwsConfiguration.BUCKET, "INVALID", String.class)); 36 | } 37 | 38 | @Test 39 | void shouldNotThrowExceptionWhenReadingFromValidBucketAndKey() { 40 | Assertions.assertDoesNotThrow(() -> awsS3Service.store(AwsConfiguration.BUCKET, "123", "Object")); 41 | Assertions.assertDoesNotThrow(() -> awsS3Service.read(AwsConfiguration.BUCKET, "123", String.class)); 42 | } 43 | 44 | @Test 45 | void shouldNotThrowExceptionWhenUploadingFile() throws IOException { 46 | final var file = new MockMultipartFile("file", "Test.txt", "text/plain", new ByteArrayInputStream("Test".getBytes())); 47 | Assertions.assertDoesNotThrow(() -> awsS3Service.upload(AwsConfiguration.BUCKET, file)); 48 | } 49 | 50 | @Test 51 | void shouldThrowExceptionWhenUploadingBytesToInvalidBucket() { 52 | Assertions.assertThrows(Exception.class, () -> awsS3Service.upload("INVALID", "Test.txt", "Object".getBytes())); 53 | } 54 | 55 | @Test 56 | void shouldThrowExceptionWhenUploadingInvalidBytes() { 57 | Assertions.assertThrows(Exception.class, () -> awsS3Service.upload(AwsConfiguration.BUCKET, "Test.txt", null)); 58 | } 59 | 60 | @Test 61 | void shouldNotThrowExceptionWhenUploadingValidBytes() { 62 | Assertions.assertDoesNotThrow(() -> awsS3Service.upload(AwsConfiguration.BUCKET, "Test.txt", "Object".getBytes())); 63 | } 64 | 65 | @Test 66 | void shouldNotThrowExceptionWhenDownloadingFile() { 67 | Assertions.assertDoesNotThrow(() -> awsS3Service.upload(AwsConfiguration.BUCKET, "Test.txt", "Object".getBytes())); 68 | Assertions.assertDoesNotThrow(() -> awsS3Service.download(AwsConfiguration.BUCKET, "Test.txt")); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /source/src/test/java/com/company/architecture/aws/AwsServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.aws; 2 | 3 | import com.company.architecture.LocalStackTest; 4 | import org.junit.jupiter.api.Assertions; 5 | import org.junit.jupiter.api.Test; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.test.context.SpringBootTest; 8 | import org.springframework.mock.web.MockMultipartFile; 9 | 10 | import java.io.ByteArrayInputStream; 11 | import java.io.IOException; 12 | 13 | @SpringBootTest(classes = {AwsConfiguration.class, AwsSqsService.class, AwsS3Service.class, AwsService.class}) 14 | class AwsServiceTest extends LocalStackTest { 15 | @Autowired 16 | AwsService awsService; 17 | 18 | @Test 19 | void shouldNotThrowExceptionWhenSendingMessage() { 20 | Assertions.assertDoesNotThrow(() -> awsService.send("Message")); 21 | } 22 | 23 | @Test 24 | void shouldNotThrowExceptionWhenListeningForMessage() { 25 | Assertions.assertDoesNotThrow(() -> awsService.listen("Message")); 26 | } 27 | 28 | @Test 29 | void shouldNotThrowExceptionWhenUploadingAndDownloadingFile() throws IOException { 30 | final var file = new MockMultipartFile("file", "Test.txt", "text/plain", new ByteArrayInputStream("Test".getBytes())); 31 | Assertions.assertDoesNotThrow(() -> awsService.upload(file)); 32 | Assertions.assertDoesNotThrow(() -> awsService.download("Test.txt")); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /source/src/test/java/com/company/architecture/aws/AwsSqsServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.aws; 2 | 3 | import com.company.architecture.LocalStackTest; 4 | import org.junit.jupiter.api.Assertions; 5 | import org.junit.jupiter.api.Test; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.test.context.SpringBootTest; 8 | 9 | @SpringBootTest(classes = {AwsConfiguration.class, AwsSqsService.class}) 10 | class AwsSqsServiceTest extends LocalStackTest { 11 | @Autowired 12 | AwsSqsService awsSqsService; 13 | 14 | @Test 15 | void shouldNotThrowExceptionWhenSendingMessageToQueue() { 16 | Assertions.assertDoesNotThrow(() -> awsSqsService.send(AwsConfiguration.QUEUE, "Message")); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /source/src/test/java/com/company/architecture/book/BookControllerTest.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.book; 2 | 3 | import com.company.architecture.ControllerTest; 4 | import com.company.architecture.book.create.CreateBookRequest; 5 | import com.company.architecture.shared.mediator.Mediator; 6 | import org.junit.jupiter.params.ParameterizedTest; 7 | import org.junit.jupiter.params.provider.Arguments; 8 | import org.junit.jupiter.params.provider.MethodSource; 9 | import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; 10 | import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; 11 | import org.springframework.test.context.bean.override.mockito.MockitoBean; 12 | 13 | import java.util.stream.Stream; 14 | 15 | import static org.hamcrest.Matchers.containsString; 16 | import static org.junit.jupiter.params.provider.Arguments.arguments; 17 | import static org.springframework.http.HttpMethod.POST; 18 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; 19 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 20 | 21 | @WebMvcTest(controllers = BookController.class, excludeAutoConfiguration = SecurityAutoConfiguration.class) 22 | class BookControllerTest extends ControllerTest { 23 | @MockitoBean 24 | Mediator mediator; 25 | 26 | private static Stream parameters() { 27 | return Stream.of( 28 | arguments(new CreateBookRequest(null, null), "[author: must not be blank, title: must not be blank]"), 29 | arguments(new CreateBookRequest("", ""), "[author: must not be blank, title: must not be blank]"), 30 | arguments(new CreateBookRequest(null, "Author"), "[title: must not be blank]"), 31 | arguments(new CreateBookRequest("", "Author"), "[title: must not be blank]"), 32 | arguments(new CreateBookRequest("Title", null), "[author: must not be blank]"), 33 | arguments(new CreateBookRequest("Title", ""), "[author: must not be blank]") 34 | ); 35 | } 36 | 37 | @ParameterizedTest 38 | @MethodSource("parameters") 39 | void shouldReturnBadRequestWhenCreatingBook(CreateBookRequest request, String expectedErrors) throws Exception { 40 | perform(POST, "/books", request).andExpectAll(status().isBadRequest()).andExpect(content().string(containsString(expectedErrors))); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /source/src/test/java/com/company/architecture/book/BookIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.book; 2 | 3 | import com.company.architecture.IntegrationTest; 4 | import com.company.architecture.book.create.CreateBookRequest; 5 | import org.junit.jupiter.api.Test; 6 | 7 | import static org.springframework.http.HttpMethod.*; 8 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 9 | 10 | class BookIntegrationTest extends IntegrationTest { 11 | @Test 12 | void shouldReturnBadRequestWhenCreatingBook() throws Exception { 13 | perform(POST, "/books", new CreateBookRequest("", "")).andExpectAll(status().isBadRequest()); 14 | } 15 | 16 | @Test 17 | void shouldReturnCreatedWhenCreatingBook() throws Exception { 18 | perform(POST, "/books", new CreateBookRequest("Title", "Author")).andExpectAll(status().isCreated()); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /source/src/test/java/com/company/architecture/book/CreateBookHandlerMediatorTest.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.book; 2 | 3 | import com.company.architecture.book.create.CreateBookHandler; 4 | import com.company.architecture.book.create.CreateBookRequest; 5 | import com.company.architecture.shared.mediator.Mediator; 6 | 7 | import jakarta.validation.ConstraintViolationException; 8 | import org.junit.jupiter.api.Assertions; 9 | import org.junit.jupiter.api.Test; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.boot.test.context.SpringBootTest; 12 | 13 | import java.util.UUID; 14 | 15 | import static org.junit.jupiter.api.Assertions.assertThrows; 16 | import static org.springframework.http.HttpStatus.CREATED; 17 | 18 | @SpringBootTest(classes = {Mediator.class, CreateBookHandler.class}) 19 | class CreateBookHandlerMediatorTest { 20 | @Autowired 21 | Mediator mediator; 22 | 23 | @Test 24 | void shouldThrowConstraintViolationException() { 25 | final var exception = assertThrows(ConstraintViolationException.class, () -> mediator.handle(new CreateBookRequest("", ""), UUID.class)); 26 | Assertions.assertEquals(2, exception.getConstraintViolations().size()); 27 | } 28 | 29 | @Test 30 | void shouldReturnCreated() { 31 | final var responseEntity = mediator.handle(new CreateBookRequest("Title", "Author"), UUID.class); 32 | Assertions.assertEquals(CREATED, responseEntity.getStatusCode()); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /source/src/test/java/com/company/architecture/book/CreateBookHandlerTest.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.book; 2 | 3 | import com.company.architecture.book.create.CreateBookHandler; 4 | import com.company.architecture.book.create.CreateBookRequest; 5 | import org.junit.jupiter.api.Assertions; 6 | import org.junit.jupiter.api.Test; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | 10 | import static org.springframework.http.HttpStatus.CREATED; 11 | 12 | @SpringBootTest(classes = {CreateBookHandler.class}) 13 | class CreateBookHandlerTest { 14 | @Autowired 15 | CreateBookHandler createBookHandler; 16 | 17 | @Test 18 | void shouldReturnCreated() { 19 | final var responseEntity = createBookHandler.handle(new CreateBookRequest("Title", "Author")); 20 | Assertions.assertEquals(CREATED, responseEntity.getStatusCode()); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /source/src/test/java/com/company/architecture/car/CarServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.car; 2 | 3 | import org.junit.jupiter.api.Assertions; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.test.context.SpringBootTest; 7 | 8 | @SpringBootTest(classes = {CarService.class}) 9 | class CarServiceTest { 10 | @Autowired 11 | CarService carService; 12 | 13 | @Test 14 | void shouldNotThrowExceptionWhenAdding() { 15 | Assertions.assertDoesNotThrow(() -> carService.add(new Car("Brand", "Model"))); 16 | } 17 | 18 | @Test 19 | void shouldNotThrowExceptionWhenListen() { 20 | Assertions.assertDoesNotThrow(() -> carService.listen(new Car("Brand", "Model"))); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /source/src/test/java/com/company/architecture/category/CategoryServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.category; 2 | 3 | import org.junit.jupiter.api.Assertions; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.test.context.SpringBootTest; 7 | 8 | @SpringBootTest(classes = {CategoryService.class, CategoryCacheService.class, CategoryRepository.class}) 9 | class CategoryServiceTest { 10 | @Autowired 11 | CategoryService categoryService; 12 | 13 | @Test 14 | void shouldNotThrowExceptionWhenListingCategories() { 15 | Assertions.assertDoesNotThrow(() -> categoryService.list()); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /source/src/test/java/com/company/architecture/game/GameServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.game; 2 | 3 | import org.junit.jupiter.api.Assertions; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.test.context.SpringBootTest; 7 | 8 | @SpringBootTest(classes = {GameService.class, GameRepository.class}) 9 | class GameServiceTest { 10 | @Autowired 11 | GameService gameService; 12 | 13 | @Test 14 | void shouldReturnGamesMatchingTitleWhenListingGames() { 15 | final var title = "Game A"; 16 | final var games = gameService.list(new Game(title)); 17 | Assertions.assertEquals(title, games.getFirst().title()); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /source/src/test/java/com/company/architecture/game/MockGameServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.game; 2 | 3 | import org.junit.jupiter.api.Assertions; 4 | import org.junit.jupiter.api.Test; 5 | import org.mockito.ArgumentMatchers; 6 | import org.mockito.Mockito; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | import org.springframework.test.context.bean.override.mockito.MockitoBean; 10 | 11 | import java.util.List; 12 | 13 | @SpringBootTest(classes = GameService.class) 14 | class MockGameServiceTest { 15 | @Autowired 16 | GameService gameService; 17 | 18 | @MockitoBean 19 | @Autowired 20 | GameRepository gameRepository; 21 | 22 | @Test 23 | void shouldReturnAllGamesWhenListingWithAnyTitle() { 24 | final var games = List.of(new Game("Game X"), new Game("Game Y"), new Game("Game Z")); 25 | Mockito.when(gameRepository.list(ArgumentMatchers.any())).thenReturn(games); 26 | Assertions.assertEquals(games.size(), gameService.list(new Game("A")).size()); 27 | Assertions.assertEquals(games.size(), gameService.list(new Game("B")).size()); 28 | Assertions.assertEquals(games.size(), gameService.list(new Game("C")).size()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /source/src/test/java/com/company/architecture/group/GroupServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.group; 2 | 3 | import org.junit.jupiter.api.Assertions; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.test.context.SpringBootTest; 7 | 8 | import java.time.LocalDate; 9 | 10 | @SpringBootTest(classes = {GroupService.class}) 11 | class GroupServiceTest { 12 | @Autowired 13 | GroupService groupService; 14 | 15 | @Test 16 | void shouldReturnEmptyGroupWhenGroupingByInvalidProperty() { 17 | final var date = LocalDate.of(2100, 1, 1); 18 | final var person = new Person(); 19 | person.setPassport("12345"); 20 | person.setPassportExpirationDate(date); 21 | person.setDriverLicense("67890"); 22 | person.setDriverLicenseExpirationDate(date); 23 | final var groups = groupService.groupPropertiesBy(person, "Invalid"); 24 | Assertions.assertEquals(0, groups.size()); 25 | } 26 | 27 | @Test 28 | void shouldGroupPropertiesBySameDateCorrectly() { 29 | final var date = LocalDate.of(2100, 1, 1); 30 | final var person = new Person(); 31 | person.setPassport("12345"); 32 | person.setPassportExpirationDate(date); 33 | person.setDriverLicense("67890"); 34 | person.setDriverLicenseExpirationDate(date); 35 | final var groups = groupService.groupPropertiesBy(person, "ExpirationDate"); 36 | final var values = groups.get(date).values().toArray(); 37 | Assertions.assertEquals(1, groups.size()); 38 | Assertions.assertEquals(person.getPassport(), values[0]); 39 | Assertions.assertEquals(person.getDriverLicense(), values[1]); 40 | } 41 | 42 | @Test 43 | void shouldGroupPropertiesByDifferentDatesCorrectly() { 44 | final var person = new Person(); 45 | person.setPassport("12345"); 46 | person.setPassportExpirationDate(LocalDate.of(2100, 1, 1)); 47 | person.setDriverLicense("67890"); 48 | person.setDriverLicenseExpirationDate(LocalDate.of(2200, 1, 1)); 49 | final var groups = groupService.groupPropertiesBy(person, "ExpirationDate"); 50 | Assertions.assertEquals(2, groups.size()); 51 | Assertions.assertEquals(person.getPassport(), groups.get(person.getPassportExpirationDate()).values().toArray()[0]); 52 | Assertions.assertEquals(person.getDriverLicense(), groups.get(person.getDriverLicenseExpirationDate()).values().toArray()[0]); 53 | } 54 | 55 | @Test 56 | void shouldSetPropertiesFromGroupCorrectly() { 57 | final var date = LocalDate.of(2100, 1, 1); 58 | final var person = new Person(); 59 | person.setPassport("ABCDE"); 60 | person.setDriverLicense("FGHIJ"); 61 | final var personUpdate = new Person(); 62 | personUpdate.setPassport("12345"); 63 | personUpdate.setPassportExpirationDate(date); 64 | final var groups = groupService.groupPropertiesBy(personUpdate, "ExpirationDate"); 65 | groups.forEach((_, map) -> groupService.setProperties(person, map)); 66 | Assertions.assertEquals("12345", person.getPassport()); 67 | Assertions.assertEquals("FGHIJ", person.getDriverLicense()); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /source/src/test/java/com/company/architecture/invoice/InvoiceIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.invoice; 2 | 3 | import com.company.architecture.IntegrationTest; 4 | import com.company.architecture.invoice.dtos.AddInvoiceDto; 5 | import com.company.architecture.invoice.dtos.AddInvoiceItemDto; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import java.math.BigDecimal; 9 | import java.time.LocalDateTime; 10 | import java.util.List; 11 | import java.util.UUID; 12 | 13 | import static org.springframework.http.HttpMethod.GET; 14 | import static org.springframework.http.HttpMethod.POST; 15 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 16 | 17 | class InvoiceIntegrationTest extends IntegrationTest { 18 | static AddInvoiceDto addInvoiceDto() { 19 | return new AddInvoiceDto( 20 | UUID.randomUUID().toString(), 21 | LocalDateTime.now(), 22 | InvoiceStatus.DRAFT, 23 | List.of(new AddInvoiceItemDto("Product", 2, BigDecimal.valueOf(250))) 24 | ); 25 | } 26 | 27 | @Test 28 | void shouldReturnOkWhenGettingInvoices() throws Exception { 29 | perform(POST, "/invoices", addInvoiceDto()).andExpectAll(status().isCreated()); 30 | perform(GET, "/invoices").andExpectAll(status().isOk()); 31 | } 32 | 33 | @Test 34 | void shouldReturnCreatedWhenPostingInvoice() throws Exception { 35 | perform(POST, "/invoices", addInvoiceDto()).andExpectAll(status().isCreated()); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /source/src/test/java/com/company/architecture/invoice/InvoiceRepositoryTest.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.invoice; 2 | 3 | import com.company.architecture.PostgreTest; 4 | import com.company.architecture.invoice.entities.Invoice; 5 | import com.company.architecture.invoice.entities.InvoiceItem; 6 | import org.junit.jupiter.api.Assertions; 7 | import org.junit.jupiter.api.RepeatedTest; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | 10 | import java.math.BigDecimal; 11 | import java.util.Set; 12 | 13 | class InvoiceRepositoryTest extends PostgreTest { 14 | @Autowired 15 | InvoiceRepository invoiceRepository; 16 | 17 | @RepeatedTest(2) 18 | void shouldPersistInvoice() { 19 | final var item = InvoiceItem.builder() 20 | .product("Ball") 21 | .quantity(BigDecimal.valueOf(2)) 22 | .unitPrice(BigDecimal.valueOf(100)) 23 | .build(); 24 | 25 | final var invoice = Invoice.builder() 26 | .number("123") 27 | .status(InvoiceStatus.ISSUED) 28 | .build() 29 | .setItems(Set.of(item)); 30 | 31 | final var persistedInvoice = testEntityManager.persist(invoice); 32 | final var originalItem = invoice.getItems().iterator().next(); 33 | final var persistedItem = persistedInvoice.getItems().iterator().next(); 34 | 35 | Assertions.assertEquals(1, persistedInvoice.getId()); 36 | Assertions.assertEquals(invoice.getNumber(), persistedInvoice.getNumber()); 37 | Assertions.assertEquals(invoice.getDateTime(), persistedInvoice.getDateTime()); 38 | Assertions.assertEquals(invoice.getStatus(), persistedInvoice.getStatus()); 39 | 40 | Assertions.assertEquals(originalItem.getProduct(), persistedItem.getProduct()); 41 | Assertions.assertEquals(originalItem.getQuantity(), persistedItem.getQuantity()); 42 | Assertions.assertEquals(originalItem.getUnitPrice(), persistedItem.getUnitPrice()); 43 | Assertions.assertEquals(originalItem.getInvoice(), persistedItem.getInvoice()); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /source/src/test/java/com/company/architecture/location/LocationServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.location; 2 | 3 | import com.company.architecture.shared.services.MapperService; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import org.junit.jupiter.api.Assertions; 6 | import org.junit.jupiter.api.Test; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | 10 | import java.util.ArrayList; 11 | 12 | @SpringBootTest(classes = {ObjectMapper.class, MapperService.class, LocationService.class}) 13 | class LocationServiceTest { 14 | @Autowired 15 | LocationService locationService; 16 | 17 | @Autowired 18 | MapperService mapperService; 19 | 20 | @Test 21 | void shouldReturnCountriesWhenGettingCountriesFromLocations() { 22 | final var locations = new ArrayList(); 23 | locations.add(new FlatLocation(new Location("BR", "Brazil"), new Location("SP", "São Paulo"), new Location("SA", "Santo André"))); 24 | locations.add(new FlatLocation(new Location("US", "United States"), new Location("CA", "California"), new Location("LA", "Los Angeles"))); 25 | locations.add(new FlatLocation(new Location("US", "United States"), new Location("CA", "California"), new Location("SF", "San Francisco"))); 26 | locations.add(new FlatLocation(new Location("US", "United States"), new Location("TX", "Texas"), new Location("HOU", "Houston"))); 27 | final var countries = locationService.getCountries(locations); 28 | Assertions.assertNotNull(countries); 29 | System.out.printf(mapperService.toJson(countries)); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /source/src/test/java/com/company/architecture/movie/MovieIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.movie; 2 | 3 | import com.company.architecture.IntegrationTest; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static org.springframework.http.HttpMethod.POST; 7 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 8 | 9 | class MovieIntegrationTest extends IntegrationTest { 10 | @Test 11 | void shouldReturnOkWhenCreatingMovie() throws Exception { 12 | perform(POST, "/movies", new Movie(0, "Movie")).andExpectAll(status().isOk()); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /source/src/test/java/com/company/architecture/notification/NotificationTest.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.notification; 2 | 3 | import com.company.architecture.KafkaTest; 4 | import org.apache.kafka.clients.producer.ProducerConfig; 5 | import org.apache.kafka.common.serialization.StringSerializer; 6 | import org.junit.jupiter.api.Assertions; 7 | import org.junit.jupiter.api.Test; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.boot.test.context.SpringBootTest; 10 | import org.springframework.context.annotation.Bean; 11 | import org.springframework.context.annotation.Import; 12 | import org.springframework.kafka.core.DefaultKafkaProducerFactory; 13 | import org.springframework.kafka.core.KafkaTemplate; 14 | import org.springframework.kafka.support.serializer.JsonSerializer; 15 | 16 | import java.util.HashMap; 17 | 18 | @SpringBootTest(classes = {NotificationService.class}) 19 | @Import(NotificationTest.Configuration.class) 20 | class NotificationTest extends KafkaTest { 21 | @Autowired 22 | NotificationService notificationService; 23 | 24 | @Test 25 | void shouldNotThrowExceptionWhenSendingNotification() { 26 | Assertions.assertDoesNotThrow(() -> notificationService.send(new Notification("Message"))); 27 | } 28 | 29 | static class Configuration { 30 | @Bean 31 | public KafkaTemplate kafkaTemplate() { 32 | final var configs = new HashMap(); 33 | configs.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, kafka.getBootstrapServers()); 34 | configs.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class); 35 | configs.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class); 36 | final var factory = new DefaultKafkaProducerFactory(configs); 37 | return new KafkaTemplate<>(factory); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /source/src/test/java/com/company/architecture/payment/PaymentServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.payment; 2 | 3 | import org.junit.jupiter.api.Assertions; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.test.context.SpringBootTest; 7 | 8 | @SpringBootTest(classes = {PaymentService.class, CreditCardPaymentStrategy.class, DebitCardPaymentStrategy.class}) 9 | class PaymentServiceTest { 10 | @Autowired 11 | PaymentService paymentService; 12 | 13 | @Test 14 | void shouldReturnCreditCard() { 15 | Assertions.assertEquals(CreditCardPaymentStrategy.class.getSimpleName(), paymentService.process(PaymentMethod.CREDIT_CARD)); 16 | } 17 | 18 | @Test 19 | void shouldReturnDebitCard() { 20 | Assertions.assertEquals(DebitCardPaymentStrategy.class.getSimpleName(), paymentService.process(PaymentMethod.DEBIT_CARD)); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /source/src/test/java/com/company/architecture/product/ProductControllerTest.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.product; 2 | 3 | import com.company.architecture.ControllerTest; 4 | import org.junit.jupiter.params.ParameterizedTest; 5 | import org.junit.jupiter.params.provider.Arguments; 6 | import org.junit.jupiter.params.provider.MethodSource; 7 | import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; 8 | import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; 9 | import org.springframework.test.context.bean.override.mockito.MockitoBean; 10 | 11 | import java.util.stream.Stream; 12 | 13 | import static org.junit.jupiter.params.provider.Arguments.arguments; 14 | import static org.springframework.http.HttpMethod.GET; 15 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; 16 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 17 | 18 | @WebMvcTest(controllers = ProductController.class, excludeAutoConfiguration = SecurityAutoConfiguration.class) 19 | class ProductControllerTest extends ControllerTest { 20 | @MockitoBean 21 | ProductService productService; 22 | 23 | private static Stream parameters() { 24 | return Stream.of( 25 | arguments("?page=-1", "[page: must be greater than or equal to 0]"), 26 | arguments("?page=9999999999", "[page: must be valid]"), 27 | arguments("?page=X", "[page: must be valid]"), 28 | arguments("?size=0", "[size: must be greater than 0]"), 29 | arguments("?size=9999999999", "[size: must be valid]"), 30 | arguments("?size=X", "[size: must be valid]"), 31 | arguments("?direction=X", "[direction: must be valid]"), 32 | arguments("/1", "[id: must be valid]"), 33 | arguments("/X", "[id: must be valid]") 34 | ); 35 | } 36 | 37 | @ParameterizedTest 38 | @MethodSource("parameters") 39 | void shouldReturnBadRequestWhenGettingProducts(String uri, String message) throws Exception { 40 | perform(GET, "/products" + uri).andExpectAll(status().isBadRequest(), content().string(message)); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /source/src/test/java/com/company/architecture/product/ProductIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.product; 2 | 3 | import com.company.architecture.IntegrationTest; 4 | import com.company.architecture.product.dtos.AddProductDto; 5 | import com.company.architecture.product.dtos.UpdateProductDto; 6 | import com.company.architecture.shared.Data; 7 | import com.company.architecture.shared.dtos.PageableDto; 8 | import org.junit.jupiter.api.Assertions; 9 | import org.junit.jupiter.api.Test; 10 | import org.junit.jupiter.params.ParameterizedTest; 11 | import org.junit.jupiter.params.provider.ValueSource; 12 | 13 | import static org.springframework.http.HttpMethod.*; 14 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; 15 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 16 | 17 | class ProductIntegrationTest extends IntegrationTest { 18 | @ParameterizedTest 19 | @ValueSource(strings = {"?description=inexistent", "/" + Data.ID_INEXISTENT}) 20 | void shouldReturnNotFoundWhenGettingProducts(String uri) throws Exception { 21 | perform(GET, "/products" + uri).andExpectAll(status().isNotFound()); 22 | } 23 | 24 | @ParameterizedTest 25 | @ValueSource(strings = {"page=0", "size=1", "sort=id", "sort=description", "direction=ASC", "direction=DESC", "description=Product"}) 26 | void shouldReturnOkWhenGettingProducts(String uri) throws Exception { 27 | mongoTemplate.save(Data.PRODUCT); 28 | perform(GET, "/products?" + uri).andExpectAll( 29 | status().isOk(), 30 | jsonPath("$.content").isArray(), 31 | jsonPath("$.content.length()").value(1), 32 | jsonPath("$.content[0].id").value(Data.PRODUCT.getId().toString()), 33 | jsonPath("$.content[0].description").value(Data.PRODUCT.getDescription()), 34 | jsonPath("$.content[0].price").value(Data.PRODUCT.getPrice()), 35 | jsonPath("$.page.number").value(0), 36 | jsonPath("$.page.totalElements").value(1), 37 | jsonPath("$.page.totalPages").value(1) 38 | ); 39 | } 40 | 41 | @Test 42 | void shouldReturnOkWhenGettingProductById() throws Exception { 43 | mongoTemplate.save(Data.PRODUCT); 44 | perform(GET, "/products/" + Data.PRODUCT.getId(), null).andExpectAll( 45 | status().isOk(), 46 | jsonPath("$.id").value(Data.PRODUCT.getId().toString()), 47 | jsonPath("$.description").value(Data.PRODUCT.getDescription()), 48 | jsonPath("$.price").value(Data.PRODUCT.getPrice()) 49 | ); 50 | } 51 | 52 | @Test 53 | void shouldReturnCreatedWhenCreatingProduct() throws Exception { 54 | perform(POST, "/products", mapperService.map(Data.PRODUCT, AddProductDto.class)).andExpectAll(status().isCreated()); 55 | } 56 | 57 | @Test 58 | void shouldReturnOkWhenUpdatingProduct() throws Exception { 59 | mongoTemplate.save(Data.PRODUCT); 60 | perform(PUT, "/products/" + Data.PRODUCT.getId(), mapperService.map(Data.PRODUCT_UPDATE, UpdateProductDto.class)).andExpectAll(status().isOk()); 61 | final var product = mongoTemplate.findById(Data.PRODUCT.getId(), Product.class); 62 | Assertions.assertNotNull(product); 63 | Assertions.assertEquals(Data.PRODUCT_UPDATE.getDescription(), product.getDescription()); 64 | Assertions.assertEquals(Data.PRODUCT_UPDATE.getPrice(), product.getPrice()); 65 | } 66 | 67 | @Test 68 | void shouldReturnNotFoundWhenUpdatingPrice() throws Exception { 69 | perform(PATCH, "/products/%s/price/100".formatted(Data.ID_INEXISTENT)).andExpectAll(status().isNotFound()); 70 | } 71 | 72 | @Test 73 | void shouldReturnOkWhenUpdatingPrice() throws Exception { 74 | mongoTemplate.save(Data.PRODUCT); 75 | perform(PATCH, "/products/%s/price/%s".formatted(Data.PRODUCT.getId(), Data.PRODUCT_UPDATE.getPrice()), null).andExpectAll(status().isOk()); 76 | final var product = mongoTemplate.findById(Data.PRODUCT.getId(), Product.class); 77 | Assertions.assertNotNull(product); 78 | Assertions.assertEquals(Data.PRODUCT.getDescription(), product.getDescription()); 79 | Assertions.assertEquals(Data.PRODUCT_UPDATE.getPrice(), product.getPrice()); 80 | } 81 | 82 | @ParameterizedTest 83 | @ValueSource(strings = {Data.ID, Data.ID_INEXISTENT}) 84 | void shouldReturnOkWhenDeletingProduct(String id) throws Exception { 85 | mongoTemplate.save(Data.PRODUCT); 86 | perform(DELETE, "/products/" + id).andExpectAll(status().isOk()); 87 | Assertions.assertNull(mongoTemplate.findById(id, Product.class)); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /source/src/test/java/com/company/architecture/product/ProductRepositoryTest.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.product; 2 | 3 | import com.company.architecture.MongoTest; 4 | import org.junit.jupiter.api.Assertions; 5 | import org.junit.jupiter.api.Test; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | 8 | import java.math.BigDecimal; 9 | import java.util.UUID; 10 | 11 | class ProductRepositoryTest extends MongoTest { 12 | @Autowired 13 | ProductRepository productRepository; 14 | 15 | @Test 16 | void shouldPersistProduct() { 17 | mongoTemplate.save(new Product(UUID.randomUUID(), "Ball", BigDecimal.valueOf(100))); 18 | Assertions.assertFalse(productRepository.findAll().isEmpty()); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /source/src/test/java/com/company/architecture/shared/Color.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.shared; 2 | 3 | public enum Color { 4 | RED, 5 | GREEN, 6 | BLUE 7 | } 8 | -------------------------------------------------------------------------------- /source/src/test/java/com/company/architecture/shared/Data.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.shared; 2 | 3 | import com.company.architecture.auth.Authority; 4 | import com.company.architecture.product.Product; 5 | import com.company.architecture.user.User; 6 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 7 | 8 | import java.math.BigDecimal; 9 | import java.util.List; 10 | import java.util.UUID; 11 | 12 | public class Data { 13 | public static final String ID = "AA8B9189-5220-4345-9230-6301765EA5A6"; 14 | public static final String ID_INEXISTENT = "D095635D-249C-469C-BA1E-6D78F940177C"; 15 | public static final String PASSWORD = "123456"; 16 | public static final String PASSWORD_ENCODED = new BCryptPasswordEncoder().encode(PASSWORD); 17 | public static final List AUTHORITIES = List.of(Authority.ADMINISTRATOR); 18 | public static final User USER = new User(UUID.fromString(ID), "User", "user@mail.com", "user", PASSWORD_ENCODED, AUTHORITIES); 19 | public static final User USER_UPDATE = new User(UUID.fromString(ID), "User Update", "user.update@mail.com", "user", PASSWORD_ENCODED, AUTHORITIES); 20 | public static final User USER_CONFLICT = new User(UUID.randomUUID(), "User Conflict", "user@mail.com", "user", PASSWORD_ENCODED, AUTHORITIES); 21 | public static final Product PRODUCT = new Product(UUID.fromString(ID), "Product", BigDecimal.valueOf(100L)); 22 | public static final Product PRODUCT_UPDATE = new Product(UUID.fromString(ID), "Product Update", BigDecimal.valueOf(200L)); 23 | } 24 | -------------------------------------------------------------------------------- /source/src/test/java/com/company/architecture/shared/Dto.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.shared; 2 | 3 | import java.math.BigDecimal; 4 | import java.time.LocalDate; 5 | import java.util.UUID; 6 | 7 | public record Dto(UUID uuid, String string, BigDecimal bigDecimal, LocalDate date, Color color) { 8 | } 9 | -------------------------------------------------------------------------------- /source/src/test/java/com/company/architecture/shared/Entity.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.shared; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | import java.math.BigDecimal; 8 | import java.time.LocalDate; 9 | import java.util.UUID; 10 | 11 | @NoArgsConstructor 12 | @AllArgsConstructor 13 | @Data 14 | public class Entity { 15 | private UUID uuid; 16 | private String string; 17 | private BigDecimal bigDecimal; 18 | private LocalDate date; 19 | private Color color; 20 | } 21 | -------------------------------------------------------------------------------- /source/src/test/java/com/company/architecture/shared/services/MapperServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.shared.services; 2 | 3 | import com.company.architecture.shared.Color; 4 | import com.company.architecture.shared.Dto; 5 | import com.company.architecture.shared.Entity; 6 | import com.company.architecture.shared.configurations.JacksonConfiguration; 7 | import com.jayway.jsonpath.JsonPath; 8 | import org.junit.jupiter.api.Assertions; 9 | import org.junit.jupiter.api.Test; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.boot.test.context.SpringBootTest; 12 | import org.springframework.data.domain.Page; 13 | import org.springframework.data.domain.PageImpl; 14 | import org.springframework.data.domain.PageRequest; 15 | 16 | import java.math.BigDecimal; 17 | import java.time.LocalDate; 18 | import java.util.Collections; 19 | import java.util.List; 20 | import java.util.UUID; 21 | 22 | @SpringBootTest(classes = {JacksonConfiguration.class, MapperService.class}) 23 | class MapperServiceTest { 24 | @Autowired 25 | MapperService mapperService; 26 | 27 | @Test 28 | void shouldReturnNullWhenMappingNullSourceToDto() { 29 | Assertions.assertNull(mapperService.map(null, Dto.class)); 30 | } 31 | 32 | @Test 33 | void shouldReturnEqualObjectWhenMappingNonNullSourceToDto() { 34 | final var entity = getEntity(); 35 | final var dto = mapperService.map(entity, Dto.class); 36 | assertEquals(entity, dto); 37 | } 38 | 39 | @Test 40 | void shouldReturnEmptyListWhenMappingNullSourceToList() { 41 | Assertions.assertEquals(Collections.emptyList(), mapperService.mapList(null, Dto.class)); 42 | } 43 | 44 | @Test 45 | void shouldReturnEqualListWhenMappingNonNullSourceToList() { 46 | final var entity = getEntity(); 47 | final var dtos = mapperService.mapList(List.of(entity), Dto.class); 48 | assertEquals(entity, dtos.getFirst()); 49 | } 50 | 51 | @Test 52 | void shouldReturnEmptyWhenMappingNullSourceToPage() { 53 | Assertions.assertEquals(Page.empty(), mapperService.mapPage(null, Dto.class)); 54 | } 55 | 56 | @Test 57 | void shouldReturnEqualPageWhenMappingNonNullSourceToPage() { 58 | final var expected = new PageImpl<>(List.of(getEntity()), PageRequest.of(0, 10), 1); 59 | final var actual = mapperService.mapPage(expected, Dto.class); 60 | 61 | Assertions.assertEquals(1, expected.getContent().size()); 62 | Assertions.assertEquals(expected.getContent().getFirst().getUuid(), actual.getContent().getFirst().uuid()); 63 | Assertions.assertEquals(expected.getContent().getFirst().getString(), actual.getContent().getFirst().string()); 64 | Assertions.assertEquals(expected.getContent().getFirst().getBigDecimal(), actual.getContent().getFirst().bigDecimal()); 65 | Assertions.assertEquals(expected.getContent().getFirst().getDate(), actual.getContent().getFirst().date()); 66 | Assertions.assertEquals(expected.getContent().getFirst().getColor(), actual.getContent().getFirst().color()); 67 | Assertions.assertEquals(expected.getTotalElements(), actual.getTotalElements()); 68 | Assertions.assertEquals(expected.getNumber(), actual.getNumber()); 69 | } 70 | 71 | @Test 72 | void shouldReturnNullWhenConvertingNullSourceToJson() { 73 | Assertions.assertNull(mapperService.toJson(null)); 74 | } 75 | 76 | @Test 77 | void shouldReturnNotNullWhenConvertingNonNullSourceToJson() { 78 | final var expected = getEntity(); 79 | final var actual = mapperService.toJson(expected); 80 | Assertions.assertEquals(expected.getUuid().toString(), JsonPath.read(actual, "$.uuid").toString()); 81 | Assertions.assertEquals(expected.getString(), JsonPath.read(actual, "$.string").toString()); 82 | Assertions.assertEquals(expected.getBigDecimal().toString(), JsonPath.read(actual, "$.bigDecimal").toString()); 83 | Assertions.assertEquals(expected.getDate().toString(), JsonPath.read(actual, "$.date").toString()); 84 | Assertions.assertEquals(expected.getColor().toString(), JsonPath.read(actual, "$.color").toString()); 85 | } 86 | 87 | @Test 88 | void shouldReturnNullWhenConvertingNullJsonToEntity() { 89 | Assertions.assertNull(mapperService.fromJson(null, Entity.class)); 90 | } 91 | 92 | @Test 93 | void shouldReturnEqualEntityWhenConvertingNonNullJsonToEntity() { 94 | final var expected = getEntity(); 95 | final var actual = mapperService.fromJson(mapperService.toJson(expected), Entity.class); 96 | 97 | Assertions.assertEquals(expected.getUuid(), actual.getUuid()); 98 | Assertions.assertEquals(expected.getString(), actual.getString()); 99 | Assertions.assertEquals(expected.getBigDecimal(), actual.getBigDecimal()); 100 | Assertions.assertEquals(expected.getDate(), actual.getDate()); 101 | Assertions.assertEquals(expected.getColor(), actual.getColor()); 102 | } 103 | 104 | private static Entity getEntity() { 105 | return new Entity(UUID.randomUUID(), "Description", BigDecimal.valueOf(100), LocalDate.now(), Color.RED); 106 | } 107 | 108 | private static void assertEquals(Entity entity, Dto dto) { 109 | Assertions.assertEquals(entity.getUuid(), dto.uuid()); 110 | Assertions.assertEquals(entity.getString(), dto.string()); 111 | Assertions.assertEquals(entity.getBigDecimal(), dto.bigDecimal()); 112 | Assertions.assertEquals(entity.getDate(), dto.date()); 113 | Assertions.assertEquals(entity.getColor(), dto.color()); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /source/src/test/java/com/company/architecture/shared/services/MessageServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.shared.services; 2 | 3 | import org.junit.jupiter.api.Assertions; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.context.support.ResourceBundleMessageSource; 7 | 8 | @SpringBootTest(classes = {ResourceBundleMessageSource.class, MessageService.class}) 9 | class MessageServiceTest { 10 | @Test 11 | void shouldReturnExpectedMessageWhenGettingMessageByKey() { 12 | Assertions.assertEquals("Test", MessageService.get("Test")); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /source/src/test/java/com/company/architecture/shared/services/ValidatorServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.shared.services; 2 | 3 | import org.junit.jupiter.api.Assertions; 4 | import org.junit.jupiter.api.Test; 5 | import org.junit.jupiter.params.ParameterizedTest; 6 | import org.junit.jupiter.params.provider.CsvSource; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | 10 | @SpringBootTest(classes = {ValidatorService.class}) 11 | class ValidatorServiceTest { 12 | @Autowired 13 | ValidatorService validatorService; 14 | 15 | @Test 16 | void shouldReturnNotNullWhenValidatingNonNullInput() { 17 | Assertions.assertNotNull(validatorService.validate("Test")); 18 | } 19 | 20 | @ParameterizedTest 21 | @CsvSource(delimiter = '|', value = { 22 | "000.000.000-00 | false", 23 | "012.345.678-99 | false", 24 | "098.765.432-10 | false", 25 | "109.876.543-21 | false", 26 | "111.111.111-11 | false", 27 | "111.222.333-44 | false", 28 | "123.456.789-00 | false", 29 | "210.987.654-32 | false", 30 | "222.222.222-22 | false", 31 | "222.333.444-55 | false", 32 | "321.098.765-43 | false", 33 | "333.333.333-33 | false", 34 | "333.444.555-66 | false", 35 | "432.109.876-54 | false", 36 | "444.444.444-44 | false", 37 | "444.555.666-77 | false", 38 | "543.210.987-65 | false", 39 | "555.555.555-55 | false", 40 | "555.666.777-88 | false", 41 | "654.321.098-76 | false", 42 | "666.666.666-66 | false", 43 | "666.777.888-99 | false", 44 | "765.432.109-87 | false", 45 | "777.777.777-77 | false", 46 | "777.888.999-00 | false", 47 | "876.543.210-98 | false", 48 | "888.888.888-88 | false", 49 | "888.999.000-11 | false", 50 | "987.654.321-09 | false", 51 | "999.999.999-99 | false", 52 | "019.369.987-77 | true", 53 | "099.994.960-83 | true", 54 | "119.250.043-17 | true", 55 | "123.456.789-09 | true", 56 | "124.977.481-01 | true", 57 | "186.204.852-53 | true", 58 | "209.849.468-88 | true", 59 | "252.912.234-21 | true", 60 | "258.531.001-90 | true", 61 | "268.366.204-16 | true", 62 | "373.665.374-38 | true", 63 | "375.770.937-34 | true", 64 | "425.941.647-20 | true", 65 | "426.239.429-86 | true", 66 | "434.373.227-45 | true", 67 | "445.932.090-80 | true", 68 | "447.203.862-53 | true", 69 | "448.704.322-00 | true", 70 | "459.382.535-00 | true", 71 | "460.941.584-40 | true", 72 | "465.653.828-08 | true", 73 | "469.728.692-85 | true", 74 | "500.555.100-00 | true", 75 | "525.582.629-47 | true", 76 | "584.703.579-99 | true", 77 | "652.190.061-77 | true", 78 | "655.360.304-93 | true", 79 | "668.745.850-70 | true", 80 | "678.202.816-69 | true", 81 | "685.632.673-45 | true", 82 | "688.669.898-27 | true", 83 | "702.591.498-37 | true", 84 | "706.506.942-79 | true", 85 | "712.257.404-01 | true", 86 | "722.521.918-99 | true", 87 | "727.797.500-65 | true", 88 | "739.485.124-93 | true", 89 | "760.695.875-02 | true", 90 | "773.121.062-69 | true", 91 | "786.008.411-27 | true", 92 | "796.587.368-07 | true", 93 | "808.841.252-89 | true", 94 | "814.506.847-93 | true", 95 | "850.741.787-62 | true", 96 | "875.375.009-83 | true", 97 | "882.226.664-10 | true", 98 | "890.049.917-35 | true", 99 | "924.006.403-60 | true", 100 | "942.022.943-27 | true", 101 | "959.596.181-76 | true" 102 | }) 103 | void shouldReturnCorrectValidationResultForCpf(String value, boolean valid) { 104 | Assertions.assertEquals(valid, validatorService.validateCpf(value)); 105 | } 106 | 107 | @ParameterizedTest 108 | @CsvSource(delimiter = '|', value = { 109 | "00.111.222/0001-38 | false", 110 | "01.234.567/0001-21 | false", 111 | "12.345.678/0001-91 | false", 112 | "23.456.789/0001-75 | false", 113 | "33.444.555/0001-67 | false", 114 | "45.678.901/0001-44 | false", 115 | "55.666.777/0001-51 | false", 116 | "67.890.123/0001-03 | false", 117 | "89.012.345/0001-62 | false", 118 | "99.000.111/0001-53 | false", 119 | "30.054.757/0001-29 | true", 120 | "47.643.621/0001-57 | true", 121 | "57.863.764/0001-28 | true", 122 | "58.524.873/0001-83 | true", 123 | "65.844.157/0001-49 | true", 124 | "67.072.607/0001-58 | true", 125 | "68.283.473/0001-87 | true", 126 | "74.730.115/0001-78 | true", 127 | "77.516.151/0001-21 | true", 128 | "80.373.001/0001-10 | true" 129 | }) 130 | void shouldReturnCorrectValidationResultForCnpj(String value, boolean valid) { 131 | Assertions.assertEquals(valid, validatorService.validateCnpj(value)); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /source/src/test/java/com/company/architecture/user/UserControllerTest.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.user; 2 | 3 | import com.company.architecture.ControllerTest; 4 | import org.junit.jupiter.params.ParameterizedTest; 5 | import org.junit.jupiter.params.provider.Arguments; 6 | import org.junit.jupiter.params.provider.MethodSource; 7 | import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; 8 | import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; 9 | import org.springframework.test.context.bean.override.mockito.MockitoBean; 10 | 11 | import java.util.stream.Stream; 12 | 13 | import static org.junit.jupiter.params.provider.Arguments.arguments; 14 | import static org.springframework.http.HttpMethod.GET; 15 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; 16 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 17 | 18 | @WebMvcTest(controllers = UserController.class, excludeAutoConfiguration = SecurityAutoConfiguration.class) 19 | class UserControllerTest extends ControllerTest { 20 | @MockitoBean 21 | UserService userService; 22 | 23 | private static Stream parameters() { 24 | return Stream.of( 25 | arguments("?page=-1", "[page: must be greater than or equal to 0]"), 26 | arguments("?page=9999999999", "[page: must be valid]"), 27 | arguments("?page=X", "[page: must be valid]"), 28 | arguments("?size=0", "[size: must be greater than 0]"), 29 | arguments("?size=9999999999", "[size: must be valid]"), 30 | arguments("?size=X", "[size: must be valid]"), 31 | arguments("?direction=X", "[direction: must be valid]"), 32 | arguments("/1", "[id: must be valid]"), 33 | arguments("/X", "[id: must be valid]") 34 | ); 35 | } 36 | 37 | @ParameterizedTest 38 | @MethodSource("parameters") 39 | void shouldReturnBadRequestWhenGettingUsers(String uri, String message) throws Exception { 40 | perform(GET, "/users" + uri).andExpectAll(status().isBadRequest(), content().string(message)); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /source/src/test/java/com/company/architecture/user/UserIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package com.company.architecture.user; 2 | 3 | import com.company.architecture.IntegrationTest; 4 | import com.company.architecture.shared.Data; 5 | import com.company.architecture.shared.dtos.PageableDto; 6 | import com.company.architecture.user.dtos.AddUserDto; 7 | import com.company.architecture.user.dtos.UpdateUserDto; 8 | import org.junit.jupiter.api.Assertions; 9 | import org.junit.jupiter.api.Test; 10 | import org.junit.jupiter.params.ParameterizedTest; 11 | import org.junit.jupiter.params.provider.ValueSource; 12 | 13 | import static org.springframework.http.HttpMethod.*; 14 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; 15 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 16 | 17 | class UserIntegrationTest extends IntegrationTest { 18 | @ParameterizedTest 19 | @ValueSource(strings = {"?name=inexistent", "/" + Data.ID_INEXISTENT}) 20 | void shouldReturnNotFoundWhenGettingUsers(String uri) throws Exception { 21 | perform(GET, "/users" + uri).andExpectAll(status().isNotFound()); 22 | } 23 | 24 | @ParameterizedTest 25 | @ValueSource(strings = {"page=0", "size=1", "sort=id", "sort=name", "direction=ASC", "direction=DESC", "name=User"}) 26 | void shouldReturnOkWhenGettingUsers(String uri) throws Exception { 27 | mongoTemplate.save(Data.USER); 28 | perform(GET, "/users?" + uri).andExpectAll( 29 | status().isOk(), 30 | jsonPath("$.content").isArray(), 31 | jsonPath("$.content.length()").value(1), 32 | jsonPath("$.content[0].id").value(Data.USER.getId().toString()), 33 | jsonPath("$.content[0].name").value(Data.USER.getName()), 34 | jsonPath("$.content[0].email").value(Data.USER.getEmail()), 35 | jsonPath("$.page.number").value(0), 36 | jsonPath("$.page.totalElements").value(1), 37 | jsonPath("$.page.totalPages").value(1) 38 | ); 39 | } 40 | 41 | @Test 42 | void shouldReturnOkWhenGettingUserById() throws Exception { 43 | mongoTemplate.save(Data.USER); 44 | perform(GET, "/users/" + Data.USER.getId()).andExpectAll( 45 | status().isOk(), 46 | jsonPath("$.id").value(Data.USER.getId().toString()), 47 | jsonPath("$.name").value(Data.USER.getName()), 48 | jsonPath("$.email").value(Data.USER.getEmail()) 49 | ); 50 | } 51 | 52 | @Test 53 | void shouldReturnConflictWhenCreatingUser() throws Exception { 54 | mongoTemplate.save(Data.USER); 55 | perform(POST, "/users", mapperService.map(Data.USER, AddUserDto.class)).andExpectAll(status().isConflict()); 56 | } 57 | 58 | @Test 59 | void shouldReturnCreatedWhenCreatingUser() throws Exception { 60 | perform(POST, "/users", mapperService.map(Data.USER, AddUserDto.class)).andExpectAll(status().isCreated()); 61 | } 62 | 63 | @Test 64 | void shouldReturnConflictWhenUpdatingUser() throws Exception { 65 | mongoTemplate.save(Data.USER); 66 | mongoTemplate.save(Data.USER_CONFLICT); 67 | perform(PUT, "/users/" + Data.USER.getId(), mapperService.map(Data.USER_CONFLICT, UpdateUserDto.class)).andExpectAll(status().isConflict()); 68 | } 69 | 70 | @Test 71 | void shouldReturnOkWhenUpdatingUser() throws Exception { 72 | mongoTemplate.save(Data.USER); 73 | perform(PUT, "/users/" + Data.USER.getId(), mapperService.map(Data.USER_UPDATE, UpdateUserDto.class)).andExpectAll(status().isOk()); 74 | final var user = mongoTemplate.findById(Data.USER.getId(), User.class); 75 | Assertions.assertNotNull(user); 76 | Assertions.assertEquals(Data.USER_UPDATE.getName(), user.getName()); 77 | Assertions.assertEquals(Data.USER_UPDATE.getEmail(), user.getEmail()); 78 | } 79 | 80 | @ParameterizedTest 81 | @ValueSource(strings = {Data.ID, Data.ID_INEXISTENT}) 82 | void shouldReturnOkWhenDeletingUser(String id) throws Exception { 83 | mongoTemplate.save(Data.USER); 84 | perform(DELETE, "/users/" + id).andExpectAll(status().isOk()); 85 | Assertions.assertNull(mongoTemplate.findById(id, User.class)); 86 | } 87 | } 88 | --------------------------------------------------------------------------------