Fix redis, mask kodik id

This commit is contained in:
2026-03-22 01:52:48 +05:00
parent 909865ea34
commit 474b088dba
11 changed files with 64 additions and 151 deletions

View File

@@ -0,0 +1,5 @@
package com.backend.unifier.title.model;
public enum ContentProviderSource {
KODIK
}

View File

@@ -1,25 +0,0 @@
package com.backend.unifier.title.model;
import java.util.UUID;
public class IdentifierPointer {
private UUID key;
private ContentIdentifier identifier;
public IdentifierPointer(UUID key, ContentIdentifier identifier) {
this.key = key;
this.identifier = identifier;
}
IdentifierPointer() {
}
public UUID getKey() {
return key;
}
public ContentIdentifier getIdentifier() {
return identifier;
}
}

View File

@@ -2,15 +2,15 @@ package com.backend.unifier.title.resource;
import com.backend.unifier.title.model.ContentDetailEntry;
import io.smallrye.openapi.internal.models.media.Content;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.QueryParam;
@Path("/")
public class DetailResource {
@GET
@Path("/detail")
public ContentDetailEntry detail(String id) {
public ContentDetailEntry detail(@QueryParam("id") String id) {
return null;
}
}

View File

@@ -6,7 +6,6 @@ import com.backend.unifier.title.api.KodikSearchService;
import com.backend.unifier.title.model.SearchEntries;
import com.backend.unifier.title.service.KodikResponseConvertService;
import io.smallrye.mutiny.Uni;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.QueryParam;
@@ -24,14 +23,6 @@ public class SearchResource {
@GET
@Path("/search")
public Uni<SearchEntries> search(@QueryParam("title") String title) {
return kodikSearchService.searchAsync(title)
.onItem().ifNotNull()
.transformToUni(response -> kodikConvertService.convertAsync(response));
}
@GET
@Path("/search-fast")
public SearchEntries searchFast(@QueryParam("title") String title) {
var result = kodikSearchService.search(title);
return kodikConvertService.convert(result);

View File

@@ -0,0 +1,31 @@
package com.backend.unifier.title.service;
import java.util.UUID;
import com.backend.unifier.title.model.ContentIdentifier;
import com.backend.unifier.title.model.ContentProviderSource;
import io.quarkus.redis.datasource.RedisDataSource;
import io.quarkus.redis.datasource.value.ValueCommands;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class IdentifierMaskService {
private ValueCommands<String, ContentIdentifier> countCommands;
public IdentifierMaskService(RedisDataSource ds) {
countCommands = ds.value(ContentIdentifier.class);
}
public UUID createMask(String id, ContentProviderSource source) {
ContentIdentifier contentIdentifier = new ContentIdentifier(source.name(), id);
UUID pointerID = UUID.randomUUID();
countCommands.set(pointerID.toString(), contentIdentifier);
return pointerID;
}
public ContentIdentifier realIdentifier(UUID id) {
return countCommands.get(id.toString());
}
}

View File

@@ -1,18 +0,0 @@
package com.backend.unifier.title.service;
import com.backend.unifier.title.model.ContentIdentifier;
import io.quarkus.redis.datasource.RedisDataSource;
import io.quarkus.redis.datasource.value.ValueCommands;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class IdentifierPointerService {
private ValueCommands<String, ContentIdentifier> countCommands;
public IdentifierPointerService(RedisDataSource ds) {
countCommands = ds.value(ContentIdentifier.class);
}
}

View File

@@ -1,60 +1,49 @@
package com.backend.unifier.title.service;
import java.util.List;
import java.util.UUID;
import org.mapstruct.factory.Mappers;
import com.backend.metadata.kodik.api.model.KodikResponse;
import com.backend.unifier.title.mapper.KodikResponseMapper;
import com.backend.unifier.title.model.ContentSearchEntry;
import com.backend.unifier.title.model.ContentProviderSource;
import com.backend.unifier.title.model.SearchEntries;
import io.smallrye.mutiny.Uni;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class KodikResponseConvertService {
private final KodikResponseMapper responseMapper = Mappers.getMapper(KodikResponseMapper.class);
private final PosterUrlValidator posterUrlValidator;
private final PosterUrlNormalizer posterUrlNormalizer;
private final IdentifierMaskService identifierMaskService;
public KodikResponseConvertService(PosterUrlValidator posterUrlValidator, PosterUrlNormalizer posterUrlNormalizer) {
this.posterUrlValidator = posterUrlValidator;
public KodikResponseConvertService(PosterUrlNormalizer posterUrlNormalizer,
IdentifierMaskService identifierMaskService) {
this.posterUrlNormalizer = posterUrlNormalizer;
this.identifierMaskService = identifierMaskService;
}
public SearchEntries convert(KodikResponse response) {
return convertSimple(response);
}
@SuppressWarnings("unchecked")
public Uni<SearchEntries> convertAsync(KodikResponse response) {
if (response == null || response.results == null) {
return Uni.createFrom().item(new SearchEntries(List.of()));
}
List<Uni<ContentSearchEntry>> entries = convertSimple(response).result()
.stream()
.map(this::resolveEntryPosters)
.toList();
if (entries.isEmpty()) {
return Uni.createFrom().item(new SearchEntries(List.of()));
}
return Uni.combine().all().unis(entries)
.with(list -> new SearchEntries((List<ContentSearchEntry>) list));
}
private Uni<ContentSearchEntry> resolveEntryPosters(ContentSearchEntry entry) {
return posterUrlValidator.resolvePosters(entry.posterURLs())
.map(entry::withPosterURLs)
.onFailure().recoverWithItem(entry);
}
private SearchEntries convertSimple(KodikResponse response) {
return normalizeUrls(responseMapper.toSearchEntries(response));
SearchEntries withNormalizedUrls = normalizeUrls(responseMapper.toSearchEntries(response));
SearchEntries result = maskIDs(withNormalizedUrls);
return result;
}
private SearchEntries maskIDs(SearchEntries response) {
var normalizedResults = response.result().stream()
.map(r -> {
String originalID = r.id();
UUID maskedID = identifierMaskService.createMask(originalID, ContentProviderSource.KODIK);
return r.withId(maskedID.toString());
})
.toList();
return response.withResult(normalizedResults);
}
private SearchEntries normalizeUrls(SearchEntries response) {

View File

@@ -1,65 +0,0 @@
package com.backend.unifier.title.service;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import io.smallrye.mutiny.Uni;
import io.vertx.core.Vertx;
import io.vertx.ext.web.client.WebClient;
import jakarta.annotation.PostConstruct;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
@ApplicationScoped
public class PosterUrlValidator {
@ConfigProperty(name = "poster.url.timeoutMs", defaultValue = "3000")
long timeoutMs;
@Inject
Vertx vertx;
private WebClient webClient;
@PostConstruct
void init() {
webClient = WebClient.create(vertx);
}
public Uni<List<String>> resolvePosters(List<String> urls) {
if (urls == null || urls.isEmpty()) {
return Uni.createFrom().item(List.of());
}
return findFirstReachable(urls, 0)
.map(firstIndex -> reorderToFront(urls, firstIndex));
}
private Uni<Integer> findFirstReachable(List<String> urls, int index) {
if (index >= urls.size()) {
return Uni.createFrom().item(-1);
}
return isReachable(urls.get(index))
.flatMap(ok -> ok ? Uni.createFrom().item(index) : findFirstReachable(urls, index + 1));
}
private List<String> reorderToFront(List<String> urls, int firstIndex) {
if (firstIndex <= 0)
return urls;
List<String> reordered = new ArrayList<>(urls);
reordered.add(0, reordered.remove(firstIndex));
return reordered;
}
private Uni<Boolean> isReachable(String url) {
if (url == null || url.isEmpty()) {
return Uni.createFrom().item(false);
}
return Uni.createFrom().completionStage(
webClient.headAbs(url).timeout(timeoutMs).send().toCompletionStage())
.map(response -> response.statusCode() >= 200 && response.statusCode() < 400)
.onFailure().recoverWithItem(false);
}
}