From b7a9e96415d5b6e5f825eddc4eef6ac2bb65fb42 Mon Sep 17 00:00:00 2001 From: Oleksii Date: Sun, 15 Sep 2024 00:43:02 +0300 Subject: [PATCH] Solved --- checkstyle.xml | 247 ++++++++++++++++++ pom.xml | 191 +++++++++----- .../rickandmorty/config/MapperConfig.java | 13 + .../controller/CharacterController.java | 38 +++ .../external/CharacterResponseDataDto.java | 12 + .../dto/external/CharacterResponseDto.java | 13 + .../dto/external/PageInfoDto.java | 9 + .../dto/internal/CharacterDto.java | 10 + .../rickandmorty/mapper/CharacterMapper.java | 21 ++ .../academy/rickandmorty/model/Character.java | 23 ++ .../repo/CharacterRepository.java | 9 + .../rickandmorty/service/CharacterClient.java | 53 ++++ .../service/CharacterService.java | 13 + .../service/impl/CharacterServiceImpl.java | 54 ++++ src/main/resources/application.properties | 8 +- .../rickandmorty/ApplicationTests.java | 6 +- 16 files changed, 650 insertions(+), 70 deletions(-) create mode 100644 checkstyle.xml 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/CharacterResponseDataDto.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/PageInfoDto.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/repo/CharacterRepository.java create mode 100644 src/main/java/mate/academy/rickandmorty/service/CharacterClient.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 diff --git a/checkstyle.xml b/checkstyle.xml new file mode 100644 index 00000000..89553b50 --- /dev/null +++ b/checkstyle.xml @@ -0,0 +1,247 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index 0c754f19..cdf6af71 100644 --- a/pom.xml +++ b/pom.xml @@ -1,74 +1,133 @@ - 4.0.0 - - org.springframework.boot - spring-boot-starter-parent - 3.1.4 - - - mate.academy - jv-rick-and-morty - 0.0.1-SNAPSHOT - jv-rick-and-morty - jv-rick-and-morty - - 17 - 3.1.1 - - https://raw.githubusercontent.com/mate-academy/style-guides/master/java/checkstyle.xml - - - - - org.springframework.boot - spring-boot-starter - + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.1.4 + + + mate.academy + jv-rick-and-morty + 0.0.1-SNAPSHOT + jv-rick-and-morty + jv-rick-and-morty + + 17 + 0.2.0 + 3.1.1 + 1.5.5.Final + checkstyle.xml + + + + org.springframework.boot + spring-boot-starter + - - org.springframework.boot - spring-boot-starter-test - test - + + org.springframework.boot + spring-boot-starter-web + - - org.springframework.boot - spring-boot-starter-data-jpa - + + org.springframework.boot + spring-boot-starter-test + test + - - com.h2database - h2 - - + + org.springframework.boot + spring-boot-starter-data-jpa + - - - - org.springframework.boot - spring-boot-maven-plugin - - - org.apache.maven.plugins - maven-checkstyle-plugin - 3.3.0 - - - compile - - check - - - - - ${maven.checkstyle.plugin.configLocation} - true - true - false - - - - + + com.h2database + h2 + + + + mysql + mysql-connector-java + 8.0.33 + + + + org.projectlombok + lombok + + + + org.mapstruct + mapstruct + ${mapstruct.version} + + + + org.mapstruct + mapstruct-processor + ${mapstruct.version} + + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + 2.1.0 + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-checkstyle-plugin + 3.3.0 + + + compile + + check + + + + + ${maven.checkstyle.plugin.configLocation} + true + true + false + src + + + + 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} + + + + + + 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..ec1e1ab4 --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/controller/CharacterController.java @@ -0,0 +1,38 @@ +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 = "Character management", description = "Endpoints for managing characters") +@RequiredArgsConstructor +@RestController +@RequestMapping("/character") +public class CharacterController { + private final CharacterService characterService; + + @Operation( + summary = "Get random character", + description = "Get available character by random id" + ) + @GetMapping("/random") + public CharacterDto getRandomCharacter() { + return characterService.getRandomCharacter(); + } + + @Operation( + summary = "Search character", + description = "Find all available characters by part of character's name" + ) + @GetMapping("/search") + public List search(@RequestParam String partName) { + return characterService.findByPartName(partName); + } +} 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..e310c6c0 --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/dto/external/CharacterResponseDataDto.java @@ -0,0 +1,12 @@ +package mate.academy.rickandmorty.dto.external; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; + +public record CharacterResponseDataDto( + @JsonProperty("info") + PageInfoDto pageInfoDto, + @JsonProperty("results") + List responseDtos +) { +} 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..f03ee791 --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/dto/external/CharacterResponseDto.java @@ -0,0 +1,13 @@ +package mate.academy.rickandmorty.dto.external; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +@Data +public class CharacterResponseDto { + @JsonProperty("id") + private Long externalId; + private String name; + private String status; + private String gender; +} diff --git a/src/main/java/mate/academy/rickandmorty/dto/external/PageInfoDto.java b/src/main/java/mate/academy/rickandmorty/dto/external/PageInfoDto.java new file mode 100644 index 00000000..9e330409 --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/dto/external/PageInfoDto.java @@ -0,0 +1,9 @@ +package mate.academy.rickandmorty.dto.external; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public record PageInfoDto( + @JsonProperty("next") + String nextPage +) { +} 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..0d70d8a4 --- /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, + 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..cc62952d --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/mapper/CharacterMapper.java @@ -0,0 +1,21 @@ +package mate.academy.rickandmorty.mapper; + +import java.util.List; +import mate.academy.rickandmorty.config.MapperConfig; +import mate.academy.rickandmorty.dto.external.CharacterResponseDto; +import mate.academy.rickandmorty.dto.internal.CharacterDto; +import mate.academy.rickandmorty.model.Character; +import org.mapstruct.Mapper; + +@Mapper(config = MapperConfig.class) +public interface CharacterMapper { + CharacterDto toDto(Character character); + + List toDtoList(List characters); + + Character toModel(CharacterDto characterDto); + + Character toModel(CharacterResponseDto characterResponseDto); + + List toModelList(List characterResponseDtos); +} 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..d4c68358 --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/model/Character.java @@ -0,0 +1,23 @@ +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.Getter; +import lombok.Setter; + +@Getter +@Setter +@Entity +@Table(name = "characters") +public class Character { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + private Long externalId; + private String name; + private String status; + private String gender; +} diff --git a/src/main/java/mate/academy/rickandmorty/repo/CharacterRepository.java b/src/main/java/mate/academy/rickandmorty/repo/CharacterRepository.java new file mode 100644 index 00000000..24cf8fd6 --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/repo/CharacterRepository.java @@ -0,0 +1,9 @@ +package mate.academy.rickandmorty.repo; + +import java.util.List; +import mate.academy.rickandmorty.model.Character; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface CharacterRepository extends JpaRepository { + List findByNameContainingIgnoreCase(String partName); +} diff --git a/src/main/java/mate/academy/rickandmorty/service/CharacterClient.java b/src/main/java/mate/academy/rickandmorty/service/CharacterClient.java new file mode 100644 index 00000000..dcfb4da0 --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/service/CharacterClient.java @@ -0,0 +1,53 @@ +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.ArrayList; +import java.util.List; +import lombok.RequiredArgsConstructor; +import mate.academy.rickandmorty.dto.external.CharacterResponseDataDto; +import mate.academy.rickandmorty.dto.external.CharacterResponseDto; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class CharacterClient { + private static final String BASE_URL = "https://rickandmortyapi.com/api/character?page=%d"; + private static final int PAGE_NUMBER = 42; + private final ObjectMapper objectMapper; + + public List getDataFromApi() { + List characterResponseDtoList = new ArrayList<>(); + HttpClient httpClient = HttpClient.newHttpClient(); + + int currentPage = 1; + + while (currentPage <= PAGE_NUMBER) { + String url = BASE_URL.formatted(currentPage); + + HttpRequest httpRequest = HttpRequest.newBuilder() + .GET() + .uri(URI.create(url)) + .build(); + try { + HttpResponse response = httpClient + .send(httpRequest, HttpResponse.BodyHandlers.ofString()); + + CharacterResponseDataDto responseData = objectMapper.readValue( + response.body(), + CharacterResponseDataDto.class + ); + characterResponseDtoList.addAll(0, responseData.responseDtos()); + } catch (IOException | InterruptedException e) { + throw new RuntimeException(e); + } + currentPage++; + } + + return characterResponseDtoList; + } +} 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..b42c0997 --- /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.CharacterResponseDto; +import mate.academy.rickandmorty.dto.internal.CharacterDto; + +public interface CharacterService { + CharacterDto save(CharacterResponseDto characterResponseDto); + + CharacterDto getRandomCharacter(); + + List findByPartName(String partName); +} 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..36b6daa5 --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/service/impl/CharacterServiceImpl.java @@ -0,0 +1,54 @@ +package mate.academy.rickandmorty.service.impl; + +import jakarta.annotation.PostConstruct; +import jakarta.persistence.EntityNotFoundException; +import java.util.List; +import java.util.Random; +import lombok.RequiredArgsConstructor; +import mate.academy.rickandmorty.dto.external.CharacterResponseDto; +import mate.academy.rickandmorty.dto.internal.CharacterDto; +import mate.academy.rickandmorty.mapper.CharacterMapper; +import mate.academy.rickandmorty.model.Character; +import mate.academy.rickandmorty.repo.CharacterRepository; +import mate.academy.rickandmorty.service.CharacterClient; +import mate.academy.rickandmorty.service.CharacterService; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class CharacterServiceImpl implements CharacterService { + private static final Random RANDOM = new Random(); + private final CharacterRepository characterRepository; + private final CharacterMapper characterMapper; + private final CharacterClient characterClient; + + @Override + public CharacterDto save(CharacterResponseDto characterResponseDto) { + Character character = characterMapper.toModel(characterResponseDto); + return characterMapper.toDto(characterRepository.save(character)); + } + + @Override + public CharacterDto getRandomCharacter() { + Long randomId = RANDOM.nextLong(characterRepository.count()); + Character character = characterRepository.findById(randomId).orElseThrow( + () -> new EntityNotFoundException("Can't find character by id: " + randomId) + ); + return characterMapper.toDto(character); + } + + @Override + public List findByPartName(String partName) { + return characterMapper.toDtoList( + characterRepository + .findByNameContainingIgnoreCase(partName) + ); + } + + @PostConstruct + public void init() { + List characters + = characterMapper.toModelList(characterClient.getDataFromApi()); + characterRepository.saveAll(characters); + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 8b137891..56d44376 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1,7 @@ - +spring.datasource.url=jdbc:mysql://localhost:3306/rick-and-morty?createDatabaseIfNotExist=true +spring.datasource.username=root +spring.datasource.password=root +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 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() { + } }