Optimize search

This commit is contained in:
2025-12-11 02:24:25 +01:00
parent b398ac869c
commit a7599069b8
4 changed files with 61 additions and 7 deletions

View File

@@ -3,5 +3,8 @@ package com.imgfloat.app.repository;
import com.imgfloat.app.model.Channel; import com.imgfloat.app.model.Channel;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface ChannelRepository extends JpaRepository<Channel, String> { public interface ChannelRepository extends JpaRepository<Channel, String> {
List<Channel> findTop50ByBroadcasterContainingIgnoreCaseOrderByBroadcasterAsc(String broadcasterFragment);
} }

View File

@@ -42,7 +42,6 @@ import java.util.Collection;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import javax.imageio.ImageReader; import javax.imageio.ImageReader;
@@ -94,13 +93,10 @@ public class ChannelDirectoryService {
public List<String> searchBroadcasters(String query) { public List<String> searchBroadcasters(String query) {
String normalizedQuery = normalize(query); String normalizedQuery = normalize(query);
return channelRepository.findAll().stream() String searchTerm = normalizedQuery == null || normalizedQuery.isBlank() ? "" : normalizedQuery;
return channelRepository.findTop50ByBroadcasterContainingIgnoreCaseOrderByBroadcasterAsc(searchTerm)
.stream()
.map(Channel::getBroadcaster) .map(Channel::getBroadcaster)
.map(this::normalize)
.filter(Objects::nonNull)
.filter(name -> normalizedQuery == null || normalizedQuery.isBlank() || name.contains(normalizedQuery))
.sorted()
.limit(50)
.toList(); .toList();
} }

View File

@@ -0,0 +1,47 @@
package com.imgfloat.app;
import com.imgfloat.app.model.Channel;
import com.imgfloat.app.repository.ChannelRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;
import static org.hamcrest.Matchers.hasSize;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@SpringBootTest(properties = {
"spring.security.oauth2.client.registration.twitch.client-id=test-client-id",
"spring.security.oauth2.client.registration.twitch.client-secret=test-client-secret"
})
@AutoConfigureMockMvc
class ChannelDirectoryApiIntegrationTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ChannelRepository channelRepository;
@BeforeEach
void cleanChannels() {
channelRepository.deleteAll();
}
@Test
void searchesBroadcastersCaseInsensitiveAndSorted() throws Exception {
channelRepository.save(new Channel("Beta"));
channelRepository.save(new Channel("alpha"));
channelRepository.save(new Channel("ALPINE"));
mockMvc.perform(get("/api/channels").param("q", "Al"))
.andExpect(status().isOk())
.andExpect(jsonPath("$", hasSize(2)))
.andExpect(jsonPath("$[0]").value("alpha"))
.andExpect(jsonPath("$[1]").value("alpine"));
}
}

View File

@@ -21,6 +21,7 @@ import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Collection; import java.util.Collection;
import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
@@ -172,6 +173,13 @@ class ChannelDirectoryServiceTest {
}); });
when(channelRepository.findAll()) when(channelRepository.findAll())
.thenAnswer(invocation -> List.copyOf(channels.values())); .thenAnswer(invocation -> List.copyOf(channels.values()));
when(channelRepository.findTop50ByBroadcasterContainingIgnoreCaseOrderByBroadcasterAsc(anyString()))
.thenAnswer(invocation -> channels.values().stream()
.filter(channel -> Optional.ofNullable(channel.getBroadcaster()).orElse("")
.contains(Optional.ofNullable(invocation.getArgument(0, String.class)).orElse("").toLowerCase()))
.sorted(Comparator.comparing(Channel::getBroadcaster))
.limit(50)
.toList());
when(assetRepository.save(any(Asset.class))) when(assetRepository.save(any(Asset.class)))
.thenAnswer(invocation -> { .thenAnswer(invocation -> {