Skip to content

Commit

Permalink
added solution
Browse files Browse the repository at this point in the history
  • Loading branch information
Serhii-Nosulia committed Apr 20, 2024
1 parent c3bbe60 commit be6f568
Show file tree
Hide file tree
Showing 16 changed files with 321 additions and 1 deletion.
41 changes: 40 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,49 @@
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</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>
</dependency>

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

<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.5.5.Final</version>
</dependency>

<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.5.5.Final</version>
</dependency>

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>

<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
</dependencies>

<build>
Expand All @@ -55,7 +94,7 @@
<version>3.3.0</version>
<executions>
<execution>
<phase>compile</phase>
<phase>verify</phase>
<goals>
<goal>check</goal>
</goals>
Expand Down
26 changes: 26 additions & 0 deletions src/main/java/mate/academy/rickandmorty/config/ApiConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package mate.academy.rickandmorty.config;

import java.net.http.HttpClient;
import java.util.Random;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ApiConfig {
@Bean
Random buildRandom() {
return new Random();
}

@Bean
ObjectMapper buildObjectMapper() {

return new ObjectMapper();
}

@Bean
HttpClient buildHttpClient() {
return HttpClient.newHttpClient();
}
}
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 io.swagger.v3.oas.annotations.tags.Tag;
import java.util.List;
import lombok.RequiredArgsConstructor;
import mate.academy.rickandmorty.dto.internal.CharacterResponseDto;
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.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@Tag(name = "Rick and Morty universe",
description = "Endpoints for managing Rick and Morty universe characters")
@RequiredArgsConstructor
@RestController
@RequestMapping("/characters")
public class CharacterController {
private final CharacterService characterService;

@GetMapping("/random")
@Operation(summary = "Get the random character",
description = "Get the random character")
public CharacterResponseDto getRandomCharacter() {
return characterService.getRandomCharacter();
}

@GetMapping()
@Operation(summary = "Find the character by name",
description = "Find the character by name")
public List<CharacterResponseDto> searchCharactersByName(@RequestParam String name) {
return characterService.findAllByName(name);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package mate.academy.rickandmorty.dto.external;

import java.util.List;

public record CharacterResponseDataDto(CharacterResponseMetaDataDto info,
List<ThirdApiCharacterResponseDto> results) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package mate.academy.rickandmorty.dto.external;

public record CharacterResponseMetaDataDto(Integer count,
Integer pages,
String next,
String prev
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package mate.academy.rickandmorty.dto.external;

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

@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class ThirdApiCharacterResponseDto {
@JsonProperty("id")
private String externalId;
private String name;
private String status;
private String species;
private String type;
private String gender;

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

public record CharacterResponseDto(Long id,
String 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 DataNotFoundException extends RuntimeException {
public DataNotFoundException(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.ThirdApiCharacterResponseDto;
import mate.academy.rickandmorty.dto.internal.CharacterResponseDto;
import mate.academy.rickandmorty.model.Character;
import org.mapstruct.Mapper;

@Mapper(config = MapperConfig.class)
public interface CharacterMapper {
CharacterResponseDto toDto(Character character);

Character toCharacter(ThirdApiCharacterResponseDto thirdApiCharacterResponseDto);
}
23 changes: 23 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,23 @@
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 lombok.Getter;
import lombok.Setter;

@Getter
@Setter
@Entity(name = "characters")
public class Character {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "external_id")
private String externalId;
private String name;
private String status;
private String gender;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package mate.academy.rickandmorty.repository;

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

public interface CharacterRepository extends JpaRepository<Character, Long> {
List<Character> findAllByNameContainingIgnoreCase(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.CharacterResponseDto;

public interface CharacterService {
CharacterResponseDto getRandomCharacter();

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

import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import mate.academy.rickandmorty.dto.external.CharacterResponseDataDto;
import mate.academy.rickandmorty.mapper.CharacterMapper;
import mate.academy.rickandmorty.repository.CharacterRepository;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
public class RickAndMortyClient {
private static final String URI_BASE = "https://rickandmortyapi.com/api/character";

private final CharacterRepository characterRepository;
private final CharacterMapper characterMapper;
private final ObjectMapper objectMapper;
private final HttpClient httpClient;

public void loadDataToDb() {
String page = URI_BASE;
while (page != null) {
HttpRequest request = HttpRequest.newBuilder()
.GET()
.uri(URI.create(page))
.build();

try {
HttpResponse<String> response = httpClient.send(
request,
HttpResponse.BodyHandlers.ofString());
CharacterResponseDataDto characterResponseDataDto = objectMapper.readValue(
response.body(),
CharacterResponseDataDto.class);
characterResponseDataDto.results().stream()
.map(characterMapper::toCharacter)
.forEach(characterRepository::save);
page = characterResponseDataDto.info().next();
} catch (IOException | InterruptedException e) {
throw new RuntimeException("Can't get data from API", e);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package mate.academy.rickandmorty.service.impl;

import java.util.List;
import java.util.Random;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import mate.academy.rickandmorty.dto.internal.CharacterResponseDto;
import mate.academy.rickandmorty.exception.DataNotFoundException;
import mate.academy.rickandmorty.mapper.CharacterMapper;
import mate.academy.rickandmorty.model.Character;
import mate.academy.rickandmorty.repository.CharacterRepository;
import mate.academy.rickandmorty.service.CharacterService;
import mate.academy.rickandmorty.service.RickAndMortyClient;
import org.springframework.stereotype.Service;

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

@PostConstruct
public void loadDataToDb() {
rickAndMortyClient.loadDataToDb();
}

@Override
public CharacterResponseDto getRandomCharacter() {
long count = characterRepository.count();
Character character = characterRepository.findById(random.nextLong(count))
.orElseThrow(
() -> new DataNotFoundException("Couldn't find data")
);
return characterMapper.toDto(character);
}

@Override
public List<CharacterResponseDto> findAllByName(String name) {
return characterRepository.findAllByNameContainingIgnoreCase(name).stream()
.map(characterMapper::toDto)
.toList();
}
}
8 changes: 8 additions & 0 deletions src/main/resources/application.properties
Original file line number Diff line number Diff line change
@@ -1 +1,9 @@
spring.datasource.url=jdbc:mysql://localhost:3306/test?serverTimeZone=UTC
spring.datasource.username=root
spring.datasource.password=12345678
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.show-sql=true

server.servlet.context-path=/api

0 comments on commit be6f568

Please sign in to comment.