From 796e61141a9954cc6a85e34c087729b5de6338a4 Mon Sep 17 00:00:00 2001 From: Andrii Mikhaylovskiy Date: Thu, 16 May 2024 20:05:15 +0300 Subject: [PATCH 1/2] -task solution/mark.1 --- pom.xml | 58 ++++++++++++++++++- .../rickandmorty/config/MapperConfig.java | 13 +++++ .../controller/CharacterController.java | 33 +++++++++++ .../dto/external/CharacterInfoDataDto.java | 9 +++ .../external/CharacterResponseDataDto.java | 9 +++ .../external/CreateCharacterRequestDto.java | 12 ++++ .../dto/internal/CharacterDto.java | 10 ++++ .../CanNotGetDataFromExternalApi.java | 7 +++ .../rickandmorty/mapper/CharacterMapper.java | 14 +++++ .../rickandmorty/model/CharacterModel.java | 21 +++++++ .../repository/CharacterRepository.java | 13 +++++ .../service/CharacterService.java | 13 +++++ .../service/impl/CharacterServiceImpl.java | 39 +++++++++++++ .../service/impl/CharactersClient.java | 53 +++++++++++++++++ src/main/resources/application.properties | 8 +++ .../rickandmorty/ApplicationTests.java | 7 +-- 16 files changed, 314 insertions(+), 5 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/CharacterInfoDataDto.java create mode 100644 src/main/java/mate/academy/rickandmorty/dto/external/CharacterResponseDataDto.java create mode 100644 src/main/java/mate/academy/rickandmorty/dto/external/CreateCharacterRequestDto.java create mode 100644 src/main/java/mate/academy/rickandmorty/dto/internal/CharacterDto.java create mode 100644 src/main/java/mate/academy/rickandmorty/exception/CanNotGetDataFromExternalApi.java create mode 100644 src/main/java/mate/academy/rickandmorty/mapper/CharacterMapper.java create mode 100644 src/main/java/mate/academy/rickandmorty/model/CharacterModel.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/impl/CharacterServiceImpl.java create mode 100644 src/main/java/mate/academy/rickandmorty/service/impl/CharactersClient.java diff --git a/pom.xml b/pom.xml index 0c754f19..b9ec51db 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ org.springframework.boot - spring-boot-starter + spring-boot-starter-web @@ -41,6 +41,34 @@ com.h2database h2 + + + org.springframework.boot + spring-boot-starter-validation + + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + 2.3.0 + + + + org.projectlombok + lombok + + + + org.mapstruct + mapstruct + 1.5.5.Final + + + + com.mysql + mysql-connector-j + 8.3.0 + @@ -66,6 +94,34 @@ true true false + src + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.5.1 + + ${java.version} + ${java.version} + + + org.mapstruct + mapstruct-processor + 1.5.5.Final + + + org.projectlombok + lombok + 1.18.28 + + + org.projectlombok + lombok-mapstruct-binding + 0.2.0 + + 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..450e58db --- /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..080a91c8 --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/controller/CharacterController.java @@ -0,0 +1,33 @@ +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 Marty character management", description = + "Endpoints for managing characters from Ricki and Marty") +@RestController +@RequiredArgsConstructor +@RequestMapping("/characters") +public class CharacterController { + private final CharacterService service; + + @Operation(summary = "Get character", description = "Get random character from db") + @GetMapping("/random") + public CharacterDto getRandomCharacter() { + return service.getRandomCharacter(); + } + + @Operation(summary = "Get list characters", description = "Get list characters by name from db") + @GetMapping() + public List getAllByName(@RequestParam String name) { + return service.getAllByName(name); + } +} diff --git a/src/main/java/mate/academy/rickandmorty/dto/external/CharacterInfoDataDto.java b/src/main/java/mate/academy/rickandmorty/dto/external/CharacterInfoDataDto.java new file mode 100644 index 00000000..798d2e9a --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/dto/external/CharacterInfoDataDto.java @@ -0,0 +1,9 @@ +package mate.academy.rickandmorty.dto.external; + +public record CharacterInfoDataDto( + Integer count, + Integer pages, + String next, + String prev +) { +} diff --git a/src/main/java/mate/academy/rickandmorty/dto/external/CharacterResponseDataDto.java b/src/main/java/mate/academy/rickandmorty/dto/external/CharacterResponseDataDto.java new file mode 100644 index 00000000..542cbdb8 --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/dto/external/CharacterResponseDataDto.java @@ -0,0 +1,9 @@ +package mate.academy.rickandmorty.dto.external; + +import java.util.List; + +public record CharacterResponseDataDto( + CharacterInfoDataDto info, + List results +) { +} diff --git a/src/main/java/mate/academy/rickandmorty/dto/external/CreateCharacterRequestDto.java b/src/main/java/mate/academy/rickandmorty/dto/external/CreateCharacterRequestDto.java new file mode 100644 index 00000000..59bbcd12 --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/dto/external/CreateCharacterRequestDto.java @@ -0,0 +1,12 @@ +package mate.academy.rickandmorty.dto.external; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public record CreateCharacterRequestDto( + @JsonProperty("id") + String externalId, + String name, + String status, + String gender +) { +} 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..e3e00952 --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/dto/internal/CharacterDto.java @@ -0,0 +1,10 @@ +package mate.academy.rickandmorty.dto.internal; + +public record CharacterDto( + Long id, + String externalId, + String name, + String status, + String gender +) { +} diff --git a/src/main/java/mate/academy/rickandmorty/exception/CanNotGetDataFromExternalApi.java b/src/main/java/mate/academy/rickandmorty/exception/CanNotGetDataFromExternalApi.java new file mode 100644 index 00000000..043d5a39 --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/exception/CanNotGetDataFromExternalApi.java @@ -0,0 +1,7 @@ +package mate.academy.rickandmorty.exception; + +public class CanNotGetDataFromExternalApi extends RuntimeException { + public CanNotGetDataFromExternalApi(String message, Exception e) { + super(message); + } +} 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..9c630a07 --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/mapper/CharacterMapper.java @@ -0,0 +1,14 @@ +package mate.academy.rickandmorty.mapper; + +import mate.academy.rickandmorty.config.MapperConfig; +import mate.academy.rickandmorty.dto.external.CreateCharacterRequestDto; +import mate.academy.rickandmorty.dto.internal.CharacterDto; +import mate.academy.rickandmorty.model.CharacterModel; +import org.mapstruct.Mapper; + +@Mapper(config = MapperConfig.class) +public interface CharacterMapper { + CharacterDto toDto(CharacterModel characterModel); + + CharacterModel toModel(CreateCharacterRequestDto requestDto); +} diff --git a/src/main/java/mate/academy/rickandmorty/model/CharacterModel.java b/src/main/java/mate/academy/rickandmorty/model/CharacterModel.java new file mode 100644 index 00000000..9c35f423 --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/model/CharacterModel.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 CharacterModel { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + private String externalId; + private String name; + private String status; + 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..06553f27 --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/repository/CharacterRepository.java @@ -0,0 +1,13 @@ +package mate.academy.rickandmorty.repository; + +import java.util.List; +import mate.academy.rickandmorty.model.CharacterModel; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +public interface CharacterRepository extends JpaRepository { + List findAllByNameContainingIgnoreCase(String name); + + @Query(value = "FROM CharacterModel ORDER BY RAND() LIMIT 1") + CharacterModel getRandomCharacter(); +} 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..00a1fd01 --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/service/CharacterService.java @@ -0,0 +1,13 @@ +package mate.academy.rickandmorty.service; + +import java.util.List; +import mate.academy.rickandmorty.dto.external.CreateCharacterRequestDto; +import mate.academy.rickandmorty.dto.internal.CharacterDto; + +public interface CharacterService { + CharacterDto getRandomCharacter(); + + List getAllByName(String name); + + CharacterDto save(CreateCharacterRequestDto requestDto); +} 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..a8734db6 --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/service/impl/CharacterServiceImpl.java @@ -0,0 +1,39 @@ +package mate.academy.rickandmorty.service.impl; + +import java.util.List; +import lombok.RequiredArgsConstructor; +import mate.academy.rickandmorty.dto.external.CreateCharacterRequestDto; +import mate.academy.rickandmorty.dto.internal.CharacterDto; +import mate.academy.rickandmorty.mapper.CharacterMapper; +import mate.academy.rickandmorty.model.CharacterModel; +import mate.academy.rickandmorty.repository.CharacterRepository; +import mate.academy.rickandmorty.service.CharacterService; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class CharacterServiceImpl implements CharacterService { + private final CharacterRepository repository; + private final CharacterMapper mapper; + + @Override + public CharacterDto getRandomCharacter() { + return mapper.toDto(repository.getRandomCharacter()); + } + + @Override + public List getAllByName(String name) { + return repository.findAllByNameContainingIgnoreCase(name) + .stream() + .map(mapper::toDto) + .toList(); + } + + @Override + public CharacterDto save(CreateCharacterRequestDto requestDto) { + CharacterModel characterModel = mapper.toModel(requestDto); + CharacterModel savedCharacterModel = repository.save(characterModel); + return mapper.toDto(savedCharacterModel); + } + +} diff --git a/src/main/java/mate/academy/rickandmorty/service/impl/CharactersClient.java b/src/main/java/mate/academy/rickandmorty/service/impl/CharactersClient.java new file mode 100644 index 00000000..47aa3f57 --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/service/impl/CharactersClient.java @@ -0,0 +1,53 @@ +package mate.academy.rickandmorty.service.impl; + +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.annotation.PostConstruct; +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import lombok.RequiredArgsConstructor; +import mate.academy.rickandmorty.dto.external.CharacterResponseDataDto; +import mate.academy.rickandmorty.exception.CanNotGetDataFromExternalApi; +import mate.academy.rickandmorty.service.CharacterService; +import org.springframework.stereotype.Component; + +@RequiredArgsConstructor +@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; + private final CharacterService characterService; + + @PostConstruct + public void init() { + getListWithExternalApi(); + } + + private void getListWithExternalApi() { + String url = BASE_URL; + while (url != null) { + HttpRequest request = HttpRequest.newBuilder() + .GET() + .uri(URI.create(url)) + .build(); + try { + HttpResponse response = httpClient.send( + request, + HttpResponse.BodyHandlers.ofString()); + CharacterResponseDataDto characterResponseDataDto = objectMapper.readValue( + response.body(), + CharacterResponseDataDto.class); + characterResponseDataDto.results() + .stream().map(createCharacterRequestDto -> + characterService.save(createCharacterRequestDto)) + .toList(); + url = characterResponseDataDto.info().next(); + } catch (IOException | InterruptedException e) { + throw new CanNotGetDataFromExternalApi("Can't get data from API", e); + } + } + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 8b137891..033d7197 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=Mikhay@987AO +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 +logging.level.org.springframework.orm.jpa.JpaTransactionManager=DEBUG diff --git a/src/test/java/mate/academy/rickandmorty/ApplicationTests.java b/src/test/java/mate/academy/rickandmorty/ApplicationTests.java index 8fec6af0..52ce9e71 100644 --- a/src/test/java/mate/academy/rickandmorty/ApplicationTests.java +++ b/src/test/java/mate/academy/rickandmorty/ApplicationTests.java @@ -6,8 +6,7 @@ @SpringBootTest class ApplicationTests { - @Test - void contextLoads() { - } - + @Test + void contextLoads() { + } } From a187f56e66607bb390e36aea6793678ad215d380 Mon Sep 17 00:00:00 2001 From: Andrii Mikhaylovskiy Date: Fri, 17 May 2024 21:34:25 +0300 Subject: [PATCH 2/2] -task solution/mark.2 --- .../rickandmorty/controller/CharacterController.java | 4 ++-- .../mate/academy/rickandmorty/model/CharacterModel.java | 6 ++++-- .../mate/academy/rickandmorty/service/CharacterService.java | 2 +- .../rickandmorty/service/impl/CharacterServiceImpl.java | 2 +- .../academy/rickandmorty/service/impl/CharactersClient.java | 3 +-- 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/main/java/mate/academy/rickandmorty/controller/CharacterController.java b/src/main/java/mate/academy/rickandmorty/controller/CharacterController.java index 080a91c8..27b4d101 100644 --- a/src/main/java/mate/academy/rickandmorty/controller/CharacterController.java +++ b/src/main/java/mate/academy/rickandmorty/controller/CharacterController.java @@ -27,7 +27,7 @@ public CharacterDto getRandomCharacter() { @Operation(summary = "Get list characters", description = "Get list characters by name from db") @GetMapping() - public List getAllByName(@RequestParam String name) { - return service.getAllByName(name); + public List findAllByName(@RequestParam String name) { + return service.findAllByName(name); } } diff --git a/src/main/java/mate/academy/rickandmorty/model/CharacterModel.java b/src/main/java/mate/academy/rickandmorty/model/CharacterModel.java index 9c35f423..af581cee 100644 --- a/src/main/java/mate/academy/rickandmorty/model/CharacterModel.java +++ b/src/main/java/mate/academy/rickandmorty/model/CharacterModel.java @@ -5,9 +5,11 @@ import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.Table; -import lombok.Data; +import lombok.Getter; +import lombok.Setter; -@Data +@Getter +@Setter @Entity @Table(name = "characters") public class CharacterModel { diff --git a/src/main/java/mate/academy/rickandmorty/service/CharacterService.java b/src/main/java/mate/academy/rickandmorty/service/CharacterService.java index 00a1fd01..094a6c4d 100644 --- a/src/main/java/mate/academy/rickandmorty/service/CharacterService.java +++ b/src/main/java/mate/academy/rickandmorty/service/CharacterService.java @@ -7,7 +7,7 @@ public interface CharacterService { CharacterDto getRandomCharacter(); - List getAllByName(String name); + List findAllByName(String name); CharacterDto save(CreateCharacterRequestDto requestDto); } 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 a8734db6..54a45daa 100644 --- a/src/main/java/mate/academy/rickandmorty/service/impl/CharacterServiceImpl.java +++ b/src/main/java/mate/academy/rickandmorty/service/impl/CharacterServiceImpl.java @@ -22,7 +22,7 @@ public CharacterDto getRandomCharacter() { } @Override - public List getAllByName(String name) { + public List findAllByName(String name) { return repository.findAllByNameContainingIgnoreCase(name) .stream() .map(mapper::toDto) diff --git a/src/main/java/mate/academy/rickandmorty/service/impl/CharactersClient.java b/src/main/java/mate/academy/rickandmorty/service/impl/CharactersClient.java index 47aa3f57..a0be0daa 100644 --- a/src/main/java/mate/academy/rickandmorty/service/impl/CharactersClient.java +++ b/src/main/java/mate/academy/rickandmorty/service/impl/CharactersClient.java @@ -41,8 +41,7 @@ private void getListWithExternalApi() { response.body(), CharacterResponseDataDto.class); characterResponseDataDto.results() - .stream().map(createCharacterRequestDto -> - characterService.save(createCharacterRequestDto)) + .stream().map(characterService::save) .toList(); url = characterResponseDataDto.info().next(); } catch (IOException | InterruptedException e) {