diff --git a/pom.xml b/pom.xml
index 0c754f19..4203abe3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,6 +1,6 @@
+ 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
@@ -16,6 +16,8 @@
17
3.1.1
+ 1.5.5.Final
+ 0.2.0
https://raw.githubusercontent.com/mate-academy/style-guides/master/java/checkstyle.xml
@@ -41,10 +43,71 @@
com.h2database
h2
-
+
+ org.mapstruct
+ mapstruct
+ ${org.mapstruct.version}
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+ org.springdoc
+ springdoc-openapi-starter-webmvc-ui
+ 2.1.0
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ com.mysql
+ mysql-connector-j
+ runtime
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.8.1
+
+
+ ${java.version}
+
+
+ org.projectlombok
+ lombok-mapstruct-binding
+ ${lombok-mapstruct-binding.version}
+
+
+ org.projectlombok
+ lombok
+ ${lombok.version}
+
+
+ org.mapstruct
+ mapstruct-processor
+ ${org.mapstruct.version}
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ org.projectlombok
+ lombok
+
+
+
+
org.springframework.boot
spring-boot-maven-plugin
@@ -68,7 +131,37 @@
false
+
+ org.apache.maven.plugins
+ maven-checkstyle-plugin
+ 3.1.1
+
+
+ compile
+
+ check
+
+
+
+
+
+ ${project.build.sourceDirectory}
+
+ ${maven.checkstyle.plugin.configLocation}
+ UTF-8
+ true
+ true
+ false
+
+
+
+ com.puppycrawl.tools
+ checkstyle
+ 9.2
+
+
+
-
+
\ No newline at end of file
diff --git a/src/main/java/mate/academy/rickandmorty/Application.java b/src/main/java/mate/academy/rickandmorty/Application.java
index cdea84fc..550ca0ed 100644
--- a/src/main/java/mate/academy/rickandmorty/Application.java
+++ b/src/main/java/mate/academy/rickandmorty/Application.java
@@ -1,12 +1,19 @@
package mate.academy.rickandmorty;
+import mate.academy.rickandmorty.service.RickAndMortyClient;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
+ private static RickAndMortyClient rickAndMortyClient;
+
+ public Application(RickAndMortyClient client) {
+ Application.rickAndMortyClient = client;
+ }
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
+ rickAndMortyClient.getCharacters();
}
}
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..b3ed21ee
--- /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..3ca0b26e
--- /dev/null
+++ b/src/main/java/mate/academy/rickandmorty/controller/CharacterController.java
@@ -0,0 +1,25 @@
+package mate.academy.rickandmorty.controller;
+
+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.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequiredArgsConstructor
+public class CharacterController {
+ private final CharacterService characterService;
+
+ @GetMapping("/random")
+ public CharacterDto getRandomCharacter() {
+ return characterService.getRandomCharacter();
+ }
+
+ @GetMapping("/search")
+ public List searchCharacter(@RequestParam String name) {
+ return characterService.findByName(name);
+ }
+}
diff --git a/src/main/java/mate/academy/rickandmorty/dto/external/CharacterMetaDto.java b/src/main/java/mate/academy/rickandmorty/dto/external/CharacterMetaDto.java
new file mode 100644
index 00000000..bdba8b29
--- /dev/null
+++ b/src/main/java/mate/academy/rickandmorty/dto/external/CharacterMetaDto.java
@@ -0,0 +1,6 @@
+package mate.academy.rickandmorty.dto.external;
+
+import java.util.Map;
+
+public record CharacterMetaDto(Map info) {
+}
diff --git a/src/main/java/mate/academy/rickandmorty/dto/external/CreateCharacterDto.java b/src/main/java/mate/academy/rickandmorty/dto/external/CreateCharacterDto.java
new file mode 100644
index 00000000..a19c6621
--- /dev/null
+++ b/src/main/java/mate/academy/rickandmorty/dto/external/CreateCharacterDto.java
@@ -0,0 +1,13 @@
+package mate.academy.rickandmorty.dto.external;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import lombok.Data;
+
+@Data
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class CreateCharacterDto {
+ private Long id;
+ private String name;
+ private String status;
+ private String gender;
+}
diff --git a/src/main/java/mate/academy/rickandmorty/dto/external/CreateCharacterDtoList.java b/src/main/java/mate/academy/rickandmorty/dto/external/CreateCharacterDtoList.java
new file mode 100644
index 00000000..616e2439
--- /dev/null
+++ b/src/main/java/mate/academy/rickandmorty/dto/external/CreateCharacterDtoList.java
@@ -0,0 +1,9 @@
+package mate.academy.rickandmorty.dto.external;
+
+import java.util.List;
+import lombok.Data;
+
+@Data
+public class CreateCharacterDtoList {
+ private List results;
+}
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..7d2bc6fc
--- /dev/null
+++ b/src/main/java/mate/academy/rickandmorty/dto/internal/CharacterDto.java
@@ -0,0 +1,12 @@
+package mate.academy.rickandmorty.dto.internal;
+
+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/mapper/CharacterMapper.java b/src/main/java/mate/academy/rickandmorty/mapper/CharacterMapper.java
new file mode 100644
index 00000000..b8f09220
--- /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.config.MapperConfig;
+import mate.academy.rickandmorty.dto.external.CreateCharacterDto;
+import mate.academy.rickandmorty.dto.internal.CharacterDto;
+import mate.academy.rickandmorty.model.Character;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+
+@Mapper(config = MapperConfig.class)
+public interface CharacterMapper {
+ @Mapping(source = "id", target = "externalId")
+ Character toModel(CreateCharacterDto createCharacterDto);
+
+ 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..803fc71a
--- /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..3113ed33
--- /dev/null
+++ b/src/main/java/mate/academy/rickandmorty/repository/CharacterRepository.java
@@ -0,0 +1,15 @@
+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.data.jpa.repository.Query;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface CharacterRepository extends JpaRepository {
+ List findByNameContaining(String name);
+
+ @Query("SELECT COUNT(*) FROM Character ")
+ Long getCountOfRecords();
+}
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..e8b78a97
--- /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.CreateCharacterDto;
+import mate.academy.rickandmorty.dto.internal.CharacterDto;
+
+public interface CharacterService {
+ void saveAll(List characterDtoList);
+
+ CharacterDto getRandomCharacter();
+
+ 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..29b3b367
--- /dev/null
+++ b/src/main/java/mate/academy/rickandmorty/service/RickAndMortyClient.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 lombok.RequiredArgsConstructor;
+import mate.academy.rickandmorty.dto.external.CharacterMetaDto;
+import mate.academy.rickandmorty.dto.external.CreateCharacterDtoList;
+import org.springframework.stereotype.Component;
+
+@Component
+@RequiredArgsConstructor
+public class RickAndMortyClient {
+ private static final String URL = "https://rickandmortyapi.com/api/character";
+
+ private final ObjectMapper objectMapper;
+
+ private final CharacterService characterService;
+
+ public void getCharacters() {
+ HttpClient httpClient = HttpClient.newHttpClient();
+
+ HttpRequest httpRequest = HttpRequest.newBuilder()
+ .GET()
+ .uri(URI.create(URL))
+ .build();
+
+ try {
+ HttpResponse response = httpClient
+ .send(httpRequest, HttpResponse.BodyHandlers.ofString());
+ CharacterMetaDto metaDto = objectMapper.readValue(
+ response.body(), CharacterMetaDto.class);
+ int countOfPages = Integer.parseInt(metaDto.info().get("pages"));
+ for (int i = 1; i <= countOfPages; i++) {
+ httpRequest = HttpRequest.newBuilder()
+ .GET()
+ .uri(URI.create(URL + "/?page=" + i))
+ .build();
+ response = httpClient
+ .send(httpRequest, HttpResponse.BodyHandlers.ofString());
+ CreateCharacterDtoList dtoList = objectMapper.readValue(
+ response.body(), CreateCharacterDtoList.class);
+ characterService.saveAll(dtoList.getResults());
+ }
+
+ } catch (IOException | InterruptedException e) {
+ throw new RuntimeException(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..fbd0f0e9
--- /dev/null
+++ b/src/main/java/mate/academy/rickandmorty/service/impl/CharacterServiceImpl.java
@@ -0,0 +1,49 @@
+package mate.academy.rickandmorty.service.impl;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.Random;
+import lombok.RequiredArgsConstructor;
+import mate.academy.rickandmorty.dto.external.CreateCharacterDto;
+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 org.springframework.stereotype.Service;
+
+@Service
+@RequiredArgsConstructor
+public class CharacterServiceImpl implements CharacterService {
+ private final CharacterRepository characterRepository;
+ private final CharacterMapper characterMapper;
+ private final Random random = new Random();
+ private int size;
+
+ @Override
+ public void saveAll(List characterDtoList) {
+ List characters = characterDtoList.stream()
+ .map(characterMapper::toModel)
+ .toList();
+ size = characters.size();
+ characterRepository.saveAll(characters);
+ }
+
+ @Override
+ public CharacterDto getRandomCharacter() {
+ Long countOfRecords = characterRepository.getCountOfRecords();
+ Long id = random.nextLong(1L, countOfRecords);
+ Optional optionalCharacter = characterRepository.findById(id);
+ if (optionalCharacter.isPresent()) {
+ return characterMapper.toDto(optionalCharacter.get());
+ }
+ throw new RuntimeException("Can't find character by id: " + id);
+ }
+
+ @Override
+ public List findByName(String name) {
+ return characterRepository.findByNameContaining(name).stream()
+ .map(characterMapper::toDto)
+ .toList();
+ }
+}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index 8b137891..d55dd1a0 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -1 +1,11 @@
+spring.datasource.url=jdbc:mysql://localhost:3306/rick_and_morty
+spring.datasource.username=root
+spring.datasource.password=root1234
+#spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
+spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect
+server.port=8090
+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/resources/application.properties b/src/test/resources/application.properties
index bc2fdde8..bd211685 100644
--- a/src/test/resources/application.properties
+++ b/src/test/resources/application.properties
@@ -1,5 +1,5 @@
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
-spring.datasource.username=sa
-spring.datasource.password=password
+spring.datasource.username=root
+spring.datasource.password=root1234
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect