diff --git a/pom.xml b/pom.xml
index 0c754f19..812515bb 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
org.springframework.boot
spring-boot-starter-parent
- 3.1.4
+ 3.3.1
mate.academy
@@ -19,6 +19,8 @@
https://raw.githubusercontent.com/mate-academy/style-guides/master/java/checkstyle.xml
+ 1.5.5.Final
+ 0.2.0
@@ -41,6 +43,35 @@
com.h2database
h2
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ com.mysql
+ mysql-connector-j
+ 8.4.0
+
+
+
+ org.mapstruct
+ mapstruct
+ ${mapstruct.version}
+
+
+
+ org.projectlombok
+ lombok
+ provided
+
+
+
+ org.springdoc
+ springdoc-openapi-starter-webmvc-ui
+ 2.1.0
+
@@ -49,6 +80,7 @@
org.springframework.boot
spring-boot-maven-plugin
+
org.apache.maven.plugins
maven-checkstyle-plugin
@@ -66,6 +98,34 @@
true
true
false
+ src
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.11.0
+
+
+ ${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/Application.java b/src/main/java/mate/academy/rickandmorty/Application.java
index cdea84fc..28161d2a 100644
--- a/src/main/java/mate/academy/rickandmorty/Application.java
+++ b/src/main/java/mate/academy/rickandmorty/Application.java
@@ -1,12 +1,26 @@
package mate.academy.rickandmorty;
+import java.util.List;
+import mate.academy.rickandmorty.dto.external.CharacterResponseDto;
+import mate.academy.rickandmorty.service.CharacterClient;
+import mate.academy.rickandmorty.service.CharacterService;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
+ private static CharacterService characterService;
+ private static CharacterClient characterClient;
+
+ public Application(CharacterService service, CharacterClient client) {
+ Application.characterService = service;
+ Application.characterClient = client;
+ }
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
+
+ List characters = characterClient.findAllCharacters();
+ characterService.saveAllCharacters(characters);
}
}
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..222fef3f
--- /dev/null
+++ b/src/main/java/mate/academy/rickandmorty/controller/CharacterController.java
@@ -0,0 +1,39 @@
+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.CharacterDto;
+import mate.academy.rickandmorty.service.CharacterService;
+import org.springframework.http.HttpStatus;
+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.ResponseStatus;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/characters")
+@RequiredArgsConstructor
+@Tag(name = "Character management",
+ description = "Endpoint for getting Rick and Morty characters")
+public class CharacterController {
+ private final CharacterService characterService;
+
+ @GetMapping
+ @ResponseStatus(HttpStatus.OK)
+ @Operation(summary = "Get characters by names part",
+ description = "Getting Rick and Morty characters by names part")
+ public List getByNameContains(@RequestParam String name) {
+ return characterService.findCharacterByName(name);
+ }
+
+ @GetMapping("/random")
+ @ResponseStatus(HttpStatus.OK)
+ @Operation(summary = "Get random character",
+ description = "Getting random character from Rick and Morty API")
+ public CharacterDto getRandomCharacter() {
+ return characterService.findRandomCharacter();
+ }
+}
diff --git a/src/main/java/mate/academy/rickandmorty/dto/external/CharacterResponseDto.java b/src/main/java/mate/academy/rickandmorty/dto/external/CharacterResponseDto.java
new file mode 100644
index 00000000..c1b1c189
--- /dev/null
+++ b/src/main/java/mate/academy/rickandmorty/dto/external/CharacterResponseDto.java
@@ -0,0 +1,11 @@
+package mate.academy.rickandmorty.dto.external;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public record CharacterResponseDto(
+ Long id,
+ String name,
+ String status,
+ String gender) {
+}
diff --git a/src/main/java/mate/academy/rickandmorty/dto/external/CharactersDataResponseDto.java b/src/main/java/mate/academy/rickandmorty/dto/external/CharactersDataResponseDto.java
new file mode 100644
index 00000000..4e149d03
--- /dev/null
+++ b/src/main/java/mate/academy/rickandmorty/dto/external/CharactersDataResponseDto.java
@@ -0,0 +1,9 @@
+package mate.academy.rickandmorty.dto.external;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import java.util.List;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public record CharactersDataResponseDto(
+ 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..ac41ca26
--- /dev/null
+++ b/src/main/java/mate/academy/rickandmorty/dto/internal/CharacterDto.java
@@ -0,0 +1,9 @@
+package mate.academy.rickandmorty.dto.internal;
+
+public record CharacterDto(
+ Long id,
+ Long externalId,
+ String name,
+ String status,
+ String gender) {
+}
diff --git a/src/main/java/mate/academy/rickandmorty/exception/DataProcessingException.java b/src/main/java/mate/academy/rickandmorty/exception/DataProcessingException.java
new file mode 100644
index 00000000..a6c154de
--- /dev/null
+++ b/src/main/java/mate/academy/rickandmorty/exception/DataProcessingException.java
@@ -0,0 +1,7 @@
+package mate.academy.rickandmorty.exception;
+
+public class DataProcessingException extends RuntimeException {
+ public DataProcessingException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
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..8243fdb8
--- /dev/null
+++ b/src/main/java/mate/academy/rickandmorty/mapper/CharacterMapper.java
@@ -0,0 +1,22 @@
+package mate.academy.rickandmorty.mapper;
+
+import java.util.List;
+import mate.academy.rickandmorty.config.MapperConfig;
+import mate.academy.rickandmorty.dto.external.CharacterResponseDto;
+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")
+ @Mapping(target = "id", ignore = true)
+ Character toModel(CharacterResponseDto responseDto);
+
+ CharacterDto toDto(Character character);
+
+ List toModelList(List responseDtoList);
+
+ 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..4eaa90ba
--- /dev/null
+++ b/src/main/java/mate/academy/rickandmorty/model/Character.java
@@ -0,0 +1,38 @@
+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 jakarta.persistence.Table;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.ToString;
+import org.hibernate.annotations.SQLDelete;
+import org.hibernate.annotations.SQLRestriction;
+
+@Entity
+@Table(name = "characters")
+@SQLDelete(sql = "UPDATE books SET is_deleted = TRUE WHERE id = ?")
+@SQLRestriction(value = "is_deleted = FALSE")
+@Getter
+@Setter
+@ToString
+@NoArgsConstructor
+public class Character {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+ @Column(nullable = false, unique = true)
+ private Long externalId;
+ @Column(nullable = false)
+ private String name;
+ @Column(nullable = false)
+ private String status;
+ @Column(nullable = false)
+ private String gender;
+ @Column(nullable = false)
+ private boolean isDeleted = false;
+}
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..aa1b886f
--- /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.JpaSpecificationExecutor;
+import org.springframework.data.jpa.repository.Query;
+
+public interface CharacterRepository extends JpaRepository,
+ JpaSpecificationExecutor {
+ List findByNameContaining(String name);
+
+ @Query("FROM Character ORDER BY RAND() LIMIT 1")
+ Character getRandomCharacter();
+}
diff --git a/src/main/java/mate/academy/rickandmorty/service/CharacterClient.java b/src/main/java/mate/academy/rickandmorty/service/CharacterClient.java
new file mode 100644
index 00000000..461a0d60
--- /dev/null
+++ b/src/main/java/mate/academy/rickandmorty/service/CharacterClient.java
@@ -0,0 +1,8 @@
+package mate.academy.rickandmorty.service;
+
+import java.util.List;
+import mate.academy.rickandmorty.dto.external.CharacterResponseDto;
+
+public interface CharacterClient {
+ List findAllCharacters();
+}
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..e00c1fca
--- /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.CharacterResponseDto;
+import mate.academy.rickandmorty.dto.internal.CharacterDto;
+
+public interface CharacterService {
+ void saveAllCharacters(List characters);
+
+ CharacterDto findRandomCharacter();
+
+ List findCharacterByName(String name);
+}
diff --git a/src/main/java/mate/academy/rickandmorty/service/impl/CharacterClientImpl.java b/src/main/java/mate/academy/rickandmorty/service/impl/CharacterClientImpl.java
new file mode 100644
index 00000000..4c4d1d98
--- /dev/null
+++ b/src/main/java/mate/academy/rickandmorty/service/impl/CharacterClientImpl.java
@@ -0,0 +1,58 @@
+package mate.academy.rickandmorty.service.impl;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+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.CharacterResponseDto;
+import mate.academy.rickandmorty.dto.external.CharactersDataResponseDto;
+import mate.academy.rickandmorty.exception.DataProcessingException;
+import mate.academy.rickandmorty.service.CharacterClient;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+@Component
+@RequiredArgsConstructor
+public class CharacterClientImpl implements CharacterClient {
+ private static final String PAGE_DELIMITER = "?page=";
+ private static final int PAGES_NUMBER = 42;
+ private static int CURRENT_PAGE = 1;
+ private final ObjectMapper objectMapper;
+ @Value("${rick-and-morty-url}")
+ private String characterUrl;
+
+ @Override
+ public List findAllCharacters() {
+ List characters = new ArrayList<>();
+ HttpClient client = HttpClient.newHttpClient();
+ while (CURRENT_PAGE <= PAGES_NUMBER) {
+ HttpRequest request = HttpRequest.newBuilder()
+ .GET()
+ .uri(URI.create(characterUrl + PAGE_DELIMITER + CURRENT_PAGE))
+ .build();
+ try {
+ HttpResponse response = client.send(
+ request, HttpResponse.BodyHandlers.ofString());
+ characters.addAll(parseResponse(response.body()).results());
+ CURRENT_PAGE++;
+ } catch (IOException | InterruptedException e) {
+ throw new DataProcessingException("Can't send request to get data", e);
+ }
+ }
+ return characters;
+ }
+
+ private CharactersDataResponseDto parseResponse(String responseBody) {
+ try {
+ return objectMapper.readValue(responseBody, CharactersDataResponseDto.class);
+ } catch (JsonProcessingException e) {
+ throw new DataProcessingException("Can't parse response body.", 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..2cf9d092
--- /dev/null
+++ b/src/main/java/mate/academy/rickandmorty/service/impl/CharacterServiceImpl.java
@@ -0,0 +1,34 @@
+package mate.academy.rickandmorty.service.impl;
+
+import java.util.List;
+import lombok.RequiredArgsConstructor;
+import mate.academy.rickandmorty.dto.external.CharacterResponseDto;
+import mate.academy.rickandmorty.dto.internal.CharacterDto;
+import mate.academy.rickandmorty.mapper.CharacterMapper;
+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;
+
+ @Override
+ public void saveAllCharacters(List responseDtoList) {
+ characterRepository.saveAll(
+ characterMapper.toModelList(responseDtoList));
+ }
+
+ @Override
+ public CharacterDto findRandomCharacter() {
+ return characterMapper.toDto(characterRepository.getRandomCharacter());
+ }
+
+ @Override
+ public List findCharacterByName(String name) {
+ return characterMapper.toDtoList(
+ characterRepository.findByNameContaining(name));
+ }
+}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index 8b137891..262526da 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -1 +1,10 @@
+spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
+spring.datasource.url=jdbc:mysql://localhost:3306/rick_and_morty_db?serverTimezone=UTC
+spring.datasource.username=root
+spring.datasource.password=Mate2023
+spring.jpa.hibernate.ddl-auto=create-drop
+spring.jpa.show-sql=true
+spring.jpa.open-in-view=false
+
+rick-and-morty-url=https://rickandmortyapi.com/api/character
diff --git a/src/main/resources/init_db.sql b/src/main/resources/init_db.sql
new file mode 100644
index 00000000..2f72bc98
--- /dev/null
+++ b/src/main/resources/init_db.sql
@@ -0,0 +1 @@
+CREATE SCHEMA IF NOT EXISTS `rick_and_morty_db` DEFAULT CHARACTER SET UTF8MB4;
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() {
+ }
}
diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties
index bc2fdde8..63824dda 100644
--- a/src/test/resources/application.properties
+++ b/src/test/resources/application.properties
@@ -3,3 +3,9 @@ spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
+
+spring.jpa.hibernate.ddl-auto=create-drop
+spring.jpa.show-sql=true
+spring.jpa.open-in-view=false
+
+rick-and-morty-url=https://rickandmortyapi.com/api/character