Skip to content

Commit

Permalink
Implemented Rick and Morty API with random character wiki generation…
Browse files Browse the repository at this point in the history
… and character search. Utilized MySQL for the main database and H2 for testing. Ensured identical configuration in both main and test properties files. Integrated Swagger for documentation
  • Loading branch information
Petro-Smoliar committed Nov 7, 2023
1 parent c3bbe60 commit d157828
Show file tree
Hide file tree
Showing 21 changed files with 404 additions and 6 deletions.
51 changes: 46 additions & 5 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,62 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
<version>${liquibase.version}</version>
</dependency>
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-maven-plugin</artifactId>
<version>${liquibase.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>

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

<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

Expand Down
16 changes: 15 additions & 1 deletion 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 mate.academy.rickandmorty.service.CharacterClient;
import mate.academy.rickandmorty.service.CharacterService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {
public class Application implements CommandLineRunner {
@Autowired
private CharacterService characterService;
@Autowired
private CharacterClient characterClient;

public static void main(String[] args) {
SpringApplication.run(Application.class, args);

}

@Override
public void run(String... args) throws Exception {
characterService.saveAllCharacter(characterClient.getAllCharacters());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package mate.academy.rickandmorty.config;

import mate.academy.rickandmorty.dto.external.CharacterExternalDto;
import mate.academy.rickandmorty.model.Character;
import org.springframework.stereotype.Component;

@Component
public class CharacterMapper {
public Character mapToCharacter(CharacterExternalDto dto) {
Character character = new Character();
character.setExternalId(dto.getId());
character.setName(dto.getName());
character.setStatus(dto.getStatus());
character.setGender(dto.getGender());
return character;
}
}
13 changes: 13 additions & 0 deletions src/main/java/mate/academy/rickandmorty/config/RandomConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package mate.academy.rickandmorty.config;

import java.util.Random;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

@Component
public class RandomConfig {
@Bean
public Random random() {
return new Random();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
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.CharacterSearchParam;
import mate.academy.rickandmorty.model.Character;
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;

@RequiredArgsConstructor
@RestController
@RequestMapping("/character/api")
@Tag(name = "Character API", description = "API endpoints for managing characters.")
public class CharacterController {
private final CharacterService characterService;

@GetMapping("/random")
@Operation(summary = "Get a Random Character",
description = "Retrieve a random character from the database.")
public Character getRandomCharacter() {
return characterService.getRandomCharacter();
}

@GetMapping("/search")
@Operation(summary = "Search Characters", description = "Search for characters by name.")
public List<Character> searchCharacter(CharacterSearchParam name) {
return characterService.search(name);
}

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

public record CharacterSearchParam(String name) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package mate.academy.rickandmorty.dto.external;

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

@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class CharacterExternalDto {
private String id;
private String name;
private String status;
private String gender;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package mate.academy.rickandmorty.dto.external;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.util.List;
import lombok.Data;

@Data
public class CharacterResponseDto {
private InfoDto info;
private List<CharacterExternalDto> results;

public CharacterResponseDto() {
}

public CharacterResponseDto(String json) throws IOException, JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
CharacterResponseDto characterResponseDto =
objectMapper.readValue(json, CharacterResponseDto.class);
this.info = characterResponseDto.getInfo();
this.results = characterResponseDto.getResults();
}
}
10 changes: 10 additions & 0 deletions src/main/java/mate/academy/rickandmorty/dto/external/InfoDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package mate.academy.rickandmorty.dto.external;

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

@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class InfoDto {
private String next;
}
24 changes: 24 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,24 @@
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.Data;

@Entity
@Data
@Table(name = "characters")
public class Character {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "externalId")
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 mate.academy.rickandmorty.model.Character;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;

public interface CharacterRepository extends JpaRepository<Character, Long>,
JpaSpecificationExecutor<Character> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package mate.academy.rickandmorty.repository;

import lombok.RequiredArgsConstructor;
import mate.academy.rickandmorty.dto.CharacterSearchParam;
import mate.academy.rickandmorty.model.Character;
import mate.academy.rickandmorty.repository.user.NameSpecification;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
public class CharacterSpecificationBuilder implements SpecificationBuilder<Character> {
private final NameSpecification nameSpecification;

@Override
public Specification<Character> build(CharacterSearchParam param) {
Specification<Character> spec = Specification.where(null);
if (param.name() != null && !param.name().isEmpty()) {
spec = spec.and(nameSpecification.getSpecification(param.name()));
}
return spec;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package mate.academy.rickandmorty.repository;

import mate.academy.rickandmorty.dto.CharacterSearchParam;
import org.springframework.data.jpa.domain.Specification;

public interface SpecificationBuilder<T> {
Specification<T> build(CharacterSearchParam param);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package mate.academy.rickandmorty.repository.user;

import mate.academy.rickandmorty.model.Character;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Component;

@Component
public class NameSpecification {
public Specification<Character> getSpecification(String param) {
return (root, query, criteriaBuilder)
-> criteriaBuilder.like(root.get("name"), "%" + param + "%");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
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.external.CharacterExternalDto;
import mate.academy.rickandmorty.dto.external.CharacterResponseDto;
import org.springframework.stereotype.Service;

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

public List<CharacterExternalDto> getAllCharacters() {
HttpClient httpClient = HttpClient.newHttpClient();
String url = BASE_URL;
List<CharacterExternalDto> listDto = new ArrayList<>();
while (url != null) {
HttpRequest httpRequest = HttpRequest.newBuilder()
.GET()
.uri(URI.create(url))
.build();
try {
HttpResponse<String> httpResponse = httpClient.send(httpRequest,
HttpResponse.BodyHandlers.ofString());
CharacterResponseDto responseDto = objectMapper.convertValue(httpResponse.body(),
CharacterResponseDto.class);
listDto.addAll(responseDto.getResults());
url = responseDto.getInfo().getNext();
} catch (IOException | InterruptedException e) {
throw new RuntimeException(e);
}
}
return listDto;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package mate.academy.rickandmorty.service;

import java.util.List;
import mate.academy.rickandmorty.dto.CharacterSearchParam;
import mate.academy.rickandmorty.dto.external.CharacterExternalDto;
import mate.academy.rickandmorty.model.Character;

public interface CharacterService {
void saveAllCharacter(List<CharacterExternalDto> listDto);

Character getRandomCharacter();

List<Character> search(CharacterSearchParam param);
}
Loading

0 comments on commit d157828

Please sign in to comment.