Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add solution #211

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 68 additions & 8 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.4</version>
<version>3.4.0</version>
<relativePath/>
</parent>
<groupId>mate.academy</groupId>
Expand All @@ -15,28 +15,64 @@
<description>jv-rick-and-morty</description>
<properties>
<java.version>17</java.version>
<maven.checkstyle.plugin.version>3.1.1</maven.checkstyle.plugin.version>
<maven.checkstyle.plugin.configLocation>
https://raw.githubusercontent.com/mate-academy/style-guides/master/java/checkstyle.xml
</maven.checkstyle.plugin.configLocation>
<lombok.mapstruct.binding.version>0.2.0</lombok.mapstruct.binding.version>
<mapstruct.version>1.6.3</mapstruct.version>
<lombok.version>1.18.36</lombok.version>
<openapi.version>2.7.0</openapi.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>

<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>9.1.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${mapstruct.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-mapstruct-binding</artifactId>
<version>${lombok.mapstruct.binding.version}</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>${openapi.version}</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webflux-ui</artifactId>
<version>${openapi.version}</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
Expand All @@ -45,6 +81,29 @@

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-mapstruct-binding</artifactId>
<version>${lombok.mapstruct.binding.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
Expand All @@ -66,6 +125,7 @@
<consoleOutput>true</consoleOutput>
<failsOnError>true</failsOnError>
<linkXRef>false</linkXRef>
<sourceDirectories>src</sourceDirectories>
</configuration>
</plugin>
</plugins>
Expand Down
25 changes: 25 additions & 0 deletions src/main/java/mate/academy/rickandmorty/Application.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,37 @@
package mate.academy.rickandmorty;

import java.util.List;
import mate.academy.rickandmorty.client.RickAndMortyApiClient;
import mate.academy.rickandmorty.dto.external.ExternalCharacterDto;
import mate.academy.rickandmorty.mapper.CharacterMapper;
import mate.academy.rickandmorty.service.CharacterService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class Application {
@Autowired
private RickAndMortyApiClient rickAndMortyApiClient;
@Autowired
private CharacterService characterService;
@Autowired
private CharacterMapper mapper;

public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}

@Bean
public CommandLineRunner commandLineRunner() {
return args -> {
List<ExternalCharacterDto> responseCharacters =
rickAndMortyApiClient.fetchAllCharacters();
responseCharacters.stream()
.map(dto -> mapper.toModel(dto))
.forEach(character -> characterService.save(character));
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package mate.academy.rickandmorty.client;

import java.util.List;
import mate.academy.rickandmorty.dto.external.ExternalCharacterDto;

public interface RickAndMortyApiClient {
List<ExternalCharacterDto> fetchAllCharacters();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package mate.academy.rickandmorty.client.impl;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
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.client.RickAndMortyApiClient;
import mate.academy.rickandmorty.dto.external.ExternalCharacterDto;
import mate.academy.rickandmorty.dto.external.ResponseAllCharactersDto;
import mate.academy.rickandmorty.exception.HttpException;
import mate.academy.rickandmorty.exception.MappingException;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

@Component
@RequiredArgsConstructor
public class RickAndMortyApiClientImpl implements RickAndMortyApiClient {
private static final String BASE_URL = "https://rickandmortyapi.com/api";
private static final String CHARACTER_URI = "/character";
private static final String PAGE_REQUEST_PARAM = "?page=";
private static final int FIRST_PAGE = 1;

private final ObjectMapper objectMapper;
private final HttpClient httpClient = HttpClient.newHttpClient();

@Override
public List<ExternalCharacterDto> fetchAllCharacters() {
int page = FIRST_PAGE;
int status;
List<ExternalCharacterDto> responseCharacterDtoList = new ArrayList<>();
boolean isListFinished = false;

do {
HttpRequest request = HttpRequest.newBuilder()
.GET()
.uri(URI.create(BASE_URL
+ CHARACTER_URI
+ PAGE_REQUEST_PARAM
+ page))
.build();
HttpResponse<String> response = null;
try {
response = httpClient
.send(request, HttpResponse.BodyHandlers.ofString());
} catch (IOException | InterruptedException e) {
throw new HttpException("Can't fetch Rick and Morty characters info", e);
}
status = response.statusCode();

if (status == HttpStatus.OK.value() && StringUtils.hasText(response.body())) {
try {
ResponseAllCharactersDto charactersDto = objectMapper
.readValue(response.body(), new TypeReference<>() {});
responseCharacterDtoList.addAll(charactersDto.results());
} catch (JsonProcessingException e) {
throw new MappingException("Can't map response body to "
+ "ResponseAllCharactersDto object. Body: " + response.body(), e);
}
} else {
isListFinished = true;
}
page++;
} while (status == HttpStatus.OK.value() && !isListFinished);

return responseCharacterDtoList;
}
}
13 changes: 13 additions & 0 deletions src/main/java/mate/academy/rickandmorty/config/MapperConfig.java
Original file line number Diff line number Diff line change
@@ -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 = "<PACKAGE_NAME>.impl"
)
public class MapperConfig {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package mate.academy.rickandmorty.controller;

import io.swagger.v3.oas.annotations.Operation;
import java.util.List;
import lombok.RequiredArgsConstructor;
import mate.academy.rickandmorty.dto.internal.InternalCharacterDto;
import mate.academy.rickandmorty.service.CharacterService;
import org.springdoc.core.annotations.ParameterObject;
import org.springframework.data.domain.Pageable;
import org.springframework.http.MediaType;
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;

@RestController
@RequestMapping("/characters")
@RequiredArgsConstructor
public class CharacterController {
private final CharacterService characterService;

@Operation(summary = "Get random \"Rick and Morty\" character")
@GetMapping(value = "/random", produces = MediaType.APPLICATION_JSON_VALUE)
public InternalCharacterDto getRandomCharacter() {
return characterService.getRandomCharacter();
}

@Operation(summary = "Get \"Rick and Morty\" characters list by name like with pagination")
@GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
public List<InternalCharacterDto> getCharactersWithNameLike(
@RequestParam("namePart") String namePart,
@ParameterObject() Pageable pageable) {
return characterService.getAllCharactersWithNameLike(namePart, pageable);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package mate.academy.rickandmorty.dto.external;

public record ExternalCharacterDto(Long id, String name, String status, String gender) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package mate.academy.rickandmorty.dto.external;

import java.util.List;

public record ResponseAllCharactersDto(List<ExternalCharacterDto> results) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package mate.academy.rickandmorty.dto.internal;

public record InternalCharacterDto(Long id,
Long externalId,
String name,
String status,
String gender) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package mate.academy.rickandmorty.exception;

public class HttpException extends RuntimeException {
public HttpException(String message, Throwable cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package mate.academy.rickandmorty.exception;

public class MappingException extends RuntimeException {
public MappingException(String message, Throwable cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package mate.academy.rickandmorty.mapper;

import mate.academy.rickandmorty.config.MapperConfig;
import mate.academy.rickandmorty.dto.external.ExternalCharacterDto;
import mate.academy.rickandmorty.dto.internal.InternalCharacterDto;
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")
@Mapping(target = "id", ignore = true)
Character toModel(ExternalCharacterDto dto);

InternalCharacterDto toDto(Character character);
}
24 changes: 24 additions & 0 deletions src/main/java/mate/academy/rickandmorty/model/Character.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
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 Long externalId;
private String name;
private String status;
private String gender;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package mate.academy.rickandmorty.repository;

import java.util.List;
import mate.academy.rickandmorty.model.Character;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;

public interface CharacterRepository extends JpaRepository<Character, Long> {
List<Character> getAllByNameLikeIgnoreCase(String partName, Pageable pageable);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package mate.academy.rickandmorty.service;

import java.util.List;
import mate.academy.rickandmorty.dto.internal.InternalCharacterDto;
import mate.academy.rickandmorty.model.Character;
import org.springframework.data.domain.Pageable;

public interface CharacterService {
Character save(Character character);

InternalCharacterDto getRandomCharacter();

List<InternalCharacterDto> getAllCharactersWithNameLike(String namePart, Pageable pageable);
}
Loading
Loading