diff --git a/README.md b/README.md
index 4815bd91..c475ff23 100644
--- a/README.md
+++ b/README.md
@@ -12,7 +12,7 @@
"id": 1,
"externalId": "1",
"name": "Rick Sanchez",
- "status": "Alive",
+ "status": "Alive",
"gender": "Male"
}
```
@@ -29,7 +29,7 @@
1. You must use [public API](https://rickandmortyapi.com/documentation/#rest) (you should use REST API).
2. All data from the public API should be fetched once, and only once, when the Application context is created
-
+
### Tech Requirements
- Use MySQL DB in your app.
diff --git a/pom.xml b/pom.xml
index 0c754f19..889e18c8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -32,6 +32,11 @@
test
+
+ org.projectlombok
+ lombok
+
+
org.springframework.boot
spring-boot-starter-data-jpa
@@ -41,6 +46,47 @@
com.h2database
h2
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ mysql
+ mysql-connector-java
+ 8.0.33
+
+
+
+ org.springdoc
+ springdoc-openapi-starter-webmvc-ui
+ 2.1.0
+
+
+
+ org.mapstruct
+ mapstruct
+ 1.5.2.Final
+
+
+
+ org.mapstruct
+ mapstruct-processor
+ 1.5.2.Final
+ provided
+
+
+
+ org.hibernate.validator
+ hibernate-validator
+
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ 2.17.2
+
@@ -66,6 +112,7 @@
true
true
false
+ main
diff --git a/src/main/java/mate/academy/rickandmorty/Application.java b/src/main/java/mate/academy/rickandmorty/Application.java
index cdea84fc..3b4e97e6 100644
--- a/src/main/java/mate/academy/rickandmorty/Application.java
+++ b/src/main/java/mate/academy/rickandmorty/Application.java
@@ -5,7 +5,6 @@
@SpringBootApplication
public class Application {
-
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
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..e44366d2
--- /dev/null
+++ b/src/main/java/mate/academy/rickandmorty/config/MapperConfig.java
@@ -0,0 +1,14 @@
+package mate.academy.rickandmorty.config;
+
+import org.mapstruct.InjectionStrategy;
+import org.mapstruct.NullValueCheckStrategy;
+
+@org.mapstruct.MapperConfig(
+ componentModel = "spring",
+ injectionStrategy = InjectionStrategy.CONSTRUCTOR,
+ nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS,
+ implementationName = "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..4b99b01b
--- /dev/null
+++ b/src/main/java/mate/academy/rickandmorty/controller/CharacterController.java
@@ -0,0 +1,34 @@
+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.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;
+
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/characters")
+@Tag(name = "Characters", description = "Operations related to characters")
+public class CharacterController {
+ private final CharacterService characterService;
+
+ @GetMapping("/random")
+ @Operation(summary = "Get a random character",
+ description = "Retrieve a random character from \"Rick and Morty\"")
+ public CharacterDto getRandomCharacter() {
+ return characterService.getRandomCharacter();
+ }
+
+ @GetMapping("/search/{name}")
+ @Operation(summary = "Get characters by name",
+ description = "Retrieve a list of characters name fragment")
+ public List getBookByName(@PathVariable String name) {
+ return characterService.searchCharacters(name);
+ }
+}
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..cf2ae582
--- /dev/null
+++ b/src/main/java/mate/academy/rickandmorty/dto/CharacterDto.java
@@ -0,0 +1,4 @@
+package mate.academy.rickandmorty.dto;
+
+public record CharacterDto(Long id, String externalId, String name, String status, String gender) {
+}
diff --git a/src/main/java/mate/academy/rickandmorty/dto/CreateCharacterRequestDto.java b/src/main/java/mate/academy/rickandmorty/dto/CreateCharacterRequestDto.java
new file mode 100644
index 00000000..d0c540bf
--- /dev/null
+++ b/src/main/java/mate/academy/rickandmorty/dto/CreateCharacterRequestDto.java
@@ -0,0 +1,20 @@
+package mate.academy.rickandmorty.dto;
+
+import jakarta.validation.constraints.NotBlank;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+
+@Setter
+@Getter
+@AllArgsConstructor
+public class CreateCharacterRequestDto {
+ @NotBlank
+ private String externalId;
+ @NotBlank
+ private String name;
+ @NotBlank
+ private String status;
+ @NotBlank
+ 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..30b9476d
--- /dev/null
+++ b/src/main/java/mate/academy/rickandmorty/mapper/CharacterMapper.java
@@ -0,0 +1,11 @@
+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;
+
+@Mapper(config = MapperConfig.class)
+public interface CharacterMapper {
+ 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..d69fe497
--- /dev/null
+++ b/src/main/java/mate/academy/rickandmorty/model/Character.java
@@ -0,0 +1,23 @@
+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.Setter;
+
+@Entity
+@Getter
+@Setter
+@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..c0465b04
--- /dev/null
+++ b/src/main/java/mate/academy/rickandmorty/repository/CharacterRepository.java
@@ -0,0 +1,12 @@
+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 findByNameContaining(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..5fd1006d
--- /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.CharacterDto;
+
+public interface CharacterService {
+ CharacterDto getRandomCharacter();
+
+ List searchCharacters(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..89c7040d
--- /dev/null
+++ b/src/main/java/mate/academy/rickandmorty/service/CharacterServiceImpl.java
@@ -0,0 +1,32 @@
+package mate.academy.rickandmorty.service;
+
+import java.util.List;
+import java.util.Random;
+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;
+
+@Service
+@RequiredArgsConstructor
+public class CharacterServiceImpl implements CharacterService {
+ private final CharacterRepository characterRepository;
+ private final CharacterMapper characterMapper;
+ private static final Random random = new Random();
+
+ @Override
+ public CharacterDto getRandomCharacter() {
+ List characters = characterRepository.findAll();
+ return characterMapper
+ .toDto(characters.get(random.nextInt(characters.size())));
+ }
+
+ @Override
+ public List searchCharacters(String name) {
+ return characterRepository.findByNameContaining(name).stream()
+ .map(characterMapper::toDto)
+ .toList();
+ }
+}
diff --git a/src/main/java/mate/academy/rickandmorty/service/DataInitializer.java b/src/main/java/mate/academy/rickandmorty/service/DataInitializer.java
new file mode 100644
index 00000000..53a8adca
--- /dev/null
+++ b/src/main/java/mate/academy/rickandmorty/service/DataInitializer.java
@@ -0,0 +1,55 @@
+package mate.academy.rickandmorty.service;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.AllArgsConstructor;
+import mate.academy.rickandmorty.model.Character;
+import mate.academy.rickandmorty.repository.CharacterRepository;
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.stereotype.Component;
+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;
+
+@Component
+@AllArgsConstructor
+public class DataInitializer implements CommandLineRunner {
+ private static final String URL = "https://rickandmortyapi.com/api/character";
+ private final CharacterRepository characterRepository;
+ private final ObjectMapper objectMapper;
+
+ @Override
+ public void run(String... args) throws IOException, InterruptedException {
+ HttpClient httpClient = HttpClient.newHttpClient();
+ String nextPageUrl = URL;
+
+ while (nextPageUrl != null) {
+ HttpRequest httpRequest = HttpRequest.newBuilder()
+ .GET()
+ .uri(URI.create(nextPageUrl))
+ .build();
+ HttpResponse response = httpClient
+ .send(httpRequest, HttpResponse.BodyHandlers.ofString());
+
+ JsonNode rootNode = objectMapper.readTree(response.body());
+ List characters = new ArrayList<>();
+ JsonNode results = rootNode.get("results");
+
+ for (JsonNode result : results) {
+ Character character = new Character();
+ character.setExternalId(result.get("id").asText());
+ character.setName(result.get("name").asText());
+ character.setGender(result.get("gender").asText());
+ character.setStatus(result.get("status").asText());
+
+ characters.add(character);
+ }
+ characterRepository.saveAll(characters);
+ nextPageUrl = rootNode.get("info").get("next").asText(null);
+ }
+ }
+}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index 8b137891..5304e22d 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -1 +1,5 @@
+spring.datasource.url=jdbc:mysql://localhost:3306/rick_and_morty
+spring.datasource.username=root
+spring.datasource.password=root12345
+spring.jpa.hibernate.ddl-auto=update
diff --git a/src/test/java/mate/academy/rickandmorty/ApplicationTests.java b/src/test/java/mate/academy/rickandmorty/ApplicationTests.java
index 8fec6af0..8697db13 100644
--- a/src/test/java/mate/academy/rickandmorty/ApplicationTests.java
+++ b/src/test/java/mate/academy/rickandmorty/ApplicationTests.java
@@ -5,9 +5,7 @@
@SpringBootTest
class ApplicationTests {
-
@Test
void contextLoads() {
}
-
}