diff --git a/Q-Apps.md b/Q-Apps.md index 41d5d756..74b09791 100644 --- a/Q-Apps.md +++ b/Q-Apps.md @@ -362,6 +362,7 @@ let res = await qortalRequest({ prefix: false, // Optional - if true, only the beginning of fields are matched in all of the above filters exactMatchNames: true, // Optional - if true, partial name matches are excluded default: false, // Optional - if true, only resources without identifiers are returned + mode: "LATEST", // Optional - whether to return all resources or just the latest for a name/service combination. Possible values: ALL,LATEST. Default: LATEST includeStatus: false, // Optional - will take time to respond, so only request if necessary includeMetadata: false, // Optional - will take time to respond, so only request if necessary nameListFilter: "QApp1234Subscriptions", // Optional - will only return results if they are from a name included in supplied list @@ -384,12 +385,16 @@ let res = await qortalRequest({ identifier: "search query goes here", // Optional - searches only the "identifier" field names: ["QortalDemo", "crowetic", "AlphaX"], // Optional - searches only the "name" field for any of the supplied names prefix: false, // Optional - if true, only the beginning of fields are matched in all of the above filters + exactMatchNames: true, // Optional - if true, partial name matches are excluded default: false, // Optional - if true, only resources without identifiers are returned + mode: "LATEST", // Optional - whether to return all resources or just the latest for a name/service combination. Possible values: ALL,LATEST. Default: LATEST includeStatus: false, // Optional - will take time to respond, so only request if necessary includeMetadata: false, // Optional - will take time to respond, so only request if necessary nameListFilter: "QApp1234Subscriptions", // Optional - will only return results if they are from a name included in supplied list followedOnly: false, // Optional - include followed names only excludeBlocked: false, // Optional - exclude blocked content + // before: 1683546000000, // Optional - limit to resources created before timestamp + // after: 1683546000000, // Optional - limit to resources created after timestamp limit: 100, offset: 0, reverse: true diff --git a/src/main/java/org/qortal/api/SearchMode.java b/src/main/java/org/qortal/api/SearchMode.java new file mode 100644 index 00000000..85c1c61a --- /dev/null +++ b/src/main/java/org/qortal/api/SearchMode.java @@ -0,0 +1,6 @@ +package org.qortal.api; + +public enum SearchMode { + LATEST, + ALL; +} diff --git a/src/main/java/org/qortal/api/resource/ArbitraryResource.java b/src/main/java/org/qortal/api/resource/ArbitraryResource.java index b3fd6d6d..295fb7c5 100644 --- a/src/main/java/org/qortal/api/resource/ArbitraryResource.java +++ b/src/main/java/org/qortal/api/resource/ArbitraryResource.java @@ -170,6 +170,7 @@ public class ArbitraryResource { @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, + @Parameter(description = "Search mode") @QueryParam("mode") SearchMode mode, @Parameter(description = "Filter names by list (exact matches only)") @QueryParam("namefilter") String nameListFilter, @Parameter(description = "Include followed names only") @QueryParam("followedonly") Boolean followedOnly, @Parameter(description = "Exclude blocked content") @QueryParam("excludeblocked") Boolean excludeBlocked, @@ -206,7 +207,7 @@ public class ArbitraryResource { List resources = repository.getArbitraryRepository() .searchArbitraryResources(service, query, identifier, names, title, description, usePrefixOnly, - exactMatchNames, defaultRes, followedOnly, excludeBlocked, includeMetadata, includeStatus, + exactMatchNames, defaultRes, mode, followedOnly, excludeBlocked, includeMetadata, includeStatus, before, after, limit, offset, reverse); if (resources == null) { diff --git a/src/main/java/org/qortal/repository/ArbitraryRepository.java b/src/main/java/org/qortal/repository/ArbitraryRepository.java index 089ca199..e773597d 100644 --- a/src/main/java/org/qortal/repository/ArbitraryRepository.java +++ b/src/main/java/org/qortal/repository/ArbitraryRepository.java @@ -1,5 +1,6 @@ package org.qortal.repository; +import org.qortal.api.SearchMode; import org.qortal.arbitrary.misc.Service; import org.qortal.data.arbitrary.ArbitraryResourceData; import org.qortal.data.arbitrary.ArbitraryResourceMetadata; @@ -39,7 +40,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, 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, boolean prefixOnly, List namesFilter, boolean defaultResource, SearchMode mode, Boolean followedOnly, Boolean excludeBlocked, Boolean includeMetadata, Boolean includeStatus, Long before, Long after, Integer limit, Integer offset, Boolean reverse) throws DataException; // Arbitrary resources cache save/load diff --git a/src/main/java/org/qortal/repository/hsqldb/HSQLDBArbitraryRepository.java b/src/main/java/org/qortal/repository/hsqldb/HSQLDBArbitraryRepository.java index d72d233b..b1e878ac 100644 --- a/src/main/java/org/qortal/repository/hsqldb/HSQLDBArbitraryRepository.java +++ b/src/main/java/org/qortal/repository/hsqldb/HSQLDBArbitraryRepository.java @@ -2,6 +2,7 @@ package org.qortal.repository.hsqldb; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.qortal.api.SearchMode; import org.qortal.arbitrary.metadata.ArbitraryDataTransactionMetadata; import org.qortal.arbitrary.misc.Category; import org.qortal.arbitrary.misc.Service; @@ -639,16 +640,34 @@ public class HSQLDBArbitraryRepository implements ArbitraryRepository { @Override public List searchArbitraryResources(Service service, String query, String identifier, List names, String title, String description, boolean prefixOnly, - List exactMatchNames, boolean defaultResource, Boolean followedOnly, Boolean excludeBlocked, + List exactMatchNames, boolean defaultResource, SearchMode mode, Boolean followedOnly, Boolean excludeBlocked, Boolean includeMetadata, Boolean includeStatus, Long before, Long after, Integer limit, Integer offset, Boolean reverse) throws DataException { StringBuilder sql = new StringBuilder(512); List bindParams = new ArrayList<>(); sql.append("SELECT name, service, identifier, size, status, created_when, updated_when, " + "title, description, category, tag1, tag2, tag3, tag4, tag5 " + - "FROM ArbitraryResourcesCache " + - "LEFT JOIN ArbitraryMetadataCache USING (service, name, identifier) " + - "WHERE name IS NOT NULL"); + "FROM ArbitraryResourcesCache"); + + // Default to "latest" mode + if (mode == null) { + mode = SearchMode.LATEST; + } + + switch (mode) { + case LATEST: + // Include latest item only for a name/service combination + sql.append(" JOIN (SELECT name, service, MAX(created_when) AS latest " + + "FROM ArbitraryResourcesCache GROUP BY name, service) LatestResources " + + "ON name=LatestResources.name AND service=LatestResources.service " + + "AND created_when=LatestResources.latest"); + break; + + case ALL: + break; + } + + sql.append(" LEFT JOIN ArbitraryMetadataCache USING (service, name, identifier) WHERE name IS NOT NULL"); if (service != null) { sql.append(" AND service = "); diff --git a/src/main/resources/q-apps/q-apps.js b/src/main/resources/q-apps/q-apps.js index ac0d6603..3069c160 100644 --- a/src/main/resources/q-apps/q-apps.js +++ b/src/main/resources/q-apps/q-apps.js @@ -223,6 +223,7 @@ window.addEventListener("message", (event) => { if (data.prefix != null) url = url.concat("&prefix=" + new Boolean(data.prefix).toString()); if (data.exactMatchNames != null) url = url.concat("&exactmatchnames=" + new Boolean(data.exactMatchNames).toString()); if (data.default != null) url = url.concat("&default=" + new Boolean(data.default).toString()); + if (data.mode != null) url = url.concat("&mode=" + data.mode); if (data.includeStatus != null) url = url.concat("&includestatus=" + new Boolean(data.includeStatus).toString()); if (data.includeMetadata != null) url = url.concat("&includemetadata=" + new Boolean(data.includeMetadata).toString()); if (data.nameListFilter != null) url = url.concat("&namefilter=" + data.nameListFilter);