Skip to content
Open
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
68 changes: 63 additions & 5 deletions pom.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
Expand All @@ -15,6 +15,9 @@
<description>jv-rick-and-morty</description>
<properties>
<java.version>17</java.version>
<lombok.version>1.18.32</lombok.version>
<lombok.mapstruct.binding.version>0.2.0</lombok.mapstruct.binding.version>
<mapstruct.version>1.6.3</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 @@ -25,22 +28,56 @@
<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.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
<scope>provided</scope>
</dependency>

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

<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>

<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${mapstruct.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.19.0</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.5.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>

<build>
Expand All @@ -55,7 +92,7 @@
<version>3.3.0</version>
<executions>
<execution>
<phase>compile</phase>
<phase>validate</phase>
<goals>
<goal>check</goal>
</goals>
Expand All @@ -68,7 +105,28 @@
<linkXRef>false</linkXRef>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>

</project>
</project>
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

)
public class MapperConfig {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package mate.academy.rickandmorty.controler;

import io.swagger.v3.oas.annotations.Operation;
import java.util.List;
import lombok.RequiredArgsConstructor;
import mate.academy.rickandmorty.dto.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.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/characters")
@RequiredArgsConstructor
public class CharacterController {

private final CharacterService characterService;

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

@Operation(summary = "Find character by name")
@GetMapping()
public List<CharacterDto> findCharacterByName(@RequestParam String name) {
return characterService.findCharactersByName(name);
}

}
10 changes: 10 additions & 0 deletions src/main/java/mate/academy/rickandmorty/dto/CharacterDataDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package mate.academy.rickandmorty.dto;

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

@Data
public class CharacterDataDto {
private List<CharacterDto> results;
private CharacterInfoDto info;
}
12 changes: 12 additions & 0 deletions src/main/java/mate/academy/rickandmorty/dto/CharacterDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package mate.academy.rickandmorty.dto;

import lombok.Data;

@Data
public class CharacterDto {
private Long id;
private Long externalId;

Choose a reason for hiding this comment

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

The externalId field is of type Long, but the requirements specify that it should be a string in the API response. Ensure that this field is serialized as a string in the API response, either by changing its type to String or by configuring serialization appropriately.

private String name;
private String status;
private String gender;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package mate.academy.rickandmorty.dto;

import lombok.Data;

@Data
public class CharacterInfoDto {
private String next;
}
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.CharacterDto;
import mate.academy.rickandmorty.model.RickAndMortyCharacter;
import org.mapstruct.Mapper;

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

RickAndMortyCharacter toModel(CharacterDto characterDto);

CharacterDto toDto(RickAndMortyCharacter rickAndMortyCharacter);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
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.Data;

@Entity
@Table(name = "characters")
@Data
public class RickAndMortyCharacter {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long externalId;

Choose a reason for hiding this comment

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

Consider adding a uniqueness constraint to the externalId field to prevent duplicate records, as required for idempotent startup data loading.

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.RickAndMortyCharacter;
import org.springframework.data.jpa.repository.JpaRepository;

public interface CharacterRepository extends JpaRepository<RickAndMortyCharacter, Long> {
List<RickAndMortyCharacter> findByNameContainingIgnoreCase(String name);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package mate.academy.rickandmorty.service;

import java.util.List;
import mate.academy.rickandmorty.dto.CharacterDto;

public interface CharacterService {
void saveAllCharacters();

CharacterDto getRandomCharacter();

List<CharacterDto> findCharactersByName(String name);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
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.ArrayList;
import java.util.List;
import lombok.RequiredArgsConstructor;
import mate.academy.rickandmorty.dto.CharacterDataDto;
import mate.academy.rickandmorty.dto.CharacterDto;
import org.springframework.stereotype.Component;

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

public List<CharacterDto> getCharacters() {
List<CharacterDto> allCharacters = new ArrayList<>();
HttpClient httpClient = HttpClient.newHttpClient();
String url = BASE_URL;
try {
while (url != null) {
HttpRequest httpRequest = HttpRequest.newBuilder().GET()
.uri(URI.create(url)).build();
HttpResponse<String> response = httpClient.send(httpRequest,
HttpResponse.BodyHandlers.ofString());
CharacterDataDto characterResponse = objectMapper
.readValue(response.body(), CharacterDataDto.class);
allCharacters.addAll(characterResponse.getResults());
url = characterResponse.getInfo().getNext();
}
return allCharacters;

} catch (IOException | InterruptedException e) {
throw new RuntimeException("Couldn't get characters from API", e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package mate.academy.rickandmorty.service.impl;

import jakarta.annotation.PostConstruct;
import java.util.List;
import java.util.Random;
import lombok.RequiredArgsConstructor;
import mate.academy.rickandmorty.dto.CharacterDto;
import mate.academy.rickandmorty.mapper.CharacterMapper;
import mate.academy.rickandmorty.model.RickAndMortyCharacter;
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 RickAndMortyClient characterClient;
private final CharacterMapper characterMapper;
private final CharacterRepository characterRepository;
private final Random random = new Random();

@PostConstruct
public void saveAllCharacters() {
List<CharacterDto> characterDtos = characterClient.getCharacters();
List<RickAndMortyCharacter> rickAndMortyCharacters = characterDtos.stream()
.map(characterMapper::toModel).toList();
characterRepository.saveAll(rickAndMortyCharacters);

Choose a reason for hiding this comment

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

The saveAllCharacters method saves all fetched characters without checking for existing entries, which can lead to duplicate records on repeated startups. The requirements specify that data loading must be idempotent and avoid duplicates. Consider checking for existing characters by externalId before saving, or enforcing uniqueness at the database level.

}

@Override
public CharacterDto getRandomCharacter() {
List<RickAndMortyCharacter> rickAndMortyCharacters = characterRepository.findAll();
if (rickAndMortyCharacters.isEmpty()) {
throw new RuntimeException("No characters found in database");
}
int index = random.nextInt(rickAndMortyCharacters.size());
RickAndMortyCharacter rickAndMortyCharacter = rickAndMortyCharacters.get(index);
return characterMapper.toDto(rickAndMortyCharacter);
}

@Override
public List<CharacterDto> findCharactersByName(String name) {
List<RickAndMortyCharacter> charactersByName = characterRepository
.findByNameContainingIgnoreCase(name);
return charactersByName.stream()
.map(characterMapper::toDto).toList();
}
}
10 changes: 9 additions & 1 deletion 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/testdb
spring.datasource.username=root
spring.datasource.password=Bs5QG4Rd2W_
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
server.error.include-message=always
server.error.include-binding-errors=always