Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
37 changes: 37 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
<description>jv-rick-and-morty</description>
<properties>
<java.version>17</java.version>
<mapstruct.version>1.5.5.Final</mapstruct.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
Expand All @@ -41,6 +42,41 @@
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>

<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${mapstruct.version}</version>
</dependency>

<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>annotationProcessor</scope>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.3.0</version>
</dependency>
</dependencies>

<build>
Expand All @@ -66,6 +102,7 @@
<consoleOutput>true</consoleOutput>
<failsOnError>true</failsOnError>
<linkXRef>false</linkXRef>
<sourceDirectories>src</sourceDirectories>
</configuration>
</plugin>
</plugins>
Expand Down
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,37 @@
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.InternalHeroDto;
import mate.academy.rickandmorty.service.RickAndMortyService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Tag(name = "Rick&MortyApi", description = "")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Empty description is not very good

@RequiredArgsConstructor
@RestController
@RequestMapping("/character")
public class RickAndMortyController {
private final RickAndMortyService rickAndMortyService;

@Operation(summary = "Add random hero", description = """
The request randomly generates a wiki about one character in
the universe the animated series Rick & Morty.""")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Does this endpoint add a random hero or return a random one?

@GetMapping("/get")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
@GetMapping("/get")
@GetMapping("/random")

public InternalHeroDto rickAndMorty() {
return rickAndMortyService.getRandomCharacter();
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You use 2 different names: hero and character for the same thing through all your project. Please, stick to one of them, because it can be confusing


@Operation(summary = "Find hero by name", description = """
The request takes a string as an argument, and returns a list of all characters
whose name contains the search string. During the application start,
the web application downloads data from a third-party service
to the internal database.""")
@GetMapping("/find")
List<InternalHeroDto> heroesByName(String name) {
return rickAndMortyService.findByName(name);
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. You don't use a @PathVariable or @RequestParam, where do you get parameter "name" from?)
Suggested change
@GetMapping("/find")
List<InternalHeroDto> heroesByName(String name) {
return rickAndMortyService.findByName(name);
}
@GetMapping
List<InternalHeroDto> getCharacterByName(@RequestParam String name) {
return rickAndMortyService.findByName(name);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package mate.academy.rickandmorty.dto.external;

import com.fasterxml.jackson.annotation.JsonProperty;

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

import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;

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

public record InternalHeroDto(Long id,
Long externalId,
String name,
String status,
String gender) {
}
15 changes: 15 additions & 0 deletions src/main/java/mate/academy/rickandmorty/mapper/HeroMapper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package mate.academy.rickandmorty.mapper;

import mate.academy.rickandmorty.config.MapperConfig;
import mate.academy.rickandmorty.dto.external.ExternalHeroDto;
import mate.academy.rickandmorty.dto.internal.InternalHeroDto;
import mate.academy.rickandmorty.model.Hero;
import org.mapstruct.Mapper;

@Mapper(config = MapperConfig.class)
public interface HeroMapper {

InternalHeroDto toDto(Hero hero);

Hero toEntity(ExternalHeroDto dto);
}
23 changes: 23 additions & 0 deletions src/main/java/mate/academy/rickandmorty/model/Hero.java
Original file line number Diff line number Diff line change
@@ -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;

@Getter
@Setter
@Entity
@Table(name = "heroes")
public class Hero {
@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,7 @@
package mate.academy.rickandmorty.repository;

import mate.academy.rickandmorty.model.Hero;
import org.springframework.data.jpa.repository.JpaRepository;

public interface HeroRepository extends JpaRepository<Hero, Long> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package mate.academy.rickandmorty.service;

import java.util.List;

public interface ClientApi<T> {
T getRandomHero(int randomNumber);

List<T> findHeroesByName(String heroName);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
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.List;
import lombok.RequiredArgsConstructor;
import mate.academy.rickandmorty.dto.external.ExternalHeroDto;
import mate.academy.rickandmorty.dto.external.ExternalHeroResponseDto;
import org.springframework.stereotype.Component;

@RequiredArgsConstructor
@Component
public class RickAndMortyClientImpl implements ClientApi<ExternalHeroDto> {
private static final String BASE_URL = "https://rickandmortyapi.com/api/character";
private static final String BY_ID = "%s/%d";
private static final String BY_NAME = "%s?name=%s";
private final ObjectMapper objectMapper;

@Override
public ExternalHeroDto getRandomHero(int randomNumber) {
HttpClient httpClient = HttpClient.newHttpClient();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move to the constants

String url = String.format(BY_ID, BASE_URL, randomNumber);
HttpRequest httpRequest = initRequest(url);
try {
HttpResponse<String> response = httpClient
.send(httpRequest, HttpResponse.BodyHandlers.ofString());
return objectMapper
.readValue(response.body(), ExternalHeroDto.class);
} catch (IOException | InterruptedException e) {
throw new RuntimeException(e);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add an informative error message

}
}

@Override
public List<ExternalHeroDto> findHeroesByName(String characterName) {
HttpClient httpClient = HttpClient.newHttpClient();
String url = String.format(BY_NAME, BASE_URL, characterName);
HttpRequest httpRequest = initRequest(url);
try {
HttpResponse<String> response = httpClient
.send(httpRequest, HttpResponse.BodyHandlers.ofString());
return objectMapper
.readValue(response.body(), ExternalHeroResponseDto.class).result();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return objectMapper
.readValue(response.body(), ExternalHeroResponseDto.class).result();
return objectMapper
.readValue(response.body(), ExternalHeroResponseDto.class)
.result();

} catch (IOException | InterruptedException e) {
throw new RuntimeException(e);
}
}

private HttpRequest initRequest(String url) {
return HttpRequest.newBuilder()
.GET()
.uri(URI.create(url))
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package mate.academy.rickandmorty.service;

import java.util.List;
import mate.academy.rickandmorty.dto.internal.InternalHeroDto;

public interface RickAndMortyService {
InternalHeroDto getRandomCharacter();

List<InternalHeroDto> findByName(String name);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package mate.academy.rickandmorty.service;

import java.util.List;
import java.util.Random;
import lombok.RequiredArgsConstructor;
import mate.academy.rickandmorty.dto.external.ExternalHeroDto;
import mate.academy.rickandmorty.dto.internal.InternalHeroDto;
import mate.academy.rickandmorty.mapper.HeroMapper;
import mate.academy.rickandmorty.model.Hero;
import mate.academy.rickandmorty.repository.HeroRepository;
import org.springframework.stereotype.Service;

@RequiredArgsConstructor
@Service
public class RickAndMortyServiceImpl implements RickAndMortyService {
private static final int API_COUNT_HEROES = 800;
private final ClientApi<ExternalHeroDto> rickAndMortyClient;
private final HeroMapper heroMapper;
private final HeroRepository heroRepository;

@Override
public InternalHeroDto getRandomCharacter() {
ExternalHeroDto randomHero = rickAndMortyClient
.getRandomHero(new Random().nextInt(API_COUNT_HEROES));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move Random to the constants

Hero entity = heroMapper.toEntity(randomHero);
return heroMapper.toDto(heroRepository.save(entity));
}

@Override
public List<InternalHeroDto> findByName(String name) {
return rickAndMortyClient
.findHeroesByName(name).stream()
.map(e -> {
Hero entity = heroMapper.toEntity(e);
return heroMapper.toDto(heroRepository.save(entity));
})
.toList();
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need always call rickAndMortyClient.findHeroesByName and perform a HTTP call + save the same values in DB (if name is repeated), instead you can load this values from DB

}
7 changes: 7 additions & 0 deletions src/main/resources/application.properties
Original file line number Diff line number Diff line change
@@ -1 +1,8 @@
spring.datasource.url=jdbc:mysql://localhost:3306/homework
spring.datasource.username=root
spring.datasource.password=databasepractice1!
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.hibernate.ddl-auto=create-drop

spring.jpa.show-sql=true
spring.jpa.open-in-view=false
8 changes: 3 additions & 5 deletions src/test/java/mate/academy/rickandmorty/ApplicationTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@

@SpringBootTest
class ApplicationTests {

@Test
void contextLoads() {
}

@Test
void contextLoads() {
}
}
Loading
Loading