From d48cce536ac2ce3fe8950be0c54448d341a096d2 Mon Sep 17 00:00:00 2001 From: Maksym Fedorenko Date: Wed, 18 Oct 2023 19:51:23 +0300 Subject: [PATCH 1/4] added integration with rick-and-morty API --- pom.xml | 72 +++++++++++++++++++ .../controller/CharacterController.java | 40 +++++++++++ .../dto/external/CharacterInfoDto.java | 10 +++ .../dto/external/CharacterResponseDto.java | 8 +++ .../dto/external/MetaInfoDto.java | 6 ++ .../dto/internal/CharacterDto.java | 6 ++ .../rickandmorty/mapper/CharacterMapper.java | 19 +++++ .../academy/rickandmorty/model/Character.java | 30 ++++++++ .../repository/CharacterRepository.java | 11 +++ .../service/CharacterService.java | 10 +++ .../service/CharactersClient.java | 50 +++++++++++++ .../service/impl/CharacterServiceImpl.java | 48 +++++++++++++ src/main/resources/application.properties | 8 ++- .../rickandmorty/ApplicationTests.java | 6 +- src/test/resources/application.properties | 1 + 15 files changed, 321 insertions(+), 4 deletions(-) create mode 100644 src/main/java/mate/academy/rickandmorty/controller/CharacterController.java create mode 100644 src/main/java/mate/academy/rickandmorty/dto/external/CharacterInfoDto.java create mode 100644 src/main/java/mate/academy/rickandmorty/dto/external/CharacterResponseDto.java create mode 100644 src/main/java/mate/academy/rickandmorty/dto/external/MetaInfoDto.java create mode 100644 src/main/java/mate/academy/rickandmorty/dto/internal/CharacterDto.java create mode 100644 src/main/java/mate/academy/rickandmorty/mapper/CharacterMapper.java create mode 100644 src/main/java/mate/academy/rickandmorty/model/Character.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/CharactersClient.java create mode 100644 src/main/java/mate/academy/rickandmorty/service/impl/CharacterServiceImpl.java diff --git a/pom.xml b/pom.xml index 0c754f19..8c76700d 100644 --- a/pom.xml +++ b/pom.xml @@ -19,6 +19,8 @@ https://raw.githubusercontent.com/mate-academy/style-guides/master/java/checkstyle.xml + 0.2.0 + 1.5.5.Final @@ -37,10 +39,46 @@ spring-boot-starter-data-jpa + + com.mysql + mysql-connector-j + runtime + + com.h2database h2 + + + org.springframework.boot + spring-boot-devtools + runtime + true + + + + org.projectlombok + lombok + true + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + 2.2.0 + + + + org.mapstruct + mapstruct + ${mapstruct.version} + @@ -48,7 +86,40 @@ org.springframework.boot spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + org.mapstruct + mapstruct-processor + ${mapstruct.version} + + + org.projectlombok + lombok + ${lombok.version} + + + org.projectlombok + lombok-mapstruct-binding + ${lombok.mapstruct.binding.version} + + + + + org.apache.maven.plugins maven-checkstyle-plugin @@ -66,6 +137,7 @@ true true false + src 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..cca4860a --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/controller/CharacterController.java @@ -0,0 +1,40 @@ +package mate.academy.rickandmorty.controller; + +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.CharacterDto; +import mate.academy.rickandmorty.service.CharacterService; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@Tag(name = "Rick and Morty character management", + description = "endpoints for working with third-party Rick and Morty API" +) +@RequiredArgsConstructor +@RestController +@RequestMapping("/api/rick-and-morty/characters") +public class CharacterController { + private final CharacterService characterService; + + @Operation( + summary = "Retrieve random character", + description = "Retrieve random character from database." + ) + @GetMapping("/random") + public CharacterDto getRandom() { + return characterService.getRandomCharacter(); + } + + @Operation( + summary = "Search characters by name", + description = "Search all characters whose name contains the search string." + ) + @GetMapping("/search") + public List searchByName(@RequestParam String name) { + return characterService.searchCharacters(name); + } +} diff --git a/src/main/java/mate/academy/rickandmorty/dto/external/CharacterInfoDto.java b/src/main/java/mate/academy/rickandmorty/dto/external/CharacterInfoDto.java new file mode 100644 index 00000000..300bafa3 --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/dto/external/CharacterInfoDto.java @@ -0,0 +1,10 @@ +package mate.academy.rickandmorty.dto.external; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonIgnoreProperties(ignoreUnknown = true) +public record CharacterInfoDto( + @JsonProperty("id") Long externalId, String name, String status, String gender +) { +} diff --git a/src/main/java/mate/academy/rickandmorty/dto/external/CharacterResponseDto.java b/src/main/java/mate/academy/rickandmorty/dto/external/CharacterResponseDto.java new file mode 100644 index 00000000..7246322f --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/dto/external/CharacterResponseDto.java @@ -0,0 +1,8 @@ +package mate.academy.rickandmorty.dto.external; + +import java.util.List; + +public record CharacterResponseDto( + MetaInfoDto info, List results +) { +} diff --git a/src/main/java/mate/academy/rickandmorty/dto/external/MetaInfoDto.java b/src/main/java/mate/academy/rickandmorty/dto/external/MetaInfoDto.java new file mode 100644 index 00000000..9a87d308 --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/dto/external/MetaInfoDto.java @@ -0,0 +1,6 @@ +package mate.academy.rickandmorty.dto.external; + +public record MetaInfoDto( + Integer count, Integer pages, String next, String prev +) { +} diff --git a/src/main/java/mate/academy/rickandmorty/dto/internal/CharacterDto.java b/src/main/java/mate/academy/rickandmorty/dto/internal/CharacterDto.java new file mode 100644 index 00000000..d6312fad --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/dto/internal/CharacterDto.java @@ -0,0 +1,6 @@ +package mate.academy.rickandmorty.dto.internal; + +public record CharacterDto( + Long id, Long externalId, String name, String status, String 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..2e221e71 --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/mapper/CharacterMapper.java @@ -0,0 +1,19 @@ +package mate.academy.rickandmorty.mapper; + +import mate.academy.rickandmorty.dto.external.CharacterInfoDto; +import mate.academy.rickandmorty.dto.internal.CharacterDto; +import mate.academy.rickandmorty.model.Character; +import org.mapstruct.InjectionStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.NullValueCheckStrategy; + +@Mapper( + componentModel = "spring", + injectionStrategy = InjectionStrategy.CONSTRUCTOR, + nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS +) +public interface CharacterMapper { + CharacterDto toDto(Character character); + + Character toCharacter(CharacterInfoDto bookRequestDto); +} 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..6f8e1649 --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/model/Character.java @@ -0,0 +1,30 @@ +package mate.academy.rickandmorty.model; + +import jakarta.persistence.Column; +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; + + @Column(nullable = false) + private Long externalId; + + @Column(nullable = false) + private String name; + + @Column(nullable = false) + private String status; + + @Column(nullable = false) + private String 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..6a435cff --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/repository/CharacterRepository.java @@ -0,0 +1,11 @@ +package mate.academy.rickandmorty.repository; + +import java.util.List; +import mate.academy.rickandmorty.model.Character; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +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..948cb241 --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/service/CharacterService.java @@ -0,0 +1,10 @@ +package mate.academy.rickandmorty.service; + +import java.util.List; +import mate.academy.rickandmorty.dto.internal.CharacterDto; + +public interface CharacterService { + CharacterDto getRandomCharacter(); + + List searchCharacters(String name); +} diff --git a/src/main/java/mate/academy/rickandmorty/service/CharactersClient.java b/src/main/java/mate/academy/rickandmorty/service/CharactersClient.java new file mode 100644 index 00000000..2c401101 --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/service/CharactersClient.java @@ -0,0 +1,50 @@ +package mate.academy.rickandmorty.service; + +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 mate.academy.rickandmorty.dto.external.CharacterInfoDto; +import mate.academy.rickandmorty.dto.external.CharacterResponseDto; +import org.springframework.stereotype.Component; + +@Component +public class CharactersClient { + private static final String BASE_URL = "https://rickandmortyapi.com/api/character"; + private final HttpClient httpClient = HttpClient.newHttpClient(); + private final ObjectMapper objectMapper = new ObjectMapper(); + + public List loadCharacters() { + try { + var characterResponseDto = getCharactersFromResponse(BASE_URL); + var characters = new ArrayList<>(characterResponseDto.results()); + String nextPageUrl = characterResponseDto.info().next(); + while (nextPageUrl != null) { + characterResponseDto = getCharactersFromResponse(nextPageUrl); + characters.addAll(characterResponseDto.results()); + nextPageUrl = characterResponseDto.info().next(); + } + return characters; + } catch (URISyntaxException | IOException | InterruptedException e) { + throw new RuntimeException("Retrieving characters failed", e); + } + } + + private CharacterResponseDto getCharactersFromResponse(String url) + throws URISyntaxException, IOException, InterruptedException { + HttpRequest request; + request = HttpRequest.newBuilder() + .GET() + .uri(new URI(url)) + .build(); + HttpResponse response = httpClient.send( + request, HttpResponse.BodyHandlers.ofString() + ); + return objectMapper.readValue(response.body(), CharacterResponseDto.class); + } +} diff --git a/src/main/java/mate/academy/rickandmorty/service/impl/CharacterServiceImpl.java b/src/main/java/mate/academy/rickandmorty/service/impl/CharacterServiceImpl.java new file mode 100644 index 00000000..6b7375bd --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/service/impl/CharacterServiceImpl.java @@ -0,0 +1,48 @@ +package mate.academy.rickandmorty.service.impl; + +import jakarta.annotation.PostConstruct; +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; +import lombok.RequiredArgsConstructor; +import mate.academy.rickandmorty.dto.internal.CharacterDto; +import mate.academy.rickandmorty.mapper.CharacterMapper; +import mate.academy.rickandmorty.model.Character; +import mate.academy.rickandmorty.repository.CharacterRepository; +import mate.academy.rickandmorty.service.CharacterService; +import mate.academy.rickandmorty.service.CharactersClient; +import org.springframework.stereotype.Service; + +@RequiredArgsConstructor +@Service +public class CharacterServiceImpl implements CharacterService { + private final CharacterRepository characterRepository; + private final CharactersClient charactersClient; + private final CharacterMapper characterMapper; + + @PostConstruct + public void initCharacters() { + List list = charactersClient.loadCharacters().stream() + .map(characterMapper::toCharacter) + .toList(); + characterRepository.saveAll(list); + } + + @Override + public CharacterDto getRandomCharacter() { + List characters = characterRepository.findAll(); + return characterMapper.toDto( + characters.get(ThreadLocalRandom + .current() + .nextInt(characters.size()) + ) + ); + } + + @Override + public List searchCharacters(String name) { + return characterRepository.findAllByNameContainingIgnoreCase(name) + .stream() + .map(characterMapper::toDto) + .toList(); + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 8b137891..b25889d2 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1,7 @@ - +server.port=8081 +spring.datasource.url=jdbc:mysql://localhost:3306/rick_and_morty?serverTimezone=UTC +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver +spring.datasource.username=root +spring.datasource.password=611001 +spring.jpa.hibernate.ddl-auto=create-drop +spring.jpa.show-sql=true diff --git a/src/test/java/mate/academy/rickandmorty/ApplicationTests.java b/src/test/java/mate/academy/rickandmorty/ApplicationTests.java index 8fec6af0..e7b5f7bd 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() { + } } diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties index bc2fdde8..ffa91698 100644 --- a/src/test/resources/application.properties +++ b/src/test/resources/application.properties @@ -1,3 +1,4 @@ +server.port=8081 spring.datasource.url=jdbc:h2:mem:testdb spring.datasource.driverClassName=org.h2.Driver spring.datasource.username=sa From c42cdb3a1840b42c9abeda4595c026f4b66bceba Mon Sep 17 00:00:00 2001 From: Maksym Fedorenko Date: Wed, 18 Oct 2023 22:31:48 +0300 Subject: [PATCH 2/4] added few fixes --- ...rsClient.java => CharactersClientService.java} | 15 +++++++-------- .../service/impl/CharacterServiceImpl.java | 4 ++-- 2 files changed, 9 insertions(+), 10 deletions(-) rename src/main/java/mate/academy/rickandmorty/service/{CharactersClient.java => CharactersClientService.java} (81%) diff --git a/src/main/java/mate/academy/rickandmorty/service/CharactersClient.java b/src/main/java/mate/academy/rickandmorty/service/CharactersClientService.java similarity index 81% rename from src/main/java/mate/academy/rickandmorty/service/CharactersClient.java rename to src/main/java/mate/academy/rickandmorty/service/CharactersClientService.java index 2c401101..3812c3f4 100644 --- a/src/main/java/mate/academy/rickandmorty/service/CharactersClient.java +++ b/src/main/java/mate/academy/rickandmorty/service/CharactersClientService.java @@ -11,21 +11,21 @@ import java.util.List; import mate.academy.rickandmorty.dto.external.CharacterInfoDto; import mate.academy.rickandmorty.dto.external.CharacterResponseDto; -import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; -@Component -public class CharactersClient { +@Service +public class CharactersClientService { private static final String BASE_URL = "https://rickandmortyapi.com/api/character"; private final HttpClient httpClient = HttpClient.newHttpClient(); private final ObjectMapper objectMapper = new ObjectMapper(); public List loadCharacters() { try { - var characterResponseDto = getCharactersFromResponse(BASE_URL); + var characterResponseDto = retrieveResponse(BASE_URL); var characters = new ArrayList<>(characterResponseDto.results()); String nextPageUrl = characterResponseDto.info().next(); while (nextPageUrl != null) { - characterResponseDto = getCharactersFromResponse(nextPageUrl); + characterResponseDto = retrieveResponse(nextPageUrl); characters.addAll(characterResponseDto.results()); nextPageUrl = characterResponseDto.info().next(); } @@ -35,10 +35,9 @@ public List loadCharacters() { } } - private CharacterResponseDto getCharactersFromResponse(String url) + private CharacterResponseDto retrieveResponse(String url) throws URISyntaxException, IOException, InterruptedException { - HttpRequest request; - request = HttpRequest.newBuilder() + HttpRequest request = HttpRequest.newBuilder() .GET() .uri(new URI(url)) .build(); diff --git a/src/main/java/mate/academy/rickandmorty/service/impl/CharacterServiceImpl.java b/src/main/java/mate/academy/rickandmorty/service/impl/CharacterServiceImpl.java index 6b7375bd..33ad7a0b 100644 --- a/src/main/java/mate/academy/rickandmorty/service/impl/CharacterServiceImpl.java +++ b/src/main/java/mate/academy/rickandmorty/service/impl/CharacterServiceImpl.java @@ -9,14 +9,14 @@ import mate.academy.rickandmorty.model.Character; import mate.academy.rickandmorty.repository.CharacterRepository; import mate.academy.rickandmorty.service.CharacterService; -import mate.academy.rickandmorty.service.CharactersClient; +import mate.academy.rickandmorty.service.CharactersClientService; import org.springframework.stereotype.Service; @RequiredArgsConstructor @Service public class CharacterServiceImpl implements CharacterService { private final CharacterRepository characterRepository; - private final CharactersClient charactersClient; + private final CharactersClientService charactersClient; private final CharacterMapper characterMapper; @PostConstruct From f0da887f14d765237f4d3e299d62cd901c77c228 Mon Sep 17 00:00:00 2001 From: Maksym Fedorenko Date: Thu, 19 Oct 2023 21:22:39 +0300 Subject: [PATCH 3/4] replaced logic of character random generation --- pom.xml | 7 --- .../controller/CharacterController.java | 7 ++- .../dto/external/CharacterInfoDto.java | 6 ++- .../dto/external/CharacterResponseDto.java | 3 +- .../dto/external/MetaInfoDto.java | 6 ++- .../dto/internal/CharacterDto.java | 7 ++- .../academy/rickandmorty/model/Character.java | 2 +- .../repository/CharacterRepository.java | 3 +- .../service/CharacterService.java | 3 +- .../service/CharactersClient.java | 37 ++++++++++++++ .../service/CharactersClientService.java | 49 ------------------- .../service/impl/CharacterServiceImpl.java | 33 ++++++++----- src/main/resources/application.properties | 1 + src/test/resources/application.properties | 1 + 14 files changed, 84 insertions(+), 81 deletions(-) create mode 100644 src/main/java/mate/academy/rickandmorty/service/CharactersClient.java delete mode 100644 src/main/java/mate/academy/rickandmorty/service/CharactersClientService.java diff --git a/pom.xml b/pom.xml index 8c76700d..c6b2acd0 100644 --- a/pom.xml +++ b/pom.xml @@ -50,13 +50,6 @@ h2 - - org.springframework.boot - spring-boot-devtools - runtime - true - - org.projectlombok lombok diff --git a/src/main/java/mate/academy/rickandmorty/controller/CharacterController.java b/src/main/java/mate/academy/rickandmorty/controller/CharacterController.java index cca4860a..248ee2b0 100644 --- a/src/main/java/mate/academy/rickandmorty/controller/CharacterController.java +++ b/src/main/java/mate/academy/rickandmorty/controller/CharacterController.java @@ -6,6 +6,8 @@ import lombok.RequiredArgsConstructor; import mate.academy.rickandmorty.dto.internal.CharacterDto; import mate.academy.rickandmorty.service.CharacterService; +import org.springframework.data.domain.Pageable; +import org.springframework.data.web.PageableDefault; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; @@ -34,7 +36,8 @@ public CharacterDto getRandom() { description = "Search all characters whose name contains the search string." ) @GetMapping("/search") - public List searchByName(@RequestParam String name) { - return characterService.searchCharacters(name); + public List searchByName( + @RequestParam String name, @PageableDefault(sort = "id", size = 5) Pageable pageable) { + return characterService.searchCharacters(name, pageable); } } diff --git a/src/main/java/mate/academy/rickandmorty/dto/external/CharacterInfoDto.java b/src/main/java/mate/academy/rickandmorty/dto/external/CharacterInfoDto.java index 300bafa3..dd1752e3 100644 --- a/src/main/java/mate/academy/rickandmorty/dto/external/CharacterInfoDto.java +++ b/src/main/java/mate/academy/rickandmorty/dto/external/CharacterInfoDto.java @@ -5,6 +5,8 @@ @JsonIgnoreProperties(ignoreUnknown = true) public record CharacterInfoDto( - @JsonProperty("id") Long externalId, String name, String status, String gender -) { + @JsonProperty("id") Long externalId, + String name, + String status, + String gender) { } diff --git a/src/main/java/mate/academy/rickandmorty/dto/external/CharacterResponseDto.java b/src/main/java/mate/academy/rickandmorty/dto/external/CharacterResponseDto.java index 7246322f..21aff382 100644 --- a/src/main/java/mate/academy/rickandmorty/dto/external/CharacterResponseDto.java +++ b/src/main/java/mate/academy/rickandmorty/dto/external/CharacterResponseDto.java @@ -3,6 +3,5 @@ import java.util.List; public record CharacterResponseDto( - MetaInfoDto info, List results -) { + MetaInfoDto info, List results) { } diff --git a/src/main/java/mate/academy/rickandmorty/dto/external/MetaInfoDto.java b/src/main/java/mate/academy/rickandmorty/dto/external/MetaInfoDto.java index 9a87d308..4ac96f53 100644 --- a/src/main/java/mate/academy/rickandmorty/dto/external/MetaInfoDto.java +++ b/src/main/java/mate/academy/rickandmorty/dto/external/MetaInfoDto.java @@ -1,6 +1,8 @@ package mate.academy.rickandmorty.dto.external; public record MetaInfoDto( - Integer count, Integer pages, String next, String prev -) { + Integer count, + Integer pages, + String next, + String prev) { } diff --git a/src/main/java/mate/academy/rickandmorty/dto/internal/CharacterDto.java b/src/main/java/mate/academy/rickandmorty/dto/internal/CharacterDto.java index d6312fad..ac41ca26 100644 --- a/src/main/java/mate/academy/rickandmorty/dto/internal/CharacterDto.java +++ b/src/main/java/mate/academy/rickandmorty/dto/internal/CharacterDto.java @@ -1,6 +1,9 @@ package mate.academy.rickandmorty.dto.internal; public record CharacterDto( - Long id, Long externalId, String name, String status, String gender -) { + Long id, + Long externalId, + String name, + String status, + String gender) { } diff --git a/src/main/java/mate/academy/rickandmorty/model/Character.java b/src/main/java/mate/academy/rickandmorty/model/Character.java index 6f8e1649..6d2b500a 100644 --- a/src/main/java/mate/academy/rickandmorty/model/Character.java +++ b/src/main/java/mate/academy/rickandmorty/model/Character.java @@ -16,7 +16,7 @@ public class Character { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @Column(nullable = false) + @Column(nullable = false, unique = true) private Long externalId; @Column(nullable = false) diff --git a/src/main/java/mate/academy/rickandmorty/repository/CharacterRepository.java b/src/main/java/mate/academy/rickandmorty/repository/CharacterRepository.java index 6a435cff..c7f22578 100644 --- a/src/main/java/mate/academy/rickandmorty/repository/CharacterRepository.java +++ b/src/main/java/mate/academy/rickandmorty/repository/CharacterRepository.java @@ -2,10 +2,11 @@ import java.util.List; import mate.academy.rickandmorty.model.Character; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository public interface CharacterRepository extends JpaRepository { - List findAllByNameContainingIgnoreCase(String name); + List findAllByNameContainingIgnoreCase(String name, Pageable pageable); } diff --git a/src/main/java/mate/academy/rickandmorty/service/CharacterService.java b/src/main/java/mate/academy/rickandmorty/service/CharacterService.java index 948cb241..62541d10 100644 --- a/src/main/java/mate/academy/rickandmorty/service/CharacterService.java +++ b/src/main/java/mate/academy/rickandmorty/service/CharacterService.java @@ -2,9 +2,10 @@ import java.util.List; import mate.academy.rickandmorty.dto.internal.CharacterDto; +import org.springframework.data.domain.Pageable; public interface CharacterService { CharacterDto getRandomCharacter(); - List searchCharacters(String name); + List searchCharacters(String name, Pageable pageable); } diff --git a/src/main/java/mate/academy/rickandmorty/service/CharactersClient.java b/src/main/java/mate/academy/rickandmorty/service/CharactersClient.java new file mode 100644 index 00000000..9e5a8a73 --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/service/CharactersClient.java @@ -0,0 +1,37 @@ +package mate.academy.rickandmorty.service; + +import java.util.ArrayList; +import java.util.List; +import lombok.RequiredArgsConstructor; +import mate.academy.rickandmorty.dto.external.CharacterInfoDto; +import mate.academy.rickandmorty.dto.external.CharacterResponseDto; +import mate.academy.rickandmorty.exception.RetrieveDataException; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; + +@Component +@RequiredArgsConstructor +public class CharactersClient { + @Value("${rick-and-morty.api.url}") + private String apiUrl; + private final RestTemplate restTemplate; + + public List loadCharacters() { + try { + var characterResponseDto = restTemplate + .getForObject(apiUrl, CharacterResponseDto.class); + var characters = new ArrayList<>(characterResponseDto.results()); + String nextPageUrl = characterResponseDto.info().next(); + while (nextPageUrl != null) { + characterResponseDto = restTemplate + .getForObject(nextPageUrl, CharacterResponseDto.class); + characters.addAll(characterResponseDto.results()); + nextPageUrl = characterResponseDto.info().next(); + } + return characters; + } catch (Exception e) { + throw new RetrieveDataException("Retrieving characters failed", e); + } + } +} diff --git a/src/main/java/mate/academy/rickandmorty/service/CharactersClientService.java b/src/main/java/mate/academy/rickandmorty/service/CharactersClientService.java deleted file mode 100644 index 3812c3f4..00000000 --- a/src/main/java/mate/academy/rickandmorty/service/CharactersClientService.java +++ /dev/null @@ -1,49 +0,0 @@ -package mate.academy.rickandmorty.service; - -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 mate.academy.rickandmorty.dto.external.CharacterInfoDto; -import mate.academy.rickandmorty.dto.external.CharacterResponseDto; -import org.springframework.stereotype.Service; - -@Service -public class CharactersClientService { - private static final String BASE_URL = "https://rickandmortyapi.com/api/character"; - private final HttpClient httpClient = HttpClient.newHttpClient(); - private final ObjectMapper objectMapper = new ObjectMapper(); - - public List loadCharacters() { - try { - var characterResponseDto = retrieveResponse(BASE_URL); - var characters = new ArrayList<>(characterResponseDto.results()); - String nextPageUrl = characterResponseDto.info().next(); - while (nextPageUrl != null) { - characterResponseDto = retrieveResponse(nextPageUrl); - characters.addAll(characterResponseDto.results()); - nextPageUrl = characterResponseDto.info().next(); - } - return characters; - } catch (URISyntaxException | IOException | InterruptedException e) { - throw new RuntimeException("Retrieving characters failed", e); - } - } - - private CharacterResponseDto retrieveResponse(String url) - throws URISyntaxException, IOException, InterruptedException { - HttpRequest request = HttpRequest.newBuilder() - .GET() - .uri(new URI(url)) - .build(); - HttpResponse response = httpClient.send( - request, HttpResponse.BodyHandlers.ofString() - ); - return objectMapper.readValue(response.body(), CharacterResponseDto.class); - } -} diff --git a/src/main/java/mate/academy/rickandmorty/service/impl/CharacterServiceImpl.java b/src/main/java/mate/academy/rickandmorty/service/impl/CharacterServiceImpl.java index 33ad7a0b..fa7ab1e2 100644 --- a/src/main/java/mate/academy/rickandmorty/service/impl/CharacterServiceImpl.java +++ b/src/main/java/mate/academy/rickandmorty/service/impl/CharacterServiceImpl.java @@ -2,21 +2,28 @@ import jakarta.annotation.PostConstruct; import java.util.List; -import java.util.concurrent.ThreadLocalRandom; +import java.util.Optional; import lombok.RequiredArgsConstructor; import mate.academy.rickandmorty.dto.internal.CharacterDto; +import mate.academy.rickandmorty.exception.CharacterNotFoundException; import mate.academy.rickandmorty.mapper.CharacterMapper; import mate.academy.rickandmorty.model.Character; import mate.academy.rickandmorty.repository.CharacterRepository; import mate.academy.rickandmorty.service.CharacterService; -import mate.academy.rickandmorty.service.CharactersClientService; +import mate.academy.rickandmorty.service.CharactersClient; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.web.PageableDefault; import org.springframework.stereotype.Service; @RequiredArgsConstructor @Service public class CharacterServiceImpl implements CharacterService { + private static final int PAGE_SIZE = 1; + private static final int FIRST_INDEX = 0; private final CharacterRepository characterRepository; - private final CharactersClientService charactersClient; + private final CharactersClient charactersClient; private final CharacterMapper characterMapper; @PostConstruct @@ -29,18 +36,20 @@ public void initCharacters() { @Override public CharacterDto getRandomCharacter() { - List characters = characterRepository.findAll(); - return characterMapper.toDto( - characters.get(ThreadLocalRandom - .current() - .nextInt(characters.size()) - ) - ); + long numberOfCharacters = characterRepository.count(); + int pageNumber = (int)(Math.random() * numberOfCharacters); + Page page = characterRepository.findAll(PageRequest.of(pageNumber, PAGE_SIZE)); + return Optional.ofNullable(page.getContent().get(FIRST_INDEX)) + .map(characterMapper::toDto) + .orElseThrow(() -> new CharacterNotFoundException( + "Can't generate random character") + ); } @Override - public List searchCharacters(String name) { - return characterRepository.findAllByNameContainingIgnoreCase(name) + public List searchCharacters( + String name, @PageableDefault(size = 5, sort = "id") Pageable pageable) { + return characterRepository.findAllByNameContainingIgnoreCase(name, pageable) .stream() .map(characterMapper::toDto) .toList(); diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index b25889d2..250df704 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -5,3 +5,4 @@ spring.datasource.username=root spring.datasource.password=611001 spring.jpa.hibernate.ddl-auto=create-drop spring.jpa.show-sql=true +rick-and-morty.api.url=https://rickandmortyapi.com/api/character diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties index ffa91698..cae0cc4a 100644 --- a/src/test/resources/application.properties +++ b/src/test/resources/application.properties @@ -4,3 +4,4 @@ spring.datasource.driverClassName=org.h2.Driver spring.datasource.username=sa spring.datasource.password=password spring.jpa.database-platform=org.hibernate.dialect.H2Dialect +rick-and-morty.api.url=https://rickandmortyapi.com/api/character From b76859404cefbf33438c22ac22bc1711ab5713e7 Mon Sep 17 00:00:00 2001 From: Maksym Fedorenko Date: Thu, 19 Oct 2023 21:23:44 +0300 Subject: [PATCH 4/4] replaced logic of character random generation --- .../academy/rickandmorty/config/WebAppConfig.java | 13 +++++++++++++ .../exception/CharacterNotFoundException.java | 7 +++++++ .../exception/RetrieveDataException.java | 7 +++++++ 3 files changed, 27 insertions(+) create mode 100644 src/main/java/mate/academy/rickandmorty/config/WebAppConfig.java create mode 100644 src/main/java/mate/academy/rickandmorty/exception/CharacterNotFoundException.java create mode 100644 src/main/java/mate/academy/rickandmorty/exception/RetrieveDataException.java diff --git a/src/main/java/mate/academy/rickandmorty/config/WebAppConfig.java b/src/main/java/mate/academy/rickandmorty/config/WebAppConfig.java new file mode 100644 index 00000000..3533de52 --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/config/WebAppConfig.java @@ -0,0 +1,13 @@ +package mate.academy.rickandmorty.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestTemplate; + +@Configuration +public class WebAppConfig { + @Bean + public RestTemplate restTemplate() { + return new RestTemplate(); + } +} diff --git a/src/main/java/mate/academy/rickandmorty/exception/CharacterNotFoundException.java b/src/main/java/mate/academy/rickandmorty/exception/CharacterNotFoundException.java new file mode 100644 index 00000000..b1f9f5e2 --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/exception/CharacterNotFoundException.java @@ -0,0 +1,7 @@ +package mate.academy.rickandmorty.exception; + +public class CharacterNotFoundException extends RuntimeException { + public CharacterNotFoundException(String message) { + super(message); + } +} diff --git a/src/main/java/mate/academy/rickandmorty/exception/RetrieveDataException.java b/src/main/java/mate/academy/rickandmorty/exception/RetrieveDataException.java new file mode 100644 index 00000000..e1a03ab5 --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/exception/RetrieveDataException.java @@ -0,0 +1,7 @@ +package mate.academy.rickandmorty.exception; + +public class RetrieveDataException extends RuntimeException { + public RetrieveDataException(String message, Throwable cause) { + super(message, cause); + } +}