diff --git a/pom.xml b/pom.xml index 0c754f19..f18a4b0d 100644 --- a/pom.xml +++ b/pom.xml @@ -19,27 +19,57 @@ https://raw.githubusercontent.com/mate-academy/style-guides/master/java/checkstyle.xml + + 3.1.2 + 3.11.0 + 1.5.5.Final + 0.2.0 + org.springframework.boot spring-boot-starter - org.springframework.boot spring-boot-starter-test test - org.springframework.boot spring-boot-starter-data-jpa + + org.springframework.boot + spring-boot-starter-web + + + org.projectlombok + lombok + + + + com.mysql + mysql-connector-j + com.h2database h2 + test + + + + org.mapstruct + mapstruct + ${mapstruct.version} + + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + 2.1.0 @@ -48,6 +78,7 @@ org.springframework.boot spring-boot-maven-plugin + ${spring.boot.maven.plugin.version} org.apache.maven.plugins @@ -66,9 +97,40 @@ true true false + src + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven.compiler.plugin.version} + + ${java.version} + ${java.version} + + + org.projectlombok + lombok + ${lombok.version} + + + org.mapstruct + mapstruct-processor + ${mapstruct.version} + + + org.projectlombok + lombok-mapstruct-binding + ${lombok.mapstruct.binding.version} + + + + + -Amapstruct.unmappedTargetPolicy=IGNORE + + - diff --git a/src/main/java/mate/academy/rickandmorty/Application.java b/src/main/java/mate/academy/rickandmorty/Application.java index cdea84fc..581d3b4d 100644 --- a/src/main/java/mate/academy/rickandmorty/Application.java +++ b/src/main/java/mate/academy/rickandmorty/Application.java @@ -1,12 +1,36 @@ package mate.academy.rickandmorty; +import java.util.List; +import mate.academy.rickandmorty.dto.CharacterResponseDto; +import mate.academy.rickandmorty.service.CharacterService; +import mate.academy.rickandmorty.service.RickAndMortyClient; +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 RickAndMortyClient rickAndMortyClient; + private static CharacterService characterService; + + @Autowired + public Application(CharacterService service, RickAndMortyClient client) { + Application.rickAndMortyClient = client; + Application.characterService = service; + } public static void main(String[] args) { SpringApplication.run(Application.class, args); } + + @Bean + public CommandLineRunner commandLineRunnerBean() { + + return (args) -> { + List characters = rickAndMortyClient.getCharacters(); + characterService.saveAll(characters); + }; + } } 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..a1670551 --- /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.CharacterDto; +import mate.academy.rickandmorty.service.CharacterService; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@Tag(name = "Character management", description = "Endpoints for getting characters from DB") +@RequiredArgsConstructor +@RestController +public class CharacterController { + private final CharacterService characterService; + + @Operation(summary = "Get random character", + description = "Get a random characters from Rick and Morty world") + @GetMapping + public CharacterDto getRandomCharacter() { + return characterService.getRandomCharacter(); + } + + @Operation(summary = "Search characters", + description = "Search characters by name") + @GetMapping("/search") + public List searchCharacter(@RequestParam String name) { + return characterService.findByName(name); + } +} + diff --git a/src/main/java/mate/academy/rickandmorty/dto/CharacterDto.java b/src/main/java/mate/academy/rickandmorty/dto/CharacterDto.java new file mode 100644 index 00000000..ee4e8282 --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/dto/CharacterDto.java @@ -0,0 +1,12 @@ +package mate.academy.rickandmorty.dto; + +import lombok.Data; + +@Data +public class CharacterDto { + private Long id; + private String externalId; + private String name; + private String status; + private String gender; +} diff --git a/src/main/java/mate/academy/rickandmorty/dto/CharacterResponseDto.java b/src/main/java/mate/academy/rickandmorty/dto/CharacterResponseDto.java new file mode 100644 index 00000000..b6d9856d --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/dto/CharacterResponseDto.java @@ -0,0 +1,13 @@ +package mate.academy.rickandmorty.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +@JsonIgnoreProperties(ignoreUnknown = true) +@Data +public class CharacterResponseDto { + private Long id; + private String name; + private String status; + private String gender; +} diff --git a/src/main/java/mate/academy/rickandmorty/dto/ListCharacterDto.java b/src/main/java/mate/academy/rickandmorty/dto/ListCharacterDto.java new file mode 100644 index 00000000..68a61e4c --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/dto/ListCharacterDto.java @@ -0,0 +1,9 @@ +package mate.academy.rickandmorty.dto; + +import java.util.List; +import lombok.Data; + +@Data +public class ListCharacterDto { + private List results; +} 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..8b194ced --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/mapper/CharacterMapper.java @@ -0,0 +1,16 @@ +package mate.academy.rickandmorty.mapper; + +import mate.academy.rickandmorty.dto.CharacterDto; +import mate.academy.rickandmorty.dto.CharacterResponseDto; +import mate.academy.rickandmorty.model.Character; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +@Mapper(componentModel = "spring") +public interface CharacterMapper { + @Mapping(source = "id", target = "externalId") + @Mapping(target = "id", ignore = true) + Character toModel(CharacterResponseDto responseDto); + + CharacterDto toDto(Character character); +} 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..bc8e21e9 --- /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 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..2e34928f --- /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 findByNameContaining(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..91855b80 --- /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.CharacterDto; +import mate.academy.rickandmorty.dto.CharacterResponseDto; + +public interface CharacterService { + CharacterDto getRandomCharacter(); + + void saveAll(List listDto); + + List findByName(String name); +} diff --git a/src/main/java/mate/academy/rickandmorty/service/RickAndMortyClient.java b/src/main/java/mate/academy/rickandmorty/service/RickAndMortyClient.java new file mode 100644 index 00000000..d2a99797 --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/service/RickAndMortyClient.java @@ -0,0 +1,37 @@ +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.List; +import lombok.RequiredArgsConstructor; +import mate.academy.rickandmorty.dto.CharacterResponseDto; +import mate.academy.rickandmorty.dto.ListCharacterDto; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class RickAndMortyClient { + private static final String CHARACTER_URL = "https://rickandmortyapi.com/api/character"; + private static final HttpClient httpClient = HttpClient.newHttpClient(); + private final ObjectMapper objectMapper; + + public List getCharacters() { + HttpRequest request = HttpRequest.newBuilder() + .GET() + .uri(URI.create(CHARACTER_URL)) + .build(); + try { + HttpResponse response = httpClient + .send(request, HttpResponse.BodyHandlers.ofString()); + ListCharacterDto listCharacterDto = objectMapper + .readValue(response.body(), ListCharacterDto.class); + return listCharacterDto.getResults(); + } catch (IOException | InterruptedException e) { + throw new RuntimeException("Can't get list of characters by request", e); + } + } +} 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..0d69b454 --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/service/impl/CharacterServiceImpl.java @@ -0,0 +1,44 @@ +package mate.academy.rickandmorty.service.impl; + +import java.util.List; +import lombok.RequiredArgsConstructor; +import mate.academy.rickandmorty.dto.CharacterDto; +import mate.academy.rickandmorty.dto.CharacterResponseDto; +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 org.springframework.stereotype.Service; + +@RequiredArgsConstructor +@Service +public class CharacterServiceImpl implements CharacterService { + private int listSize; + private final RandomUtil random; + private final CharacterRepository characterRepository; + private final CharacterMapper mapper; + + @Override + public CharacterDto getRandomCharacter() { + int id = random.util().nextInt(listSize) + 1; + return mapper.toDto(characterRepository.findById((long) id).orElseThrow( + () -> new RuntimeException("Characters not found by id: " + id)) + ); + } + + @Override + public void saveAll(List listDto) { + List characters = listDto.stream() + .map(mapper::toModel) + .toList(); + listSize = characters.size(); + characterRepository.saveAll(characters); + } + + @Override + public List findByName(String name) { + return characterRepository.findByNameContaining(name).stream() + .map(mapper::toDto) + .toList(); + } +} diff --git a/src/main/java/mate/academy/rickandmorty/service/impl/RandomUtil.java b/src/main/java/mate/academy/rickandmorty/service/impl/RandomUtil.java new file mode 100644 index 00000000..60f05ea3 --- /dev/null +++ b/src/main/java/mate/academy/rickandmorty/service/impl/RandomUtil.java @@ -0,0 +1,13 @@ +package mate.academy.rickandmorty.service.impl; + +import java.util.Random; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class RandomUtil { + @Bean + Random util() { + return new Random(); + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 8b137891..ce8f2591 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1,9 @@ +spring.datasource.url=jdbc:mysql://localhost:3306/rick_and_morty?serverTimezone=UTC +spring.datasource.username=root +spring.datasource.password=Romaxa051979 +spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect +spring.jpa.hibernate.ddl-auto=create-drop +server.servlet.context-path=/api +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..4fe47c0d 100644 --- a/src/test/java/mate/academy/rickandmorty/ApplicationTests.java +++ b/src/test/java/mate/academy/rickandmorty/ApplicationTests.java @@ -5,9 +5,7 @@ @SpringBootTest class ApplicationTests { - - @Test - void contextLoads() { - } - + @Test + void contextLoads() { + } }