mirror of
https://github.com/Qortal/qortal.git
synced 2025-04-16 16:15:53 +00:00
Merge pull request #4 from Philreact/feature/search-keywords
Feature/search keywords
This commit is contained in:
commit
92fb52220a
@ -172,6 +172,7 @@ public class ArbitraryResource {
|
|||||||
@Parameter(description = "Name (searches name field only)") @QueryParam("name") List<String> names,
|
@Parameter(description = "Name (searches name field only)") @QueryParam("name") List<String> names,
|
||||||
@Parameter(description = "Title (searches title metadata field only)") @QueryParam("title") String title,
|
@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 only)") @QueryParam("description") String description,
|
||||||
|
@Parameter(description = "Keyword (searches description metadata field by keywords)") @QueryParam("keywords") List<String> keywords,
|
||||||
@Parameter(description = "Prefix only (if true, only the beginning of fields are matched)") @QueryParam("prefix") Boolean prefixOnly,
|
@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 = "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,
|
@Parameter(description = "Default resources (without identifiers) only") @QueryParam("default") Boolean defaultResource,
|
||||||
@ -212,7 +213,7 @@ public class ArbitraryResource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<ArbitraryResourceData> resources = repository.getArbitraryRepository()
|
List<ArbitraryResourceData> 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,
|
exactMatchNames, defaultRes, mode, minLevel, followedOnly, excludeBlocked, includeMetadata, includeStatus,
|
||||||
before, after, limit, offset, reverse);
|
before, after, limit, offset, reverse);
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ public interface ArbitraryRepository {
|
|||||||
|
|
||||||
public List<ArbitraryResourceData> getArbitraryResources(Service service, String identifier, List<String> names, boolean defaultResource, Boolean followedOnly, Boolean excludeBlocked, Boolean includeMetadata, Boolean includeStatus, Integer limit, Integer offset, Boolean reverse) throws DataException;
|
public List<ArbitraryResourceData> getArbitraryResources(Service service, String identifier, List<String> names, boolean defaultResource, Boolean followedOnly, Boolean excludeBlocked, Boolean includeMetadata, Boolean includeStatus, Integer limit, Integer offset, Boolean reverse) throws DataException;
|
||||||
|
|
||||||
public List<ArbitraryResourceData> searchArbitraryResources(Service service, String query, String identifier, List<String> names, String title, String description, boolean prefixOnly, List<String> 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<ArbitraryResourceData> searchArbitraryResources(Service service, String query, String identifier, List<String> names, String title, String description, List<String> keywords, boolean prefixOnly, List<String> 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<ArbitraryResourceData> searchArbitraryResourcesSimple(
|
List<ArbitraryResourceData> searchArbitraryResourcesSimple(
|
||||||
Service service,
|
Service service,
|
||||||
|
@ -28,6 +28,7 @@ import org.qortal.utils.ListUtils;
|
|||||||
import java.sql.ResultSet;
|
import java.sql.ResultSet;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
@ -861,12 +862,11 @@ public class HSQLDBArbitraryRepository implements ArbitraryRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<ArbitraryResourceData> searchArbitraryResources(Service service, String query, String identifier, List<String> names, String title, String description, boolean prefixOnly,
|
public List<ArbitraryResourceData> searchArbitraryResources(Service service, String query, String identifier, List<String> names, String title, String description, List<String> keywords, boolean prefixOnly,
|
||||||
List<String> exactMatchNames, boolean defaultResource, SearchMode mode, Integer minLevel, Boolean followedOnly, Boolean excludeBlocked,
|
List<String> 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 {
|
Boolean includeMetadata, Boolean includeStatus, Long before, Long after, Integer limit, Integer offset, Boolean reverse) throws DataException {
|
||||||
|
|
||||||
if(Settings.getInstance().isDbCacheEnabled()) {
|
if(Settings.getInstance().isDbCacheEnabled()) {
|
||||||
|
|
||||||
List<ArbitraryResourceData> list
|
List<ArbitraryResourceData> list
|
||||||
= HSQLDBCacheUtils.callCache(
|
= HSQLDBCacheUtils.callCache(
|
||||||
ArbitraryResourceCache.getInstance(),
|
ArbitraryResourceCache.getInstance(),
|
||||||
@ -888,6 +888,7 @@ public class HSQLDBArbitraryRepository implements ArbitraryRepository {
|
|||||||
Optional.ofNullable(description),
|
Optional.ofNullable(description),
|
||||||
prefixOnly,
|
prefixOnly,
|
||||||
Optional.ofNullable(exactMatchNames),
|
Optional.ofNullable(exactMatchNames),
|
||||||
|
Optional.ofNullable(keywords),
|
||||||
defaultResource,
|
defaultResource,
|
||||||
Optional.ofNullable(minLevel),
|
Optional.ofNullable(minLevel),
|
||||||
Optional.ofNullable(() -> ListUtils.followedNames()),
|
Optional.ofNullable(() -> ListUtils.followedNames()),
|
||||||
@ -908,6 +909,7 @@ public class HSQLDBArbitraryRepository implements ArbitraryRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
StringBuilder sql = new StringBuilder(512);
|
StringBuilder sql = new StringBuilder(512);
|
||||||
List<Object> bindParams = new ArrayList<>();
|
List<Object> bindParams = new ArrayList<>();
|
||||||
|
|
||||||
@ -994,6 +996,26 @@ public class HSQLDBArbitraryRepository implements ArbitraryRepository {
|
|||||||
bindParams.add(queryWildcard);
|
bindParams.add(queryWildcard);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (keywords != null && !keywords.isEmpty()) {
|
||||||
|
List<String> searchKeywords = new ArrayList<>(keywords);
|
||||||
|
|
||||||
|
List<String> conditions = new ArrayList<>();
|
||||||
|
List<String> bindValues = new ArrayList<>();
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Handle name searches
|
// Handle name searches
|
||||||
if (names != null && !names.isEmpty()) {
|
if (names != null && !names.isEmpty()) {
|
||||||
sql.append(" AND (");
|
sql.append(" AND (");
|
||||||
|
@ -167,6 +167,7 @@ public class HSQLDBCacheUtils {
|
|||||||
Optional<String> description,
|
Optional<String> description,
|
||||||
boolean prefixOnly,
|
boolean prefixOnly,
|
||||||
Optional<List<String>> exactMatchNames,
|
Optional<List<String>> exactMatchNames,
|
||||||
|
Optional<List<String>> keywords,
|
||||||
boolean defaultResource,
|
boolean defaultResource,
|
||||||
Optional<Integer> minLevel,
|
Optional<Integer> minLevel,
|
||||||
Optional<Supplier<List<String>>> includeOnly,
|
Optional<Supplier<List<String>>> includeOnly,
|
||||||
@ -207,6 +208,36 @@ public class HSQLDBCacheUtils {
|
|||||||
stream = filterTerm(title, data -> data.metadata != null ? data.metadata.getTitle() : null, prefixOnly, stream);
|
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);
|
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<String> 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<String> 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 exact names is set, retain resources with exact names
|
||||||
if( exactMatchNames.isPresent() && !exactMatchNames.get().isEmpty()) {
|
if( exactMatchNames.isPresent() && !exactMatchNames.get().isEmpty()) {
|
||||||
|
|
||||||
|
@ -84,6 +84,7 @@ isDOMContentLoaded: isDOMContentLoaded ? true : false
|
|||||||
|
|
||||||
function handleQDNResourceDisplayed(pathurl, isDOMContentLoaded) {
|
function handleQDNResourceDisplayed(pathurl, isDOMContentLoaded) {
|
||||||
// make sure that an empty string the root path
|
// make sure that an empty string the root path
|
||||||
|
if(pathurl?.startsWith('/render/hash/')) return;
|
||||||
const path = pathurl || '/'
|
const path = pathurl || '/'
|
||||||
if (!isManualNavigation) {
|
if (!isManualNavigation) {
|
||||||
isManualNavigation = true
|
isManualNavigation = true
|
||||||
@ -284,8 +285,6 @@ window.addEventListener("message", async (event) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("Core received action: " + JSON.stringify(event.data.action));
|
|
||||||
|
|
||||||
let url;
|
let url;
|
||||||
let data = event.data;
|
let data = event.data;
|
||||||
let identifier;
|
let identifier;
|
||||||
@ -694,6 +693,7 @@ const qortalRequestWithTimeout = (request, timeout) =>
|
|||||||
* Send current page details to UI
|
* Send current page details to UI
|
||||||
*/
|
*/
|
||||||
document.addEventListener('DOMContentLoaded', (event) => {
|
document.addEventListener('DOMContentLoaded', (event) => {
|
||||||
|
|
||||||
resetVariables()
|
resetVariables()
|
||||||
qortalRequest({
|
qortalRequest({
|
||||||
action: "QDN_RESOURCE_DISPLAYED",
|
action: "QDN_RESOURCE_DISPLAYED",
|
||||||
@ -712,6 +712,7 @@ resetVariables()
|
|||||||
* Handle app navigation
|
* Handle app navigation
|
||||||
*/
|
*/
|
||||||
navigation.addEventListener('navigate', (event) => {
|
navigation.addEventListener('navigate', (event) => {
|
||||||
|
|
||||||
const url = new URL(event.destination.url);
|
const url = new URL(event.destination.url);
|
||||||
|
|
||||||
let fullpath = url.pathname + url.hash;
|
let fullpath = url.pathname + url.hash;
|
||||||
|
@ -26,6 +26,7 @@ public class HSQLDBCacheUtilsTests {
|
|||||||
private static final String DESCRIPTION = "description";
|
private static final String DESCRIPTION = "description";
|
||||||
private static final String PREFIX_ONLY = "prefixOnly";
|
private static final String PREFIX_ONLY = "prefixOnly";
|
||||||
private static final String EXACT_MATCH_NAMES = "exactMatchNames";
|
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 DEFAULT_RESOURCE = "defaultResource";
|
||||||
private static final String MODE = "mode";
|
private static final String MODE = "mode";
|
||||||
private static final String MIN_LEVEL = "minLevel";
|
private static final String MIN_LEVEL = "minLevel";
|
||||||
@ -634,6 +635,7 @@ public class HSQLDBCacheUtilsTests {
|
|||||||
Optional<String> description = Optional.ofNullable((String) valueByKey.get(DESCRIPTION));
|
Optional<String> description = Optional.ofNullable((String) valueByKey.get(DESCRIPTION));
|
||||||
boolean prefixOnly = valueByKey.containsKey(PREFIX_ONLY);
|
boolean prefixOnly = valueByKey.containsKey(PREFIX_ONLY);
|
||||||
Optional<List<String>> exactMatchNames = Optional.ofNullable((List<String>) valueByKey.get(EXACT_MATCH_NAMES));
|
Optional<List<String>> exactMatchNames = Optional.ofNullable((List<String>) valueByKey.get(EXACT_MATCH_NAMES));
|
||||||
|
Optional<List<String>> keywords = Optional.ofNullable((List<String>) valueByKey.get(KEYWORDS));
|
||||||
boolean defaultResource = valueByKey.containsKey(DEFAULT_RESOURCE);
|
boolean defaultResource = valueByKey.containsKey(DEFAULT_RESOURCE);
|
||||||
Optional<SearchMode> mode = Optional.of((SearchMode) valueByKey.getOrDefault(MODE, SearchMode.ALL));
|
Optional<SearchMode> mode = Optional.of((SearchMode) valueByKey.getOrDefault(MODE, SearchMode.ALL));
|
||||||
Optional<Integer> minLevel = Optional.ofNullable((Integer) valueByKey.get(MIN_LEVEL));
|
Optional<Integer> minLevel = Optional.ofNullable((Integer) valueByKey.get(MIN_LEVEL));
|
||||||
@ -660,6 +662,7 @@ public class HSQLDBCacheUtilsTests {
|
|||||||
description,
|
description,
|
||||||
prefixOnly,
|
prefixOnly,
|
||||||
exactMatchNames,
|
exactMatchNames,
|
||||||
|
keywords,
|
||||||
defaultResource,
|
defaultResource,
|
||||||
minLevel,
|
minLevel,
|
||||||
followedOnly,
|
followedOnly,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user