From 258eb3a0f90499eb5e245cc510277c59ca492e13 Mon Sep 17 00:00:00 2001 From: PhilReact Date: Tue, 4 Feb 2025 15:42:25 +0200 Subject: [PATCH 1/5] added keywords query for arbitrary research search --- .../api/resource/ArbitraryResource.java | 3 ++- .../repository/ArbitraryRepository.java | 2 +- .../hsqldb/HSQLDBArbitraryRepository.java | 22 ++++++++++++++++++- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/qortal/api/resource/ArbitraryResource.java b/src/main/java/org/qortal/api/resource/ArbitraryResource.java index 754c3467..72d6c5ba 100644 --- a/src/main/java/org/qortal/api/resource/ArbitraryResource.java +++ b/src/main/java/org/qortal/api/resource/ArbitraryResource.java @@ -172,6 +172,7 @@ public class ArbitraryResource { @Parameter(description = "Name (searches name field only)") @QueryParam("name") List names, @Parameter(description = "Title (searches title metadata field only)") @QueryParam("title") String title, @Parameter(description = "Description (searches description metadata field only)") @QueryParam("description") String description, + @Parameter(description = "Description (searches description metadata field by keywords. Input is a string of keywords separated by commas.)") @QueryParam("keywords") String keywords, @Parameter(description = "Prefix only (if true, only the beginning of fields are matched)") @QueryParam("prefix") Boolean prefixOnly, @Parameter(description = "Exact match names only (if true, partial name matches are excluded)") @QueryParam("exactmatchnames") Boolean exactMatchNamesOnly, @Parameter(description = "Default resources (without identifiers) only") @QueryParam("default") Boolean defaultResource, @@ -212,7 +213,7 @@ public class ArbitraryResource { } List resources = repository.getArbitraryRepository() - .searchArbitraryResources(service, query, identifier, names, title, description, usePrefixOnly, + .searchArbitraryResources(service, query, identifier, names, title, description, keywords, usePrefixOnly, exactMatchNames, defaultRes, mode, minLevel, followedOnly, excludeBlocked, includeMetadata, includeStatus, before, after, limit, offset, reverse); diff --git a/src/main/java/org/qortal/repository/ArbitraryRepository.java b/src/main/java/org/qortal/repository/ArbitraryRepository.java index 1c0e84e2..a88de129 100644 --- a/src/main/java/org/qortal/repository/ArbitraryRepository.java +++ b/src/main/java/org/qortal/repository/ArbitraryRepository.java @@ -42,7 +42,7 @@ public interface ArbitraryRepository { public List getArbitraryResources(Service service, String identifier, List names, boolean defaultResource, Boolean followedOnly, Boolean excludeBlocked, Boolean includeMetadata, Boolean includeStatus, Integer limit, Integer offset, Boolean reverse) throws DataException; - public List searchArbitraryResources(Service service, String query, String identifier, List names, String title, String description, boolean prefixOnly, List namesFilter, boolean defaultResource, SearchMode mode, Integer minLevel, Boolean followedOnly, Boolean excludeBlocked, Boolean includeMetadata, Boolean includeStatus, Long before, Long after, Integer limit, Integer offset, Boolean reverse) throws DataException; + public List searchArbitraryResources(Service service, String query, String identifier, List names, String title, String description, String keywords, boolean prefixOnly, List namesFilter, boolean defaultResource, SearchMode mode, Integer minLevel, Boolean followedOnly, Boolean excludeBlocked, Boolean includeMetadata, Boolean includeStatus, Long before, Long after, Integer limit, Integer offset, Boolean reverse) throws DataException; List searchArbitraryResourcesSimple( Service service, diff --git a/src/main/java/org/qortal/repository/hsqldb/HSQLDBArbitraryRepository.java b/src/main/java/org/qortal/repository/hsqldb/HSQLDBArbitraryRepository.java index 049e98aa..d45479c5 100644 --- a/src/main/java/org/qortal/repository/hsqldb/HSQLDBArbitraryRepository.java +++ b/src/main/java/org/qortal/repository/hsqldb/HSQLDBArbitraryRepository.java @@ -29,6 +29,7 @@ import org.qortal.utils.ListUtils; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.Optional; @@ -724,7 +725,7 @@ public class HSQLDBArbitraryRepository implements ArbitraryRepository { } @Override - public List searchArbitraryResources(Service service, String query, String identifier, List names, String title, String description, boolean prefixOnly, + public List searchArbitraryResources(Service service, String query, String identifier, List names, String title, String description, String keywords, boolean prefixOnly, List exactMatchNames, boolean defaultResource, SearchMode mode, Integer minLevel, Boolean followedOnly, Boolean excludeBlocked, Boolean includeMetadata, Boolean includeStatus, Long before, Long after, Integer limit, Integer offset, Boolean reverse) throws DataException { @@ -857,6 +858,25 @@ public class HSQLDBArbitraryRepository implements ArbitraryRepository { bindParams.add(queryWildcard); } + if (keywords != null) { + String[] encryptedTokens = keywords.split(","); + + List conditions = new ArrayList<>(); + List bindValues = new ArrayList<>(); + + for (String token : encryptedTokens) { + conditions.add("POSITION(? IN description) > 0"); + bindValues.add(token.trim()); + } + + + String finalCondition = String.join(" OR ", conditions); + sql.append(" AND (").append(finalCondition).append(")"); + + bindParams.addAll(bindValues); + + } + // Handle name searches if (names != null && !names.isEmpty()) { sql.append(" AND ("); From c22abc440bda59e314eb6558637cf0aa33e7b183 Mon Sep 17 00:00:00 2001 From: PhilReact Date: Tue, 4 Feb 2025 18:02:56 +0200 Subject: [PATCH 2/5] change label --- src/main/java/org/qortal/api/resource/ArbitraryResource.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/qortal/api/resource/ArbitraryResource.java b/src/main/java/org/qortal/api/resource/ArbitraryResource.java index 72d6c5ba..748ebda9 100644 --- a/src/main/java/org/qortal/api/resource/ArbitraryResource.java +++ b/src/main/java/org/qortal/api/resource/ArbitraryResource.java @@ -172,7 +172,7 @@ public class ArbitraryResource { @Parameter(description = "Name (searches name field only)") @QueryParam("name") List names, @Parameter(description = "Title (searches title metadata field only)") @QueryParam("title") String title, @Parameter(description = "Description (searches description metadata field only)") @QueryParam("description") String description, - @Parameter(description = "Description (searches description metadata field by keywords. Input is a string of keywords separated by commas.)") @QueryParam("keywords") String keywords, + @Parameter(description = "Keywords (searches description metadata field by keywords. Input is a string of keywords separated by commas.)") @QueryParam("keywords") String keywords, @Parameter(description = "Prefix only (if true, only the beginning of fields are matched)") @QueryParam("prefix") Boolean prefixOnly, @Parameter(description = "Exact match names only (if true, partial name matches are excluded)") @QueryParam("exactmatchnames") Boolean exactMatchNamesOnly, @Parameter(description = "Default resources (without identifiers) only") @QueryParam("default") Boolean defaultResource, From 33650cc43222feb0328dd3bdbefaaca0696bc7b4 Mon Sep 17 00:00:00 2001 From: PhilReact Date: Wed, 5 Feb 2025 15:11:48 +0200 Subject: [PATCH 3/5] when the path is render/hash do not save path for nav history --- src/main/resources/q-apps/q-apps.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/resources/q-apps/q-apps.js b/src/main/resources/q-apps/q-apps.js index 25656370..5df8765c 100644 --- a/src/main/resources/q-apps/q-apps.js +++ b/src/main/resources/q-apps/q-apps.js @@ -84,6 +84,7 @@ isDOMContentLoaded: isDOMContentLoaded ? true : false function handleQDNResourceDisplayed(pathurl, isDOMContentLoaded) { // make sure that an empty string the root path +if(pathurl?.startsWith('/render/hash/')) return; const path = pathurl || '/' if (!isManualNavigation) { isManualNavigation = true @@ -284,8 +285,6 @@ window.addEventListener("message", async (event) => { return; } - console.log("Core received action: " + JSON.stringify(event.data.action)); - let url; let data = event.data; @@ -694,6 +693,7 @@ const qortalRequestWithTimeout = (request, timeout) => * Send current page details to UI */ document.addEventListener('DOMContentLoaded', (event) => { + resetVariables() qortalRequest({ action: "QDN_RESOURCE_DISPLAYED", @@ -712,6 +712,8 @@ resetVariables() * Handle app navigation */ navigation.addEventListener('navigate', (event) => { + console.log('navigate', event) + const url = new URL(event.destination.url); let fullpath = url.pathname + url.hash; From 086b0809d63f81bab29108456dc1459a30cce835 Mon Sep 17 00:00:00 2001 From: PhilReact Date: Sat, 8 Feb 2025 22:20:03 +0200 Subject: [PATCH 4/5] remove log --- src/main/resources/q-apps/q-apps.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/resources/q-apps/q-apps.js b/src/main/resources/q-apps/q-apps.js index 5df8765c..b2d7ba6b 100644 --- a/src/main/resources/q-apps/q-apps.js +++ b/src/main/resources/q-apps/q-apps.js @@ -712,7 +712,6 @@ resetVariables() * Handle app navigation */ navigation.addEventListener('navigate', (event) => { - console.log('navigate', event) const url = new URL(event.destination.url); From 2e9f358d0bc84a9b0eddafc40cda25973b7eaedc Mon Sep 17 00:00:00 2001 From: PhilReact Date: Thu, 6 Mar 2025 16:10:30 +0200 Subject: [PATCH 5/5] changed to list and added to cache --- .../api/resource/ArbitraryResource.java | 2 +- .../repository/ArbitraryRepository.java | 2 +- .../hsqldb/HSQLDBArbitraryRepository.java | 26 +++++++++------- .../repository/hsqldb/HSQLDBCacheUtils.java | 31 +++++++++++++++++++ .../repository/HSQLDBCacheUtilsTests.java | 3 ++ 5 files changed, 50 insertions(+), 14 deletions(-) diff --git a/src/main/java/org/qortal/api/resource/ArbitraryResource.java b/src/main/java/org/qortal/api/resource/ArbitraryResource.java index 748ebda9..2aa7b07d 100644 --- a/src/main/java/org/qortal/api/resource/ArbitraryResource.java +++ b/src/main/java/org/qortal/api/resource/ArbitraryResource.java @@ -172,7 +172,7 @@ public class ArbitraryResource { @Parameter(description = "Name (searches name field only)") @QueryParam("name") List names, @Parameter(description = "Title (searches title metadata field only)") @QueryParam("title") String title, @Parameter(description = "Description (searches description metadata field only)") @QueryParam("description") String description, - @Parameter(description = "Keywords (searches description metadata field by keywords. Input is a string of keywords separated by commas.)") @QueryParam("keywords") String keywords, + @Parameter(description = "Keyword (searches description metadata field by keywords)") @QueryParam("keywords") List keywords, @Parameter(description = "Prefix only (if true, only the beginning of fields are matched)") @QueryParam("prefix") Boolean prefixOnly, @Parameter(description = "Exact match names only (if true, partial name matches are excluded)") @QueryParam("exactmatchnames") Boolean exactMatchNamesOnly, @Parameter(description = "Default resources (without identifiers) only") @QueryParam("default") Boolean defaultResource, diff --git a/src/main/java/org/qortal/repository/ArbitraryRepository.java b/src/main/java/org/qortal/repository/ArbitraryRepository.java index 53bbfad5..4770d29b 100644 --- a/src/main/java/org/qortal/repository/ArbitraryRepository.java +++ b/src/main/java/org/qortal/repository/ArbitraryRepository.java @@ -46,7 +46,7 @@ public interface ArbitraryRepository { public List getArbitraryResources(Service service, String identifier, List names, boolean defaultResource, Boolean followedOnly, Boolean excludeBlocked, Boolean includeMetadata, Boolean includeStatus, Integer limit, Integer offset, Boolean reverse) throws DataException; - public List searchArbitraryResources(Service service, String query, String identifier, List names, String title, String description, String keywords, boolean prefixOnly, List namesFilter, boolean defaultResource, SearchMode mode, Integer minLevel, Boolean followedOnly, Boolean excludeBlocked, Boolean includeMetadata, Boolean includeStatus, Long before, Long after, Integer limit, Integer offset, Boolean reverse) throws DataException; + public List searchArbitraryResources(Service service, String query, String identifier, List names, String title, String description, List keywords, boolean prefixOnly, List namesFilter, boolean defaultResource, SearchMode mode, Integer minLevel, Boolean followedOnly, Boolean excludeBlocked, Boolean includeMetadata, Boolean includeStatus, Long before, Long after, Integer limit, Integer offset, Boolean reverse) throws DataException; List searchArbitraryResourcesSimple( Service service, diff --git a/src/main/java/org/qortal/repository/hsqldb/HSQLDBArbitraryRepository.java b/src/main/java/org/qortal/repository/hsqldb/HSQLDBArbitraryRepository.java index 599ac8c0..0e15be77 100644 --- a/src/main/java/org/qortal/repository/hsqldb/HSQLDBArbitraryRepository.java +++ b/src/main/java/org/qortal/repository/hsqldb/HSQLDBArbitraryRepository.java @@ -862,12 +862,11 @@ public class HSQLDBArbitraryRepository implements ArbitraryRepository { } @Override - public List searchArbitraryResources(Service service, String query, String identifier, List names, String title, String description, String keywords, boolean prefixOnly, + public List searchArbitraryResources(Service service, String query, String identifier, List names, String title, String description, List keywords, boolean prefixOnly, List exactMatchNames, boolean defaultResource, SearchMode mode, Integer minLevel, Boolean followedOnly, Boolean excludeBlocked, Boolean includeMetadata, Boolean includeStatus, Long before, Long after, Integer limit, Integer offset, Boolean reverse) throws DataException { if(Settings.getInstance().isDbCacheEnabled()) { - List list = HSQLDBCacheUtils.callCache( ArbitraryResourceCache.getInstance(), @@ -889,6 +888,7 @@ public class HSQLDBArbitraryRepository implements ArbitraryRepository { Optional.ofNullable(description), prefixOnly, Optional.ofNullable(exactMatchNames), + Optional.ofNullable(keywords), defaultResource, Optional.ofNullable(minLevel), Optional.ofNullable(() -> ListUtils.followedNames()), @@ -909,6 +909,7 @@ public class HSQLDBArbitraryRepository implements ArbitraryRepository { } } + StringBuilder sql = new StringBuilder(512); List bindParams = new ArrayList<>(); @@ -995,24 +996,25 @@ public class HSQLDBArbitraryRepository implements ArbitraryRepository { bindParams.add(queryWildcard); } - if (keywords != null) { - String[] encryptedTokens = keywords.split(","); - + if (keywords != null && !keywords.isEmpty()) { + List searchKeywords = new ArrayList<>(keywords); + List conditions = new ArrayList<>(); - List bindValues = new ArrayList<>(); + List bindValues = new ArrayList<>(); - for (String token : encryptedTokens) { - conditions.add("POSITION(? IN description) > 0"); - bindValues.add(token.trim()); + for (int i = 0; i < searchKeywords.size(); i++) { + conditions.add("LOWER(description) LIKE ?"); + bindValues.add("%" + searchKeywords.get(i).trim().toLowerCase() + "%"); } - String finalCondition = String.join(" OR ", conditions); sql.append(" AND (").append(finalCondition).append(")"); - bindParams.addAll(bindValues); - + bindParams.addAll(bindValues); } + + + // Handle name searches if (names != null && !names.isEmpty()) { diff --git a/src/main/java/org/qortal/repository/hsqldb/HSQLDBCacheUtils.java b/src/main/java/org/qortal/repository/hsqldb/HSQLDBCacheUtils.java index 49566be2..726a0c33 100644 --- a/src/main/java/org/qortal/repository/hsqldb/HSQLDBCacheUtils.java +++ b/src/main/java/org/qortal/repository/hsqldb/HSQLDBCacheUtils.java @@ -167,6 +167,7 @@ public class HSQLDBCacheUtils { Optional description, boolean prefixOnly, Optional> exactMatchNames, + Optional> keywords, boolean defaultResource, Optional minLevel, Optional>> includeOnly, @@ -207,6 +208,36 @@ public class HSQLDBCacheUtils { stream = filterTerm(title, data -> data.metadata != null ? data.metadata.getTitle() : null, prefixOnly, stream); stream = filterTerm(description, data -> data.metadata != null ? data.metadata.getDescription() : null, prefixOnly, stream); + // New: Filter by keywords if provided + if (keywords.isPresent() && !keywords.get().isEmpty()) { + List searchKeywords = keywords.get().stream() + .map(String::toLowerCase) + .collect(Collectors.toList()); + + stream = stream.filter(candidate -> { + + if (candidate.metadata != null && candidate.metadata.getDescription() != null) { + String descriptionLower = candidate.metadata.getDescription().toLowerCase(); + return searchKeywords.stream().anyMatch(descriptionLower::contains); + } + return false; + }); + } + + if (keywords.isPresent() && !keywords.get().isEmpty()) { + List searchKeywords = keywords.get().stream() + .map(String::toLowerCase) + .collect(Collectors.toList()); + + stream = stream.filter(candidate -> { + if (candidate.metadata != null && candidate.metadata.getDescription() != null) { + String descriptionLower = candidate.metadata.getDescription().toLowerCase(); + return searchKeywords.stream().anyMatch(descriptionLower::contains); + } + return false; + }); + } + // if exact names is set, retain resources with exact names if( exactMatchNames.isPresent() && !exactMatchNames.get().isEmpty()) { diff --git a/src/test/java/org/qortal/test/repository/HSQLDBCacheUtilsTests.java b/src/test/java/org/qortal/test/repository/HSQLDBCacheUtilsTests.java index 4a75b438..0cda76d4 100644 --- a/src/test/java/org/qortal/test/repository/HSQLDBCacheUtilsTests.java +++ b/src/test/java/org/qortal/test/repository/HSQLDBCacheUtilsTests.java @@ -26,6 +26,7 @@ public class HSQLDBCacheUtilsTests { private static final String DESCRIPTION = "description"; private static final String PREFIX_ONLY = "prefixOnly"; private static final String EXACT_MATCH_NAMES = "exactMatchNames"; + private static final String KEYWORDS = "keywords"; private static final String DEFAULT_RESOURCE = "defaultResource"; private static final String MODE = "mode"; private static final String MIN_LEVEL = "minLevel"; @@ -634,6 +635,7 @@ public class HSQLDBCacheUtilsTests { Optional description = Optional.ofNullable((String) valueByKey.get(DESCRIPTION)); boolean prefixOnly = valueByKey.containsKey(PREFIX_ONLY); Optional> exactMatchNames = Optional.ofNullable((List) valueByKey.get(EXACT_MATCH_NAMES)); + Optional> keywords = Optional.ofNullable((List) valueByKey.get(KEYWORDS)); boolean defaultResource = valueByKey.containsKey(DEFAULT_RESOURCE); Optional mode = Optional.of((SearchMode) valueByKey.getOrDefault(MODE, SearchMode.ALL)); Optional minLevel = Optional.ofNullable((Integer) valueByKey.get(MIN_LEVEL)); @@ -660,6 +662,7 @@ public class HSQLDBCacheUtilsTests { description, prefixOnly, exactMatchNames, + keywords, defaultResource, minLevel, followedOnly,