Compare commits
4 Commits
3e8409f150
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
34bb221af7
|
|||
|
aa14a4c89f
|
|||
|
efc3553e64
|
|||
|
14625ea289
|
@@ -1,4 +1,4 @@
|
||||
package com.backend.metadata.kodik.service;
|
||||
package com.backend.metadata.kodik;
|
||||
|
||||
import org.eclipse.microprofile.config.inject.ConfigProperty;
|
||||
import org.jboss.logging.Logger;
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.backend.metadata.kodik.service;
|
||||
package com.backend.metadata.kodik;
|
||||
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.backend.metadata.kodik.service.api;
|
||||
package com.backend.metadata.kodik.api;
|
||||
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.backend.metadata.kodik.service.api;
|
||||
package com.backend.metadata.kodik.api;
|
||||
|
||||
import com.backend.metadata.kodik.service.api.model.KodikResponse;
|
||||
import com.backend.metadata.kodik.api.model.KodikResponse;
|
||||
|
||||
import io.github.easyretrofit.core.annotation.RetrofitBuilder;
|
||||
import retrofit2.Call;
|
||||
@@ -10,6 +10,21 @@ import retrofit2.http.POST;
|
||||
|
||||
@RetrofitBuilder(baseUrl = "${kodik.api.url}", addConverterFactory = { JacksonConvertFactoryBuilder.class })
|
||||
public interface KodikAPI {
|
||||
@FormUrlEncoded
|
||||
@POST("list")
|
||||
Call<KodikResponse> list(
|
||||
@Field("token") String token,
|
||||
@Field("limit") int limit,
|
||||
@Field("with_material_data") int withMaterialData);
|
||||
|
||||
@FormUrlEncoded
|
||||
@POST("list")
|
||||
Call<KodikResponse> list(
|
||||
@Field("token") String token,
|
||||
@Field("next") String next,
|
||||
@Field("limit") int limit,
|
||||
@Field("with_material_data") int withMaterialData);
|
||||
|
||||
@FormUrlEncoded
|
||||
@POST("search")
|
||||
Call<KodikResponse> search(
|
||||
@@ -1,7 +1,8 @@
|
||||
package com.backend.metadata.kodik.service.api.model;
|
||||
package com.backend.metadata.kodik.api.model;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
@@ -9,6 +10,10 @@ import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
public class KodikResponse {
|
||||
public int total;
|
||||
public List<Result> results;
|
||||
@JsonProperty("prev_page")
|
||||
public String previousPage;
|
||||
@JsonProperty("next_page")
|
||||
public String nextPage;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public static class Result {
|
||||
@@ -21,6 +26,7 @@ public class KodikResponse {
|
||||
@JsonProperty("other_title")
|
||||
public String otherTitle;
|
||||
public Translation translation;
|
||||
public List<Translation> translations;
|
||||
public int year;
|
||||
@JsonProperty("last_season")
|
||||
public int lastSeason;
|
||||
@@ -67,6 +73,40 @@ public class KodikResponse {
|
||||
public int id;
|
||||
public String title;
|
||||
public String type;
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + id;
|
||||
result = prime * result + ((title == null) ? 0 : title.hashCode());
|
||||
result = prime * result + ((type == null) ? 0 : type.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
Translation other = (Translation) obj;
|
||||
if (id != other.id)
|
||||
return false;
|
||||
if (title == null) {
|
||||
if (other.title != null)
|
||||
return false;
|
||||
} else if (!title.equals(other.title))
|
||||
return false;
|
||||
if (type == null) {
|
||||
if (other.type != null)
|
||||
return false;
|
||||
} else if (!type.equals(other.type))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.backend.metadata.kodik.service.api.model;
|
||||
package com.backend.metadata.kodik.api.model;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.backend.metadata.kodik.service.resource;
|
||||
package com.backend.metadata.kodik.resource;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@@ -6,10 +6,10 @@ import javax.naming.ServiceUnavailableException;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
import com.backend.metadata.kodik.service.api.KodikAPI;
|
||||
import com.backend.metadata.kodik.service.api.model.KodikResponse;
|
||||
import com.backend.metadata.kodik.service.service.KodikAPITokenProvider;
|
||||
import com.backend.metadata.kodik.service.service.KodikSearchFilterService;
|
||||
import com.backend.metadata.kodik.api.KodikAPI;
|
||||
import com.backend.metadata.kodik.api.model.KodikResponse;
|
||||
import com.backend.metadata.kodik.service.KodikAPITokenProvider;
|
||||
import com.backend.metadata.kodik.service.KodikSearchFilterService;
|
||||
|
||||
import io.quarkiverse.retrofit.runtime.EnableRetrofit;
|
||||
import jakarta.ws.rs.BadRequestException;
|
||||
@@ -19,7 +19,7 @@ import jakarta.ws.rs.PathParam;
|
||||
import jakarta.ws.rs.QueryParam;
|
||||
import retrofit2.Response;
|
||||
|
||||
@EnableRetrofit("com.backend.metadata.kodik.service.api")
|
||||
@EnableRetrofit("com.backend.metadata.kodik.api")
|
||||
@Path("/kodik")
|
||||
public class SearchResource {
|
||||
private static final Logger LOG = Logger.getLogger(SearchResource.class);
|
||||
@@ -34,6 +34,24 @@ public class SearchResource {
|
||||
this.searchFilterService = searchFilterService;
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/list")
|
||||
public KodikResponse list() throws ServiceUnavailableException {
|
||||
try {
|
||||
Response<KodikResponse> response = kodikAPI.list(tokenProvider.getKodikToken(), 100, 1).execute();
|
||||
if (!response.isSuccessful()) {
|
||||
LOG.errorv("failed list request with response code {0}, message {1}",
|
||||
response.code(),
|
||||
response.message());
|
||||
throw new BadRequestException("bad response, code: " + response.code());
|
||||
}
|
||||
return filterResults(response.body());
|
||||
} catch (IOException e) {
|
||||
LOG.warn("i/o error", e);
|
||||
throw new ServiceUnavailableException("i/o error");
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/search")
|
||||
public KodikResponse search(@QueryParam("title") String title) throws ServiceUnavailableException {
|
||||
@@ -45,7 +63,7 @@ public class SearchResource {
|
||||
response.message());
|
||||
throw new BadRequestException("bad response, code: " + response.code());
|
||||
}
|
||||
return searchFilterService.filter(response.body());
|
||||
return filterResults(response.body());
|
||||
} catch (IOException e) {
|
||||
LOG.warn("i/o error", e);
|
||||
throw new ServiceUnavailableException("i/o error");
|
||||
@@ -56,14 +74,14 @@ public class SearchResource {
|
||||
@Path("/id/{id}")
|
||||
public KodikResponse findByKodikId(@PathParam("id") String id) throws ServiceUnavailableException {
|
||||
try {
|
||||
Response<KodikResponse> response = kodikAPI.findByKodikID(tokenProvider.getKodikToken(), id, 1, 1)
|
||||
Response<KodikResponse> response = kodikAPI.findByKodikID(tokenProvider.getKodikToken(), id, 100, 1)
|
||||
.execute();
|
||||
if (!response.isSuccessful()) {
|
||||
LOG.errorv("failed find by kodik id {0}, response code {1}, message {2}", id,
|
||||
response.code(), response.message());
|
||||
throw new BadRequestException("bad response, code: " + response.code());
|
||||
}
|
||||
return searchFilterService.filter(response.body());
|
||||
return filterResults(response.body());
|
||||
} catch (IOException e) {
|
||||
LOG.warn("i/o error", e);
|
||||
throw new ServiceUnavailableException("i/o error");
|
||||
@@ -74,14 +92,14 @@ public class SearchResource {
|
||||
@Path("/shikimori/{id}")
|
||||
public KodikResponse findByShikimoriId(@PathParam("id") String id) throws ServiceUnavailableException {
|
||||
try {
|
||||
Response<KodikResponse> response = kodikAPI.findByShikimoriID(tokenProvider.getKodikToken(), id, 1, 1)
|
||||
Response<KodikResponse> response = kodikAPI.findByShikimoriID(tokenProvider.getKodikToken(), id, 100, 1)
|
||||
.execute();
|
||||
if (!response.isSuccessful()) {
|
||||
LOG.errorv("failed find by shikimori id {0}, response code {1}, message {2}", id,
|
||||
response.code(), response.message());
|
||||
throw new BadRequestException("bad response, code: " + response.code());
|
||||
}
|
||||
return searchFilterService.filter(response.body());
|
||||
return filterResults(response.body());
|
||||
} catch (IOException e) {
|
||||
LOG.warn("i/o error", e);
|
||||
throw new ServiceUnavailableException("i/o error");
|
||||
@@ -92,14 +110,14 @@ public class SearchResource {
|
||||
@Path("/kinopoisk/{id}")
|
||||
public KodikResponse findByKinopoiskId(@PathParam("id") String id) throws ServiceUnavailableException {
|
||||
try {
|
||||
Response<KodikResponse> response = kodikAPI.findByKinopoiskID(tokenProvider.getKodikToken(), id, 1, 1)
|
||||
Response<KodikResponse> response = kodikAPI.findByKinopoiskID(tokenProvider.getKodikToken(), id, 100, 1)
|
||||
.execute();
|
||||
if (!response.isSuccessful()) {
|
||||
LOG.errorv("failed find by kinopoisk id {0}, response code {1}, message {2}", id,
|
||||
response.code(), response.message());
|
||||
throw new BadRequestException("bad response, code: " + response.code());
|
||||
}
|
||||
return searchFilterService.filter(response.body());
|
||||
return filterResults(response.body());
|
||||
} catch (IOException e) {
|
||||
LOG.warn("i/o error", e);
|
||||
throw new ServiceUnavailableException("i/o error");
|
||||
@@ -110,17 +128,23 @@ public class SearchResource {
|
||||
@Path("/imdb/{id}")
|
||||
public KodikResponse findByImdbId(@PathParam("id") String id) throws ServiceUnavailableException {
|
||||
try {
|
||||
Response<KodikResponse> response = kodikAPI.findByImdbID(tokenProvider.getKodikToken(), id, 1, 1).execute();
|
||||
Response<KodikResponse> response = kodikAPI.findByImdbID(tokenProvider.getKodikToken(), id, 100, 1)
|
||||
.execute();
|
||||
if (!response.isSuccessful()) {
|
||||
LOG.errorv("failed find by imdb id {0}, response code {1}, message {2}", id,
|
||||
response.code(), response.message());
|
||||
throw new BadRequestException("bad response, code: " + response.code());
|
||||
}
|
||||
return searchFilterService.filter(response.body());
|
||||
return filterResults(response.body());
|
||||
} catch (IOException e) {
|
||||
LOG.warn("i/o error", e);
|
||||
throw new ServiceUnavailableException("i/o error");
|
||||
}
|
||||
}
|
||||
|
||||
private KodikResponse filterResults(KodikResponse response) {
|
||||
response.results = searchFilterService.filter(response.results);
|
||||
return response;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.backend.metadata.kodik.service.service;
|
||||
package com.backend.metadata.kodik.service;
|
||||
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import org.eclipse.microprofile.config.inject.ConfigProperty;
|
||||
@@ -0,0 +1,69 @@
|
||||
package com.backend.metadata.kodik.service;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import com.backend.metadata.kodik.api.model.KodikResponse;
|
||||
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
|
||||
@ApplicationScoped
|
||||
public class KodikSearchFilterService {
|
||||
|
||||
private static final Set<String> ALLOWED_TYPES = Set.of("anime-serial", "anime");
|
||||
|
||||
public List<KodikResponse.Result> filter(List<KodikResponse.Result> target) {
|
||||
return filter(target, Collections.emptyList());
|
||||
}
|
||||
|
||||
public List<KodikResponse.Result> filter(List<KodikResponse.Result> target, List<KodikResponse.Result> fullSeen) {
|
||||
Set<String> seenIds = fullSeen.stream()
|
||||
.map(this::identifier)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
Map<String, List<KodikResponse.Result>> grouped = Stream.concat(fullSeen.stream(), target.stream())
|
||||
.filter(this::isAllowedType)
|
||||
.collect(Collectors.groupingBy(this::identifier, LinkedHashMap::new, Collectors.toList()));
|
||||
|
||||
return grouped.entrySet().stream()
|
||||
.filter(e -> !seenIds.contains(e.getKey()))
|
||||
.map(entry -> {
|
||||
List<KodikResponse.Result> group = entry.getValue();
|
||||
KodikResponse.Result first = group.get(0);
|
||||
first.translations = group.stream()
|
||||
.map(r -> r.translation)
|
||||
.filter(Objects::nonNull)
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
return first;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private boolean isAllowedType(KodikResponse.Result result) {
|
||||
return result != null && ALLOWED_TYPES.contains(result.type);
|
||||
}
|
||||
|
||||
public String identifier(KodikResponse.Result result) {
|
||||
if (result == null)
|
||||
return "null-0";
|
||||
|
||||
String primaryId = Arrays.asList(
|
||||
result.shikimoriId,
|
||||
result.kinopoiskId,
|
||||
result.imdbId,
|
||||
result.worldartLink).stream()
|
||||
.filter(id -> id != null && !id.isBlank())
|
||||
.findFirst()
|
||||
.orElse(result.id);
|
||||
|
||||
return primaryId + "-" + Math.max(result.lastSeason, 1);
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
package com.backend.metadata.kodik.service.service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
import com.backend.metadata.kodik.service.api.model.KodikResponse;
|
||||
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
|
||||
@ApplicationScoped
|
||||
public class KodikSearchFilterService {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(KodikSearchFilterService.class);
|
||||
private static final Set<String> ALLOWED_TYPES = Set.of("anime-serial", "anime");
|
||||
|
||||
public KodikResponse filter(KodikResponse body) {
|
||||
Objects.requireNonNull(body, "KodikResponse body cannot be null");
|
||||
Objects.requireNonNull(body.results, "KodikResponse.results cannot be null");
|
||||
|
||||
Set<String> seenIds = new HashSet<>();
|
||||
List<KodikResponse.Result> filteredResults = new ArrayList<>();
|
||||
|
||||
for (KodikResponse.Result result : body.results) {
|
||||
if (isAllowedType(result) && isUnique(result, seenIds)) {
|
||||
filteredResults.add(result);
|
||||
}
|
||||
}
|
||||
|
||||
body.results = filteredResults;
|
||||
return body;
|
||||
}
|
||||
|
||||
private boolean isAllowedType(KodikResponse.Result result) {
|
||||
return result != null && ALLOWED_TYPES.contains(result.type);
|
||||
}
|
||||
|
||||
private boolean isUnique(KodikResponse.Result result, Set<String> seenIds) {
|
||||
return seenIds.add(identifier(result));
|
||||
}
|
||||
|
||||
public String identifier(KodikResponse.Result result) {
|
||||
if (result == null)
|
||||
return "null-0";
|
||||
|
||||
String primaryId = Arrays.asList(
|
||||
result.kinopoiskId,
|
||||
result.imdbId,
|
||||
result.shikimoriId,
|
||||
result.worldartLink).stream()
|
||||
.filter(id -> id != null && !id.isBlank())
|
||||
.findFirst()
|
||||
.orElse(result.id);
|
||||
|
||||
return primaryId + "-" + Math.max(result.lastSeason, 1);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
quarkus.application.name=kodik-metadata-service
|
||||
kodik.api.url=https://kodikapi.com/
|
||||
kodik.api.url=https://kodik-api.com/
|
||||
|
||||
quarkus.stork.kodik-metadata-service.service-registrar.type=consul
|
||||
|
||||
|
||||
Reference in New Issue
Block a user