diff --git a/pom.xml b/pom.xml
index 0c754f19..1eba7fff 100644
--- a/pom.xml
+++ b/pom.xml
@@ -41,6 +41,57 @@
com.h2database
h2
+
+
+ mysql
+ mysql-connector-java
+ 8.0.33
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ org.projectlombok
+ lombok
+
+
+
+ org.mapstruct
+ mapstruct
+ 1.5.5.Final
+
+
+
+ mysql
+ mysql-connector-java
+ 8.0.33
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ org.projectlombok
+ lombok
+
+
+
+ org.mapstruct
+ mapstruct
+ 1.5.5.Final
+
+
+
+ org.springdoc
+ springdoc-openapi-starter-webmvc-ui
+ 2.1.0
+
+
@@ -66,6 +117,32 @@
true
true
false
+ src
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ ${java.version}
+
+
+ org.projectlombok
+ lombok
+ ${lombok.version}
+
+
+ org.projectlombok
+ lombok-mapstruct-binding
+ 0.2.0
+
+
+ org.mapstruct
+ mapstruct-processor
+ 1.5.5.Final
+
+
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..7d9fdd4f
--- /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.CharacterInternalResponseDto;
+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 character management",
+ description = "Endpoints for managing Rick and Morty characters")
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("/characters")
+public class CharacterController {
+ private final CharacterService characterService;
+
+ @GetMapping("/by-name")
+ @Operation(summary = "Get a character by name", description = "Get a character by name")
+ public List getCharactersByName(@RequestParam String name) {
+ return characterService.getCharactersByName(name);
+ }
+
+ @GetMapping("/random")
+ @Operation(summary = "Get a random character", description = "Get a random character")
+ public CharacterInternalResponseDto getRandomCharacter() {
+ return characterService.getRandomCharacter();
+ }
+}
diff --git a/src/main/java/mate/academy/rickandmorty/dto/external/CharacterExternalResponseDto.java b/src/main/java/mate/academy/rickandmorty/dto/external/CharacterExternalResponseDto.java
new file mode 100644
index 00000000..6ba4a71c
--- /dev/null
+++ b/src/main/java/mate/academy/rickandmorty/dto/external/CharacterExternalResponseDto.java
@@ -0,0 +1,19 @@
+package mate.academy.rickandmorty.dto.external;
+
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+
+public record CharacterExternalResponseDto(
+ Long id,
+ String name,
+ String status,
+ String species,
+ String type,
+ String gender,
+ OriginResponseDto origin,
+ OriginResponseDto location,
+ String image,
+ ArrayList episode,
+ String url,
+ LocalDateTime created) {
+}
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..c7901706
--- /dev/null
+++ b/src/main/java/mate/academy/rickandmorty/dto/external/CharacterResponseDataDto.java
@@ -0,0 +1,8 @@
+package mate.academy.rickandmorty.dto.external;
+
+import java.util.List;
+
+public record CharacterResponseDataDto(
+ List results,
+ CharactersInfoDto info) {
+}
diff --git a/src/main/java/mate/academy/rickandmorty/dto/external/CharactersInfoDto.java b/src/main/java/mate/academy/rickandmorty/dto/external/CharactersInfoDto.java
new file mode 100644
index 00000000..9ef6db91
--- /dev/null
+++ b/src/main/java/mate/academy/rickandmorty/dto/external/CharactersInfoDto.java
@@ -0,0 +1,4 @@
+package mate.academy.rickandmorty.dto.external;
+
+public record CharactersInfoDto(String next) {
+}
diff --git a/src/main/java/mate/academy/rickandmorty/dto/external/OriginResponseDto.java b/src/main/java/mate/academy/rickandmorty/dto/external/OriginResponseDto.java
new file mode 100644
index 00000000..d2a39fa7
--- /dev/null
+++ b/src/main/java/mate/academy/rickandmorty/dto/external/OriginResponseDto.java
@@ -0,0 +1,6 @@
+package mate.academy.rickandmorty.dto.external;
+
+public record OriginResponseDto(
+ String name,
+ String url) {
+}
diff --git a/src/main/java/mate/academy/rickandmorty/dto/internal/CharacterInternalResponseDto.java b/src/main/java/mate/academy/rickandmorty/dto/internal/CharacterInternalResponseDto.java
new file mode 100644
index 00000000..dc296809
--- /dev/null
+++ b/src/main/java/mate/academy/rickandmorty/dto/internal/CharacterInternalResponseDto.java
@@ -0,0 +1,9 @@
+package mate.academy.rickandmorty.dto.internal;
+
+public record CharacterInternalResponseDto(
+ 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..327c64a1
--- /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.CharacterExternalResponseDto;
+import mate.academy.rickandmorty.dto.internal.CharacterInternalResponseDto;
+import mate.academy.rickandmorty.model.Character;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+
+@Mapper(config = MapperConfig.class)
+public interface CharacterMapper {
+ @Mapping(target = "externalId", source = "id")
+ Character toModel(CharacterExternalResponseDto characterExternalResponseDto);
+
+ CharacterInternalResponseDto 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..71be2a3c
--- /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;
+
+@Entity
+@Data
+@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/repository/CharacterRepository.java b/src/main/java/mate/academy/rickandmorty/repository/CharacterRepository.java
new file mode 100644
index 00000000..6a435cff
--- /dev/null
+++ b/src/main/java/mate/academy/rickandmorty/repository/CharacterRepository.java
@@ -0,0 +1,11 @@
+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.stereotype.Repository;
+
+@Repository
+public interface CharacterRepository extends JpaRepository {
+ List findAllByNameContainingIgnoreCase(String name);
+}
diff --git a/src/main/java/mate/academy/rickandmorty/service/CharacterInit.java b/src/main/java/mate/academy/rickandmorty/service/CharacterInit.java
new file mode 100644
index 00000000..ad056445
--- /dev/null
+++ b/src/main/java/mate/academy/rickandmorty/service/CharacterInit.java
@@ -0,0 +1,20 @@
+package mate.academy.rickandmorty.service;
+
+import jakarta.annotation.PostConstruct;
+import java.util.List;
+import lombok.RequiredArgsConstructor;
+import mate.academy.rickandmorty.dto.external.CharacterExternalResponseDto;
+import org.springframework.stereotype.Component;
+
+@Component
+@RequiredArgsConstructor
+public class CharacterInit {
+ private final RickAndMortyClient client;
+ private final CharacterService characterService;
+
+ @PostConstruct
+ public void initCharacters() {
+ List characters = client.getCharacters();
+ characterService.saveAll(characters);
+ }
+}
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..1fcfe0f7
--- /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.CharacterExternalResponseDto;
+import mate.academy.rickandmorty.dto.internal.CharacterInternalResponseDto;
+
+public interface CharacterService {
+ CharacterInternalResponseDto getRandomCharacter();
+
+ List getCharactersByName(String name);
+
+ void saveAll(List characterExternalResponseDtoList);
+}
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..351b065d
--- /dev/null
+++ b/src/main/java/mate/academy/rickandmorty/service/RickAndMortyClient.java
@@ -0,0 +1,46 @@
+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.CharacterExternalResponseDto;
+import mate.academy.rickandmorty.dto.external.CharacterResponseDataDto;
+import org.springframework.stereotype.Component;
+
+@Component
+@RequiredArgsConstructor
+public class RickAndMortyClient {
+ public static final String BASE_URL = "https://rickandmortyapi.com/api/character";
+ private final ObjectMapper objectMapper;
+
+ public List getCharacters() {
+ List allCharacters = new ArrayList<>();
+ HttpClient httpClient = HttpClient.newHttpClient();
+ 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);
+
+ allCharacters.addAll(characterResponseDataDto.results());
+ url = characterResponseDataDto.info().next();
+ } catch (IOException | InterruptedException e) {
+ throw new RuntimeException("Can't load characters from given URL");
+ }
+ }
+ return allCharacters;
+ }
+}
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..b3c4936e
--- /dev/null
+++ b/src/main/java/mate/academy/rickandmorty/service/impl/CharacterServiceImpl.java
@@ -0,0 +1,46 @@
+package mate.academy.rickandmorty.service.impl;
+
+import jakarta.persistence.EntityNotFoundException;
+import java.util.List;
+import java.util.Random;
+import lombok.RequiredArgsConstructor;
+import mate.academy.rickandmorty.dto.external.CharacterExternalResponseDto;
+import mate.academy.rickandmorty.dto.internal.CharacterInternalResponseDto;
+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 CharacterMapper characterMapper;
+ private final CharacterRepository characterRepository;
+ private final Random random = new Random();
+
+ @Override
+ public CharacterInternalResponseDto getRandomCharacter() {
+ long randomId = random.nextLong(characterRepository.count());
+ Character character = characterRepository.findById(randomId)
+ .orElseThrow(() ->
+ new EntityNotFoundException("Cannot find a random character by id: "
+ + randomId));
+ return characterMapper.toDto(character);
+ }
+
+ @Override
+ public List getCharactersByName(String name) {
+ return characterRepository.findAllByNameContainingIgnoreCase(name).stream()
+ .map(characterMapper::toDto)
+ .toList();
+ }
+
+ @Override
+ public void saveAll(List characterExternalResponseDtoList) {
+ List characters = characterExternalResponseDtoList.stream()
+ .map(characterMapper::toModel)
+ .toList();
+ characterRepository.saveAll(characters);
+ }
+}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index 8b137891..a2dbb89f 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?serverTimeZone=UTC
+spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
+spring.datasource.username=root
+spring.datasource.password=root
+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() {
+ }
}