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

Implemented Rick and Morty app #172

Open
wants to merge 3 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
62 changes: 61 additions & 1 deletion 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.3.1</version>
<relativePath/>
</parent>
<groupId>mate.academy</groupId>
Expand All @@ -19,6 +19,8 @@
<maven.checkstyle.plugin.configLocation>
https://raw.githubusercontent.com/mate-academy/style-guides/master/java/checkstyle.xml
</maven.checkstyle.plugin.configLocation>
<mapstruct.version>1.5.5.Final</mapstruct.version>
<lombok-mapstruct-binding.version>0.2.0</lombok-mapstruct-binding.version>
</properties>
<dependencies>
<dependency>
Expand All @@ -41,6 +43,35 @@
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>

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

<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.4.0</version>
</dependency>

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

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.1.0</version>
</dependency>
</dependencies>

<build>
Expand All @@ -49,6 +80,7 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
Expand All @@ -66,6 +98,34 @@
<consoleOutput>true</consoleOutput>
<failsOnError>true</failsOnError>
<linkXRef>false</linkXRef>
<sourceDirectories>src</sourceDirectories>
</configuration>
</plugin>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<annotationProcessorPaths>
<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>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
Expand Down
14 changes: 14 additions & 0 deletions src/main/java/mate/academy/rickandmorty/Application.java
Original file line number Diff line number Diff line change
@@ -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<CharacterResponseDto> characters = characterClient.findAllCharacters();
characterService.saveAllCharacters(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,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<CharacterDto> 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();
}
}
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.JsonIgnoreProperties;

@JsonIgnoreProperties(ignoreUnknown = true)
public record CharacterResponseDto(
Long id,
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.JsonIgnoreProperties;
import java.util.List;

@JsonIgnoreProperties(ignoreUnknown = true)
public record CharactersDataResponseDto(
List<CharacterResponseDto> results) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package mate.academy.rickandmorty.dto.internal;

public record CharacterDto(
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 DataProcessingException extends RuntimeException {
public DataProcessingException(String message, Throwable cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
@@ -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<Character> toModelList(List<CharacterResponseDto> responseDtoList);

List<CharacterDto> toDtoList(List<Character> characterList);
}
38 changes: 38 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,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;
}
Original file line number Diff line number Diff line change
@@ -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<Character, Long>,
JpaSpecificationExecutor<Character> {
List<Character> findByNameContaining(String name);

@Query("FROM Character ORDER BY RAND() LIMIT 1")
Character getRandomCharacter();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package mate.academy.rickandmorty.service;

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

public interface CharacterClient {
List<CharacterResponseDto> findAllCharacters();
}
Original file line number Diff line number Diff line change
@@ -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<CharacterResponseDto> characters);

CharacterDto findRandomCharacter();

List<CharacterDto> findCharacterByName(String name);
}
Original file line number Diff line number Diff line change
@@ -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<CharacterResponseDto> findAllCharacters() {
List<CharacterResponseDto> 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<String> 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);
}
}
}
Loading
Loading