From 72e4428d842c6c1aff214589c7b38b6c2dfa599f Mon Sep 17 00:00:00 2001 From: Sergey gordeichuk Date: Tue, 19 Mar 2024 10:14:40 +0200 Subject: [PATCH 1/7] Added properties to pom.xml and conection to MySQL in /main/resources/application.properties. --- README.md | 15 +++++ pom.xml | 73 +++++++++++++++++++++++ src/main/resources/application.properties | 8 +++ src/test/resources/application.properties | 3 + 4 files changed, 99 insertions(+) diff --git a/README.md b/README.md index 4815bd91..4fd65238 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ - Task: Create API. It should contain two methods: 1. The request randomly generates a wiki about one character in the universe the animated series Rick & Morty. + Запит випадковим чином генерує вікі про одного персонажа всесвіту мультсеріалу «Рік і Морті». Response example: ```json @@ -19,16 +20,24 @@ NOTE: `externalId` field should save the original character ID received from the external API. `id` field should represent the identifier of entire `Character` entity, that is associated with internal DB. + ПРИМІТКА: поле `externalId` має зберігати вихідний ідентифікатор символу, отриманий із зовнішнього API. Поле `id` + має представляти ідентифікатор усієї сутності `Character`, яка пов'язана з внутрішньою БД. 2. The request takes a string as an argument, and returns a list of all characters whose name contains the search string. + Запит приймає рядок як аргумент і повертає список усіх символів, ім’я яких містить пошуковий рядок. + During the application start, the web application downloads data from a third-party service to the internal database. + Під час запуску програми веб-програма завантажує дані зі стороннього сервісу у внутрішню базу даних. + Implemented API requests must work with a local database (i.e. fetch data from a database). + Реалізовані запити API мають працювати з локальною базою даних (тобто отримувати дані з бази даних). - What to use: 1. You must use [public API](https://rickandmortyapi.com/documentation/#rest) (you should use REST API). 2. All data from the public API should be fetched once, and only once, when the Application context is created + Усі дані з загальнодоступного API слід отримати один раз і лише один раз під час створення контексту програми ### Tech Requirements @@ -39,3 +48,9 @@ and `src/test/resources/application.properties` files. In other case you may face a problem with Application Context creation during the `mvn test` phase. - Requests must be documented using Swagger. +- Використовуйте базу даних MySQL у своїй програмі. +- Використовуйте H2 DB у своїй тестовій конфігурації (її вже налаштовано у файлі`src/test/resources/application.properties`). +- Зберігайте ідентичний набір параметрів у файлах `src/main/resources/application.properties` +- і `src/test/resources/application.properties`. В іншому випадку ви можете зіткнутися з проблемою +- створення контексту програми під час фази `mvn test`. +- Запити мають бути задокументовані за допомогою Swagger. \ No newline at end of file diff --git a/pom.xml b/pom.xml index 0c754f19..7de873b1 100644 --- a/pom.xml +++ b/pom.xml @@ -15,6 +15,8 @@ jv-rick-and-morty 17 + 0.2.0 + 1.5.5.Final 3.1.1 https://raw.githubusercontent.com/mate-academy/style-guides/master/java/checkstyle.xml @@ -25,12 +27,46 @@ org.springframework.boot spring-boot-starter + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-validation + + + org.mapstruct + mapstruct + ${mapstruct.version} + + + + org.hibernate.validator + hibernate-validator + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + 2.1.0 + org.springframework.boot spring-boot-starter-test test + + com.mysql + mysql-connector-j + runtime + + + + org.projectlombok + lombok + true + org.springframework.boot @@ -45,6 +81,43 @@ + + org.apache.maven.plugins + maven-compiler-plugin + + ${java.version} + ${java.version} + + + org.projectlombok + lombok + ${lombok.version} + + + org.projectlombok + lombok-mapstruct-binding + ${lombok.mapstruct.binding.version} + + + org.mapstruct + mapstruct-processor + ${mapstruct.version} + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + org.springframework.boot spring-boot-maven-plugin diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 8b137891..ccaa92b0 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1,9 @@ +spring.datasource.url=jdbc:mysql://localhost:3306/test?serverTimezone=UTC +spring.datasource.username=root +spring.datasource.password=3052821794gsv +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver +spring.jpa.hibernate.ddl-auto=create-drop +spring.jpa.show-sql=true +spring.jpa.open-in-view=false +spring.jooq.sql-dialect=org.hibernate.dialect.MySQLDialect diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties index bc2fdde8..f2c24a4c 100644 --- a/src/test/resources/application.properties +++ b/src/test/resources/application.properties @@ -3,3 +3,6 @@ spring.datasource.driverClassName=org.h2.Driver spring.datasource.username=sa spring.datasource.password=password spring.jpa.database-platform=org.hibernate.dialect.H2Dialect + +spring.h2.console.enabled=true +spring.jpa.open-in-view=false From 533453d5184dddd8b290977cf9a001e3731d2807 Mon Sep 17 00:00:00 2001 From: Sergey gordeichuk Date: Tue, 19 Mar 2024 11:25:28 +0200 Subject: [PATCH 2/7] Created Character.class, Gender.class and Status.class --- .../academy/rickandmorty/model/Character.java | 21 +++++++++++++++++++ .../academy/rickandmorty/model/Gender.java | 13 ++++++++++++ .../academy/rickandmorty/model/Status.java | 12 +++++++++++ 3 files changed, 46 insertions(+) create mode 100644 src/main/java/mate/academy/rickandmorty/model/Character.java create mode 100644 src/main/java/mate/academy/rickandmorty/model/Gender.java create mode 100644 src/main/java/mate/academy/rickandmorty/model/Status.java diff --git a/src/main/java/mate/academy/rickandmorty/model/Character.java b/src/main/java/mate/academy/rickandmorty/model/Character.java new file mode 100644 index 00000000..206f4164 --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/model/Character.java @@ -0,0 +1,21 @@ +package mate.academy.rickandmorty.model; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.Data; + +@Data +@Entity +@Table(name = "characters") +public class Character { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + private Long externalId; + private String name; + private Status status; + private Gender gender; +} diff --git a/src/main/java/mate/academy/rickandmorty/model/Gender.java b/src/main/java/mate/academy/rickandmorty/model/Gender.java new file mode 100644 index 00000000..552c8505 --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/model/Gender.java @@ -0,0 +1,13 @@ +package mate.academy.rickandmorty.model; + +public enum Gender { + FEMALE("Female"), + MALE("Male"), + GENDERLESS("Genderless"), + UNKNOWN("unknown"); + private String value; + + Gender(String value) { + this.value = value; + } +} diff --git a/src/main/java/mate/academy/rickandmorty/model/Status.java b/src/main/java/mate/academy/rickandmorty/model/Status.java new file mode 100644 index 00000000..7018e622 --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/model/Status.java @@ -0,0 +1,12 @@ +package mate.academy.rickandmorty.model; + +public enum Status { + ALIVE("Alive"), + DEAD("Dead"), + UNKNOWN("unknown"); + private String value; + + Status(String value) { + this.value = value; + } +} From feff513828395d726282d2e9ea0bcced48749804 Mon Sep 17 00:00:00 2001 From: Sergey gordeichuk Date: Wed, 20 Mar 2024 15:22:52 +0200 Subject: [PATCH 3/7] Made home work --- README.md | 21 +----- pom.xml | 11 ++- .../academy/rickandmorty/Application.java | 13 ++++ .../rickandmorty/config/MapperConfig.java | 13 ++++ .../controller/CharacterController.java | 50 +++++++++++++ .../dto/external/ApiCharacterDto.java | 11 +++ .../rickandmorty/dto/external/ApiInfoDto.java | 11 +++ .../dto/external/ApiResponseDto.java | 11 +++ .../dto/external/CharacterDtoFromApi.java | 13 ++++ .../dto/internal/CharacterResponseDto.java | 14 ++++ .../rickandmorty/mapper/CharacterMapper.java | 40 +++++++++++ .../academy/rickandmorty/model/Character.java | 4 ++ .../repository/CharacterRepository.java | 9 +++ .../service/CharacterService.java | 18 +++++ .../service/CharacterServiceImpl.java | 72 +++++++++++++++++++ .../service/CharactersApiClient.java | 43 +++++++++++ src/main/resources/application.properties | 6 +- .../rickandmorty/ApplicationTests.java | 6 +- 18 files changed, 341 insertions(+), 25 deletions(-) create mode 100644 src/main/java/mate/academy/rickandmorty/config/MapperConfig.java create mode 100644 src/main/java/mate/academy/rickandmorty/controller/CharacterController.java create mode 100644 src/main/java/mate/academy/rickandmorty/dto/external/ApiCharacterDto.java create mode 100644 src/main/java/mate/academy/rickandmorty/dto/external/ApiInfoDto.java create mode 100644 src/main/java/mate/academy/rickandmorty/dto/external/ApiResponseDto.java create mode 100644 src/main/java/mate/academy/rickandmorty/dto/external/CharacterDtoFromApi.java create mode 100644 src/main/java/mate/academy/rickandmorty/dto/internal/CharacterResponseDto.java create mode 100644 src/main/java/mate/academy/rickandmorty/mapper/CharacterMapper.java create mode 100644 src/main/java/mate/academy/rickandmorty/repository/CharacterRepository.java create mode 100644 src/main/java/mate/academy/rickandmorty/service/CharacterService.java create mode 100644 src/main/java/mate/academy/rickandmorty/service/CharacterServiceImpl.java create mode 100644 src/main/java/mate/academy/rickandmorty/service/CharactersApiClient.java diff --git a/README.md b/README.md index 4fd65238..b3c10a4c 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,6 @@ - Task: Create API. It should contain two methods: 1. The request randomly generates a wiki about one character in the universe the animated series Rick & Morty. - Запит випадковим чином генерує вікі про одного персонажа всесвіту мультсеріалу «Рік і Морті». Response example: ```json @@ -17,27 +16,19 @@ "gender": "Male" } ``` - - NOTE: `externalId` field should save the original character ID received from the external API. `id` field should - represent the identifier of entire `Character` entity, that is associated with internal DB. - ПРИМІТКА: поле `externalId` має зберігати вихідний ідентифікатор символу, отриманий із зовнішнього API. Поле `id` - має представляти ідентифікатор усієї сутності `Character`, яка пов'язана з внутрішньою БД. + + NOTE: `externalId` field should save the original character ID received from the external API. `id` field should + represent the identifier of entire `Character` entity, that is associated with internal DB. 2. The request takes a string as an argument, and returns a list of all characters whose name contains the search string. - Запит приймає рядок як аргумент і повертає список усіх символів, ім’я яких містить пошуковий рядок. - During the application start, the web application downloads data from a third-party service to the internal database. - Під час запуску програми веб-програма завантажує дані зі стороннього сервісу у внутрішню базу даних. - Implemented API requests must work with a local database (i.e. fetch data from a database). - Реалізовані запити API мають працювати з локальною базою даних (тобто отримувати дані з бази даних). - What to use: 1. You must use [public API](https://rickandmortyapi.com/documentation/#rest) (you should use REST API). 2. All data from the public API should be fetched once, and only once, when the Application context is created - Усі дані з загальнодоступного API слід отримати один раз і лише один раз під час створення контексту програми ### Tech Requirements @@ -48,9 +39,3 @@ and `src/test/resources/application.properties` files. In other case you may face a problem with Application Context creation during the `mvn test` phase. - Requests must be documented using Swagger. -- Використовуйте базу даних MySQL у своїй програмі. -- Використовуйте H2 DB у своїй тестовій конфігурації (її вже налаштовано у файлі`src/test/resources/application.properties`). -- Зберігайте ідентичний набір параметрів у файлах `src/main/resources/application.properties` -- і `src/test/resources/application.properties`. В іншому випадку ви можете зіткнутися з проблемою -- створення контексту програми під час фази `mvn test`. -- Запити мають бути задокументовані за допомогою Swagger. \ No newline at end of file diff --git a/pom.xml b/pom.xml index 7de873b1..7543e174 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.boot spring-boot-starter-parent - 3.1.4 + 3.2.3 mate.academy @@ -45,6 +45,11 @@ org.hibernate.validator hibernate-validator + + org.apache.commons + commons-dbcp2 + 2.9.0 + org.springdoc springdoc-openapi-starter-webmvc-ui @@ -135,6 +140,10 @@ + + ${project.build.sourceDirectory} + ${project.build.testSourceDirectory} + ${maven.checkstyle.plugin.configLocation} true true diff --git a/src/main/java/mate/academy/rickandmorty/Application.java b/src/main/java/mate/academy/rickandmorty/Application.java index cdea84fc..f953d10a 100644 --- a/src/main/java/mate/academy/rickandmorty/Application.java +++ b/src/main/java/mate/academy/rickandmorty/Application.java @@ -1,12 +1,25 @@ package mate.academy.rickandmorty; +import mate.academy.rickandmorty.service.CharacterService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; @SpringBootApplication public class Application { + private static final String BASE_URL = "https://rickandmortyapi.com/api/character/"; + @Autowired + private CharacterService characterService; public static void main(String[] args) { SpringApplication.run(Application.class, args); + + } + + @Bean + public CommandLineRunner commandLineRunner() { + return args -> characterService.saveCharactersToDb(BASE_URL); } } diff --git a/src/main/java/mate/academy/rickandmorty/config/MapperConfig.java b/src/main/java/mate/academy/rickandmorty/config/MapperConfig.java new file mode 100644 index 00000000..e720930f --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/config/MapperConfig.java @@ -0,0 +1,13 @@ +package mate.academy.rickandmorty.config; + +import org.mapstruct.InjectionStrategy; +import org.mapstruct.NullValueCheckStrategy; + +@org.mapstruct.MapperConfig( + componentModel = "spring", + injectionStrategy = InjectionStrategy.CONSTRUCTOR, + nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS, + implementationPackage = ".impl" +) +public class MapperConfig { +} diff --git a/src/main/java/mate/academy/rickandmorty/controller/CharacterController.java b/src/main/java/mate/academy/rickandmorty/controller/CharacterController.java new file mode 100644 index 00000000..f260c54b --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/controller/CharacterController.java @@ -0,0 +1,50 @@ +package mate.academy.rickandmorty.controller; + +import java.util.List; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import mate.academy.rickandmorty.dto.internal.CharacterResponseDto; +import mate.academy.rickandmorty.service.CharacterService; +import org.springframework.data.domain.Pageable; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@Tag(name = "Character controller", description = "For managing characters") +@RequiredArgsConstructor +@RestController +@RequestMapping("/characters") +public class CharacterController { + private final CharacterService characterService; + + @GetMapping("/random") + @Operation(summary = "Get random character", + description = "You get a random character") + public CharacterResponseDto getRandomCharacters() { + return characterService.getRandomCharacter(); + } + + @GetMapping("/by-name") + @Operation(summary = "Find characters by name", + description = "You can find characters by name") + public List findAllByName(@RequestParam("name") String name) { + return characterService.findAllByName(name); + } + + @GetMapping + @Operation(summary = "Get all characters", + description = "You get all characters, default parameters: page=0, size=20, sort=id") + public List findAll(Pageable pageable) { + return characterService.findAll(pageable); + } + + @GetMapping("/{id}") + @Operation(summary = "Find character by id", + description = "You can find character by id") + public CharacterResponseDto findById(@PathVariable Long id) { + return characterService.findById(id); + } +} diff --git a/src/main/java/mate/academy/rickandmorty/dto/external/ApiCharacterDto.java b/src/main/java/mate/academy/rickandmorty/dto/external/ApiCharacterDto.java new file mode 100644 index 00000000..51b12aa6 --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/dto/external/ApiCharacterDto.java @@ -0,0 +1,11 @@ +package mate.academy.rickandmorty.dto.external; + +import lombok.Data; + +@Data +public class ApiCharacterDto { + private Long id; + private String name; + private String status; + private String gender; +} diff --git a/src/main/java/mate/academy/rickandmorty/dto/external/ApiInfoDto.java b/src/main/java/mate/academy/rickandmorty/dto/external/ApiInfoDto.java new file mode 100644 index 00000000..1ace4914 --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/dto/external/ApiInfoDto.java @@ -0,0 +1,11 @@ +package mate.academy.rickandmorty.dto.external; + +import lombok.Data; + +@Data +public class ApiInfoDto { + private Long count; + private Long pages; + private String next; + private String prev; +} diff --git a/src/main/java/mate/academy/rickandmorty/dto/external/ApiResponseDto.java b/src/main/java/mate/academy/rickandmorty/dto/external/ApiResponseDto.java new file mode 100644 index 00000000..d09040c5 --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/dto/external/ApiResponseDto.java @@ -0,0 +1,11 @@ +package mate.academy.rickandmorty.dto.external; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +@Data +public class ApiResponseDto { + @JsonProperty("info") + private ApiInfoDto apiInfoDto; + private ApiCharacterDto[] results; +} diff --git a/src/main/java/mate/academy/rickandmorty/dto/external/CharacterDtoFromApi.java b/src/main/java/mate/academy/rickandmorty/dto/external/CharacterDtoFromApi.java new file mode 100644 index 00000000..bfd3bb07 --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/dto/external/CharacterDtoFromApi.java @@ -0,0 +1,13 @@ +package mate.academy.rickandmorty.dto.external; + +import lombok.Data; +import mate.academy.rickandmorty.model.Gender; +import mate.academy.rickandmorty.model.Status; + +@Data +public class CharacterDtoFromApi { + private Long externalId; + private String name; + private Status status; + private Gender gender; +} diff --git a/src/main/java/mate/academy/rickandmorty/dto/internal/CharacterResponseDto.java b/src/main/java/mate/academy/rickandmorty/dto/internal/CharacterResponseDto.java new file mode 100644 index 00000000..8c138dfa --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/dto/internal/CharacterResponseDto.java @@ -0,0 +1,14 @@ +package mate.academy.rickandmorty.dto.internal; + +import lombok.Data; +import mate.academy.rickandmorty.model.Gender; +import mate.academy.rickandmorty.model.Status; + +@Data +public class CharacterResponseDto { + private Long id; + private Long externalId; + private String name; + private Status status; + private Gender gender; +} diff --git a/src/main/java/mate/academy/rickandmorty/mapper/CharacterMapper.java b/src/main/java/mate/academy/rickandmorty/mapper/CharacterMapper.java new file mode 100644 index 00000000..2a59c51b --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/mapper/CharacterMapper.java @@ -0,0 +1,40 @@ +package mate.academy.rickandmorty.mapper; + +import mate.academy.rickandmorty.dto.external.ApiCharacterDto; +import mate.academy.rickandmorty.dto.external.CharacterDtoFromApi; +import mate.academy.rickandmorty.dto.internal.CharacterResponseDto; +import mate.academy.rickandmorty.model.Character; +import mate.academy.rickandmorty.model.Gender; +import mate.academy.rickandmorty.model.Status; +import org.springframework.stereotype.Component; + +@Component +public class CharacterMapper { + public CharacterResponseDto toResponseDto(Character character) { + CharacterResponseDto characterDto = new CharacterResponseDto(); + characterDto.setId(character.getId()); + characterDto.setExternalId(character.getExternalId()); + characterDto.setName(character.getName()); + characterDto.setStatus(character.getStatus()); + characterDto.setGender(character.getGender()); + return characterDto; + } + + public Character toModel(ApiCharacterDto dtoForDb) { + Character character = new Character(); + character.setExternalId(dtoForDb.getId()); + character.setName(dtoForDb.getName()); + character.setGender(Gender.valueOf(dtoForDb.getGender().toUpperCase())); + character.setStatus(Status.valueOf(dtoForDb.getStatus().toUpperCase())); + return character; + } + + public CharacterDtoFromApi toCharacterDtoForDb(ApiCharacterDto apiCharacterDto) { + CharacterDtoFromApi forDb = new CharacterDtoFromApi(); + forDb.setExternalId(apiCharacterDto.getId()); + forDb.setName(apiCharacterDto.getName()); + forDb.setGender(Gender.valueOf(apiCharacterDto.getGender().toUpperCase())); + forDb.setStatus(Status.valueOf(apiCharacterDto.getStatus().toUpperCase())); + return forDb; + } +} diff --git a/src/main/java/mate/academy/rickandmorty/model/Character.java b/src/main/java/mate/academy/rickandmorty/model/Character.java index 206f4164..429e8bc4 100644 --- a/src/main/java/mate/academy/rickandmorty/model/Character.java +++ b/src/main/java/mate/academy/rickandmorty/model/Character.java @@ -1,6 +1,8 @@ package mate.academy.rickandmorty.model; import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; @@ -16,6 +18,8 @@ public class Character { private Long id; private Long externalId; private String name; + @Enumerated(EnumType.STRING) private Status status; + @Enumerated(EnumType.STRING) private Gender gender; } diff --git a/src/main/java/mate/academy/rickandmorty/repository/CharacterRepository.java b/src/main/java/mate/academy/rickandmorty/repository/CharacterRepository.java new file mode 100644 index 00000000..6b923714 --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/repository/CharacterRepository.java @@ -0,0 +1,9 @@ +package mate.academy.rickandmorty.repository; + +import java.util.List; +import mate.academy.rickandmorty.model.Character; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface CharacterRepository extends JpaRepository { + List findAllByNameContainingIgnoreCase(String name); +} diff --git a/src/main/java/mate/academy/rickandmorty/service/CharacterService.java b/src/main/java/mate/academy/rickandmorty/service/CharacterService.java new file mode 100644 index 00000000..1b8fb05e --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/service/CharacterService.java @@ -0,0 +1,18 @@ +package mate.academy.rickandmorty.service; + +import java.util.List; +import mate.academy.rickandmorty.dto.internal.CharacterResponseDto; +import org.springframework.data.domain.Pageable; + +public interface CharacterService { + + List findAll(Pageable pageable); + + CharacterResponseDto findById(Long id); + + CharacterResponseDto getRandomCharacter(); + + List findAllByName(String name); + + void saveCharactersToDb(String url); +} diff --git a/src/main/java/mate/academy/rickandmorty/service/CharacterServiceImpl.java b/src/main/java/mate/academy/rickandmorty/service/CharacterServiceImpl.java new file mode 100644 index 00000000..f156d92b --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/service/CharacterServiceImpl.java @@ -0,0 +1,72 @@ +package mate.academy.rickandmorty.service; + +import java.util.Arrays; +import java.util.List; +import lombok.RequiredArgsConstructor; +import mate.academy.rickandmorty.dto.external.ApiResponseDto; +import mate.academy.rickandmorty.dto.internal.CharacterResponseDto; +import mate.academy.rickandmorty.mapper.CharacterMapper; +import mate.academy.rickandmorty.model.Character; +import mate.academy.rickandmorty.repository.CharacterRepository; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; + +@RequiredArgsConstructor +@Service +public class CharacterServiceImpl implements CharacterService { + + private final CharacterRepository characterRepository; + private final CharacterMapper characterMapper; + private final CharactersApiClient client; + + @Override + public List findAll(Pageable pageable) { + return characterRepository.findAll(pageable) + .stream() + .map(characterMapper::toResponseDto) + .toList(); + } + + @Override + public CharacterResponseDto findById(Long id) { + Character character = characterRepository.findById(id).orElseThrow( + () -> new RuntimeException("Can't find Character by id " + id)); + return characterMapper.toResponseDto(character); + } + + @Override + public CharacterResponseDto getRandomCharacter() { + long count = characterRepository.count(); + long randomId = (long) (Math.random() * count); + return findById(randomId); + } + + @Override + public List findAllByName(String name) { + return characterRepository.findAllByNameContainingIgnoreCase(name) + .stream() + .map(characterMapper::toResponseDto) + .toList(); + } + + @Override + public void saveCharactersToDb(String url) { + ApiResponseDto apiResponseDto = client.getAllCharacterFromApi(url); + List characterList = + Arrays.stream(apiResponseDto.getResults()) + .map(characterMapper::toModel) + .toList(); + characterRepository.saveAll(characterList); + String next = apiResponseDto.getApiInfoDto().getNext(); + + while (next != null) { + ApiResponseDto apiResponseDtoNext = client.getAllCharacterFromApi(next); + List characterListNext = + Arrays.stream(apiResponseDtoNext.getResults()) + .map(characterMapper::toModel) + .toList(); + characterRepository.saveAll(characterListNext); + next = apiResponseDtoNext.getApiInfoDto().getNext(); + } + } +} diff --git a/src/main/java/mate/academy/rickandmorty/service/CharactersApiClient.java b/src/main/java/mate/academy/rickandmorty/service/CharactersApiClient.java new file mode 100644 index 00000000..9b350484 --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/service/CharactersApiClient.java @@ -0,0 +1,43 @@ +package mate.academy.rickandmorty.service; + +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.util.Arrays; +import java.util.List; +import lombok.RequiredArgsConstructor; +import mate.academy.rickandmorty.dto.external.ApiResponseDto; +import mate.academy.rickandmorty.dto.external.CharacterDtoFromApi; +import mate.academy.rickandmorty.mapper.CharacterMapper; +import org.springframework.stereotype.Component; + +@RequiredArgsConstructor +@Component +public class CharactersApiClient { + private final ObjectMapper objectMapper; + private final CharacterMapper characterMapper; + + public ApiResponseDto getAllCharacterFromApi(String url) { + HttpClient httpClient = HttpClient.newHttpClient(); + HttpRequest httpRequest = HttpRequest.newBuilder() + .GET() + .uri(URI.create(url)) + .build(); + try { + HttpResponse httpResponse = httpClient.send( + httpRequest, + HttpResponse.BodyHandlers.ofString()); + ApiResponseDto apiResponseDto = objectMapper + .readValue(httpResponse.body(), ApiResponseDto.class); + List list = Arrays.stream(apiResponseDto.getResults()) + .map(characterMapper::toCharacterDtoForDb) + .toList(); + return apiResponseDto; + } catch (IOException | InterruptedException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index ccaa92b0..82f12fe4 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,6 +1,6 @@ -spring.datasource.url=jdbc:mysql://localhost:3306/test?serverTimezone=UTC -spring.datasource.username=root -spring.datasource.password=3052821794gsv +spring.datasource.url=jdbc:mysql://localhost:3306/?serverTimezone=UTC +spring.datasource.username= +spring.datasource.password= spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.jpa.hibernate.ddl-auto=create-drop diff --git a/src/test/java/mate/academy/rickandmorty/ApplicationTests.java b/src/test/java/mate/academy/rickandmorty/ApplicationTests.java index 8fec6af0..09b17308 100644 --- a/src/test/java/mate/academy/rickandmorty/ApplicationTests.java +++ b/src/test/java/mate/academy/rickandmorty/ApplicationTests.java @@ -6,8 +6,8 @@ @SpringBootTest class ApplicationTests { - @Test - void contextLoads() { - } + @Test + void contextLoads() { + } } From 4362cf616940cb9d8147e4c8038aedfb6943a270 Mon Sep 17 00:00:00 2001 From: Sergey gordeichuk Date: Wed, 20 Mar 2024 15:27:10 +0200 Subject: [PATCH 4/7] Fixed lexicographical in CharacterController --- .../academy/rickandmorty/controller/CharacterController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/mate/academy/rickandmorty/controller/CharacterController.java b/src/main/java/mate/academy/rickandmorty/controller/CharacterController.java index f260c54b..2c2334e4 100644 --- a/src/main/java/mate/academy/rickandmorty/controller/CharacterController.java +++ b/src/main/java/mate/academy/rickandmorty/controller/CharacterController.java @@ -1,8 +1,8 @@ package mate.academy.rickandmorty.controller; -import java.util.List; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import java.util.List; import lombok.RequiredArgsConstructor; import mate.academy.rickandmorty.dto.internal.CharacterResponseDto; import mate.academy.rickandmorty.service.CharacterService; From 533dee58f4242dac5b76d3663c76a4cf8b5af66d Mon Sep 17 00:00:00 2001 From: Sergey gordeichuk Date: Fri, 22 Mar 2024 11:04:36 +0200 Subject: [PATCH 5/7] Deleted @DATA in Character.class, method toCharacterDtoForDb in CharacterMapper and CharacterDtoFromApi.class.Created Getters, Setters and toString methods in Character.class. Modified CharactersApiClient.class and CharacterServiceImpl.class. --- .../controller/CharacterController.java | 2 +- .../dto/external/CharacterDtoFromApi.java | 13 ----- .../rickandmorty/mapper/CharacterMapper.java | 10 ---- .../academy/rickandmorty/model/Character.java | 53 ++++++++++++++++++- .../service/CharacterServiceImpl.java | 1 - .../service/CharactersApiClient.java | 24 +++------ 6 files changed, 60 insertions(+), 43 deletions(-) delete mode 100644 src/main/java/mate/academy/rickandmorty/dto/external/CharacterDtoFromApi.java diff --git a/src/main/java/mate/academy/rickandmorty/controller/CharacterController.java b/src/main/java/mate/academy/rickandmorty/controller/CharacterController.java index 2c2334e4..6814a592 100644 --- a/src/main/java/mate/academy/rickandmorty/controller/CharacterController.java +++ b/src/main/java/mate/academy/rickandmorty/controller/CharacterController.java @@ -30,7 +30,7 @@ public CharacterResponseDto getRandomCharacters() { @GetMapping("/by-name") @Operation(summary = "Find characters by name", description = "You can find characters by name") - public List findAllByName(@RequestParam("name") String name) { + public List findAllByName(@RequestParam String name) { return characterService.findAllByName(name); } diff --git a/src/main/java/mate/academy/rickandmorty/dto/external/CharacterDtoFromApi.java b/src/main/java/mate/academy/rickandmorty/dto/external/CharacterDtoFromApi.java deleted file mode 100644 index bfd3bb07..00000000 --- a/src/main/java/mate/academy/rickandmorty/dto/external/CharacterDtoFromApi.java +++ /dev/null @@ -1,13 +0,0 @@ -package mate.academy.rickandmorty.dto.external; - -import lombok.Data; -import mate.academy.rickandmorty.model.Gender; -import mate.academy.rickandmorty.model.Status; - -@Data -public class CharacterDtoFromApi { - private Long externalId; - private String name; - private Status status; - private Gender gender; -} diff --git a/src/main/java/mate/academy/rickandmorty/mapper/CharacterMapper.java b/src/main/java/mate/academy/rickandmorty/mapper/CharacterMapper.java index 2a59c51b..ebaa1948 100644 --- a/src/main/java/mate/academy/rickandmorty/mapper/CharacterMapper.java +++ b/src/main/java/mate/academy/rickandmorty/mapper/CharacterMapper.java @@ -1,7 +1,6 @@ package mate.academy.rickandmorty.mapper; import mate.academy.rickandmorty.dto.external.ApiCharacterDto; -import mate.academy.rickandmorty.dto.external.CharacterDtoFromApi; import mate.academy.rickandmorty.dto.internal.CharacterResponseDto; import mate.academy.rickandmorty.model.Character; import mate.academy.rickandmorty.model.Gender; @@ -28,13 +27,4 @@ public Character toModel(ApiCharacterDto dtoForDb) { character.setStatus(Status.valueOf(dtoForDb.getStatus().toUpperCase())); return character; } - - public CharacterDtoFromApi toCharacterDtoForDb(ApiCharacterDto apiCharacterDto) { - CharacterDtoFromApi forDb = new CharacterDtoFromApi(); - forDb.setExternalId(apiCharacterDto.getId()); - forDb.setName(apiCharacterDto.getName()); - forDb.setGender(Gender.valueOf(apiCharacterDto.getGender().toUpperCase())); - forDb.setStatus(Status.valueOf(apiCharacterDto.getStatus().toUpperCase())); - return forDb; - } } diff --git a/src/main/java/mate/academy/rickandmorty/model/Character.java b/src/main/java/mate/academy/rickandmorty/model/Character.java index 429e8bc4..59667b36 100644 --- a/src/main/java/mate/academy/rickandmorty/model/Character.java +++ b/src/main/java/mate/academy/rickandmorty/model/Character.java @@ -7,9 +7,7 @@ import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.Table; -import lombok.Data; -@Data @Entity @Table(name = "characters") public class Character { @@ -22,4 +20,55 @@ public class Character { private Status status; @Enumerated(EnumType.STRING) private Gender gender; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getExternalId() { + return externalId; + } + + public void setExternalId(Long externalId) { + this.externalId = externalId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Status getStatus() { + return status; + } + + public void setStatus(Status status) { + this.status = status; + } + + public Gender getGender() { + return gender; + } + + public void setGender(Gender gender) { + this.gender = gender; + } + + @Override + public String toString() { + return "Character{" + + "id=" + id + + ", externalId=" + externalId + + ", name='" + name + '\'' + + ", status=" + status + + ", gender=" + gender + + '}'; + } } diff --git a/src/main/java/mate/academy/rickandmorty/service/CharacterServiceImpl.java b/src/main/java/mate/academy/rickandmorty/service/CharacterServiceImpl.java index f156d92b..39cff73a 100644 --- a/src/main/java/mate/academy/rickandmorty/service/CharacterServiceImpl.java +++ b/src/main/java/mate/academy/rickandmorty/service/CharacterServiceImpl.java @@ -58,7 +58,6 @@ public void saveCharactersToDb(String url) { .toList(); characterRepository.saveAll(characterList); String next = apiResponseDto.getApiInfoDto().getNext(); - while (next != null) { ApiResponseDto apiResponseDtoNext = client.getAllCharacterFromApi(next); List characterListNext = diff --git a/src/main/java/mate/academy/rickandmorty/service/CharactersApiClient.java b/src/main/java/mate/academy/rickandmorty/service/CharactersApiClient.java index 9b350484..6a7f1fb6 100644 --- a/src/main/java/mate/academy/rickandmorty/service/CharactersApiClient.java +++ b/src/main/java/mate/academy/rickandmorty/service/CharactersApiClient.java @@ -3,40 +3,32 @@ import com.fasterxml.jackson.databind.ObjectMapper; import java.io.IOException; import java.net.URI; +import java.net.URISyntaxException; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; -import java.util.Arrays; -import java.util.List; import lombok.RequiredArgsConstructor; import mate.academy.rickandmorty.dto.external.ApiResponseDto; -import mate.academy.rickandmorty.dto.external.CharacterDtoFromApi; -import mate.academy.rickandmorty.mapper.CharacterMapper; import org.springframework.stereotype.Component; @RequiredArgsConstructor @Component public class CharactersApiClient { private final ObjectMapper objectMapper; - private final CharacterMapper characterMapper; + private final HttpClient httpClient = HttpClient.newHttpClient(); public ApiResponseDto getAllCharacterFromApi(String url) { - HttpClient httpClient = HttpClient.newHttpClient(); - HttpRequest httpRequest = HttpRequest.newBuilder() - .GET() - .uri(URI.create(url)) - .build(); try { + HttpRequest httpRequest = HttpRequest.newBuilder() + .GET() + .uri(new URI(url)) + .build(); HttpResponse httpResponse = httpClient.send( httpRequest, HttpResponse.BodyHandlers.ofString()); - ApiResponseDto apiResponseDto = objectMapper + return objectMapper .readValue(httpResponse.body(), ApiResponseDto.class); - List list = Arrays.stream(apiResponseDto.getResults()) - .map(characterMapper::toCharacterDtoForDb) - .toList(); - return apiResponseDto; - } catch (IOException | InterruptedException e) { + } catch (IOException | InterruptedException | URISyntaxException e) { throw new RuntimeException(e); } } From 7ccc8cb51103ffa317dd2c1c42ff72448c2f13dd Mon Sep 17 00:00:00 2001 From: Sergey gordeichuk Date: Fri, 22 Mar 2024 11:39:11 +0200 Subject: [PATCH 6/7] Changed properties --- src/main/resources/application.properties | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 82f12fe4..ccaa92b0 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,6 +1,6 @@ -spring.datasource.url=jdbc:mysql://localhost:3306/?serverTimezone=UTC -spring.datasource.username= -spring.datasource.password= +spring.datasource.url=jdbc:mysql://localhost:3306/test?serverTimezone=UTC +spring.datasource.username=root +spring.datasource.password=3052821794gsv spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.jpa.hibernate.ddl-auto=create-drop From 29d502679f4f5c208f18cdd7afffb690d82c49bc Mon Sep 17 00:00:00 2001 From: Sergey gordeichuk Date: Sat, 23 Mar 2024 18:31:58 +0200 Subject: [PATCH 7/7] Changed ApiCharacterDto in ApiResponseDto from array to list. Modified /CharacterService.java, /CharacterServiceImpl.java and /CharactersApiClient.java. --- .../academy/rickandmorty/Application.java | 3 +- .../dto/external/ApiResponseDto.java | 3 +- .../service/CharacterService.java | 2 +- .../service/CharacterServiceImpl.java | 25 ++++---------- .../service/CharactersApiClient.java | 33 ++++++++++++------- src/main/resources/application.properties | 6 ++-- 6 files changed, 35 insertions(+), 37 deletions(-) diff --git a/src/main/java/mate/academy/rickandmorty/Application.java b/src/main/java/mate/academy/rickandmorty/Application.java index f953d10a..da22951a 100644 --- a/src/main/java/mate/academy/rickandmorty/Application.java +++ b/src/main/java/mate/academy/rickandmorty/Application.java @@ -9,7 +9,6 @@ @SpringBootApplication public class Application { - private static final String BASE_URL = "https://rickandmortyapi.com/api/character/"; @Autowired private CharacterService characterService; @@ -20,6 +19,6 @@ public static void main(String[] args) { @Bean public CommandLineRunner commandLineRunner() { - return args -> characterService.saveCharactersToDb(BASE_URL); + return args -> characterService.saveCharactersToDb(); } } diff --git a/src/main/java/mate/academy/rickandmorty/dto/external/ApiResponseDto.java b/src/main/java/mate/academy/rickandmorty/dto/external/ApiResponseDto.java index d09040c5..fdad05c2 100644 --- a/src/main/java/mate/academy/rickandmorty/dto/external/ApiResponseDto.java +++ b/src/main/java/mate/academy/rickandmorty/dto/external/ApiResponseDto.java @@ -1,11 +1,12 @@ package mate.academy.rickandmorty.dto.external; import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; import lombok.Data; @Data public class ApiResponseDto { @JsonProperty("info") private ApiInfoDto apiInfoDto; - private ApiCharacterDto[] results; + private List results; } diff --git a/src/main/java/mate/academy/rickandmorty/service/CharacterService.java b/src/main/java/mate/academy/rickandmorty/service/CharacterService.java index 1b8fb05e..81ca4555 100644 --- a/src/main/java/mate/academy/rickandmorty/service/CharacterService.java +++ b/src/main/java/mate/academy/rickandmorty/service/CharacterService.java @@ -14,5 +14,5 @@ public interface CharacterService { List findAllByName(String name); - void saveCharactersToDb(String url); + void saveCharactersToDb(); } diff --git a/src/main/java/mate/academy/rickandmorty/service/CharacterServiceImpl.java b/src/main/java/mate/academy/rickandmorty/service/CharacterServiceImpl.java index 39cff73a..4b8071f2 100644 --- a/src/main/java/mate/academy/rickandmorty/service/CharacterServiceImpl.java +++ b/src/main/java/mate/academy/rickandmorty/service/CharacterServiceImpl.java @@ -1,9 +1,7 @@ package mate.academy.rickandmorty.service; -import java.util.Arrays; import java.util.List; import lombok.RequiredArgsConstructor; -import mate.academy.rickandmorty.dto.external.ApiResponseDto; import mate.academy.rickandmorty.dto.internal.CharacterResponseDto; import mate.academy.rickandmorty.mapper.CharacterMapper; import mate.academy.rickandmorty.model.Character; @@ -50,22 +48,11 @@ public List findAllByName(String name) { } @Override - public void saveCharactersToDb(String url) { - ApiResponseDto apiResponseDto = client.getAllCharacterFromApi(url); - List characterList = - Arrays.stream(apiResponseDto.getResults()) - .map(characterMapper::toModel) - .toList(); - characterRepository.saveAll(characterList); - String next = apiResponseDto.getApiInfoDto().getNext(); - while (next != null) { - ApiResponseDto apiResponseDtoNext = client.getAllCharacterFromApi(next); - List characterListNext = - Arrays.stream(apiResponseDtoNext.getResults()) - .map(characterMapper::toModel) - .toList(); - characterRepository.saveAll(characterListNext); - next = apiResponseDtoNext.getApiInfoDto().getNext(); - } + public void saveCharactersToDb() { + List characters = client.getAllCharacterFromApi() + .stream() + .map(characterMapper::toModel) + .toList(); + characterRepository.saveAll(characters); } } diff --git a/src/main/java/mate/academy/rickandmorty/service/CharactersApiClient.java b/src/main/java/mate/academy/rickandmorty/service/CharactersApiClient.java index 6a7f1fb6..b1c04452 100644 --- a/src/main/java/mate/academy/rickandmorty/service/CharactersApiClient.java +++ b/src/main/java/mate/academy/rickandmorty/service/CharactersApiClient.java @@ -3,33 +3,44 @@ import com.fasterxml.jackson.databind.ObjectMapper; import java.io.IOException; import java.net.URI; -import java.net.URISyntaxException; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; +import java.util.ArrayList; +import java.util.List; import lombok.RequiredArgsConstructor; +import mate.academy.rickandmorty.dto.external.ApiCharacterDto; import mate.academy.rickandmorty.dto.external.ApiResponseDto; import org.springframework.stereotype.Component; @RequiredArgsConstructor @Component public class CharactersApiClient { + private static final String BASE_URL = "https://rickandmortyapi.com/api/character/"; private final ObjectMapper objectMapper; private final HttpClient httpClient = HttpClient.newHttpClient(); - public ApiResponseDto getAllCharacterFromApi(String url) { - try { + public List getAllCharacterFromApi() { + List apiCharacterDtoList = new ArrayList<>(); + String nextPage = BASE_URL; + + while (nextPage != null) { HttpRequest httpRequest = HttpRequest.newBuilder() .GET() - .uri(new URI(url)) + .uri(URI.create(nextPage)) .build(); - HttpResponse httpResponse = httpClient.send( - httpRequest, - HttpResponse.BodyHandlers.ofString()); - return objectMapper - .readValue(httpResponse.body(), ApiResponseDto.class); - } catch (IOException | InterruptedException | URISyntaxException e) { - throw new RuntimeException(e); + try { + HttpResponse httpResponse = httpClient.send( + httpRequest, + HttpResponse.BodyHandlers.ofString()); + ApiResponseDto apiResponseDto = objectMapper + .readValue(httpResponse.body(), ApiResponseDto.class); + apiCharacterDtoList.addAll(apiResponseDto.getResults()); + nextPage = apiResponseDto.getApiInfoDto().getNext(); + } catch (IOException | InterruptedException e) { + throw new RuntimeException(e); + } } + return apiCharacterDtoList; } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index ccaa92b0..82f12fe4 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,6 +1,6 @@ -spring.datasource.url=jdbc:mysql://localhost:3306/test?serverTimezone=UTC -spring.datasource.username=root -spring.datasource.password=3052821794gsv +spring.datasource.url=jdbc:mysql://localhost:3306/?serverTimezone=UTC +spring.datasource.username= +spring.datasource.password= spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.jpa.hibernate.ddl-auto=create-drop