Implement /search endpoint with basic info

This commit is contained in:
2026-02-28 00:42:48 +05:00
parent ddf975b3e6
commit 377ea90bfe
5 changed files with 191 additions and 33 deletions

View File

@@ -1,32 +0,0 @@
package com.backend.unifier.title.service;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.jboss.logging.Logger;
import com.backend.unifier.title.service.api.KodikSearchService;
import com.backend.unifier.title.service.api.ShikimoriSearchService;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
@Path("/hello")
public class GreetingResource {
Logger logger = Logger.getLogger(GreetingResource.class);
private final KodikSearchService kodikSearchService;
private final ShikimoriSearchService shikimoriSearchService;
public GreetingResource(@RestClient KodikSearchService kodikSearchService,
@RestClient ShikimoriSearchService shikimoriSearchService) {
this.kodikSearchService = kodikSearchService;
this.shikimoriSearchService = shikimoriSearchService;
}
@GET
@Produces(MediaType.TEXT_PLAIN)
public void hello() {
logger.info(kodikSearchService.search("Стальной алхимик"));
logger.info(shikimoriSearchService.findById("3701"));
}
}

View File

@@ -0,0 +1,149 @@
package com.backend.unifier.title.service.mapper;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Named;
import com.backend.metadata.kodik.service.api.model.KodikResponse;
import com.backend.metadata.kodik.service.api.model.MaterialData;
import com.backend.unifier.title.service.model.SearchResponseDTO;
@Mapper
public interface KodikResponseMapper {
default SearchResponseDTO toSearchResponseDTO(KodikResponse kodikResponse) {
if (kodikResponse == null || kodikResponse.results == null) {
return new SearchResponseDTO(List.of());
}
List<SearchResponseDTO.SearchEntryDTO> entries = kodikResponse.results.stream()
.map(this::toSearchEntryDTO)
.filter(Objects::nonNull)
.collect(Collectors.toList());
return new SearchResponseDTO(entries);
}
@Mapping(target = "posterURL", source = "materialData", qualifiedByName = "extractPosterUrl")
@Mapping(target = "title", source = "result", qualifiedByName = "extractTitle")
@Mapping(target = "episodeCount", source = "episodesCount")
@Mapping(target = "rating", source = "materialData", qualifiedByName = "extractRating")
@Mapping(target = "ratingSource", source = "materialData", qualifiedByName = "extractRatingSource")
@Mapping(target = "type", source = "type")
@Mapping(target = "studio", source = "materialData", qualifiedByName = "extractStudio")
@Mapping(target = "genres", source = "materialData", qualifiedByName = "extractGenres")
@Mapping(target = "durationMin", source = "materialData", qualifiedByName = "extractDurationMin")
SearchResponseDTO.SearchEntryDTO toSearchEntryDTO(KodikResponse.Result result);
@Named("extractPosterUrl")
default String extractPosterUrl(MaterialData materialData) {
if (materialData == null)
return "";
if (materialData.animePosterUrl != null && !materialData.animePosterUrl.isEmpty()) {
return materialData.animePosterUrl;
}
return materialData.posterUrl != null ? materialData.posterUrl : "";
}
@Named("extractTitle")
default String extractTitle(KodikResponse.Result result) {
if (result == null)
return "";
MaterialData materialData = result.materialData;
if (materialData != null) {
if (materialData.animeTitle != null && !materialData.animeTitle.isEmpty()) {
return materialData.animeTitle;
}
if (materialData.title != null && !materialData.title.isEmpty()) {
return materialData.title;
}
if (materialData.titleEn != null && !materialData.titleEn.isEmpty()) {
return materialData.titleEn;
}
}
if (result.title != null && !result.title.isEmpty()) {
return result.title;
}
return result.titleOrig != null ? result.titleOrig : "";
}
@Named("extractRating")
default double extractRating(MaterialData materialData) {
if (materialData == null)
return 0.0;
if (materialData.kinopoiskRating > 0) {
return materialData.kinopoiskRating;
}
if (materialData.imdbRating > 0) {
return materialData.imdbRating;
}
if (materialData.shikimoriRating > 0) {
return materialData.shikimoriRating;
}
return 0.0;
}
@Named("extractRatingSource")
default String extractRatingSource(MaterialData materialData) {
if (materialData == null)
return "";
if (materialData.kinopoiskRating > 0) {
return "Kinopoisk";
}
if (materialData.imdbRating > 0) {
return "IMDb";
}
if (materialData.shikimoriRating > 0) {
return "Shikimori";
}
return "";
}
@Named("extractStudio")
default String extractStudio(MaterialData materialData) {
if (materialData == null || materialData.animeStudios == null || materialData.animeStudios.isEmpty()) {
return "";
}
return String.join(", ", materialData.animeStudios);
}
@Named("extractGenres")
default List<String> extractGenres(MaterialData materialData) {
if (materialData == null)
return List.of();
if (materialData.animeGenres != null && !materialData.animeGenres.isEmpty()) {
return materialData.animeGenres;
}
if (materialData.genres != null && !materialData.genres.isEmpty()) {
return materialData.genres;
}
if (materialData.allGenres != null && !materialData.allGenres.isEmpty()) {
return materialData.allGenres;
}
return List.of();
}
@Named("extractDurationMin")
default int extractDurationMin(MaterialData materialData) {
if (materialData == null || materialData.duration <= 0) {
return 0;
}
return materialData.duration;
}
}

View File

@@ -3,7 +3,15 @@ package com.backend.unifier.title.service.model;
import java.util.List; import java.util.List;
public record SearchResponseDTO(List<SearchEntryDTO> result) { public record SearchResponseDTO(List<SearchEntryDTO> result) {
public record SearchEntryDTO(String title, int episodeCount) { public record SearchEntryDTO(
String posterURL,
String title,
int episodeCount,
double rating, String ratingSource,
String type,
String studio,
List<String> genres,
int durationMin) {
} }
} }

View File

@@ -0,0 +1,30 @@
package com.backend.unifier.title.service.resource;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.mapstruct.factory.Mappers;
import com.backend.unifier.title.service.api.KodikSearchService;
import com.backend.unifier.title.service.mapper.KodikResponseMapper;
import com.backend.unifier.title.service.model.SearchResponseDTO;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.QueryParam;
@Path("/search")
public class SearchResource {
KodikResponseMapper responseMapper = Mappers.getMapper(KodikResponseMapper.class);
private final KodikSearchService kodikSearchService;
public SearchResource(@RestClient KodikSearchService kodikSearchService) {
this.kodikSearchService = kodikSearchService;
}
@GET
public SearchResponseDTO search(@QueryParam("title") String title) {
var kodikResponse = kodikSearchService.search(title);
return responseMapper.toSearchResponseDTO(kodikResponse);
}
}

3
startup.sh Executable file
View File

@@ -0,0 +1,3 @@
#!/bin/bash
./mvnw jar:jar install:install && ./mvnw clean package && docker compose -f src/main/docker-compose/jvm.yml up -d --build