diff --git a/pom.xml b/pom.xml
index 0c754f19..58bf662d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
org.springframework.boot
spring-boot-starter-parent
- 3.1.4
+ 3.3.4
mate.academy
@@ -19,6 +19,9 @@
https://raw.githubusercontent.com/mate-academy/style-guides/master/java/checkstyle.xml
+ 1.18.34
+ 0.2.0
+ 1.5.5.Final
@@ -37,10 +40,35 @@
spring-boot-starter-data-jpa
+
+ org.mapstruct
+ mapstruct
+ ${mapstruct.version}
+
+
+
+ org.mapstruct
+ mapstruct-processor
+ 1.5.5.Final
+
+
com.h2database
h2
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.projectlombok
+ lombok
+
+
+ com.mysql
+ mysql-connector-j
+ runtime
+
@@ -48,24 +76,38 @@
org.springframework.boot
spring-boot-maven-plugin
+
+
+
+ org.projectlombok
+ lombok
+
+
+
org.apache.maven.plugins
- maven-checkstyle-plugin
- 3.3.0
-
-
- compile
-
- check
-
-
-
+ maven-compiler-plugin
- ${maven.checkstyle.plugin.configLocation}
- true
- true
- false
+
+ ${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..b5872dbe
--- /dev/null
+++ b/src/main/java/mate/academy/rickandmorty/controller/CharacterController.java
@@ -0,0 +1,27 @@
+package mate.academy.rickandmorty.controller;
+
+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.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import java.util.List;
+
+@RequiredArgsConstructor
+@RestController
+@RequestMapping(value = "/character")
+public class CharacterController {
+ private final CharacterService characterService;
+
+ @GetMapping
+ public CharacterDto getRandomCharacter() {
+ return characterService.getRandomCharacter();
+ }
+
+ @GetMapping("/{name}")
+ public List getCharactersByName(@PathVariable String name) {
+ return characterService.findCharacterByName(name);
+ }
+}
diff --git a/src/main/java/mate/academy/rickandmorty/dto/ApiPageInfo.java b/src/main/java/mate/academy/rickandmorty/dto/ApiPageInfo.java
new file mode 100644
index 00000000..1ef22a41
--- /dev/null
+++ b/src/main/java/mate/academy/rickandmorty/dto/ApiPageInfo.java
@@ -0,0 +1,9 @@
+package mate.academy.rickandmorty.dto;
+
+public record ApiPageInfo(
+ int pages,
+ int count,
+ String next,
+ String prev
+) {
+}
diff --git a/src/main/java/mate/academy/rickandmorty/dto/ApiResponse.java b/src/main/java/mate/academy/rickandmorty/dto/ApiResponse.java
new file mode 100644
index 00000000..5d23428f
--- /dev/null
+++ b/src/main/java/mate/academy/rickandmorty/dto/ApiResponse.java
@@ -0,0 +1,9 @@
+package mate.academy.rickandmorty.dto;
+
+import java.util.List;
+
+public record ApiResponse(
+ ApiPageInfo info,
+ List results
+) {
+}
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..db943632
--- /dev/null
+++ b/src/main/java/mate/academy/rickandmorty/dto/CharacterDto.java
@@ -0,0 +1,8 @@
+package mate.academy.rickandmorty.dto;
+
+public record CharacterDto(
+ Long id,
+ 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..be43977b
--- /dev/null
+++ b/src/main/java/mate/academy/rickandmorty/mapper/CharacterMapper.java
@@ -0,0 +1,22 @@
+package mate.academy.rickandmorty.mapper;
+
+import mate.academy.rickandmorty.config.MapperConfig;
+import mate.academy.rickandmorty.dto.CharacterDto;
+import mate.academy.rickandmorty.model.Character;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import java.util.List;
+
+@Mapper(config = MapperConfig.class)
+public interface CharacterMapper {
+
+ @Mapping(source = "id", target = "externalId")
+ Character toModel(CharacterDto characterDto);
+
+ @Mapping(source = "id", target = "externalId")
+ List toModelList(List characterDtoList);
+
+ CharacterDto toDto(Character character);
+
+ List toDtoList(List characterList);
+}
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..1342dacd
--- /dev/null
+++ b/src/main/java/mate/academy/rickandmorty/model/Character.java
@@ -0,0 +1,26 @@
+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.NoArgsConstructor;
+import lombok.Setter;
+
+@Entity
+@Getter
+@Setter
+@NoArgsConstructor
+@Table(name = "results")
+
+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..dab6d4e8
--- /dev/null
+++ b/src/main/java/mate/academy/rickandmorty/repository/CharacterRepository.java
@@ -0,0 +1,11 @@
+package mate.academy.rickandmorty.repository;
+
+import mate.academy.rickandmorty.model.Character;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import java.util.List;
+
+public interface CharacterRepository extends JpaRepository, JpaSpecificationExecutor {
+
+ List findByNameContainingIgnoreCase(String name);
+}
diff --git a/src/main/java/mate/academy/rickandmorty/service/CharacterInitializer.java b/src/main/java/mate/academy/rickandmorty/service/CharacterInitializer.java
new file mode 100644
index 00000000..c25d65b1
--- /dev/null
+++ b/src/main/java/mate/academy/rickandmorty/service/CharacterInitializer.java
@@ -0,0 +1,61 @@
+package mate.academy.rickandmorty.service;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import jakarta.annotation.PostConstruct;
+import jakarta.persistence.EntityNotFoundException;
+import lombok.RequiredArgsConstructor;
+import mate.academy.rickandmorty.dto.ApiResponse;
+import mate.academy.rickandmorty.dto.CharacterDto;
+import mate.academy.rickandmorty.mapper.CharacterMapper;
+import mate.academy.rickandmorty.model.Character;
+import mate.academy.rickandmorty.repository.CharacterRepository;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.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 java.util.ArrayList;
+import java.util.List;
+
+@Service
+@RequiredArgsConstructor
+public class CharacterInitializer {
+ @Value("${api.url}")
+ private String apiUrl;
+ private final CharacterRepository characterRepository;
+ private final ObjectMapper objectMapper;
+ private final CharacterMapper characterMapper;
+
+ @PostConstruct
+ public void init() {
+ if(characterRepository.count() == 0) {
+ initCharactersFromExternalApi(apiUrl);
+ }
+ }
+ private void initCharactersFromExternalApi(String apiUrl) {
+ HttpClient client = HttpClient.newHttpClient();
+ List characterDtoList = new ArrayList<>();
+ try {
+ while (apiUrl != null) {
+ HttpRequest getRequest = HttpRequest.newBuilder()
+ .GET()
+ .uri(URI.create(apiUrl))
+ .build();
+ HttpResponse response = client.send(getRequest,
+ HttpResponse.BodyHandlers.ofString());
+ ApiResponse apiResponse = objectMapper.readValue(
+ response.body(),
+ ApiResponse.class
+ );
+ characterDtoList.addAll(apiResponse.results());
+ apiUrl = apiResponse.info().next();
+ }
+ List characters = characterMapper.toModelList(characterDtoList);
+ characterRepository.saveAll(characters);
+ } catch (IOException | InterruptedException e) {
+ throw new EntityNotFoundException("Can't access results from external API", e);
+ }
+ }
+}
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..a4b0342f
--- /dev/null
+++ b/src/main/java/mate/academy/rickandmorty/service/CharacterService.java
@@ -0,0 +1,9 @@
+package mate.academy.rickandmorty.service;
+
+import mate.academy.rickandmorty.dto.CharacterDto;
+import java.util.List;
+
+public interface CharacterService {
+ CharacterDto getRandomCharacter();
+ List findCharacterByName(String name);
+}
diff --git a/src/main/java/mate/academy/rickandmorty/service/CharacterServiceImpl.java b/src/main/java/mate/academy/rickandmorty/service/CharacterServiceImpl.java
new file mode 100644
index 00000000..bb31fecd
--- /dev/null
+++ b/src/main/java/mate/academy/rickandmorty/service/CharacterServiceImpl.java
@@ -0,0 +1,37 @@
+package mate.academy.rickandmorty.service;
+
+import jakarta.persistence.EntityNotFoundException;
+import lombok.RequiredArgsConstructor;
+import mate.academy.rickandmorty.dto.CharacterDto;
+import mate.academy.rickandmorty.mapper.CharacterMapper;
+import mate.academy.rickandmorty.model.Character;
+import mate.academy.rickandmorty.repository.CharacterRepository;
+import org.springframework.stereotype.Service;
+import java.util.List;
+import java.util.concurrent.ThreadLocalRandom;
+
+@Service
+@RequiredArgsConstructor
+public class CharacterServiceImpl implements CharacterService {
+
+ private final CharacterRepository characterRepository;
+ private final CharacterMapper characterMapper;
+
+ @Override
+ public CharacterDto getRandomCharacter() {
+ long count = characterRepository.count();
+ if(count == 0) {
+ throw new EntityNotFoundException("No character found");
+ }
+ long randomNumber = ThreadLocalRandom.current().nextLong(count);
+ return characterRepository.findById(randomNumber)
+ .map(characterMapper::toDto)
+ .orElseThrow(() -> new EntityNotFoundException("Character not found for ID: " + randomNumber));
+ }
+
+ @Override
+ public List findCharacterByName(String name) {
+ List character = characterRepository.findByNameContainingIgnoreCase(name);
+ return characterMapper.toDtoList(character);
+ }
+}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index 8b137891..cc5dbc13 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -1 +1,10 @@
+spring.datasource.url=jdbc:mysql://localhost:3306/rick-and-morty?createDatabaseIfNotExist=true
+spring.datasource.username=root
+spring.datasource.password=Styczen10!
+spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
+spring.jpa.open-in-view=false
+spring.jpa.hibernate.ddl-auto=create-drop
+spring.jpa.show-sql=true
+
+api.url=https://rickandmortyapi.com/api/character
\ No newline at end of file
diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties
index bc2fdde8..ca131643 100644
--- a/src/test/resources/application.properties
+++ b/src/test/resources/application.properties
@@ -3,3 +3,5 @@ spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
+
+api.url=https://rickandmortyapi.com/api/character