diff --git a/pom.xml b/pom.xml
index 0c754f19..7f8fadfc 100644
--- a/pom.xml
+++ b/pom.xml
@@ -37,10 +37,49 @@
spring-boot-starter-data-jpa
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ org.springdoc
+ springdoc-openapi-starter-webmvc-ui
+ 2.1.0
+
+
com.h2database
h2
+
+
+ org.projectlombok
+ lombok
+
+
+
+ org.mapstruct
+ mapstruct
+ 1.5.5.Final
+
+
+
+ org.mapstruct
+ mapstruct-processor
+ 1.5.5.Final
+
+
+
+ mysql
+ mysql-connector-java
+ 8.0.33
+
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+
@@ -55,7 +94,7 @@
3.3.0
- compile
+ verify
check
diff --git a/src/main/java/mate/academy/rickandmorty/config/ApiConfig.java b/src/main/java/mate/academy/rickandmorty/config/ApiConfig.java
new file mode 100644
index 00000000..19ec1826
--- /dev/null
+++ b/src/main/java/mate/academy/rickandmorty/config/ApiConfig.java
@@ -0,0 +1,26 @@
+package mate.academy.rickandmorty.config;
+
+import java.net.http.HttpClient;
+import java.util.Random;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class ApiConfig {
+ @Bean
+ Random buildRandom() {
+ return new Random();
+ }
+
+ @Bean
+ ObjectMapper buildObjectMapper() {
+
+ return new ObjectMapper();
+ }
+
+ @Bean
+ HttpClient buildHttpClient() {
+ return HttpClient.newHttpClient();
+ }
+}
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..b049b596
--- /dev/null
+++ b/src/main/java/mate/academy/rickandmorty/controller/CharacterController.java
@@ -0,0 +1,35 @@
+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.CharacterResponseDto;
+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 universe",
+ description = "Endpoints for managing Rick and Morty universe characters")
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/characters")
+public class CharacterController {
+ private final CharacterService characterService;
+
+ @GetMapping("/random")
+ @Operation(summary = "Get the random character",
+ description = "Get the random character")
+ public CharacterResponseDto getRandomCharacter() {
+ return characterService.getRandomCharacter();
+ }
+
+ @GetMapping()
+ @Operation(summary = "Find the character by name",
+ description = "Find the character by name")
+ public List searchCharactersByName(@RequestParam String name) {
+ return characterService.findAllByName(name);
+ }
+}
\ No newline at end of file
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..1619c515
--- /dev/null
+++ b/src/main/java/mate/academy/rickandmorty/dto/external/CharacterResponseDataDto.java
@@ -0,0 +1,7 @@
+package mate.academy.rickandmorty.dto.external;
+
+import java.util.List;
+
+public record CharacterResponseDataDto(CharacterResponseMetaDataDto info,
+ List results) {
+}
diff --git a/src/main/java/mate/academy/rickandmorty/dto/external/CharacterResponseMetaDataDto.java b/src/main/java/mate/academy/rickandmorty/dto/external/CharacterResponseMetaDataDto.java
new file mode 100644
index 00000000..1ec569d6
--- /dev/null
+++ b/src/main/java/mate/academy/rickandmorty/dto/external/CharacterResponseMetaDataDto.java
@@ -0,0 +1,8 @@
+package mate.academy.rickandmorty.dto.external;
+
+public record CharacterResponseMetaDataDto(Integer count,
+ Integer pages,
+ String next,
+ String prev
+) {
+}
diff --git a/src/main/java/mate/academy/rickandmorty/dto/external/ThirdApiCharacterResponseDto.java b/src/main/java/mate/academy/rickandmorty/dto/external/ThirdApiCharacterResponseDto.java
new file mode 100644
index 00000000..92ad7145
--- /dev/null
+++ b/src/main/java/mate/academy/rickandmorty/dto/external/ThirdApiCharacterResponseDto.java
@@ -0,0 +1,18 @@
+package mate.academy.rickandmorty.dto.external;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+@Data
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class ThirdApiCharacterResponseDto {
+ @JsonProperty("id")
+ private String externalId;
+ private String name;
+ private String status;
+ private String species;
+ private String type;
+ private String gender;
+
+}
diff --git a/src/main/java/mate/academy/rickandmorty/dto/internal/CharacterResponseDto.java b/src/main/java/mate/academy/rickandmorty/dto/internal/CharacterResponseDto.java
new file mode 100644
index 00000000..83ca93d1
--- /dev/null
+++ b/src/main/java/mate/academy/rickandmorty/dto/internal/CharacterResponseDto.java
@@ -0,0 +1,9 @@
+package mate.academy.rickandmorty.dto.internal;
+
+public record CharacterResponseDto(Long id,
+ String externalId,
+ String name,
+ String status,
+ String gender
+) {
+}
diff --git a/src/main/java/mate/academy/rickandmorty/exception/DataNotFoundException.java b/src/main/java/mate/academy/rickandmorty/exception/DataNotFoundException.java
new file mode 100644
index 00000000..fab22e68
--- /dev/null
+++ b/src/main/java/mate/academy/rickandmorty/exception/DataNotFoundException.java
@@ -0,0 +1,7 @@
+package mate.academy.rickandmorty.exception;
+
+public class DataNotFoundException extends RuntimeException {
+ public DataNotFoundException(String message) {
+ 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..73284e15
--- /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.ThirdApiCharacterResponseDto;
+import mate.academy.rickandmorty.dto.internal.CharacterResponseDto;
+import mate.academy.rickandmorty.model.Character;
+import org.mapstruct.Mapper;
+
+@Mapper(config = MapperConfig.class)
+public interface CharacterMapper {
+ CharacterResponseDto toDto(Character character);
+
+ Character toCharacter(ThirdApiCharacterResponseDto thirdApiCharacterResponseDto);
+}
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..e64c02d9
--- /dev/null
+++ b/src/main/java/mate/academy/rickandmorty/model/Character.java
@@ -0,0 +1,23 @@
+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 lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+@Entity(name = "characters")
+public class Character {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+ @Column(name = "external_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..dc5399f8
--- /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 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..26d4a04f
--- /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.CharacterResponseDto;
+
+public interface CharacterService {
+ CharacterResponseDto getRandomCharacter();
+
+ List findAllByName(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..99f9482d
--- /dev/null
+++ b/src/main/java/mate/academy/rickandmorty/service/RickAndMortyClient.java
@@ -0,0 +1,49 @@
+package mate.academy.rickandmorty.service;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.RequiredArgsConstructor;
+import mate.academy.rickandmorty.dto.external.CharacterResponseDataDto;
+import mate.academy.rickandmorty.mapper.CharacterMapper;
+import mate.academy.rickandmorty.repository.CharacterRepository;
+import org.springframework.stereotype.Component;
+
+@Component
+@RequiredArgsConstructor
+public class RickAndMortyClient {
+ private static final String URI_BASE = "https://rickandmortyapi.com/api/character";
+
+ private final CharacterRepository characterRepository;
+ private final CharacterMapper characterMapper;
+ private final ObjectMapper objectMapper;
+ private final HttpClient httpClient;
+
+ public void loadDataToDb() {
+ String page = URI_BASE;
+ while (page != null) {
+ HttpRequest request = HttpRequest.newBuilder()
+ .GET()
+ .uri(URI.create(page))
+ .build();
+
+ try {
+ HttpResponse response = httpClient.send(
+ request,
+ HttpResponse.BodyHandlers.ofString());
+ CharacterResponseDataDto characterResponseDataDto = objectMapper.readValue(
+ response.body(),
+ CharacterResponseDataDto.class);
+ characterResponseDataDto.results().stream()
+ .map(characterMapper::toCharacter)
+ .forEach(characterRepository::save);
+ page = characterResponseDataDto.info().next();
+ } catch (IOException | InterruptedException e) {
+ throw new RuntimeException("Can't get data from API", 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..f0151f98
--- /dev/null
+++ b/src/main/java/mate/academy/rickandmorty/service/impl/CharacterServiceImpl.java
@@ -0,0 +1,45 @@
+package mate.academy.rickandmorty.service.impl;
+
+import java.util.List;
+import java.util.Random;
+import jakarta.annotation.PostConstruct;
+import lombok.RequiredArgsConstructor;
+import mate.academy.rickandmorty.dto.internal.CharacterResponseDto;
+import mate.academy.rickandmorty.exception.DataNotFoundException;
+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.RickAndMortyClient;
+import org.springframework.stereotype.Service;
+
+@Service
+@RequiredArgsConstructor
+public class CharacterServiceImpl implements CharacterService {
+ private final CharacterRepository characterRepository;
+ private final CharacterMapper characterMapper;
+ private final Random random;
+ private final RickAndMortyClient rickAndMortyClient;
+
+ @PostConstruct
+ public void loadDataToDb() {
+ rickAndMortyClient.loadDataToDb();
+ }
+
+ @Override
+ public CharacterResponseDto getRandomCharacter() {
+ long count = characterRepository.count();
+ Character character = characterRepository.findById(random.nextLong(count))
+ .orElseThrow(
+ () -> new DataNotFoundException("Couldn't find data")
+ );
+ return characterMapper.toDto(character);
+ }
+
+ @Override
+ public List findAllByName(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..9777b53e 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=12345678
+spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
+spring.jpa.hibernate.ddl-auto=create-drop
+spring.jpa.show-sql=true
+
+server.servlet.context-path=/api