diff --git a/src/main/java/org/qortal/api/resource/ArbitraryResource.java b/src/main/java/org/qortal/api/resource/ArbitraryResource.java index 99fc0020..754c3467 100644 --- a/src/main/java/org/qortal/api/resource/ArbitraryResource.java +++ b/src/main/java/org/qortal/api/resource/ArbitraryResource.java @@ -227,6 +227,49 @@ public class ArbitraryResource { } } + @GET + @Path("/resources/searchsimple") + @Operation( + summary = "Search arbitrary resources available on chain, optionally filtered by service.", + responses = { + @ApiResponse( + content = @Content(mediaType = MediaType.APPLICATION_JSON, schema = @Schema(implementation = ArbitraryResourceData.class)) + ) + } + ) + @ApiErrors({ApiError.REPOSITORY_ISSUE}) + public List searchResourcesSimple( + @QueryParam("service") Service service, + @Parameter(description = "Identifier (searches identifier field only)") @QueryParam("identifier") String identifier, + @Parameter(description = "Name (searches name field only)") @QueryParam("name") List names, + @Parameter(description = "Prefix only (if true, only the beginning of fields are matched)") @QueryParam("prefix") Boolean prefixOnly, + @Parameter(description = "Case insensitive (ignore leter case on search)") @QueryParam("caseInsensitive") Boolean caseInsensitive, + @Parameter(description = "Creation date before timestamp") @QueryParam("before") Long before, + @Parameter(description = "Creation date after timestamp") @QueryParam("after") Long after, + @Parameter(ref = "limit") @QueryParam("limit") Integer limit, + @Parameter(ref = "offset") @QueryParam("offset") Integer offset, + @Parameter(ref = "reverse") @QueryParam("reverse") Boolean reverse) { + + try (final Repository repository = RepositoryManager.getRepository()) { + + boolean usePrefixOnly = Boolean.TRUE.equals(prefixOnly); + boolean ignoreCase = Boolean.TRUE.equals(caseInsensitive); + + List resources = repository.getArbitraryRepository() + .searchArbitraryResourcesSimple(service, identifier, names, usePrefixOnly, + before, after, limit, offset, reverse, ignoreCase); + + if (resources == null) { + return new ArrayList<>(); + } + + return resources; + + } catch (DataException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e); + } + } + @GET @Path("/resource/status/{service}/{name}") @Operation( diff --git a/src/main/java/org/qortal/repository/ArbitraryRepository.java b/src/main/java/org/qortal/repository/ArbitraryRepository.java index 175f1daf..1c0e84e2 100644 --- a/src/main/java/org/qortal/repository/ArbitraryRepository.java +++ b/src/main/java/org/qortal/repository/ArbitraryRepository.java @@ -44,6 +44,17 @@ public interface ArbitraryRepository { 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; + List searchArbitraryResourcesSimple( + Service service, + String identifier, + List names, + boolean prefixOnly, + Long before, + Long after, + Integer limit, + Integer offset, + Boolean reverse, + Boolean caseInsensitive) 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 c49074c5..e30d61bf 100644 --- a/src/main/java/org/qortal/repository/hsqldb/HSQLDBArbitraryRepository.java +++ b/src/main/java/org/qortal/repository/hsqldb/HSQLDBArbitraryRepository.java @@ -954,6 +954,128 @@ public class HSQLDBArbitraryRepository implements ArbitraryRepository { } } + @Override + public List searchArbitraryResourcesSimple( + Service service, + String identifier, + List names, + boolean prefixOnly, + Long before, + Long after, + Integer limit, + Integer offset, + Boolean reverse, + Boolean caseInsensitive) throws DataException { + StringBuilder sql = new StringBuilder(512); + List bindParams = new ArrayList<>(); + + sql.append("SELECT name, service, identifier, size, status, created_when, updated_when "); + sql.append("FROM ArbitraryResourcesCache "); + sql.append("WHERE name IS NOT NULL"); + + if (service != null) { + sql.append(" AND service = ?"); + bindParams.add(service.value); + } + + // Handle identifier matches + if (identifier != null) { + if(caseInsensitive || prefixOnly) { + // Search anywhere in the identifier, unless "prefixOnly" has been requested + String queryWildcard = getQueryWildcard(identifier, prefixOnly, caseInsensitive); + sql.append(caseInsensitive ? " AND LCASE(identifier) LIKE ?" : " AND identifier LIKE ?"); + bindParams.add(queryWildcard); + } + else { + sql.append(" AND identifier = ?"); + bindParams.add(identifier); + } + } + + // Handle name searches + if (names != null && !names.isEmpty()) { + sql.append(" AND ("); + + if( caseInsensitive || prefixOnly ) { + for (int i = 0; i < names.size(); ++i) { + // Search anywhere in the name, unless "prefixOnly" has been requested + String queryWildcard = getQueryWildcard(names.get(i), prefixOnly, caseInsensitive); + if (i > 0) sql.append(" OR "); + sql.append(caseInsensitive ? "LCASE(name) LIKE ?" : "name LIKE ?"); + bindParams.add(queryWildcard); + } + } + else { + for (int i = 0; i < names.size(); ++i) { + if (i > 0) sql.append(" OR "); + sql.append("name = ?"); + bindParams.add(names.get(i)); + } + } + + sql.append(")"); + } + + // Timestamp range + if (before != null) { + sql.append(" AND created_when < ?"); + bindParams.add(before); + } + if (after != null) { + sql.append(" AND created_when > ?"); + bindParams.add(after); + } + + sql.append(" ORDER BY created_when"); + + if (reverse != null && reverse) { + sql.append(" DESC"); + } + + HSQLDBRepository.limitOffsetSql(sql, limit, offset); + + List arbitraryResources = new ArrayList<>(); + + try (ResultSet resultSet = this.repository.checkedExecute(sql.toString(), bindParams.toArray())) { + if (resultSet == null) + return arbitraryResources; + + do { + String nameResult = resultSet.getString(1); + Service serviceResult = Service.valueOf(resultSet.getInt(2)); + String identifierResult = resultSet.getString(3); + Integer sizeResult = resultSet.getInt(4); + Integer status = resultSet.getInt(5); + Long created = resultSet.getLong(6); + Long updated = resultSet.getLong(7); + + if (Objects.equals(identifierResult, "default")) { + // Map "default" back to null. This is optional but probably less confusing than returning "default". + identifierResult = null; + } + + ArbitraryResourceData arbitraryResourceData = new ArbitraryResourceData(); + arbitraryResourceData.name = nameResult; + arbitraryResourceData.service = serviceResult; + arbitraryResourceData.identifier = identifierResult; + arbitraryResourceData.size = sizeResult; + arbitraryResourceData.created = created; + arbitraryResourceData.updated = (updated == 0) ? null : updated; + + arbitraryResources.add(arbitraryResourceData); + } while (resultSet.next()); + + return arbitraryResources; + } catch (SQLException e) { + throw new DataException("Unable to fetch simple arbitrary resources from repository", e); + } + } + + private static String getQueryWildcard(String value, boolean prefixOnly, boolean caseInsensitive) { + String valueToUse = caseInsensitive ? value.toLowerCase() : value; + return prefixOnly ? String.format("%s%%", valueToUse) : valueToUse; + } + // Arbitrary resources cache save/load