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

done #192

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open

done #192

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
86 changes: 85 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.3</version>
<relativePath/>
</parent>
<groupId>mate.academy</groupId>
Expand All @@ -19,6 +19,7 @@
<maven.checkstyle.plugin.configLocation>
https://raw.githubusercontent.com/mate-academy/style-guides/master/java/checkstyle.xml
</maven.checkstyle.plugin.configLocation>
<org.mapstruct.version>1.6.0</org.mapstruct.version>
</properties>
<dependencies>
<dependency>
Expand All @@ -37,14 +38,95 @@
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

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

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<version>3.3.3</version>
</dependency>

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

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>6.1.12</version>
</dependency>

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

<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>

<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
</dependency>

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

<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20231013</version>
</dependency>

</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<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>0.2.0</version>
</path>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
Expand All @@ -64,8 +146,10 @@
<configuration>
<configLocation>${maven.checkstyle.plugin.configLocation}</configLocation>
<consoleOutput>true</consoleOutput>
<resourceExcludes>application.properties</resourceExcludes>
<failsOnError>true</failsOnError>
<linkXRef>false</linkXRef>
<sourceDirectories>src</sourceDirectories>
</configuration>
</plugin>
</plugins>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package mate.academy.rickandmorty.config.mapper;

import mate.academy.rickandmorty.dto.external.CharacterResponseDataDto;
import mate.academy.rickandmorty.dto.internal.CharacterDto;
import mate.academy.rickandmorty.model.CharacterRickAndMorty;
import org.mapstruct.InjectionStrategy;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.MappingConstants;
import org.mapstruct.NullValueCheckStrategy;

@Mapper(componentModel = MappingConstants.ComponentModel.SPRING,
injectionStrategy = InjectionStrategy.CONSTRUCTOR,
nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS,
implementationPackage = "<PACKAGE_NAME>.impl"
)
public interface CharacterMapper {
@Mapping(source = "id", target = "externalId")
CharacterRickAndMorty toCharacterModel(CharacterResponseDataDto responseDto);

CharacterDto toDto(CharacterRickAndMorty characterRickAndMorty);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package mate.academy.rickandmorty.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
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.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
@RequestMapping("/character")
@Tag(
name = "Rick and Morty controller",
description = "endpoints for getting characters")
public class RickAndMortyController {
private final CharacterService client;

@GetMapping("/random")
@Operation(
summary = "get random character",
description = "get random character",
responses = {@ApiResponse(
responseCode = "200",
description = "success"
)}
)
public CharacterDto getRandomCharacter() {
return client.getRandomCharacter();
}

@GetMapping("/by-name")
@Operation(
summary = "get all characters containing name",
description = "get all characters containing name",
responses = {@ApiResponse(
responseCode = "200",
description = "success"
)}
)
public List<CharacterDto> getCharacterByName(String name) {
return client.findByPartOfName(name);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package mate.academy.rickandmorty.dto.external;

import lombok.Data;

@Data
public class ApiInfoDto {
private Long count;
private Long pages;
private String next;
private String prev;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package mate.academy.rickandmorty.dto.external;

import java.util.List;
import lombok.Data;

@Data
public class ApiResponseDto {
private ApiInfoDto info;
private List<CharacterResponseDataDto> results;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package mate.academy.rickandmorty.dto.external;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;

@JsonIgnoreProperties(ignoreUnknown = true)
@Data
public class CharacterResponseDataDto {
private Long id;
private String name;
private String gender;
private String status;

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

import lombok.Data;

@Data
public class CharacterDto {
private Long id;
private Long externalId;
private String name;
private String gender;
private String status;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package mate.academy.rickandmorty.exception;

public class ApiResponseParsingException extends RuntimeException {
public ApiResponseParsingException(String message, Throwable cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
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.Setter;

@Entity
@Table(name = "characters")
@Getter
@Setter
public class CharacterRickAndMorty {
@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 gender;

@Column(nullable = false)
private String status;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package mate.academy.rickandmorty.repository;

import java.util.List;
import mate.academy.rickandmorty.model.CharacterRickAndMorty;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface CharacterRepository extends JpaRepository<CharacterRickAndMorty, Long> {
long count();

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

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

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.PostConstruct;
import java.util.List;
import lombok.RequiredArgsConstructor;
import mate.academy.rickandmorty.config.mapper.CharacterMapper;
import mate.academy.rickandmorty.dto.external.ApiResponseDto;
import mate.academy.rickandmorty.dto.external.CharacterResponseDataDto;
import mate.academy.rickandmorty.exception.ApiResponseParsingException;
import mate.academy.rickandmorty.model.CharacterRickAndMorty;
import mate.academy.rickandmorty.repository.CharacterRepository;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

@RequiredArgsConstructor
@Component
public class CharacterClientImpl implements CharacterClient {
@Value("${rick-and-morty.url}")
private String baseUrl;
private final CharacterRepository characterRepository;
private final CharacterMapper mapper;
private final ObjectMapper objectMapper = new ObjectMapper();

@Override
@PostConstruct
public void pullAllFromExternalApiToDb() {
String nextPageUrl = baseUrl;
while (nextPageUrl != null) {
saveAllCharactersFromPage(nextPageUrl);
ApiResponseDto responseDto = getApiResponseDto(nextPageUrl);
nextPageUrl = responseDto.getInfo().getNext();
}

}

private ApiResponseDto getApiResponseDto(String url) {
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> response = restTemplate.getForEntity(url, String.class);
try {
return objectMapper.readValue(response.getBody(), ApiResponseDto.class);
} catch (JsonProcessingException e) {
throw new ApiResponseParsingException("Failed to parse API response", e);
}
}

private void saveAllCharactersFromPage(String url) {
ApiResponseDto apiResponseDto = getApiResponseDto(url);
List<CharacterResponseDataDto> characterResponseDataDtos = apiResponseDto.getResults();
List<CharacterRickAndMorty> characterRicksAndMorties = characterResponseDataDtos
.stream()
.map(mapper::toCharacterModel)
.toList();

characterRepository.saveAll(characterRicksAndMorties);
}
}
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.CharacterDto;

public interface CharacterService {
CharacterDto getRandomCharacter();

List<CharacterDto> findByPartOfName(String partOfName);
}
Loading
Loading