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

Did the task -Rick and Morty- #146

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
38 changes: 35 additions & 3 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,50 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>

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

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

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.4.2.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/mate/academy/rickandmorty/Application.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication
@ComponentScan("mate.academy.rickandmorty.mapper")

Choose a reason for hiding this comment

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

Your spring application is already scanning this package, so this line is redundant

Suggested change
@ComponentScan("mate.academy.rickandmorty.mapper")

Copy link

Choose a reason for hiding this comment

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

not fixed, doesn't it work without it?

public class Application {

public static void main(String[] args) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package mate.academy.rickandmorty.client;

public interface CharacterClient {
void getAndSaveCharacters();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package mate.academy.rickandmorty.client.impl;

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.CharacterClient;
import mate.academy.rickandmorty.dto.external.CharacterResponseDto;
import mate.academy.rickandmorty.dto.external.ExternalCharacterDto;
import mate.academy.rickandmorty.mapper.CharacterMapper;
import mate.academy.rickandmorty.model.Character;
import mate.academy.rickandmorty.repository.CharacterRepository;
import org.springframework.stereotype.Component;

@RequiredArgsConstructor
@Component
public class CharacterClientImpl implements CharacterClient {
private static final String API_URL = "https://rickandmortyapi.com/api/character";
private final CharacterRepository characterRepository;
private final CharacterMapper characterMapper;
private final ObjectMapper objectMapper;

@Override
public void getAndSaveCharacters() {

Choose a reason for hiding this comment

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

image
image
You need to tell Spring that you want to fetch all the data once, at the start of the application. Now your application never calls this method. So no data is fetched

HttpClient httpClient = HttpClient.newHttpClient();
String url = API_URL;
List<ExternalCharacterDto> externalCharacterDtoList = new ArrayList<>();

do {
HttpRequest httpRequest = HttpRequest.newBuilder()
.GET()
.uri(URI.create(url))
.build();

try {
HttpResponse<String> response = httpClient.send(httpRequest,
HttpResponse.BodyHandlers.ofString());
CharacterResponseDto dataDto = objectMapper

Choose a reason for hiding this comment

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

Suggested change
CharacterResponseDto dataDto = objectMapper
CharacterResponseDto characterDto = objectMapper

.readValue(response.body(), CharacterResponseDto.class);

url = dataDto.getInfo().getNextUrl();
externalCharacterDtoList.addAll(dataDto.getResult().stream().toList());

} catch (IOException | InterruptedException e) {
throw new RuntimeException(e);
}
} while (url != null);

Iterable<Character> characters = externalCharacterDtoList.stream()

Choose a reason for hiding this comment

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

Suggested change
Iterable<Character> characters = externalCharacterDtoList.stream()
List<Character> characters = externalCharacterDtoList.stream()

.map(characterMapper::toModel)
.toList();

Choose a reason for hiding this comment

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

Mapstruct supports mapping collections from the box


characterRepository.saveAll(characters);
}
}
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,32 @@
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.InternalCharacterDto;
import mate.academy.rickandmorty.service.CharacterService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Tag(name = "Character management", description = "Endpoints for managing products")
@RequiredArgsConstructor
@RestController
@RequestMapping(value = "/character")
public class CharacterController {
private final CharacterService characterService;

@GetMapping("/random")
@Operation(summary = "Get randomly character", description = "Return a character by random id")

Choose a reason for hiding this comment

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

Suggested change
@Operation(summary = "Get randomly character", description = "Return a character by random id")
@Operation(summary = "Get random character", description = "Return a character by random id")

Also Return a character by random id I don't see any id in method declaration, think of better description, it's confusing

public InternalCharacterDto getRandomlyCharacter() {

Choose a reason for hiding this comment

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

Suggested change
public InternalCharacterDto getRandomlyCharacter() {
public InternalCharacterDto getRandomCharacter() {

return characterService.getRandomlyCharacter();

Choose a reason for hiding this comment

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

Suggested change
return characterService.getRandomlyCharacter();
return characterService.getRandomCharacter();

}

@GetMapping
@Operation(summary = "Filter by the given name",
description = "Returns a list of all characters whose name contains the search string")
public List<InternalCharacterDto> getCharacterByName(String name) {

Choose a reason for hiding this comment

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

Suggested change
public List<InternalCharacterDto> getCharacterByName(String name) {
public List<InternalCharacterDto> getCharactersByName(@RequestParam String name) {

return characterService.getCharacterByName(name);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package mate.academy.rickandmorty.dto.external;

import java.util.List;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Setter
@Getter
@ToString
public class CharacterResponseDto {
private InfoResponseDto info;
private List<ExternalCharacterDto> result;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package mate.academy.rickandmorty.dto.external;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Setter
@Getter
@ToString
public class ExternalCharacterDto {
@JsonProperty("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,13 @@
package mate.academy.rickandmorty.dto.external;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Setter
@Getter
@ToString
public class InfoResponseDto {

Choose a reason for hiding this comment

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

You can use @DaTa with Dtos instead of these annotations

private String nextUrl;

Choose a reason for hiding this comment

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

Have you checked the result? I don't think next deserializes into nextUrl


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

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Setter
@Getter
@ToString
public class InternalCharacterDto {
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.exception;

public class CharacterNotFoundException extends RuntimeException {
public CharacterNotFoundException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
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;

@Mapper(config = MapperConfig.class)
public interface CharacterMapper {
Character toModel(ExternalCharacterDto externalCharacterDto);

InternalCharacterDto toDto(Character character);
}
25 changes: 25 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,25 @@
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;
import lombok.ToString;

@Setter
@Getter
@ToString
@Entity
@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,11 @@
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<Character, Long> {
List<Character> findAllByName(String name);
}
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.InternalCharacterDto;

public interface CharacterService {
InternalCharacterDto getRandomlyCharacter();

List<InternalCharacterDto> getCharacterByName(String name);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package mate.academy.rickandmorty.service.impl;

import java.util.List;
import java.util.Random;
import lombok.RequiredArgsConstructor;
import mate.academy.rickandmorty.dto.internal.InternalCharacterDto;
import mate.academy.rickandmorty.exception.CharacterNotFoundException;
import mate.academy.rickandmorty.mapper.CharacterMapper;
import mate.academy.rickandmorty.repository.CharacterRepository;
import mate.academy.rickandmorty.service.CharacterService;
import org.springframework.stereotype.Service;

@RequiredArgsConstructor
@Service
public class CharacterServiceImpl implements CharacterService {
private final CharacterRepository characterRepository;
private final CharacterMapper characterMapper;
private final Random random;

@Override
public InternalCharacterDto getRandomlyCharacter() {

Choose a reason for hiding this comment

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

@transactional. You make more than one call to DB

Long randomId = random.nextLong(characterRepository.count()) + 1;
return characterMapper.toDto(characterRepository.findById(randomId)
.orElseThrow(()
-> new CharacterNotFoundException("Can`t find a character by id: "
+ randomId)));
}

@Override
public List<InternalCharacterDto> getCharacterByName(String name) {

Choose a reason for hiding this comment

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

Suggested change
public List<InternalCharacterDto> getCharacterByName(String name) {
public List<InternalCharacterDto> getCharactersByName(String name) {

return characterRepository.findAllByName(name)

Choose a reason for hiding this comment

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

Suggested change
return characterRepository.findAllByName(name)
return characterRepository.findAllByNameIn(name)

image
Let's say you have Rick in DB. If I call getCharacterByName("ck"), I want it to return Rick, Now your method will return Rick only if I call getCharacterByName("Rick")

.stream().map(characterMapper::toDto)
.toList();

Choose a reason for hiding this comment

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

Mapstruct supports mapping collections from the box

}
}
8 changes: 7 additions & 1 deletion src/main/resources/application.properties
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@

spring.datasource.url=jdbc:mysql://localhost:3306/rick_and_morty
spring.datasource.username=root
spring.datasource.password=6855
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
Loading