mirror of
https://github.com/Qortal/qortal.git
synced 2025-02-15 03:35:49 +00:00
Unify API calls that return lists + offload pagination to repository
API calls that return lists now take limit, offset and reverse params. API calls that used to return data & optional list (e.g. blockWithTransactions) now only return base data. The optional lists can be fetched via a different API call. Also: SLF4J now routes logging to log4j2 so start up output cleaned up. Suppressed extraneous Jersey warning about Providers during start-up injection.
This commit is contained in:
parent
782bc2000f
commit
4be58514c0
@ -2,5 +2,6 @@ eclipse.preferences.version=1
|
|||||||
encoding//src/main/java=UTF-8
|
encoding//src/main/java=UTF-8
|
||||||
encoding//src/main/resources=UTF-8
|
encoding//src/main/resources=UTF-8
|
||||||
encoding//src/test/java=UTF-8
|
encoding//src/test/java=UTF-8
|
||||||
|
encoding//target/generated-sources/package-info=UTF-8
|
||||||
encoding/<project>=UTF-8
|
encoding/<project>=UTF-8
|
||||||
encoding/src=UTF-8
|
encoding/src=UTF-8
|
||||||
|
@ -9,17 +9,14 @@ rootLogger.appenderRef.rolling.ref = FILE
|
|||||||
# Override HSQLDB logging level to "warn" as too much is logged at "info"
|
# Override HSQLDB logging level to "warn" as too much is logged at "info"
|
||||||
logger.hsqldb.name = hsqldb.db
|
logger.hsqldb.name = hsqldb.db
|
||||||
logger.hsqldb.level = warn
|
logger.hsqldb.level = warn
|
||||||
logger.hsqldb.appenderRef.rolling.ref = FILE
|
|
||||||
|
|
||||||
# Override logging level for this class
|
# Suppress extraneous Jersey warning
|
||||||
logger.voting.name = qora.transaction.VoteOnPollTransaction
|
logger.jerseyInject.name = org.glassfish.jersey.internal.inject.Providers
|
||||||
logger.voting.level = trace
|
logger.jerseyInject.level = error
|
||||||
logger.voting.appenderRef.rolling.ref = FILE
|
|
||||||
|
|
||||||
# Override logging level for this class
|
# Debugging transaction searches
|
||||||
logger.assets.name = qora.assets.Order
|
logger.txSearch.name = org.qora.repository.hsqldb.transaction.HSQLDBTransactionRepository
|
||||||
logger.assets.level = trace
|
logger.txSearch.level = trace
|
||||||
logger.assets.appenderRef.rolling.ref = FILE
|
|
||||||
|
|
||||||
appender.console.type = Console
|
appender.console.type = Console
|
||||||
appender.console.name = stdout
|
appender.console.name = stdout
|
||||||
|
19
pom.xml
19
pom.xml
@ -305,17 +305,24 @@
|
|||||||
<artifactId>log4j-api</artifactId>
|
<artifactId>log4j-api</artifactId>
|
||||||
<version>${log4j.version}</version>
|
<version>${log4j.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<!-- Logging: slf4j used by Jetty -->
|
<!-- redirect slf4j to log4j2 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.logging.log4j</groupId>
|
||||||
|
<artifactId>log4j-slf4j-impl</artifactId>
|
||||||
|
<version>${log4j.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- redirect java.utils.logging to log4j2 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.logging.log4j</groupId>
|
||||||
|
<artifactId>log4j-jul</artifactId>
|
||||||
|
<version>${log4j.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- Logging: slf4j used by Jetty/Jersey -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.slf4j</groupId>
|
<groupId>org.slf4j</groupId>
|
||||||
<artifactId>slf4j-api</artifactId>
|
<artifactId>slf4j-api</artifactId>
|
||||||
<version>${slf4j.version}</version>
|
<version>${slf4j.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.slf4j</groupId>
|
|
||||||
<artifactId>slf4j-simple</artifactId>
|
|
||||||
<version>${slf4j.version}</version>
|
|
||||||
</dependency>
|
|
||||||
<!-- Servlet related -->
|
<!-- Servlet related -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>javax.servlet</groupId>
|
<groupId>javax.servlet</groupId>
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
package org.qora.api;
|
|
||||||
|
|
||||||
import javax.xml.bind.Unmarshaller.Listener;
|
|
||||||
|
|
||||||
import org.qora.data.transaction.TransactionData;
|
|
||||||
|
|
||||||
public class UnmarshalListener extends Listener {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void afterUnmarshal(Object target, Object parent) {
|
|
||||||
if (!(target instanceof TransactionData))
|
|
||||||
return;
|
|
||||||
|
|
||||||
// do something
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,34 +0,0 @@
|
|||||||
package org.qora.api.model;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import javax.xml.bind.annotation.XmlAccessType;
|
|
||||||
import javax.xml.bind.annotation.XmlAccessorType;
|
|
||||||
import javax.xml.bind.annotation.XmlElement;
|
|
||||||
|
|
||||||
import org.qora.data.account.AccountBalanceData;
|
|
||||||
import org.qora.data.asset.AssetData;
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
|
||||||
|
|
||||||
@Schema(description = "Asset info, maybe including asset holders")
|
|
||||||
// All properties to be converted to JSON via JAX-RS
|
|
||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
|
||||||
public class AssetWithHolders {
|
|
||||||
|
|
||||||
@Schema(implementation = AssetData.class, name = "asset", title = "asset data")
|
|
||||||
@XmlElement(name = "asset")
|
|
||||||
public AssetData assetData;
|
|
||||||
|
|
||||||
public List<AccountBalanceData> holders;
|
|
||||||
|
|
||||||
// For JAX-RS
|
|
||||||
protected AssetWithHolders() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public AssetWithHolders(AssetData assetData, List<AccountBalanceData> holders) {
|
|
||||||
this.assetData = assetData;
|
|
||||||
this.holders = holders;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,34 +0,0 @@
|
|||||||
package org.qora.api.model;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import javax.xml.bind.annotation.XmlAccessType;
|
|
||||||
import javax.xml.bind.annotation.XmlAccessorType;
|
|
||||||
import javax.xml.bind.annotation.XmlElement;
|
|
||||||
|
|
||||||
import org.qora.data.block.BlockData;
|
|
||||||
import org.qora.data.transaction.TransactionData;
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
|
||||||
|
|
||||||
@Schema(description = "Block info, maybe including transactions")
|
|
||||||
// All properties to be converted to JSON via JAX-RS
|
|
||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
|
||||||
public class BlockWithTransactions {
|
|
||||||
|
|
||||||
@Schema(implementation = BlockData.class, name = "block", title = "block data")
|
|
||||||
@XmlElement(name = "block")
|
|
||||||
public BlockData blockData;
|
|
||||||
|
|
||||||
public List<TransactionData> transactions;
|
|
||||||
|
|
||||||
// For JAX-RS
|
|
||||||
protected BlockWithTransactions() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public BlockWithTransactions(BlockData blockData, List<TransactionData> transactions) {
|
|
||||||
this.blockData = blockData;
|
|
||||||
this.transactions = transactions;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -6,38 +6,31 @@ import javax.xml.bind.annotation.XmlAccessType;
|
|||||||
import javax.xml.bind.annotation.XmlAccessorType;
|
import javax.xml.bind.annotation.XmlAccessorType;
|
||||||
import javax.xml.bind.annotation.XmlElement;
|
import javax.xml.bind.annotation.XmlElement;
|
||||||
|
|
||||||
import org.qora.data.group.GroupData;
|
|
||||||
import org.qora.data.group.GroupMemberData;
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
|
||||||
@Schema(description = "Group info, maybe including members")
|
@Schema(description = "Group info, maybe including members")
|
||||||
// All properties to be converted to JSON via JAX-RS
|
// All properties to be converted to JSON via JAX-RS
|
||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
public class GroupWithMemberInfo {
|
public class GroupMembers {
|
||||||
|
|
||||||
@Schema(implementation = GroupData.class, name = "group", title = "group info")
|
|
||||||
@XmlElement(name = "group")
|
|
||||||
public GroupData groupData;
|
|
||||||
|
|
||||||
Integer memberCount;
|
Integer memberCount;
|
||||||
|
Integer adminCount;
|
||||||
@XmlElement(name = "admins")
|
|
||||||
public List<String> groupAdminAddresses;
|
|
||||||
|
|
||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
@Schema(description = "Member info")
|
@Schema(description = "Member info")
|
||||||
public static class MemberInfo {
|
public static class MemberInfo {
|
||||||
public String member;
|
public String member;
|
||||||
public long joined;
|
public Long joined;
|
||||||
|
public Boolean isAdmin;
|
||||||
|
|
||||||
// For JAX-RS
|
// For JAX-RS
|
||||||
protected MemberInfo() {
|
protected MemberInfo() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public MemberInfo(GroupMemberData groupMemberData) {
|
public MemberInfo(String member, Long joined, boolean isAdmin) {
|
||||||
this.member = groupMemberData.getMember();
|
this.member = member;
|
||||||
this.joined = groupMemberData.getJoined();
|
this.joined = joined;
|
||||||
|
this.isAdmin = isAdmin ? true : null; // null so field is not displayed by API
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,14 +38,13 @@ public class GroupWithMemberInfo {
|
|||||||
public List<MemberInfo> groupMembers;
|
public List<MemberInfo> groupMembers;
|
||||||
|
|
||||||
// For JAX-RS
|
// For JAX-RS
|
||||||
protected GroupWithMemberInfo() {
|
protected GroupMembers() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public GroupWithMemberInfo(GroupData groupData, List<String> groupAdminAddresses, List<MemberInfo> groupMembers, Integer memberCount) {
|
public GroupMembers(List<MemberInfo> groupMembers, Integer memberCount, Integer adminCount) {
|
||||||
this.groupData = groupData;
|
|
||||||
this.groupAdminAddresses = groupAdminAddresses;
|
|
||||||
this.groupMembers = groupMembers;
|
this.groupMembers = groupMembers;
|
||||||
this.memberCount = memberCount;
|
this.memberCount = memberCount;
|
||||||
|
this.adminCount = adminCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -1,21 +1,18 @@
|
|||||||
package org.qora.api.resource;
|
package org.qora.api.resource;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.media.ArraySchema;
|
|
||||||
import io.swagger.v3.oas.annotations.media.Content;
|
import io.swagger.v3.oas.annotations.media.Content;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.ws.rs.GET;
|
import javax.ws.rs.GET;
|
||||||
import javax.ws.rs.Path;
|
import javax.ws.rs.Path;
|
||||||
import javax.ws.rs.PathParam;
|
import javax.ws.rs.PathParam;
|
||||||
import javax.ws.rs.Produces;
|
import javax.ws.rs.Produces;
|
||||||
import javax.ws.rs.QueryParam;
|
|
||||||
import javax.ws.rs.core.Context;
|
import javax.ws.rs.core.Context;
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
|
|
||||||
@ -26,9 +23,7 @@ import org.qora.api.ApiException;
|
|||||||
import org.qora.api.ApiExceptionFactory;
|
import org.qora.api.ApiExceptionFactory;
|
||||||
import org.qora.asset.Asset;
|
import org.qora.asset.Asset;
|
||||||
import org.qora.crypto.Crypto;
|
import org.qora.crypto.Crypto;
|
||||||
import org.qora.data.account.AccountBalanceData;
|
|
||||||
import org.qora.data.account.AccountData;
|
import org.qora.data.account.AccountData;
|
||||||
import org.qora.data.asset.OrderData;
|
|
||||||
import org.qora.repository.DataException;
|
import org.qora.repository.DataException;
|
||||||
import org.qora.repository.Repository;
|
import org.qora.repository.Repository;
|
||||||
import org.qora.repository.RepositoryManager;
|
import org.qora.repository.RepositoryManager;
|
||||||
@ -145,59 +140,6 @@ public class AddressesResource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
|
||||||
@Path("/assetbalance/{assetid}/{address}")
|
|
||||||
@Operation(
|
|
||||||
summary = "Asset-specific balance request",
|
|
||||||
description = "Returns the confirmed balance of the given address for the given asset key.",
|
|
||||||
responses = {
|
|
||||||
@ApiResponse(
|
|
||||||
description = "the balance",
|
|
||||||
content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string", format = "number"))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
@ApiErrors({ApiError.INVALID_ADDRESS, ApiError.REPOSITORY_ISSUE})
|
|
||||||
public BigDecimal getAssetBalance(@PathParam("assetid") long assetid, @PathParam("address") String address) {
|
|
||||||
if (!Crypto.isValidAddress(address))
|
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS);
|
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
Account account = new Account(repository, address);
|
|
||||||
return account.getConfirmedBalance(assetid);
|
|
||||||
} catch (ApiException e) {
|
|
||||||
throw e;
|
|
||||||
} catch (DataException e) {
|
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@GET
|
|
||||||
@Path("/assets/{address}")
|
|
||||||
@Operation(
|
|
||||||
summary = "All assets owned by this address",
|
|
||||||
description = "Returns the list of assets for this address, with balances.",
|
|
||||||
responses = {
|
|
||||||
@ApiResponse(
|
|
||||||
description = "the list of assets",
|
|
||||||
content = @Content(array = @ArraySchema(schema = @Schema(implementation = AccountBalanceData.class)))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
@ApiErrors({ApiError.INVALID_ADDRESS, ApiError.REPOSITORY_ISSUE})
|
|
||||||
public List<AccountBalanceData> getAssets(@PathParam("address") String address) {
|
|
||||||
if (!Crypto.isValidAddress(address))
|
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS);
|
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
return repository.getAccountRepository().getAllBalances(address);
|
|
||||||
} catch (ApiException e) {
|
|
||||||
throw e;
|
|
||||||
} catch (DataException e) {
|
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path("/balance/{address}/{confirmations}")
|
@Path("/balance/{address}/{confirmations}")
|
||||||
@Operation(
|
@Operation(
|
||||||
@ -283,38 +225,4 @@ public class AddressesResource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
|
||||||
@Path("/assetorders/{address}")
|
|
||||||
@Operation(
|
|
||||||
summary = "Asset orders created by this address",
|
|
||||||
responses = {
|
|
||||||
@ApiResponse(
|
|
||||||
description = "Asset orders",
|
|
||||||
content = @Content(array = @ArraySchema(schema = @Schema(implementation = OrderData.class)))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
@ApiErrors({ApiError.INVALID_ADDRESS, ApiError.ADDRESS_NO_EXISTS, ApiError.REPOSITORY_ISSUE})
|
|
||||||
public List<OrderData> getAssetOrders(@PathParam("address") String address, @QueryParam("includeClosed") boolean includeClosed, @QueryParam("includeFulfilled") boolean includeFulfilled) {
|
|
||||||
if (!Crypto.isValidAddress(address))
|
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS);
|
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
AccountData accountData = repository.getAccountRepository().getAccount(address);
|
|
||||||
|
|
||||||
if (accountData == null)
|
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.ADDRESS_NO_EXISTS);
|
|
||||||
|
|
||||||
byte[] publicKey = accountData.getPublicKey();
|
|
||||||
if (publicKey == null)
|
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.ADDRESS_NO_EXISTS);
|
|
||||||
|
|
||||||
return repository.getAssetRepository().getAccountsOrders(publicKey, includeClosed, includeFulfilled);
|
|
||||||
} catch (ApiException e) {
|
|
||||||
throw e;
|
|
||||||
} catch (DataException e) {
|
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -28,16 +28,13 @@ public class AdminResource {
|
|||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path("/unused")
|
@Path("/unused")
|
||||||
@Parameter(in = ParameterIn.PATH, name = "blockSignature", description = "Block signature", schema = @Schema(type = "string", format = "byte"), example = "very_long_block_signature_in_base58")
|
@Parameter(in = ParameterIn.PATH, name = "assetid", description = "Asset ID, 0 is native coin", schema = @Schema(type = "integer"))
|
||||||
@Parameter(in = ParameterIn.PATH, name = "assetId", description = "Asset ID, 0 is native coin", schema = @Schema(type = "string", format = "byte"))
|
@Parameter(in = ParameterIn.PATH, name = "otherassetid", description = "Asset ID, 0 is native coin", schema = @Schema(type = "integer"))
|
||||||
@Parameter(in = ParameterIn.PATH, name = "otherAssetId", description = "Asset ID, 0 is native coin", schema = @Schema(type = "string", format = "byte"))
|
|
||||||
@Parameter(in = ParameterIn.PATH, name = "address", description = "an account address", example = "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v")
|
@Parameter(in = ParameterIn.PATH, name = "address", description = "an account address", example = "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v")
|
||||||
@Parameter(in = ParameterIn.QUERY, name = "count", description = "Maximum number of entries to return, 0 means none", schema = @Schema(type = "integer", defaultValue = "20"))
|
@Parameter(in = ParameterIn.QUERY, name = "count", description = "Maximum number of entries to return, 0 means none", schema = @Schema(type = "integer", defaultValue = "20"))
|
||||||
@Parameter(in = ParameterIn.QUERY, name = "limit", description = "Maximum number of entries to return, 0 means unlimited", schema = @Schema(type = "integer", defaultValue = "20"))
|
@Parameter(in = ParameterIn.QUERY, name = "limit", description = "Maximum number of entries to return, 0 means unlimited", schema = @Schema(type = "integer", defaultValue = "20"))
|
||||||
@Parameter(in = ParameterIn.QUERY, name = "offset", description = "Starting entry in results, 0 is first entry", schema = @Schema(type = "integer"))
|
@Parameter(in = ParameterIn.QUERY, name = "offset", description = "Starting entry in results, 0 is first entry", schema = @Schema(type = "integer"))
|
||||||
@Parameter(in = ParameterIn.QUERY, name = "includeTransactions", description = "Include associated transactions in results", schema = @Schema(type = "boolean"))
|
@Parameter(in = ParameterIn.QUERY, name = "reverse", description = "Reverse results", schema = @Schema(type = "boolean"))
|
||||||
@Parameter(in = ParameterIn.QUERY, name = "includeHolders", description = "Include asset holders in results", schema = @Schema(type = "boolean"))
|
|
||||||
@Parameter(in = ParameterIn.QUERY, name = "queryAssetId", description = "Asset ID, 0 is native coin", schema = @Schema(type = "string", format = "byte"))
|
|
||||||
public String globalParameters() {
|
public String globalParameters() {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ import io.swagger.v3.oas.annotations.parameters.RequestBody;
|
|||||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -22,13 +23,15 @@ import javax.ws.rs.QueryParam;
|
|||||||
import javax.ws.rs.core.Context;
|
import javax.ws.rs.core.Context;
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
|
|
||||||
|
import org.qora.account.Account;
|
||||||
import org.qora.api.ApiError;
|
import org.qora.api.ApiError;
|
||||||
import org.qora.api.ApiErrors;
|
import org.qora.api.ApiErrors;
|
||||||
|
import org.qora.api.ApiException;
|
||||||
import org.qora.api.ApiExceptionFactory;
|
import org.qora.api.ApiExceptionFactory;
|
||||||
import org.qora.api.model.AssetWithHolders;
|
|
||||||
import org.qora.api.model.OrderWithTrades;
|
|
||||||
import org.qora.api.model.TradeWithOrderInfo;
|
import org.qora.api.model.TradeWithOrderInfo;
|
||||||
|
import org.qora.crypto.Crypto;
|
||||||
import org.qora.data.account.AccountBalanceData;
|
import org.qora.data.account.AccountBalanceData;
|
||||||
|
import org.qora.data.account.AccountData;
|
||||||
import org.qora.data.asset.AssetData;
|
import org.qora.data.asset.AssetData;
|
||||||
import org.qora.data.asset.OrderData;
|
import org.qora.data.asset.OrderData;
|
||||||
import org.qora.data.asset.TradeData;
|
import org.qora.data.asset.TradeData;
|
||||||
@ -47,8 +50,12 @@ import org.qora.transform.transaction.IssueAssetTransactionTransformer;
|
|||||||
import org.qora.utils.Base58;
|
import org.qora.utils.Base58;
|
||||||
|
|
||||||
@Path("/assets")
|
@Path("/assets")
|
||||||
@Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN})
|
@Produces({
|
||||||
@Tag(name = "Assets")
|
MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN
|
||||||
|
})
|
||||||
|
@Tag(
|
||||||
|
name = "Assets"
|
||||||
|
)
|
||||||
public class AssetsResource {
|
public class AssetsResource {
|
||||||
|
|
||||||
@Context
|
@Context
|
||||||
@ -60,21 +67,28 @@ public class AssetsResource {
|
|||||||
responses = {
|
responses = {
|
||||||
@ApiResponse(
|
@ApiResponse(
|
||||||
description = "asset info",
|
description = "asset info",
|
||||||
content = @Content(array = @ArraySchema(schema = @Schema(implementation = AssetData.class)))
|
content = @Content(
|
||||||
|
array = @ArraySchema(
|
||||||
|
schema = @Schema(
|
||||||
|
implementation = AssetData.class
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiErrors({ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({
|
||||||
public List<AssetData> getAllAssets(@Parameter(ref = "limit") @QueryParam("limit") int limit, @Parameter(ref = "offset") @QueryParam("offset") int offset) {
|
ApiError.REPOSITORY_ISSUE
|
||||||
|
})
|
||||||
|
public List<AssetData> getAllAssets(@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()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
List<AssetData> assets = repository.getAssetRepository().getAllAssets();
|
return repository.getAssetRepository().getAllAssets(limit, offset, reverse);
|
||||||
|
|
||||||
// Pagination would take effect here (or as part of the repository access)
|
|
||||||
int fromIndex = Integer.min(offset, assets.size());
|
|
||||||
int toIndex = limit == 0 ? assets.size() : Integer.min(fromIndex + limit, assets.size());
|
|
||||||
assets = assets.subList(fromIndex, toIndex);
|
|
||||||
|
|
||||||
return assets;
|
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||||
}
|
}
|
||||||
@ -88,12 +102,18 @@ public class AssetsResource {
|
|||||||
responses = {
|
responses = {
|
||||||
@ApiResponse(
|
@ApiResponse(
|
||||||
description = "asset info",
|
description = "asset info",
|
||||||
content = @Content(array = @ArraySchema(schema = @Schema(implementation = AssetWithHolders.class)))
|
content = @Content(
|
||||||
|
schema = @Schema(
|
||||||
|
implementation = AssetData.class
|
||||||
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiErrors({ApiError.INVALID_CRITERIA, ApiError.INVALID_ASSET_ID, ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({
|
||||||
public AssetWithHolders getAssetInfo(@QueryParam("assetId") Integer assetId, @QueryParam("assetName") String assetName, @Parameter(ref = "includeHolders") @QueryParam("includeHolders") boolean includeHolders) {
|
ApiError.INVALID_CRITERIA, ApiError.INVALID_ASSET_ID, ApiError.REPOSITORY_ISSUE
|
||||||
|
})
|
||||||
|
public AssetData getAssetInfo(@QueryParam("assetId") Integer assetId, @QueryParam("assetName") String assetName) {
|
||||||
if (assetId == null && (assetName == null || assetName.isEmpty()))
|
if (assetId == null && (assetName == null || assetName.isEmpty()))
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
|
||||||
|
|
||||||
@ -108,31 +128,81 @@ public class AssetsResource {
|
|||||||
if (assetData == null)
|
if (assetData == null)
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ASSET_ID);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ASSET_ID);
|
||||||
|
|
||||||
List<AccountBalanceData> holders = null;
|
return assetData;
|
||||||
if (includeHolders)
|
|
||||||
holders = repository.getAccountRepository().getAssetBalances(assetData.getAssetId());
|
|
||||||
|
|
||||||
return new AssetWithHolders(assetData, holders);
|
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path("/orderbook/{assetId}/{otherAssetId}")
|
@Path("/holders/{assetid}")
|
||||||
|
@Operation(
|
||||||
|
summary = "List holders of an asset",
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(
|
||||||
|
description = "asset holders",
|
||||||
|
content = @Content(
|
||||||
|
array = @ArraySchema(
|
||||||
|
schema = @Schema(
|
||||||
|
implementation = AccountBalanceData.class
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@ApiErrors({
|
||||||
|
ApiError.INVALID_CRITERIA, ApiError.INVALID_ASSET_ID, ApiError.REPOSITORY_ISSUE
|
||||||
|
})
|
||||||
|
public List<AccountBalanceData> getAssetHolders(@PathParam("assetid") int assetId, @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()) {
|
||||||
|
if (!repository.getAssetRepository().assetExists(assetId))
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ASSET_ID);
|
||||||
|
|
||||||
|
return repository.getAccountRepository().getAssetBalances(assetId, limit, offset, reverse);
|
||||||
|
} catch (DataException e) {
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("/orderbook/{assetid}/{otherassetid}")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Asset order book",
|
summary = "Asset order book",
|
||||||
description = "Returns open orders, offering {assetId} for {otherAssetId} in return.",
|
description = "Returns open orders, offering {assetid} for {otherassetid} in return.",
|
||||||
responses = {
|
responses = {
|
||||||
@ApiResponse(
|
@ApiResponse(
|
||||||
description = "asset orders",
|
description = "asset orders",
|
||||||
content = @Content(array = @ArraySchema(schema = @Schema(implementation = OrderData.class)))
|
content = @Content(
|
||||||
|
array = @ArraySchema(
|
||||||
|
schema = @Schema(
|
||||||
|
implementation = OrderData.class
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiErrors({ApiError.INVALID_ASSET_ID, ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({
|
||||||
public List<OrderData> getAssetOrders(@Parameter(ref = "assetId") @PathParam("assetId") int assetId, @Parameter(ref = "otherAssetId") @PathParam("otherAssetId") int otherAssetId,
|
ApiError.INVALID_ASSET_ID, ApiError.REPOSITORY_ISSUE
|
||||||
@Parameter(ref = "limit") @QueryParam("limit") int limit, @Parameter(ref = "offset") @QueryParam("offset") int offset) {
|
})
|
||||||
|
public List<OrderData> getAssetOrders(@Parameter(
|
||||||
|
ref = "assetid"
|
||||||
|
) @PathParam("assetid") int assetId, @Parameter(
|
||||||
|
ref = "otherassetid"
|
||||||
|
) @PathParam("otherassetid") int otherAssetId, @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()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
if (!repository.getAssetRepository().assetExists(assetId))
|
if (!repository.getAssetRepository().assetExists(assetId))
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ASSET_ID);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ASSET_ID);
|
||||||
@ -140,36 +210,45 @@ public class AssetsResource {
|
|||||||
if (!repository.getAssetRepository().assetExists(otherAssetId))
|
if (!repository.getAssetRepository().assetExists(otherAssetId))
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ASSET_ID);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ASSET_ID);
|
||||||
|
|
||||||
List<OrderData> orders = repository.getAssetRepository().getOpenOrders(assetId, otherAssetId);
|
return repository.getAssetRepository().getOpenOrders(assetId, otherAssetId, limit, offset, reverse);
|
||||||
|
|
||||||
// Pagination would take effect here (or as part of the repository access)
|
|
||||||
int fromIndex = Integer.min(offset, orders.size());
|
|
||||||
int toIndex = limit == 0 ? orders.size() : Integer.min(fromIndex + limit, orders.size());
|
|
||||||
orders = orders.subList(fromIndex, toIndex);
|
|
||||||
|
|
||||||
return orders;
|
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path("/trades/{assetId}/{otherAssetId}")
|
@Path("/trades/{assetid}/{otherassetid}")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Asset trades",
|
summary = "Asset trades",
|
||||||
description = "Returns successful trades of {assetId} for {otherAssetId}.<br>" +
|
description = "Returns successful trades of {assetid} for {otherassetid}.<br>" + "Does NOT include trades of {otherassetid} for {assetid}!<br>"
|
||||||
"Does NOT include trades of {otherAssetId} for {assetId}!<br>" +
|
+ "\"Initiating\" order is the order that caused the actual trade by matching up with the \"target\" order.",
|
||||||
"\"Initiating\" order is the order that caused the actual trade by matching up with the \"target\" order.",
|
|
||||||
responses = {
|
responses = {
|
||||||
@ApiResponse(
|
@ApiResponse(
|
||||||
description = "asset trades",
|
description = "asset trades",
|
||||||
content = @Content(array = @ArraySchema(schema = @Schema(implementation = TradeWithOrderInfo.class)))
|
content = @Content(
|
||||||
|
array = @ArraySchema(
|
||||||
|
schema = @Schema(
|
||||||
|
implementation = TradeWithOrderInfo.class
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiErrors({ApiError.INVALID_ASSET_ID, ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({
|
||||||
public List<TradeWithOrderInfo> getAssetTrades(@Parameter(ref = "assetId") @PathParam("assetId") int assetId, @Parameter(ref = "otherAssetId") @PathParam("otherAssetId") int otherAssetId,
|
ApiError.INVALID_ASSET_ID, ApiError.REPOSITORY_ISSUE
|
||||||
@Parameter(ref = "limit") @QueryParam("limit") int limit, @Parameter(ref = "offset") @QueryParam("offset") int offset) {
|
})
|
||||||
|
public List<TradeWithOrderInfo> getAssetTrades(@Parameter(
|
||||||
|
ref = "assetid"
|
||||||
|
) @PathParam("assetid") int assetId, @Parameter(
|
||||||
|
ref = "otherassetid"
|
||||||
|
) @PathParam("otherassetid") int otherAssetId, @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()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
if (!repository.getAssetRepository().assetExists(assetId))
|
if (!repository.getAssetRepository().assetExists(assetId))
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ASSET_ID);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ASSET_ID);
|
||||||
@ -177,12 +256,7 @@ public class AssetsResource {
|
|||||||
if (!repository.getAssetRepository().assetExists(otherAssetId))
|
if (!repository.getAssetRepository().assetExists(otherAssetId))
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ASSET_ID);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ASSET_ID);
|
||||||
|
|
||||||
List<TradeData> trades = repository.getAssetRepository().getTrades(assetId, otherAssetId);
|
List<TradeData> trades = repository.getAssetRepository().getTrades(assetId, otherAssetId, limit, offset, reverse);
|
||||||
|
|
||||||
// Pagination would take effect here (or as part of the repository access)
|
|
||||||
int fromIndex = Integer.min(offset, trades.size());
|
|
||||||
int toIndex = limit == 0 ? trades.size() : Integer.min(fromIndex + limit, trades.size());
|
|
||||||
trades = trades.subList(fromIndex, toIndex);
|
|
||||||
|
|
||||||
// Expanding remaining entries
|
// Expanding remaining entries
|
||||||
List<TradeWithOrderInfo> fullTrades = new ArrayList<>();
|
List<TradeWithOrderInfo> fullTrades = new ArrayList<>();
|
||||||
@ -199,19 +273,25 @@ public class AssetsResource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path("/order/{orderId}")
|
@Path("/order/{orderid}")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Fetch asset order",
|
summary = "Fetch asset order",
|
||||||
description = "Returns asset order info.",
|
description = "Returns asset order info.",
|
||||||
responses = {
|
responses = {
|
||||||
@ApiResponse(
|
@ApiResponse(
|
||||||
description = "asset order",
|
description = "asset order",
|
||||||
content = @Content(schema = @Schema(implementation = OrderData.class))
|
content = @Content(
|
||||||
|
schema = @Schema(
|
||||||
|
implementation = OrderData.class
|
||||||
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiErrors({ApiError.INVALID_ORDER_ID, ApiError.ORDER_NO_EXISTS, ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({
|
||||||
public OrderWithTrades getAssetOrder(@PathParam("orderId") String orderId58) {
|
ApiError.INVALID_ORDER_ID, ApiError.ORDER_NO_EXISTS, ApiError.REPOSITORY_ISSUE
|
||||||
|
})
|
||||||
|
public OrderData getAssetOrder(@PathParam("orderid") String orderId58) {
|
||||||
// Decode orderID
|
// Decode orderID
|
||||||
byte[] orderId;
|
byte[] orderId;
|
||||||
try {
|
try {
|
||||||
@ -225,9 +305,178 @@ public class AssetsResource {
|
|||||||
if (orderData == null)
|
if (orderData == null)
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.ORDER_NO_EXISTS);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.ORDER_NO_EXISTS);
|
||||||
|
|
||||||
List<TradeData> trades = repository.getAssetRepository().getOrdersTrades(orderId);
|
return orderData;
|
||||||
|
} catch (DataException e) {
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return new OrderWithTrades(orderData, trades);
|
@GET
|
||||||
|
@Path("/order/{orderid}/trades")
|
||||||
|
@Operation(
|
||||||
|
summary = "Fetch asset order's matching trades",
|
||||||
|
description = "Returns asset order trades",
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(
|
||||||
|
description = "asset trades",
|
||||||
|
content = @Content(
|
||||||
|
array = @ArraySchema(
|
||||||
|
schema = @Schema(
|
||||||
|
implementation = TradeData.class
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@ApiErrors({
|
||||||
|
ApiError.INVALID_ORDER_ID, ApiError.ORDER_NO_EXISTS, ApiError.REPOSITORY_ISSUE
|
||||||
|
})
|
||||||
|
public List<TradeData> getAssetOrderTrades(@PathParam("orderid") String orderId58, @Parameter(
|
||||||
|
ref = "limit"
|
||||||
|
) @QueryParam("limit") Integer limit, @Parameter(
|
||||||
|
ref = "offset"
|
||||||
|
) @QueryParam("offset") Integer offset, @Parameter(
|
||||||
|
ref = "reverse"
|
||||||
|
) @QueryParam("reverse") Boolean reverse) {
|
||||||
|
// Decode orderID
|
||||||
|
byte[] orderId;
|
||||||
|
try {
|
||||||
|
orderId = Base58.decode(orderId58);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ORDER_ID, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
|
OrderData orderData = repository.getAssetRepository().fromOrderId(orderId);
|
||||||
|
if (orderData == null)
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.ORDER_NO_EXISTS);
|
||||||
|
|
||||||
|
return repository.getAssetRepository().getOrdersTrades(orderId, limit, offset, reverse);
|
||||||
|
} catch (DataException e) {
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("/address/{address}")
|
||||||
|
@Operation(
|
||||||
|
summary = "All assets owned by this address",
|
||||||
|
description = "Returns the list of assets for this address, with balances.",
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(
|
||||||
|
description = "the list of assets",
|
||||||
|
content = @Content(
|
||||||
|
array = @ArraySchema(
|
||||||
|
schema = @Schema(
|
||||||
|
implementation = AccountBalanceData.class
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@ApiErrors({
|
||||||
|
ApiError.INVALID_ADDRESS, ApiError.REPOSITORY_ISSUE
|
||||||
|
})
|
||||||
|
public List<AccountBalanceData> getAssets(@PathParam("address") String address, @Parameter(
|
||||||
|
ref = "limit"
|
||||||
|
) @QueryParam("limit") Integer limit, @Parameter(
|
||||||
|
ref = "offset"
|
||||||
|
) @QueryParam("offset") Integer offset, @Parameter(
|
||||||
|
ref = "reverse"
|
||||||
|
) @QueryParam("reverse") Boolean reverse) {
|
||||||
|
if (!Crypto.isValidAddress(address))
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS);
|
||||||
|
|
||||||
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
|
return repository.getAccountRepository().getAllBalances(address, limit, offset, reverse);
|
||||||
|
} catch (ApiException e) {
|
||||||
|
throw e;
|
||||||
|
} catch (DataException e) {
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("/balance/{assetid}/{address}")
|
||||||
|
@Operation(
|
||||||
|
summary = "Asset-specific balance request",
|
||||||
|
description = "Returns the confirmed balance of the given address for the given asset key.",
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(
|
||||||
|
description = "the balance",
|
||||||
|
content = @Content(
|
||||||
|
mediaType = MediaType.TEXT_PLAIN,
|
||||||
|
schema = @Schema(
|
||||||
|
type = "string",
|
||||||
|
format = "number"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@ApiErrors({
|
||||||
|
ApiError.INVALID_ADDRESS, ApiError.REPOSITORY_ISSUE
|
||||||
|
})
|
||||||
|
public BigDecimal getAssetBalance(@PathParam("assetid") long assetid, @PathParam("address") String address) {
|
||||||
|
if (!Crypto.isValidAddress(address))
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS);
|
||||||
|
|
||||||
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
|
Account account = new Account(repository, address);
|
||||||
|
return account.getConfirmedBalance(assetid);
|
||||||
|
} catch (ApiException e) {
|
||||||
|
throw e;
|
||||||
|
} catch (DataException e) {
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("/orders/{address}")
|
||||||
|
@Operation(
|
||||||
|
summary = "Asset orders created by this address",
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(
|
||||||
|
description = "Asset orders",
|
||||||
|
content = @Content(
|
||||||
|
array = @ArraySchema(
|
||||||
|
schema = @Schema(
|
||||||
|
implementation = OrderData.class
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@ApiErrors({
|
||||||
|
ApiError.INVALID_ADDRESS, ApiError.ADDRESS_NO_EXISTS, ApiError.REPOSITORY_ISSUE
|
||||||
|
})
|
||||||
|
public List<OrderData> getAssetOrders(@PathParam("address") String address, @QueryParam("includeClosed") boolean includeClosed,
|
||||||
|
@QueryParam("includeFulfilled") boolean includeFulfilled, @Parameter(
|
||||||
|
ref = "limit"
|
||||||
|
) @QueryParam("limit") Integer limit, @Parameter(
|
||||||
|
ref = "offset"
|
||||||
|
) @QueryParam("offset") Integer offset, @Parameter(
|
||||||
|
ref = "reverse"
|
||||||
|
) @QueryParam("reverse") Boolean reverse) {
|
||||||
|
if (!Crypto.isValidAddress(address))
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS);
|
||||||
|
|
||||||
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
|
AccountData accountData = repository.getAccountRepository().getAccount(address);
|
||||||
|
|
||||||
|
if (accountData == null)
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.ADDRESS_NO_EXISTS);
|
||||||
|
|
||||||
|
byte[] publicKey = accountData.getPublicKey();
|
||||||
|
if (publicKey == null)
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.ADDRESS_NO_EXISTS);
|
||||||
|
|
||||||
|
return repository.getAssetRepository().getAccountsOrders(publicKey, includeClosed, includeFulfilled, limit, offset, reverse);
|
||||||
|
} catch (ApiException e) {
|
||||||
|
throw e;
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||||
}
|
}
|
||||||
@ -241,7 +490,9 @@ public class AssetsResource {
|
|||||||
required = true,
|
required = true,
|
||||||
content = @Content(
|
content = @Content(
|
||||||
mediaType = MediaType.APPLICATION_JSON,
|
mediaType = MediaType.APPLICATION_JSON,
|
||||||
schema = @Schema(implementation = CancelAssetOrderTransactionData.class)
|
schema = @Schema(
|
||||||
|
implementation = CancelAssetOrderTransactionData.class
|
||||||
|
)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
responses = {
|
responses = {
|
||||||
@ -256,7 +507,9 @@ public class AssetsResource {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiErrors({ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE, ApiError.TRANSACTION_INVALID})
|
@ApiErrors({
|
||||||
|
ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE, ApiError.TRANSACTION_INVALID
|
||||||
|
})
|
||||||
public String cancelOrder(CancelAssetOrderTransactionData transactionData) {
|
public String cancelOrder(CancelAssetOrderTransactionData transactionData) {
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
Transaction transaction = Transaction.fromData(repository, transactionData);
|
Transaction transaction = Transaction.fromData(repository, transactionData);
|
||||||
@ -282,7 +535,9 @@ public class AssetsResource {
|
|||||||
required = true,
|
required = true,
|
||||||
content = @Content(
|
content = @Content(
|
||||||
mediaType = MediaType.APPLICATION_JSON,
|
mediaType = MediaType.APPLICATION_JSON,
|
||||||
schema = @Schema(implementation = IssueAssetTransactionData.class)
|
schema = @Schema(
|
||||||
|
implementation = IssueAssetTransactionData.class
|
||||||
|
)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
responses = {
|
responses = {
|
||||||
@ -297,7 +552,9 @@ public class AssetsResource {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiErrors({ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE, ApiError.TRANSACTION_INVALID})
|
@ApiErrors({
|
||||||
|
ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE, ApiError.TRANSACTION_INVALID
|
||||||
|
})
|
||||||
public String issueAsset(IssueAssetTransactionData transactionData) {
|
public String issueAsset(IssueAssetTransactionData transactionData) {
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
Transaction transaction = Transaction.fromData(repository, transactionData);
|
Transaction transaction = Transaction.fromData(repository, transactionData);
|
||||||
@ -323,7 +580,9 @@ public class AssetsResource {
|
|||||||
required = true,
|
required = true,
|
||||||
content = @Content(
|
content = @Content(
|
||||||
mediaType = MediaType.APPLICATION_JSON,
|
mediaType = MediaType.APPLICATION_JSON,
|
||||||
schema = @Schema(implementation = CreateAssetOrderTransactionData.class)
|
schema = @Schema(
|
||||||
|
implementation = CreateAssetOrderTransactionData.class
|
||||||
|
)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
responses = {
|
responses = {
|
||||||
@ -338,7 +597,9 @@ public class AssetsResource {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiErrors({ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE, ApiError.TRANSACTION_INVALID})
|
@ApiErrors({
|
||||||
|
ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE, ApiError.TRANSACTION_INVALID
|
||||||
|
})
|
||||||
public String createOrder(CreateAssetOrderTransactionData transactionData) {
|
public String createOrder(CreateAssetOrderTransactionData transactionData) {
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
Transaction transaction = Transaction.fromData(repository, transactionData);
|
Transaction transaction = Transaction.fromData(repository, transactionData);
|
||||||
|
@ -2,6 +2,7 @@ package org.qora.api.resource;
|
|||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.media.ArraySchema;
|
||||||
import io.swagger.v3.oas.annotations.media.Content;
|
import io.swagger.v3.oas.annotations.media.Content;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||||
@ -10,7 +11,6 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
|||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
@ -26,7 +26,6 @@ import org.qora.api.ApiError;
|
|||||||
import org.qora.api.ApiErrors;
|
import org.qora.api.ApiErrors;
|
||||||
import org.qora.api.ApiException;
|
import org.qora.api.ApiException;
|
||||||
import org.qora.api.ApiExceptionFactory;
|
import org.qora.api.ApiExceptionFactory;
|
||||||
import org.qora.api.model.BlockWithTransactions;
|
|
||||||
import org.qora.block.Block;
|
import org.qora.block.Block;
|
||||||
import org.qora.data.block.BlockData;
|
import org.qora.data.block.BlockData;
|
||||||
import org.qora.data.transaction.TransactionData;
|
import org.qora.data.transaction.TransactionData;
|
||||||
@ -36,8 +35,12 @@ import org.qora.repository.RepositoryManager;
|
|||||||
import org.qora.utils.Base58;
|
import org.qora.utils.Base58;
|
||||||
|
|
||||||
@Path("/blocks")
|
@Path("/blocks")
|
||||||
@Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN})
|
@Produces({
|
||||||
@Tag(name = "Blocks")
|
MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN
|
||||||
|
})
|
||||||
|
@Tag(
|
||||||
|
name = "Blocks"
|
||||||
|
)
|
||||||
public class BlocksResource {
|
public class BlocksResource {
|
||||||
|
|
||||||
@Context
|
@Context
|
||||||
@ -53,14 +56,16 @@ public class BlocksResource {
|
|||||||
description = "the block",
|
description = "the block",
|
||||||
content = @Content(
|
content = @Content(
|
||||||
schema = @Schema(
|
schema = @Schema(
|
||||||
implementation = BlockWithTransactions.class
|
implementation = BlockData.class
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiErrors({ApiError.INVALID_SIGNATURE, ApiError.BLOCK_NO_EXISTS, ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({
|
||||||
public BlockWithTransactions getBlock(@PathParam("signature") String signature58, @Parameter(ref = "includeTransactions") @QueryParam("includeTransactions") boolean includeTransactions) {
|
ApiError.INVALID_SIGNATURE, ApiError.BLOCK_NO_EXISTS, ApiError.REPOSITORY_ISSUE
|
||||||
|
})
|
||||||
|
public BlockData getBlock(@PathParam("signature") String signature58) {
|
||||||
// Decode signature
|
// Decode signature
|
||||||
byte[] signature;
|
byte[] signature;
|
||||||
try {
|
try {
|
||||||
@ -70,8 +75,55 @@ public class BlocksResource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
BlockData blockData = repository.getBlockRepository().fromSignature(signature);
|
return repository.getBlockRepository().fromSignature(signature);
|
||||||
return packageBlockData(repository, blockData, includeTransactions);
|
} catch (ApiException e) {
|
||||||
|
throw e;
|
||||||
|
} catch (DataException e) {
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("/signature/{signature}/transactions")
|
||||||
|
@Operation(
|
||||||
|
summary = "Fetch block using base58 signature",
|
||||||
|
description = "Returns the block that matches the given signature",
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(
|
||||||
|
description = "the block",
|
||||||
|
content = @Content(
|
||||||
|
array = @ArraySchema(
|
||||||
|
schema = @Schema(
|
||||||
|
implementation = TransactionData.class
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@ApiErrors({
|
||||||
|
ApiError.INVALID_SIGNATURE, ApiError.BLOCK_NO_EXISTS, ApiError.REPOSITORY_ISSUE
|
||||||
|
})
|
||||||
|
public List<TransactionData> getBlockTransactions(@PathParam("signature") String signature58, @Parameter(
|
||||||
|
ref = "limit"
|
||||||
|
) @QueryParam("limit") Integer limit, @Parameter(
|
||||||
|
ref = "offset"
|
||||||
|
) @QueryParam("offset") Integer offset, @Parameter(
|
||||||
|
ref = "reverse"
|
||||||
|
) @QueryParam("reverse") Boolean reverse) {
|
||||||
|
// Decode signature
|
||||||
|
byte[] signature;
|
||||||
|
try {
|
||||||
|
signature = Base58.decode(signature58);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_SIGNATURE, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
|
if (repository.getBlockRepository().getHeightFromSignature(signature) == 0)
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.BLOCK_NO_EXISTS);
|
||||||
|
|
||||||
|
return repository.getBlockRepository().getTransactionsFromSignature(signature, limit, offset, reverse);
|
||||||
} catch (ApiException e) {
|
} catch (ApiException e) {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
@ -89,17 +141,18 @@ public class BlocksResource {
|
|||||||
description = "the block",
|
description = "the block",
|
||||||
content = @Content(
|
content = @Content(
|
||||||
schema = @Schema(
|
schema = @Schema(
|
||||||
implementation = BlockWithTransactions.class
|
implementation = BlockData.class
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiErrors({ApiError.BLOCK_NO_EXISTS, ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({
|
||||||
public BlockWithTransactions getFirstBlock(@Parameter(ref = "includeTransactions") @QueryParam("includeTransactions") boolean includeTransactions) {
|
ApiError.BLOCK_NO_EXISTS, ApiError.REPOSITORY_ISSUE
|
||||||
|
})
|
||||||
|
public BlockData getFirstBlock() {
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
BlockData blockData = repository.getBlockRepository().fromHeight(1);
|
return repository.getBlockRepository().fromHeight(1);
|
||||||
return packageBlockData(repository, blockData, includeTransactions);
|
|
||||||
} catch (ApiException e) {
|
} catch (ApiException e) {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
@ -117,17 +170,18 @@ public class BlocksResource {
|
|||||||
description = "the block",
|
description = "the block",
|
||||||
content = @Content(
|
content = @Content(
|
||||||
schema = @Schema(
|
schema = @Schema(
|
||||||
implementation = BlockWithTransactions.class
|
implementation = BlockData.class
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiErrors({ApiError.BLOCK_NO_EXISTS, ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({
|
||||||
public BlockWithTransactions getLastBlock(@Parameter(ref = "includeTransactions") @QueryParam("includeTransactions") boolean includeTransactions) {
|
ApiError.BLOCK_NO_EXISTS, ApiError.REPOSITORY_ISSUE
|
||||||
|
})
|
||||||
|
public BlockData getLastBlock() {
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
BlockData blockData = repository.getBlockRepository().getLastBlock();
|
return repository.getBlockRepository().getLastBlock();
|
||||||
return packageBlockData(repository, blockData, includeTransactions);
|
|
||||||
} catch (ApiException e) {
|
} catch (ApiException e) {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
@ -145,14 +199,16 @@ public class BlocksResource {
|
|||||||
description = "the block",
|
description = "the block",
|
||||||
content = @Content(
|
content = @Content(
|
||||||
schema = @Schema(
|
schema = @Schema(
|
||||||
implementation = BlockWithTransactions.class
|
implementation = BlockData.class
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiErrors({ApiError.INVALID_SIGNATURE, ApiError.BLOCK_NO_EXISTS, ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({
|
||||||
public BlockWithTransactions getChild(@PathParam("signature") String signature58, @Parameter(ref = "includeTransactions") @QueryParam("includeTransactions") boolean includeTransactions) {
|
ApiError.INVALID_SIGNATURE, ApiError.BLOCK_NO_EXISTS, ApiError.REPOSITORY_ISSUE
|
||||||
|
})
|
||||||
|
public BlockData getChild(@PathParam("signature") String signature58) {
|
||||||
// Decode signature
|
// Decode signature
|
||||||
byte[] signature;
|
byte[] signature;
|
||||||
try {
|
try {
|
||||||
@ -170,8 +226,11 @@ public class BlocksResource {
|
|||||||
|
|
||||||
BlockData childBlockData = repository.getBlockRepository().fromReference(signature);
|
BlockData childBlockData = repository.getBlockRepository().fromReference(signature);
|
||||||
|
|
||||||
// Checking child exists is handled by packageBlockData()
|
// Check child block exists
|
||||||
return packageBlockData(repository, childBlockData, includeTransactions);
|
if (childBlockData == null)
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.BLOCK_NO_EXISTS);
|
||||||
|
|
||||||
|
return childBlockData;
|
||||||
} catch (ApiException e) {
|
} catch (ApiException e) {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
@ -196,7 +255,9 @@ public class BlocksResource {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiErrors({ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({
|
||||||
|
ApiError.REPOSITORY_ISSUE
|
||||||
|
})
|
||||||
public BigDecimal getGeneratingBalance() {
|
public BigDecimal getGeneratingBalance() {
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
BlockData blockData = repository.getBlockRepository().getLastBlock();
|
BlockData blockData = repository.getBlockRepository().getLastBlock();
|
||||||
@ -226,7 +287,9 @@ public class BlocksResource {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiErrors({ApiError.INVALID_SIGNATURE, ApiError.BLOCK_NO_EXISTS, ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({
|
||||||
|
ApiError.INVALID_SIGNATURE, ApiError.BLOCK_NO_EXISTS, ApiError.REPOSITORY_ISSUE
|
||||||
|
})
|
||||||
public BigDecimal getGeneratingBalance(@PathParam("signature") String signature58) {
|
public BigDecimal getGeneratingBalance(@PathParam("signature") String signature58) {
|
||||||
// Decode signature
|
// Decode signature
|
||||||
byte[] signature;
|
byte[] signature;
|
||||||
@ -269,7 +332,9 @@ public class BlocksResource {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiErrors({ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({
|
||||||
|
ApiError.REPOSITORY_ISSUE
|
||||||
|
})
|
||||||
public long getTimePerBlock() {
|
public long getTimePerBlock() {
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
BlockData blockData = repository.getBlockRepository().getLastBlock();
|
BlockData blockData = repository.getBlockRepository().getLastBlock();
|
||||||
@ -319,7 +384,9 @@ public class BlocksResource {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiErrors({ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({
|
||||||
|
ApiError.REPOSITORY_ISSUE
|
||||||
|
})
|
||||||
public int getHeight() {
|
public int getHeight() {
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
return repository.getBlockRepository().getBlockchainHeight();
|
return repository.getBlockRepository().getBlockchainHeight();
|
||||||
@ -347,7 +414,9 @@ public class BlocksResource {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiErrors({ApiError.INVALID_SIGNATURE, ApiError.BLOCK_NO_EXISTS, ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({
|
||||||
|
ApiError.INVALID_SIGNATURE, ApiError.BLOCK_NO_EXISTS, ApiError.REPOSITORY_ISSUE
|
||||||
|
})
|
||||||
public int getHeight(@PathParam("signature") String signature58) {
|
public int getHeight(@PathParam("signature") String signature58) {
|
||||||
// Decode signature
|
// Decode signature
|
||||||
byte[] signature;
|
byte[] signature;
|
||||||
@ -382,17 +451,22 @@ public class BlocksResource {
|
|||||||
description = "the block",
|
description = "the block",
|
||||||
content = @Content(
|
content = @Content(
|
||||||
schema = @Schema(
|
schema = @Schema(
|
||||||
implementation = BlockWithTransactions.class
|
implementation = BlockData.class
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiErrors({ApiError.BLOCK_NO_EXISTS, ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({
|
||||||
public BlockWithTransactions getByHeight(@PathParam("height") int height, @Parameter(ref = "includeTransactions") @QueryParam("includeTransactions") boolean includeTransactions) {
|
ApiError.BLOCK_NO_EXISTS, ApiError.REPOSITORY_ISSUE
|
||||||
|
})
|
||||||
|
public BlockData getByHeight(@PathParam("height") int height) {
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
BlockData blockData = repository.getBlockRepository().fromHeight(height);
|
BlockData blockData = repository.getBlockRepository().fromHeight(height);
|
||||||
return packageBlockData(repository, blockData, includeTransactions);
|
if (blockData == null)
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.BLOCK_NO_EXISTS);
|
||||||
|
|
||||||
|
return blockData;
|
||||||
} catch (ApiException e) {
|
} catch (ApiException e) {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
@ -409,19 +483,23 @@ public class BlocksResource {
|
|||||||
@ApiResponse(
|
@ApiResponse(
|
||||||
description = "blocks",
|
description = "blocks",
|
||||||
content = @Content(
|
content = @Content(
|
||||||
|
array = @ArraySchema(
|
||||||
schema = @Schema(
|
schema = @Schema(
|
||||||
implementation = BlockWithTransactions.class
|
implementation = BlockData.class
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiErrors({ApiError.BLOCK_NO_EXISTS, ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({
|
||||||
public List<BlockWithTransactions> getBlockRange(@PathParam("height") int height, @Parameter(ref = "count") @QueryParam("count") int count) {
|
ApiError.BLOCK_NO_EXISTS, ApiError.REPOSITORY_ISSUE
|
||||||
boolean includeTransactions = false;
|
})
|
||||||
|
public List<BlockData> getBlockRange(@PathParam("height") int height, @Parameter(
|
||||||
|
ref = "count"
|
||||||
|
) @QueryParam("count") int count) {
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
List<BlockWithTransactions> blocks = new ArrayList<BlockWithTransactions>();
|
List<BlockData> blocks = new ArrayList<>();
|
||||||
|
|
||||||
for (/* count already set */; count > 0; --count, ++height) {
|
for (/* count already set */; count > 0; --count, ++height) {
|
||||||
BlockData blockData = repository.getBlockRepository().fromHeight(height);
|
BlockData blockData = repository.getBlockRepository().fromHeight(height);
|
||||||
@ -429,7 +507,7 @@ public class BlocksResource {
|
|||||||
// Run out of blocks!
|
// Run out of blocks!
|
||||||
break;
|
break;
|
||||||
|
|
||||||
blocks.add(packageBlockData(repository, blockData, includeTransactions));
|
blocks.add(blockData);
|
||||||
}
|
}
|
||||||
|
|
||||||
return blocks;
|
return blocks;
|
||||||
@ -440,29 +518,4 @@ public class BlocksResource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns block, optionally including transactions.
|
|
||||||
* <p>
|
|
||||||
* Throws ApiException using ApiError.BLOCK_NO_EXISTS if blockData is null.
|
|
||||||
*
|
|
||||||
* @param repository
|
|
||||||
* @param blockData
|
|
||||||
* @param includeTransactions
|
|
||||||
* @return packaged block, with optional transactions
|
|
||||||
* @throws DataException
|
|
||||||
* @throws ApiException ApiError.BLOCK_NO_EXISTS
|
|
||||||
*/
|
|
||||||
private BlockWithTransactions packageBlockData(Repository repository, BlockData blockData, boolean includeTransactions) throws DataException {
|
|
||||||
if (blockData == null)
|
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.BLOCK_NO_EXISTS);
|
|
||||||
|
|
||||||
List<TransactionData> transactions = null;
|
|
||||||
if (includeTransactions) {
|
|
||||||
Block block = new Block(repository, blockData);
|
|
||||||
transactions = block.getTransactions().stream().map(transaction -> transaction.getTransactionData()).collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
return new BlockWithTransactions(blockData, transactions);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
|||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.function.Predicate;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
@ -25,8 +26,8 @@ import javax.ws.rs.core.MediaType;
|
|||||||
import org.qora.api.ApiError;
|
import org.qora.api.ApiError;
|
||||||
import org.qora.api.ApiErrors;
|
import org.qora.api.ApiErrors;
|
||||||
import org.qora.api.ApiExceptionFactory;
|
import org.qora.api.ApiExceptionFactory;
|
||||||
import org.qora.api.model.GroupWithMemberInfo;
|
import org.qora.api.model.GroupMembers;
|
||||||
import org.qora.api.model.GroupWithMemberInfo.MemberInfo;
|
import org.qora.api.model.GroupMembers.MemberInfo;
|
||||||
import org.qora.crypto.Crypto;
|
import org.qora.crypto.Crypto;
|
||||||
import org.qora.data.group.GroupAdminData;
|
import org.qora.data.group.GroupAdminData;
|
||||||
import org.qora.data.group.GroupBanData;
|
import org.qora.data.group.GroupBanData;
|
||||||
@ -86,14 +87,15 @@ public class GroupsResource {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiErrors({ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({ApiError.REPOSITORY_ISSUE})
|
||||||
public List<GroupData> getAllGroups(@Parameter(ref = "limit") @QueryParam("limit") int limit, @Parameter(ref = "offset") @QueryParam("offset") int offset) {
|
public List<GroupData> getAllGroups(@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()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
List<GroupData> groups = repository.getGroupRepository().getAllGroups();
|
List<GroupData> groups = repository.getGroupRepository().getAllGroups(limit, offset, reverse);
|
||||||
|
|
||||||
// Pagination would take effect here (or as part of the repository access)
|
|
||||||
int fromIndex = Integer.min(offset, groups.size());
|
|
||||||
int toIndex = limit == 0 ? groups.size() : Integer.min(fromIndex + limit, groups.size());
|
|
||||||
groups = groups.subList(fromIndex, toIndex);
|
|
||||||
|
|
||||||
return groups;
|
return groups;
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
@ -158,7 +160,7 @@ public class GroupsResource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path("/{groupname}")
|
@Path("/{groupid}")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Info on group",
|
summary = "Info on group",
|
||||||
responses = {
|
responses = {
|
||||||
@ -166,45 +168,72 @@ public class GroupsResource {
|
|||||||
description = "group info",
|
description = "group info",
|
||||||
content = @Content(
|
content = @Content(
|
||||||
mediaType = MediaType.APPLICATION_JSON,
|
mediaType = MediaType.APPLICATION_JSON,
|
||||||
schema = @Schema(implementation = GroupWithMemberInfo.class)
|
schema = @Schema(implementation = GroupData.class)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiErrors({ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({ApiError.REPOSITORY_ISSUE})
|
||||||
public GroupWithMemberInfo getGroup(@PathParam("groupname") String groupName, @QueryParam("includeMembers") boolean includeMembers) {
|
public GroupData getGroupData(@PathParam("groupid") int groupId) {
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
GroupData groupData = repository.getGroupRepository().fromGroupName(groupName);
|
GroupData groupData = repository.getGroupRepository().fromGroupId(groupId);
|
||||||
if (groupData == null)
|
if (groupData == null)
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.GROUP_UNKNOWN);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.GROUP_UNKNOWN);
|
||||||
|
|
||||||
List<MemberInfo> members = null;
|
return groupData;
|
||||||
Integer memberCount = null;
|
|
||||||
|
|
||||||
if (includeMembers) {
|
|
||||||
List<GroupMemberData> groupMembers = repository.getGroupRepository().getGroupMembers(groupData.getGroupId());
|
|
||||||
|
|
||||||
// Convert to MemberInfo
|
|
||||||
members = groupMembers.stream().map(groupMemberData -> new MemberInfo(groupMemberData)).collect(Collectors.toList());
|
|
||||||
|
|
||||||
memberCount = members.size();
|
|
||||||
} else {
|
|
||||||
// Just count members instead
|
|
||||||
memberCount = repository.getGroupRepository().countGroupMembers(groupData.getGroupId());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Always include admins
|
|
||||||
List<GroupAdminData> groupAdmins = repository.getGroupRepository().getGroupAdmins(groupData.getGroupId());
|
|
||||||
|
|
||||||
// We only need admin addresses
|
|
||||||
List<String> adminAddresses = groupAdmins.stream().map(groupAdminData -> groupAdminData.getAdmin()).collect(Collectors.toList());
|
|
||||||
|
|
||||||
return new GroupWithMemberInfo(groupData, adminAddresses, members, memberCount);
|
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("/members/{groupid}")
|
||||||
|
@Operation(
|
||||||
|
summary = "List group members",
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(
|
||||||
|
description = "group info",
|
||||||
|
content = @Content(
|
||||||
|
mediaType = MediaType.APPLICATION_JSON,
|
||||||
|
schema = @Schema(implementation = GroupMembers.class)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@ApiErrors({ApiError.REPOSITORY_ISSUE})
|
||||||
|
public GroupMembers getGroup(@PathParam("groupid") int groupId, @QueryParam("onlyAdmins") Boolean onlyAdmins,
|
||||||
|
@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()) {
|
||||||
|
if (!repository.getGroupRepository().groupExists(groupId))
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.GROUP_UNKNOWN);
|
||||||
|
|
||||||
|
int adminCount = repository.getGroupRepository().countGroupAdmins(groupId);
|
||||||
|
int memberCount = repository.getGroupRepository().countGroupMembers(groupId);
|
||||||
|
|
||||||
|
if (onlyAdmins != null && onlyAdmins) {
|
||||||
|
// Shortcut
|
||||||
|
List<GroupAdminData> admins = repository.getGroupRepository().getGroupAdmins(groupId, limit, offset, reverse);
|
||||||
|
|
||||||
|
// Convert form
|
||||||
|
List<MemberInfo> membersInfo = admins.stream().map(admin -> new MemberInfo(admin.getAdmin(), null, true)).collect(Collectors.toList());
|
||||||
|
|
||||||
|
return new GroupMembers(membersInfo, memberCount, adminCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<GroupAdminData> admins = repository.getGroupRepository().getGroupAdmins(groupId, limit, offset, reverse);
|
||||||
|
|
||||||
|
List<GroupMemberData> members = repository.getGroupRepository().getGroupMembers(groupId, limit, offset, reverse);
|
||||||
|
|
||||||
|
// Convert form
|
||||||
|
Predicate<GroupMemberData> memberIsAdmin = member -> admins.stream().anyMatch(admin -> admin.getAdmin().equals(member.getMember()));
|
||||||
|
List<MemberInfo> membersInfo = members.stream().map(member -> new MemberInfo(member.getMember(), member.getJoined(), memberIsAdmin.test(member))).collect(Collectors.toList());
|
||||||
|
|
||||||
|
return new GroupMembers(membersInfo, memberCount, adminCount);
|
||||||
|
} catch (DataException e) {
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@Path("/create")
|
@Path("/create")
|
||||||
|
@ -68,15 +68,12 @@ public class NamesResource {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiErrors({ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({ApiError.REPOSITORY_ISSUE})
|
||||||
public List<NameSummary> getAllNames(@Parameter(ref = "limit") @QueryParam("limit") int limit, @Parameter(ref = "offset") @QueryParam("offset") int offset) {
|
public List<NameSummary> getAllNames(@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()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
List<NameData> names = repository.getNameRepository().getAllNames();
|
List<NameData> names = repository.getNameRepository().getAllNames(limit, offset, reverse);
|
||||||
|
|
||||||
// Pagination would take effect here (or as part of the repository access)
|
|
||||||
int fromIndex = Integer.min(offset, names.size());
|
|
||||||
int toIndex = limit == 0 ? names.size() : Integer.min(fromIndex + limit, names.size());
|
|
||||||
names = names.subList(fromIndex, toIndex);
|
|
||||||
|
|
||||||
|
// Convert to summary
|
||||||
return names.stream().map(nameData -> new NameSummary(nameData)).collect(Collectors.toList());
|
return names.stream().map(nameData -> new NameSummary(nameData)).collect(Collectors.toList());
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||||
@ -98,12 +95,13 @@ public class NamesResource {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiErrors({ApiError.INVALID_ADDRESS, ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({ApiError.INVALID_ADDRESS, ApiError.REPOSITORY_ISSUE})
|
||||||
public List<NameSummary> getNamesByAddress(@PathParam("address") String address) {
|
public List<NameSummary> getNamesByAddress(@PathParam("address") String address, @Parameter(ref = "limit") @QueryParam("limit") Integer limit, @Parameter(ref = "offset") @QueryParam("offset") Integer offset,
|
||||||
|
@Parameter(ref="reverse") @QueryParam("reverse") Boolean reverse) {
|
||||||
if (!Crypto.isValidAddress(address))
|
if (!Crypto.isValidAddress(address))
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS);
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
List<NameData> names = repository.getNameRepository().getNamesByOwner(address);
|
List<NameData> names = repository.getNameRepository().getNamesByOwner(address, limit, offset, reverse);
|
||||||
|
|
||||||
return names.stream().map(nameData -> new NameSummary(nameData)).collect(Collectors.toList());
|
return names.stream().map(nameData -> new NameSummary(nameData)).collect(Collectors.toList());
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
@ -365,16 +363,10 @@ public class NamesResource {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiErrors({ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({ApiError.REPOSITORY_ISSUE})
|
||||||
public List<NameData> getNamesForSale(@Parameter(ref = "limit") @QueryParam("limit") int limit, @Parameter(ref = "offset") @QueryParam("offset") int offset) {
|
public List<NameData> getNamesForSale(@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()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
List<NameData> names = repository.getNameRepository().getNamesForSale();
|
return repository.getNameRepository().getNamesForSale(limit, offset, reverse);
|
||||||
|
|
||||||
// Pagination would take effect here (or as part of the repository access)
|
|
||||||
int fromIndex = Integer.min(offset, names.size());
|
|
||||||
int toIndex = limit == 0 ? names.size() : Integer.min(fromIndex + limit, names.size());
|
|
||||||
names = names.subList(fromIndex, toIndex);
|
|
||||||
|
|
||||||
return names;
|
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||||
}
|
}
|
||||||
|
@ -28,8 +28,6 @@ import org.qora.api.ApiErrors;
|
|||||||
import org.qora.api.ApiException;
|
import org.qora.api.ApiException;
|
||||||
import org.qora.api.ApiExceptionFactory;
|
import org.qora.api.ApiExceptionFactory;
|
||||||
import org.qora.api.model.SimpleTransactionSignRequest;
|
import org.qora.api.model.SimpleTransactionSignRequest;
|
||||||
import org.qora.data.transaction.GenesisTransactionData;
|
|
||||||
import org.qora.data.transaction.PaymentTransactionData;
|
|
||||||
import org.qora.data.transaction.TransactionData;
|
import org.qora.data.transaction.TransactionData;
|
||||||
import org.qora.globalization.Translator;
|
import org.qora.globalization.Translator;
|
||||||
import org.qora.repository.DataException;
|
import org.qora.repository.DataException;
|
||||||
@ -45,8 +43,12 @@ import org.qora.utils.Base58;
|
|||||||
import com.google.common.primitives.Bytes;
|
import com.google.common.primitives.Bytes;
|
||||||
|
|
||||||
@Path("/transactions")
|
@Path("/transactions")
|
||||||
@Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN})
|
@Produces({
|
||||||
@Tag(name = "Transactions")
|
MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN
|
||||||
|
})
|
||||||
|
@Tag(
|
||||||
|
name = "Transactions"
|
||||||
|
)
|
||||||
public class TransactionsResource {
|
public class TransactionsResource {
|
||||||
|
|
||||||
@Context
|
@Context
|
||||||
@ -68,7 +70,9 @@ public class TransactionsResource {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiErrors({ApiError.INVALID_SIGNATURE, ApiError.TRANSACTION_NO_EXISTS, ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({
|
||||||
|
ApiError.INVALID_SIGNATURE, ApiError.TRANSACTION_NO_EXISTS, ApiError.REPOSITORY_ISSUE
|
||||||
|
})
|
||||||
public TransactionData getTransaction(@PathParam("signature") String signature58) {
|
public TransactionData getTransaction(@PathParam("signature") String signature58) {
|
||||||
byte[] signature;
|
byte[] signature;
|
||||||
try {
|
try {
|
||||||
@ -100,12 +104,16 @@ public class TransactionsResource {
|
|||||||
description = "raw transaction encoded in Base58",
|
description = "raw transaction encoded in Base58",
|
||||||
content = @Content(
|
content = @Content(
|
||||||
mediaType = MediaType.TEXT_PLAIN,
|
mediaType = MediaType.TEXT_PLAIN,
|
||||||
schema = @Schema(type = "string")
|
schema = @Schema(
|
||||||
|
type = "string"
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiErrors({ApiError.INVALID_SIGNATURE, ApiError.TRANSACTION_NO_EXISTS, ApiError.REPOSITORY_ISSUE, ApiError.TRANSFORMATION_ERROR})
|
@ApiErrors({
|
||||||
|
ApiError.INVALID_SIGNATURE, ApiError.TRANSACTION_NO_EXISTS, ApiError.REPOSITORY_ISSUE, ApiError.TRANSFORMATION_ERROR
|
||||||
|
})
|
||||||
public String getRawTransaction(@PathParam("signature") String signature58) {
|
public String getRawTransaction(@PathParam("signature") String signature58) {
|
||||||
byte[] signature;
|
byte[] signature;
|
||||||
try {
|
try {
|
||||||
@ -138,21 +146,28 @@ public class TransactionsResource {
|
|||||||
description = "Returns list of transactions",
|
description = "Returns list of transactions",
|
||||||
responses = {
|
responses = {
|
||||||
@ApiResponse(
|
@ApiResponse(
|
||||||
description = "list of transactions",
|
description = "the block",
|
||||||
content = @Content(
|
content = @Content(
|
||||||
array = @ArraySchema(
|
array = @ArraySchema(
|
||||||
schema = @Schema(
|
schema = @Schema(
|
||||||
oneOf = {
|
implementation = TransactionData.class
|
||||||
GenesisTransactionData.class, PaymentTransactionData.class
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiErrors({ApiError.INVALID_SIGNATURE, ApiError.BLOCK_NO_EXISTS, ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({
|
||||||
public List<TransactionData> getBlockTransactions(@PathParam("signature") String signature58, @Parameter(ref = "limit") @QueryParam("limit") int limit, @Parameter(ref = "offset") @QueryParam("offset") int offset) {
|
ApiError.INVALID_SIGNATURE, ApiError.BLOCK_NO_EXISTS, ApiError.REPOSITORY_ISSUE
|
||||||
|
})
|
||||||
|
public List<TransactionData> getBlockTransactions(@PathParam("signature") String signature58, @Parameter(
|
||||||
|
ref = "limit"
|
||||||
|
) @QueryParam("limit") Integer limit, @Parameter(
|
||||||
|
ref = "offset"
|
||||||
|
) @QueryParam("offset") Integer offset, @Parameter(
|
||||||
|
ref = "reverse"
|
||||||
|
) @QueryParam("reverse") Boolean reverse) {
|
||||||
|
// Decode signature
|
||||||
byte[] signature;
|
byte[] signature;
|
||||||
try {
|
try {
|
||||||
signature = Base58.decode(signature58);
|
signature = Base58.decode(signature58);
|
||||||
@ -161,18 +176,10 @@ public class TransactionsResource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
List<TransactionData> transactions = repository.getBlockRepository().getTransactionsFromSignature(signature);
|
if (repository.getBlockRepository().getHeightFromSignature(signature) == 0)
|
||||||
|
|
||||||
// check if block exists
|
|
||||||
if (transactions == null)
|
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.BLOCK_NO_EXISTS);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.BLOCK_NO_EXISTS);
|
||||||
|
|
||||||
// Pagination would take effect here (or as part of the repository access)
|
return repository.getBlockRepository().getTransactionsFromSignature(signature, limit, offset, reverse);
|
||||||
int fromIndex = Integer.min(offset, transactions.size());
|
|
||||||
int toIndex = limit == 0 ? transactions.size() : Integer.min(fromIndex + limit, transactions.size());
|
|
||||||
transactions = transactions.subList(fromIndex, toIndex);
|
|
||||||
|
|
||||||
return transactions;
|
|
||||||
} catch (ApiException e) {
|
} catch (ApiException e) {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
@ -198,10 +205,18 @@ public class TransactionsResource {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiErrors({ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({
|
||||||
public List<TransactionData> getUnconfirmedTransactions() {
|
ApiError.REPOSITORY_ISSUE
|
||||||
|
})
|
||||||
|
public List<TransactionData> getUnconfirmedTransactions(@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()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
return repository.getTransactionRepository().getAllUnconfirmedTransactions();
|
return repository.getTransactionRepository().getUnconfirmedTransactions(limit, offset, reverse);
|
||||||
} catch (ApiException e) {
|
} catch (ApiException e) {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
@ -209,23 +224,17 @@ public class TransactionsResource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum ConfirmationStatus {
|
||||||
|
CONFIRMED,
|
||||||
|
UNCONFIRMED,
|
||||||
|
BOTH;
|
||||||
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path("/search")
|
@Path("/search")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Find matching transactions",
|
summary = "Find matching transactions",
|
||||||
description = "Returns transactions that match criteria. At least either txType or address must be provided.",
|
description = "Returns transactions that match criteria. At least either txType or address or limit <= 20 must be provided. Block height ranges allowed when searching CONFIRMED transactions ONLY.",
|
||||||
/*
|
|
||||||
* parameters = {
|
|
||||||
*
|
|
||||||
* @Parameter(in = ParameterIn.QUERY, name = "txType", description = "Transaction type", schema = @Schema(type = "integer")),
|
|
||||||
*
|
|
||||||
* @Parameter(in = ParameterIn.QUERY, name = "address", description = "Account's address", schema = @Schema(type = "string")),
|
|
||||||
*
|
|
||||||
* @Parameter(in = ParameterIn.QUERY, name = "startBlock", description = "Start block height", schema = @Schema(type = "integer")),
|
|
||||||
*
|
|
||||||
* @Parameter(in = ParameterIn.QUERY, name = "blockLimit", description = "Maximum number of blocks to search", schema = @Schema(type = "integer"))
|
|
||||||
* },
|
|
||||||
*/
|
|
||||||
responses = {
|
responses = {
|
||||||
@ApiResponse(
|
@ApiResponse(
|
||||||
description = "transactions",
|
description = "transactions",
|
||||||
@ -239,30 +248,31 @@ public class TransactionsResource {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiErrors({ApiError.INVALID_CRITERIA, ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({
|
||||||
|
ApiError.INVALID_CRITERIA, ApiError.REPOSITORY_ISSUE
|
||||||
|
})
|
||||||
public List<TransactionData> searchTransactions(@QueryParam("startBlock") Integer startBlock, @QueryParam("blockLimit") Integer blockLimit,
|
public List<TransactionData> searchTransactions(@QueryParam("startBlock") Integer startBlock, @QueryParam("blockLimit") Integer blockLimit,
|
||||||
@QueryParam("txType") Integer txTypeNum, @QueryParam("address") String address, @Parameter(
|
@QueryParam("txType") TransactionType txType, @QueryParam("address") String address, @Parameter(
|
||||||
|
description = "whether to include confirmed, unconfirmed or both",
|
||||||
|
required = true
|
||||||
|
) @QueryParam("confirmationStatus") ConfirmationStatus confirmationStatus, @Parameter(
|
||||||
ref = "limit"
|
ref = "limit"
|
||||||
) @QueryParam("limit") int limit, @Parameter(
|
) @QueryParam("limit") Integer limit, @Parameter(
|
||||||
ref = "offset"
|
ref = "offset"
|
||||||
) @QueryParam("offset") int offset) {
|
) @QueryParam("offset") Integer offset, @Parameter(
|
||||||
if ((txTypeNum == null || txTypeNum == 0) && (address == null || address.isEmpty()))
|
ref = "reverse"
|
||||||
|
) @QueryParam("reverse") Boolean reverse) {
|
||||||
|
// Must have at least one of txType / address / limit <= 20
|
||||||
|
if (txType == null && (address == null || address.isEmpty()) && (limit == null || limit > 20))
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
|
||||||
|
|
||||||
TransactionType txType = null;
|
// You can't ask for unconfirmed and impose a block height range
|
||||||
if (txTypeNum != null) {
|
if (confirmationStatus != ConfirmationStatus.CONFIRMED && (startBlock != null || blockLimit != null))
|
||||||
txType = TransactionType.valueOf(txTypeNum);
|
|
||||||
if (txType == null)
|
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
|
||||||
}
|
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
List<byte[]> signatures = repository.getTransactionRepository().getAllSignaturesMatchingCriteria(startBlock, blockLimit, txType, address);
|
List<byte[]> signatures = repository.getTransactionRepository().getSignaturesMatchingCriteria(startBlock, blockLimit, txType, address,
|
||||||
|
confirmationStatus, limit, offset, reverse);
|
||||||
// Pagination would take effect here (or as part of the repository access)
|
|
||||||
int fromIndex = Integer.min(offset, signatures.size());
|
|
||||||
int toIndex = limit == 0 ? signatures.size() : Integer.min(fromIndex + limit, signatures.size());
|
|
||||||
signatures = signatures.subList(fromIndex, toIndex);
|
|
||||||
|
|
||||||
// Expand signatures to transactions
|
// Expand signatures to transactions
|
||||||
List<TransactionData> transactions = new ArrayList<TransactionData>(signatures.size());
|
List<TransactionData> transactions = new ArrayList<TransactionData>(signatures.size());
|
||||||
@ -302,7 +312,9 @@ public class TransactionsResource {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiErrors({ApiError.INVALID_PRIVATE_KEY, ApiError.TRANSFORMATION_ERROR})
|
@ApiErrors({
|
||||||
|
ApiError.INVALID_PRIVATE_KEY, ApiError.TRANSFORMATION_ERROR
|
||||||
|
})
|
||||||
public String signTransaction(SimpleTransactionSignRequest signRequest) {
|
public String signTransaction(SimpleTransactionSignRequest signRequest) {
|
||||||
if (signRequest.transactionBytes.length == 0)
|
if (signRequest.transactionBytes.length == 0)
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.JSON);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.JSON);
|
||||||
@ -356,7 +368,9 @@ public class TransactionsResource {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiErrors({ApiError.INVALID_SIGNATURE, ApiError.INVALID_DATA, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({
|
||||||
|
ApiError.INVALID_SIGNATURE, ApiError.INVALID_DATA, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE
|
||||||
|
})
|
||||||
public String processTransaction(String rawBytes58) {
|
public String processTransaction(String rawBytes58) {
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
byte[] rawBytes = Base58.decode(rawBytes58);
|
byte[] rawBytes = Base58.decode(rawBytes58);
|
||||||
@ -412,7 +426,9 @@ public class TransactionsResource {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiErrors({ApiError.INVALID_SIGNATURE, ApiError.INVALID_DATA, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
@ApiErrors({
|
||||||
|
ApiError.INVALID_SIGNATURE, ApiError.INVALID_DATA, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE
|
||||||
|
})
|
||||||
public TransactionData decodeTransaction(String rawBytes58, @QueryParam("ignoreValidityChecks") boolean ignoreValidityChecks) {
|
public TransactionData decodeTransaction(String rawBytes58, @QueryParam("ignoreValidityChecks") boolean ignoreValidityChecks) {
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
byte[] rawBytes = Base58.decode(rawBytes58);
|
byte[] rawBytes = Base58.decode(rawBytes58);
|
||||||
|
@ -50,7 +50,7 @@ public class BlockGenerator extends Thread {
|
|||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
if (Settings.getInstance().getWipeUnconfirmedOnStart()) {
|
if (Settings.getInstance().getWipeUnconfirmedOnStart()) {
|
||||||
// Wipe existing unconfirmed transactions
|
// Wipe existing unconfirmed transactions
|
||||||
List<TransactionData> unconfirmedTransactions = repository.getTransactionRepository().getAllUnconfirmedTransactions();
|
List<TransactionData> unconfirmedTransactions = repository.getTransactionRepository().getUnconfirmedTransactions();
|
||||||
|
|
||||||
for (TransactionData transactionData : unconfirmedTransactions) {
|
for (TransactionData transactionData : unconfirmedTransactions) {
|
||||||
LOGGER.trace(String.format("Deleting unconfirmed transaction %s", Base58.encode(transactionData.getSignature())));
|
LOGGER.trace(String.format("Deleting unconfirmed transaction %s", Base58.encode(transactionData.getSignature())));
|
||||||
|
@ -17,6 +17,11 @@ import org.qora.utils.Base58;
|
|||||||
|
|
||||||
public class Controller {
|
public class Controller {
|
||||||
|
|
||||||
|
static {
|
||||||
|
// This must go before any calls to LogManager/Logger
|
||||||
|
System.setProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager");
|
||||||
|
}
|
||||||
|
|
||||||
private static final Logger LOGGER = LogManager.getLogger(Controller.class);
|
private static final Logger LOGGER = LogManager.getLogger(Controller.class);
|
||||||
|
|
||||||
public static final String connectionUrl = "jdbc:hsqldb:file:db/blockchain;create=true";
|
public static final String connectionUrl = "jdbc:hsqldb:file:db/blockchain;create=true";
|
||||||
|
@ -10,8 +10,9 @@ import javax.xml.bind.annotation.XmlElement;
|
|||||||
import javax.xml.bind.annotation.XmlSeeAlso;
|
import javax.xml.bind.annotation.XmlSeeAlso;
|
||||||
import javax.xml.bind.annotation.XmlTransient;
|
import javax.xml.bind.annotation.XmlTransient;
|
||||||
|
|
||||||
import org.eclipse.persistence.oxm.annotations.XmlClassExtractor;
|
// XXX are this still needed? see below
|
||||||
import org.qora.api.TransactionClassExtractor;
|
// import org.eclipse.persistence.oxm.annotations.XmlClassExtractor;
|
||||||
|
// import org.qora.api.TransactionClassExtractor;
|
||||||
import org.qora.crypto.Crypto;
|
import org.qora.crypto.Crypto;
|
||||||
import org.qora.transaction.Transaction.TransactionType;
|
import org.qora.transaction.Transaction.TransactionType;
|
||||||
|
|
||||||
@ -26,7 +27,8 @@ import io.swagger.v3.oas.annotations.media.Schema.AccessMode;
|
|||||||
* then chances are that class is missing a no-argument constructor!
|
* then chances are that class is missing a no-argument constructor!
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@XmlClassExtractor(TransactionClassExtractor.class)
|
// XXX is this still in use?
|
||||||
|
// @XmlClassExtractor(TransactionClassExtractor.class)
|
||||||
@XmlSeeAlso({GenesisTransactionData.class, PaymentTransactionData.class, RegisterNameTransactionData.class, UpdateNameTransactionData.class,
|
@XmlSeeAlso({GenesisTransactionData.class, PaymentTransactionData.class, RegisterNameTransactionData.class, UpdateNameTransactionData.class,
|
||||||
SellNameTransactionData.class, CancelSellNameTransactionData.class, BuyNameTransactionData.class,
|
SellNameTransactionData.class, CancelSellNameTransactionData.class, BuyNameTransactionData.class,
|
||||||
CreatePollTransactionData.class, VoteOnPollTransactionData.class, ArbitraryTransactionData.class,
|
CreatePollTransactionData.class, VoteOnPollTransactionData.class, ArbitraryTransactionData.class,
|
||||||
|
@ -21,9 +21,17 @@ public interface AccountRepository {
|
|||||||
|
|
||||||
public AccountBalanceData getBalance(String address, long assetId) throws DataException;
|
public AccountBalanceData getBalance(String address, long assetId) throws DataException;
|
||||||
|
|
||||||
public List<AccountBalanceData> getAllBalances(String address) throws DataException;
|
public List<AccountBalanceData> getAllBalances(String address, Integer limit, Integer offset, Boolean reverse) throws DataException;
|
||||||
|
|
||||||
public List<AccountBalanceData> getAssetBalances(long assetId) throws DataException;
|
public default List<AccountBalanceData> getAllBalances(String address) throws DataException {
|
||||||
|
return getAllBalances(address, null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<AccountBalanceData> getAssetBalances(long assetId, Integer limit, Integer offset, Boolean reverse) throws DataException;
|
||||||
|
|
||||||
|
public default List<AccountBalanceData> getAssetBalances(long assetId) throws DataException {
|
||||||
|
return getAssetBalances(assetId, null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
public void save(AccountBalanceData accountBalanceData) throws DataException;
|
public void save(AccountBalanceData accountBalanceData) throws DataException;
|
||||||
|
|
||||||
|
@ -18,7 +18,11 @@ public interface AssetRepository {
|
|||||||
|
|
||||||
public boolean assetExists(String assetName) throws DataException;
|
public boolean assetExists(String assetName) throws DataException;
|
||||||
|
|
||||||
public List<AssetData> getAllAssets() throws DataException;
|
public List<AssetData> getAllAssets(Integer limit, Integer offset, Boolean reverse) throws DataException;
|
||||||
|
|
||||||
|
public default List<AssetData> getAllAssets() throws DataException {
|
||||||
|
return getAllAssets(null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
// For a list of asset holders, see AccountRepository.getAssetBalances
|
// For a list of asset holders, see AccountRepository.getAssetBalances
|
||||||
|
|
||||||
@ -30,9 +34,18 @@ public interface AssetRepository {
|
|||||||
|
|
||||||
public OrderData fromOrderId(byte[] orderId) throws DataException;
|
public OrderData fromOrderId(byte[] orderId) throws DataException;
|
||||||
|
|
||||||
public List<OrderData> getOpenOrders(long haveAssetId, long wantAssetId) throws DataException;
|
public List<OrderData> getOpenOrders(long haveAssetId, long wantAssetId, Integer limit, Integer offset, Boolean reverse) throws DataException;
|
||||||
|
|
||||||
public List<OrderData> getAccountsOrders(byte[] publicKey, boolean includeClosed, boolean includeFulfilled) throws DataException;
|
public default List<OrderData> getOpenOrders(long haveAssetId, long wantAssetId) throws DataException {
|
||||||
|
return getOpenOrders(haveAssetId, wantAssetId, null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<OrderData> getAccountsOrders(byte[] publicKey, boolean includeClosed, boolean includeFulfilled, Integer limit, Integer offset, Boolean reverse)
|
||||||
|
throws DataException;
|
||||||
|
|
||||||
|
public default List<OrderData> getAccountsOrders(byte[] publicKey, boolean includeClosed, boolean includeFulfilled) throws DataException {
|
||||||
|
return getAccountsOrders(publicKey, includeClosed, includeFulfilled, null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
public void save(OrderData orderData) throws DataException;
|
public void save(OrderData orderData) throws DataException;
|
||||||
|
|
||||||
@ -40,10 +53,18 @@ public interface AssetRepository {
|
|||||||
|
|
||||||
// Trades
|
// Trades
|
||||||
|
|
||||||
public List<TradeData> getTrades(long haveAssetId, long wantAssetId) throws DataException;
|
public List<TradeData> getTrades(long haveAssetId, long wantAssetId, Integer limit, Integer offset, Boolean reverse) throws DataException;
|
||||||
|
|
||||||
|
public default List<TradeData> getTrades(long haveAssetId, long wantAssetId) throws DataException {
|
||||||
|
return getTrades(haveAssetId, wantAssetId, null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
/** Returns TradeData for trades where orderId was involved, i.e. either initiating OR target order */
|
/** Returns TradeData for trades where orderId was involved, i.e. either initiating OR target order */
|
||||||
public List<TradeData> getOrdersTrades(byte[] orderId) throws DataException;
|
public List<TradeData> getOrdersTrades(byte[] orderId, Integer limit, Integer offset, Boolean reverse) throws DataException;
|
||||||
|
|
||||||
|
public default List<TradeData> getOrdersTrades(byte[] orderId) throws DataException {
|
||||||
|
return getOrdersTrades(orderId, null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
public void save(TradeData tradeData) throws DataException;
|
public void save(TradeData tradeData) throws DataException;
|
||||||
|
|
||||||
|
@ -59,6 +59,17 @@ public interface BlockRepository {
|
|||||||
*/
|
*/
|
||||||
public BlockData getLastBlock() throws DataException;
|
public BlockData getLastBlock() throws DataException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns block's transactions given block's signature.
|
||||||
|
* <p>
|
||||||
|
* This is typically used by API to fetch a block's transactions.
|
||||||
|
*
|
||||||
|
* @param signature
|
||||||
|
* @return list of transactions, or null if block not found in blockchain.
|
||||||
|
* @throws DataException
|
||||||
|
*/
|
||||||
|
public List<TransactionData> getTransactionsFromSignature(byte[] signature, Integer limit, Integer offset, Boolean reverse) throws DataException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns block's transactions given block's signature.
|
* Returns block's transactions given block's signature.
|
||||||
* <p>
|
* <p>
|
||||||
@ -68,7 +79,9 @@ public interface BlockRepository {
|
|||||||
* @return list of transactions, or null if block not found in blockchain.
|
* @return list of transactions, or null if block not found in blockchain.
|
||||||
* @throws DataException
|
* @throws DataException
|
||||||
*/
|
*/
|
||||||
public List<TransactionData> getTransactionsFromSignature(byte[] signature) throws DataException;
|
public default List<TransactionData> getTransactionsFromSignature(byte[] signature) throws DataException {
|
||||||
|
return getTransactionsFromSignature(signature, null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Saves block into repository.
|
* Saves block into repository.
|
||||||
|
@ -21,11 +21,23 @@ public interface GroupRepository {
|
|||||||
|
|
||||||
public boolean groupExists(String groupName) throws DataException;
|
public boolean groupExists(String groupName) throws DataException;
|
||||||
|
|
||||||
public List<GroupData> getAllGroups() throws DataException;
|
public List<GroupData> getAllGroups(Integer limit, Integer offset, Boolean reverse) throws DataException;
|
||||||
|
|
||||||
public List<GroupData> getGroupsByOwner(String address) throws DataException;
|
public default List<GroupData> getAllGroups() throws DataException {
|
||||||
|
return getAllGroups(null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
public List<GroupData> getGroupsWithMember(String member) throws DataException;
|
public List<GroupData> getGroupsByOwner(String address, Integer limit, Integer offset, Boolean reverse) throws DataException;
|
||||||
|
|
||||||
|
public default List<GroupData> getGroupsByOwner(String address) throws DataException {
|
||||||
|
return getGroupsByOwner(address, null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<GroupData> getGroupsWithMember(String member, Integer limit, Integer offset, Boolean reverse) throws DataException;
|
||||||
|
|
||||||
|
public default List<GroupData> getGroupsWithMember(String member) throws DataException {
|
||||||
|
return getGroupsWithMember(member, null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
public void save(GroupData groupData) throws DataException;
|
public void save(GroupData groupData) throws DataException;
|
||||||
|
|
||||||
@ -39,7 +51,14 @@ public interface GroupRepository {
|
|||||||
|
|
||||||
public boolean adminExists(int groupId, String address) throws DataException;
|
public boolean adminExists(int groupId, String address) throws DataException;
|
||||||
|
|
||||||
public List<GroupAdminData> getGroupAdmins(int groupId) throws DataException;
|
public List<GroupAdminData> getGroupAdmins(int groupId, Integer limit, Integer offset, Boolean reverse) throws DataException;
|
||||||
|
|
||||||
|
public default List<GroupAdminData> getGroupAdmins(int groupId) throws DataException {
|
||||||
|
return getGroupAdmins(groupId, null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns number of group admins, or null if group doesn't exist */
|
||||||
|
public Integer countGroupAdmins(int groupId) throws DataException;
|
||||||
|
|
||||||
public void save(GroupAdminData groupAdminData) throws DataException;
|
public void save(GroupAdminData groupAdminData) throws DataException;
|
||||||
|
|
||||||
@ -51,7 +70,11 @@ public interface GroupRepository {
|
|||||||
|
|
||||||
public boolean memberExists(int groupId, String address) throws DataException;
|
public boolean memberExists(int groupId, String address) throws DataException;
|
||||||
|
|
||||||
public List<GroupMemberData> getGroupMembers(int groupId) throws DataException;
|
public List<GroupMemberData> getGroupMembers(int groupId, Integer limit, Integer offset, Boolean reverse) throws DataException;
|
||||||
|
|
||||||
|
public default List<GroupMemberData> getGroupMembers(int groupId) throws DataException {
|
||||||
|
return getGroupMembers(groupId, null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
/** Returns number of group members, or null if group doesn't exist */
|
/** Returns number of group members, or null if group doesn't exist */
|
||||||
public Integer countGroupMembers(int groupId) throws DataException;
|
public Integer countGroupMembers(int groupId) throws DataException;
|
||||||
@ -66,9 +89,17 @@ public interface GroupRepository {
|
|||||||
|
|
||||||
public boolean inviteExists(int groupId, String invitee) throws DataException;
|
public boolean inviteExists(int groupId, String invitee) throws DataException;
|
||||||
|
|
||||||
public List<GroupInviteData> getInvitesByGroupId(int groupId) throws DataException;
|
public List<GroupInviteData> getInvitesByGroupId(int groupId, Integer limit, Integer offset, Boolean reverse) throws DataException;
|
||||||
|
|
||||||
public List<GroupInviteData> getInvitesByInvitee(String invitee) throws DataException;
|
public default List<GroupInviteData> getInvitesByGroupId(int groupId) throws DataException {
|
||||||
|
return getInvitesByGroupId(groupId, null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<GroupInviteData> getInvitesByInvitee(String invitee, Integer limit, Integer offset, Boolean reverse) throws DataException;
|
||||||
|
|
||||||
|
public default List<GroupInviteData> getInvitesByInvitee(String invitee) throws DataException {
|
||||||
|
return getInvitesByInvitee(invitee, null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
public void save(GroupInviteData groupInviteData) throws DataException;
|
public void save(GroupInviteData groupInviteData) throws DataException;
|
||||||
|
|
||||||
@ -80,7 +111,11 @@ public interface GroupRepository {
|
|||||||
|
|
||||||
public boolean joinRequestExists(int groupId, String joiner) throws DataException;
|
public boolean joinRequestExists(int groupId, String joiner) throws DataException;
|
||||||
|
|
||||||
public List<GroupJoinRequestData> getGroupJoinRequests(int groupId) throws DataException;
|
public List<GroupJoinRequestData> getGroupJoinRequests(int groupId, Integer limit, Integer offset, Boolean reverse) throws DataException;
|
||||||
|
|
||||||
|
public default List<GroupJoinRequestData> getGroupJoinRequests(int groupId) throws DataException {
|
||||||
|
return getGroupJoinRequests(groupId, null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
public void save(GroupJoinRequestData groupJoinRequestData) throws DataException;
|
public void save(GroupJoinRequestData groupJoinRequestData) throws DataException;
|
||||||
|
|
||||||
@ -92,7 +127,11 @@ public interface GroupRepository {
|
|||||||
|
|
||||||
public boolean banExists(int groupId, String offender) throws DataException;
|
public boolean banExists(int groupId, String offender) throws DataException;
|
||||||
|
|
||||||
public List<GroupBanData> getGroupBans(int groupId) throws DataException;
|
public List<GroupBanData> getGroupBans(int groupId, Integer limit, Integer offset, Boolean reverse) throws DataException;
|
||||||
|
|
||||||
|
public default List<GroupBanData> getGroupBans(int groupId) throws DataException {
|
||||||
|
return getGroupBans(groupId, null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
public void save(GroupBanData groupBanData) throws DataException;
|
public void save(GroupBanData groupBanData) throws DataException;
|
||||||
|
|
||||||
|
@ -10,11 +10,23 @@ public interface NameRepository {
|
|||||||
|
|
||||||
public boolean nameExists(String name) throws DataException;
|
public boolean nameExists(String name) throws DataException;
|
||||||
|
|
||||||
public List<NameData> getAllNames() throws DataException;
|
public List<NameData> getAllNames(Integer limit, Integer offset, Boolean reverse) throws DataException;
|
||||||
|
|
||||||
public List<NameData> getNamesForSale() throws DataException;
|
public default List<NameData> getAllNames() throws DataException {
|
||||||
|
return getAllNames(null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
public List<NameData> getNamesByOwner(String address) throws DataException;
|
public List<NameData> getNamesForSale(Integer limit, Integer offset, Boolean reverse) throws DataException;
|
||||||
|
|
||||||
|
public default List<NameData> getNamesForSale() throws DataException {
|
||||||
|
return getNamesForSale(null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<NameData> getNamesByOwner(String address, Integer limit, Integer offset, Boolean reverse) throws DataException;
|
||||||
|
|
||||||
|
public default List<NameData> getNamesByOwner(String address) throws DataException {
|
||||||
|
return getNamesByOwner(address, null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
public void save(NameData nameData) throws DataException;
|
public void save(NameData nameData) throws DataException;
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ package org.qora.repository;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.qora.api.resource.TransactionsResource.ConfirmationStatus;
|
||||||
import org.qora.data.transaction.TransactionData;
|
import org.qora.data.transaction.TransactionData;
|
||||||
import org.qora.transaction.Transaction.TransactionType;
|
import org.qora.transaction.Transaction.TransactionType;
|
||||||
|
|
||||||
@ -20,7 +21,7 @@ public interface TransactionRepository {
|
|||||||
|
|
||||||
// Transaction participants
|
// Transaction participants
|
||||||
|
|
||||||
public List<byte[]> getAllSignaturesInvolvingAddress(String address) throws DataException;
|
public List<byte[]> getSignaturesInvolvingAddress(String address) throws DataException;
|
||||||
|
|
||||||
public void saveParticipants(TransactionData transactionData, List<String> participants) throws DataException;
|
public void saveParticipants(TransactionData transactionData, List<String> participants) throws DataException;
|
||||||
|
|
||||||
@ -28,7 +29,21 @@ public interface TransactionRepository {
|
|||||||
|
|
||||||
// Searching transactions
|
// Searching transactions
|
||||||
|
|
||||||
public List<byte[]> getAllSignaturesMatchingCriteria(Integer startBlock, Integer blockLimit, TransactionType txType, String address) throws DataException;
|
public List<byte[]> getSignaturesMatchingCriteria(Integer startBlock, Integer blockLimit, TransactionType txType, String address,
|
||||||
|
ConfirmationStatus confirmationStatus, Integer limit, Integer offset, Boolean reverse) throws DataException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns list of unconfirmed transactions in timestamp-else-signature order.
|
||||||
|
* <p>
|
||||||
|
* This is typically called by the API.
|
||||||
|
*
|
||||||
|
* @param limit
|
||||||
|
* @param offset
|
||||||
|
* @param reverse
|
||||||
|
* @return list of transactions, or empty if none.
|
||||||
|
* @throws DataException
|
||||||
|
*/
|
||||||
|
public List<TransactionData> getUnconfirmedTransactions(Integer limit, Integer offset, Boolean reverse) throws DataException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns list of unconfirmed transactions in timestamp-else-signature order.
|
* Returns list of unconfirmed transactions in timestamp-else-signature order.
|
||||||
@ -36,7 +51,9 @@ public interface TransactionRepository {
|
|||||||
* @return list of transactions, or empty if none.
|
* @return list of transactions, or empty if none.
|
||||||
* @throws DataException
|
* @throws DataException
|
||||||
*/
|
*/
|
||||||
public List<TransactionData> getAllUnconfirmedTransactions() throws DataException;
|
public default List<TransactionData> getUnconfirmedTransactions() throws DataException {
|
||||||
|
return getUnconfirmedTransactions(null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove transaction from unconfirmed transactions pile.
|
* Remove transaction from unconfirmed transactions pile.
|
||||||
|
@ -94,10 +94,15 @@ public class HSQLDBAccountRepository implements AccountRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<AccountBalanceData> getAllBalances(String address) throws DataException {
|
public List<AccountBalanceData> getAllBalances(String address, Integer limit, Integer offset, Boolean reverse) throws DataException {
|
||||||
|
String sql = "SELECT asset_id, balance FROM AccountBalances WHERE account = ? ORDER BY asset_id";
|
||||||
|
if (reverse != null && reverse)
|
||||||
|
sql += " DESC";
|
||||||
|
sql += HSQLDBRepository.limitOffsetSql(limit, offset);
|
||||||
|
|
||||||
List<AccountBalanceData> balances = new ArrayList<AccountBalanceData>();
|
List<AccountBalanceData> balances = new ArrayList<AccountBalanceData>();
|
||||||
|
|
||||||
try (ResultSet resultSet = this.repository.checkedExecute("SELECT asset_id, balance FROM AccountBalances WHERE account = ?", address)) {
|
try (ResultSet resultSet = this.repository.checkedExecute(sql, address)) {
|
||||||
if (resultSet == null)
|
if (resultSet == null)
|
||||||
return balances;
|
return balances;
|
||||||
|
|
||||||
@ -115,10 +120,15 @@ public class HSQLDBAccountRepository implements AccountRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<AccountBalanceData> getAssetBalances(long assetId) throws DataException {
|
public List<AccountBalanceData> getAssetBalances(long assetId, Integer limit, Integer offset, Boolean reverse) throws DataException {
|
||||||
|
String sql = "SELECT account, balance FROM AccountBalances WHERE asset_id = ? ORDER BY account";
|
||||||
|
if (reverse != null && reverse)
|
||||||
|
sql += " DESC";
|
||||||
|
sql += HSQLDBRepository.limitOffsetSql(limit, offset);
|
||||||
|
|
||||||
List<AccountBalanceData> balances = new ArrayList<AccountBalanceData>();
|
List<AccountBalanceData> balances = new ArrayList<AccountBalanceData>();
|
||||||
|
|
||||||
try (ResultSet resultSet = this.repository.checkedExecute("SELECT account, balance FROM AccountBalances WHERE asset_id = ?", assetId)) {
|
try (ResultSet resultSet = this.repository.checkedExecute(sql, assetId)) {
|
||||||
if (resultSet == null)
|
if (resultSet == null)
|
||||||
return balances;
|
return balances;
|
||||||
|
|
||||||
|
@ -83,11 +83,15 @@ public class HSQLDBAssetRepository implements AssetRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<AssetData> getAllAssets() throws DataException {
|
public List<AssetData> getAllAssets(Integer limit, Integer offset, Boolean reverse) throws DataException {
|
||||||
|
String sql = "SELECT owner, asset_id, description, quantity, is_divisible, reference, asset_name FROM Assets ORDER BY asset_name";
|
||||||
|
if (reverse != null && reverse)
|
||||||
|
sql += " DESC";
|
||||||
|
sql += HSQLDBRepository.limitOffsetSql(limit, offset);
|
||||||
|
|
||||||
List<AssetData> assets = new ArrayList<AssetData>();
|
List<AssetData> assets = new ArrayList<AssetData>();
|
||||||
|
|
||||||
try (ResultSet resultSet = this.repository
|
try (ResultSet resultSet = this.repository.checkedExecute(sql)) {
|
||||||
.checkedExecute("SELECT owner, asset_id, description, quantity, is_divisible, reference, asset_name FROM Assets ORDER BY asset_id ASC")) {
|
|
||||||
if (resultSet == null)
|
if (resultSet == null)
|
||||||
return assets;
|
return assets;
|
||||||
|
|
||||||
@ -170,13 +174,19 @@ public class HSQLDBAssetRepository implements AssetRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<OrderData> getOpenOrders(long haveAssetId, long wantAssetId) throws DataException {
|
public List<OrderData> getOpenOrders(long haveAssetId, long wantAssetId, Integer limit, Integer offset, Boolean reverse) throws DataException {
|
||||||
|
String sql = "SELECT creator, asset_order_id, amount, fulfilled, price, ordered FROM AssetOrders "
|
||||||
|
+ "WHERE have_asset_id = ? AND want_asset_id = ? AND is_closed = FALSE AND is_fulfilled = FALSE ORDER BY price";
|
||||||
|
if (reverse != null && reverse)
|
||||||
|
sql += " DESC";
|
||||||
|
sql += ", ordered";
|
||||||
|
if (reverse != null && reverse)
|
||||||
|
sql += " DESC";
|
||||||
|
sql += HSQLDBRepository.limitOffsetSql(limit, offset);
|
||||||
|
|
||||||
List<OrderData> orders = new ArrayList<OrderData>();
|
List<OrderData> orders = new ArrayList<OrderData>();
|
||||||
|
|
||||||
try (ResultSet resultSet = this.repository.checkedExecute(
|
try (ResultSet resultSet = this.repository.checkedExecute(sql, haveAssetId, wantAssetId)) {
|
||||||
"SELECT creator, asset_order_id, amount, fulfilled, price, ordered FROM AssetOrders "
|
|
||||||
+ "WHERE have_asset_id = ? AND want_asset_id = ? AND is_closed = FALSE AND is_fulfilled = FALSE ORDER BY price ASC, ordered ASC",
|
|
||||||
haveAssetId, wantAssetId)) {
|
|
||||||
if (resultSet == null)
|
if (resultSet == null)
|
||||||
return orders;
|
return orders;
|
||||||
|
|
||||||
@ -202,16 +212,18 @@ public class HSQLDBAssetRepository implements AssetRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<OrderData> getAccountsOrders(byte[] publicKey, boolean includeClosed, boolean includeFulfilled) throws DataException {
|
public List<OrderData> getAccountsOrders(byte[] publicKey, boolean includeClosed, boolean includeFulfilled, Integer limit, Integer offset, Boolean reverse) throws DataException {
|
||||||
List<OrderData> orders = new ArrayList<OrderData>();
|
String sql = "SELECT asset_order_id, have_asset_id, want_asset_id, amount, fulfilled, price, ordered, is_closed, is_fulfilled FROM AssetOrders WHERE creator = ?";
|
||||||
|
|
||||||
String sql = "SELECT asset_order_id, have_asset_id, want_asset_id, amount, fulfilled, price, ordered, is_closed, is_fulfilled "
|
|
||||||
+ "FROM AssetOrders WHERE creator = ?";
|
|
||||||
if (!includeClosed)
|
if (!includeClosed)
|
||||||
sql += " AND is_closed = FALSE";
|
sql += " AND is_closed = FALSE";
|
||||||
if (!includeFulfilled)
|
if (!includeFulfilled)
|
||||||
sql += " AND is_fulfilled = FALSE";
|
sql += " AND is_fulfilled = FALSE";
|
||||||
sql += " ORDER BY ordered ASC";
|
sql += " ORDER BY ordered";
|
||||||
|
if (reverse != null && reverse)
|
||||||
|
sql += " DESC";
|
||||||
|
sql += HSQLDBRepository.limitOffsetSql(limit, offset);
|
||||||
|
|
||||||
|
List<OrderData> orders = new ArrayList<OrderData>();
|
||||||
|
|
||||||
try (ResultSet resultSet = this.repository.checkedExecute(sql, publicKey)) {
|
try (ResultSet resultSet = this.repository.checkedExecute(sql, publicKey)) {
|
||||||
if (resultSet == null)
|
if (resultSet == null)
|
||||||
@ -267,13 +279,16 @@ public class HSQLDBAssetRepository implements AssetRepository {
|
|||||||
// Trades
|
// Trades
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<TradeData> getTrades(long haveAssetId, long wantAssetId) throws DataException {
|
public List<TradeData> getTrades(long haveAssetId, long wantAssetId, Integer limit, Integer offset, Boolean reverse) throws DataException {
|
||||||
|
String sql = "SELECT initiating_order_id, target_order_id, AssetTrades.amount, AssetTrades.price, traded FROM AssetOrders JOIN AssetTrades ON initiating_order_id = asset_order_id "
|
||||||
|
+ "WHERE have_asset_id = ? AND want_asset_id = ? ORDER BY traded";
|
||||||
|
if (reverse != null && reverse)
|
||||||
|
sql += " DESC";
|
||||||
|
sql += HSQLDBRepository.limitOffsetSql(limit, offset);
|
||||||
|
|
||||||
List<TradeData> trades = new ArrayList<TradeData>();
|
List<TradeData> trades = new ArrayList<TradeData>();
|
||||||
|
|
||||||
try (ResultSet resultSet = this.repository.checkedExecute(
|
try (ResultSet resultSet = this.repository.checkedExecute(sql, haveAssetId, wantAssetId)) {
|
||||||
"SELECT initiating_order_id, target_order_id, AssetTrades.amount, AssetTrades.price, traded FROM AssetOrders JOIN AssetTrades ON initiating_order_id = asset_order_id "
|
|
||||||
+ "WHERE have_asset_id = ? AND want_asset_id = ? ORDER BY traded ASC",
|
|
||||||
haveAssetId, wantAssetId)) {
|
|
||||||
if (resultSet == null)
|
if (resultSet == null)
|
||||||
return trades;
|
return trades;
|
||||||
|
|
||||||
@ -295,12 +310,15 @@ public class HSQLDBAssetRepository implements AssetRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<TradeData> getOrdersTrades(byte[] orderId) throws DataException {
|
public List<TradeData> getOrdersTrades(byte[] orderId, Integer limit, Integer offset, Boolean reverse) throws DataException {
|
||||||
|
String sql = "SELECT initiating_order_id, target_order_id, amount, price, traded FROM AssetTrades WHERE initiating_order_id = ? OR target_order_id = ? ORDER BY traded";
|
||||||
|
if (reverse != null && reverse)
|
||||||
|
sql += " DESC";
|
||||||
|
sql += HSQLDBRepository.limitOffsetSql(limit, offset);
|
||||||
|
|
||||||
List<TradeData> trades = new ArrayList<TradeData>();
|
List<TradeData> trades = new ArrayList<TradeData>();
|
||||||
|
|
||||||
try (ResultSet resultSet = this.repository.checkedExecute(
|
try (ResultSet resultSet = this.repository.checkedExecute(sql, orderId, orderId)) {
|
||||||
"SELECT initiating_order_id, target_order_id, amount, price, traded FROM AssetTrades WHERE initiating_order_id = ? OR target_order_id = ?",
|
|
||||||
orderId, orderId)) {
|
|
||||||
if (resultSet == null)
|
if (resultSet == null)
|
||||||
return trades;
|
return trades;
|
||||||
|
|
||||||
|
@ -108,10 +108,15 @@ public class HSQLDBBlockRepository implements BlockRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<TransactionData> getTransactionsFromSignature(byte[] signature) throws DataException {
|
public List<TransactionData> getTransactionsFromSignature(byte[] signature, Integer limit, Integer offset, Boolean reverse) throws DataException {
|
||||||
|
String sql = "SELECT transaction_signature FROM BlockTransactions WHERE block_signature = ? ORDER BY sequence";
|
||||||
|
if (reverse != null && reverse)
|
||||||
|
sql += " DESC";
|
||||||
|
sql += HSQLDBRepository.limitOffsetSql(limit, offset);
|
||||||
|
|
||||||
List<TransactionData> transactions = new ArrayList<TransactionData>();
|
List<TransactionData> transactions = new ArrayList<TransactionData>();
|
||||||
|
|
||||||
try (ResultSet resultSet = this.repository.checkedExecute("SELECT transaction_signature FROM BlockTransactions WHERE block_signature = ?", signature)) {
|
try (ResultSet resultSet = this.repository.checkedExecute(sql, signature)) {
|
||||||
if (resultSet == null)
|
if (resultSet == null)
|
||||||
return transactions; // No transactions in this block
|
return transactions; // No transactions in this block
|
||||||
|
|
||||||
|
@ -95,11 +95,15 @@ public class HSQLDBGroupRepository implements GroupRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<GroupData> getAllGroups() throws DataException {
|
public List<GroupData> getAllGroups(Integer limit, Integer offset, Boolean reverse) throws DataException {
|
||||||
|
String sql = "SELECT group_id, owner, group_name, description, created, updated, reference, is_open FROM Groups ORDER BY group_name";
|
||||||
|
if (reverse != null && reverse)
|
||||||
|
sql += " DESC";
|
||||||
|
sql += HSQLDBRepository.limitOffsetSql(limit, offset);
|
||||||
|
|
||||||
List<GroupData> groups = new ArrayList<>();
|
List<GroupData> groups = new ArrayList<>();
|
||||||
|
|
||||||
try (ResultSet resultSet = this.repository
|
try (ResultSet resultSet = this.repository.checkedExecute(sql)) {
|
||||||
.checkedExecute("SELECT group_id, owner, group_name, description, created, updated, reference, is_open FROM Groups")) {
|
|
||||||
if (resultSet == null)
|
if (resultSet == null)
|
||||||
return groups;
|
return groups;
|
||||||
|
|
||||||
@ -127,11 +131,15 @@ public class HSQLDBGroupRepository implements GroupRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<GroupData> getGroupsByOwner(String owner) throws DataException {
|
public List<GroupData> getGroupsByOwner(String owner, Integer limit, Integer offset, Boolean reverse) throws DataException {
|
||||||
|
String sql = "SELECT group_id, group_name, description, created, updated, reference, is_open FROM Groups WHERE owner = ? ORDER BY group_name";
|
||||||
|
if (reverse != null && reverse)
|
||||||
|
sql += " DESC";
|
||||||
|
sql += HSQLDBRepository.limitOffsetSql(limit, offset);
|
||||||
|
|
||||||
List<GroupData> groups = new ArrayList<>();
|
List<GroupData> groups = new ArrayList<>();
|
||||||
|
|
||||||
try (ResultSet resultSet = this.repository
|
try (ResultSet resultSet = this.repository.checkedExecute(sql, owner)) {
|
||||||
.checkedExecute("SELECT group_id, group_name, description, created, updated, reference, is_open FROM Groups WHERE owner = ?", owner)) {
|
|
||||||
if (resultSet == null)
|
if (resultSet == null)
|
||||||
return groups;
|
return groups;
|
||||||
|
|
||||||
@ -158,12 +166,15 @@ public class HSQLDBGroupRepository implements GroupRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<GroupData> getGroupsWithMember(String member) throws DataException {
|
public List<GroupData> getGroupsWithMember(String member, Integer limit, Integer offset, Boolean reverse) throws DataException {
|
||||||
|
String sql = "SELECT group_id, owner, group_name, description, created, updated, reference, is_open FROM Groups JOIN GroupMembers USING (group_id) WHERE address = ? ORDER BY group_name";
|
||||||
|
if (reverse != null && reverse)
|
||||||
|
sql += " DESC";
|
||||||
|
sql += HSQLDBRepository.limitOffsetSql(limit, offset);
|
||||||
|
|
||||||
List<GroupData> groups = new ArrayList<>();
|
List<GroupData> groups = new ArrayList<>();
|
||||||
|
|
||||||
try (ResultSet resultSet = this.repository.checkedExecute(
|
try (ResultSet resultSet = this.repository.checkedExecute(sql, member)) {
|
||||||
"SELECT group_id, owner, group_name, description, created, updated, reference, is_open FROM Groups JOIN GroupMembers USING (group_id) WHERE address = ?",
|
|
||||||
member)) {
|
|
||||||
if (resultSet == null)
|
if (resultSet == null)
|
||||||
return groups;
|
return groups;
|
||||||
|
|
||||||
@ -266,10 +277,15 @@ public class HSQLDBGroupRepository implements GroupRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<GroupAdminData> getGroupAdmins(int groupId) throws DataException {
|
public List<GroupAdminData> getGroupAdmins(int groupId, Integer limit, Integer offset, Boolean reverse) throws DataException {
|
||||||
|
String sql = "SELECT admin, reference FROM GroupAdmins WHERE group_id = ? ORDER BY admin";
|
||||||
|
if (reverse != null && reverse)
|
||||||
|
sql += " DESC";
|
||||||
|
sql += HSQLDBRepository.limitOffsetSql(limit, offset);
|
||||||
|
|
||||||
List<GroupAdminData> admins = new ArrayList<>();
|
List<GroupAdminData> admins = new ArrayList<>();
|
||||||
|
|
||||||
try (ResultSet resultSet = this.repository.checkedExecute("SELECT admin, reference FROM GroupAdmins WHERE group_id = ?", groupId)) {
|
try (ResultSet resultSet = this.repository.checkedExecute(sql, groupId)) {
|
||||||
if (resultSet == null)
|
if (resultSet == null)
|
||||||
return admins;
|
return admins;
|
||||||
|
|
||||||
@ -286,6 +302,21 @@ public class HSQLDBGroupRepository implements GroupRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer countGroupAdmins(int groupId) throws DataException {
|
||||||
|
try (ResultSet resultSet = this.repository.checkedExecute("SELECT COUNT(*) FROM GroupAdmins WHERE group_id = ?", groupId)) {
|
||||||
|
int count = resultSet.getInt(1);
|
||||||
|
|
||||||
|
if (count == 0)
|
||||||
|
// There must be at least one admin: the group owner
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return count;
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new DataException("Unable to fetch group admin count from repository", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void save(GroupAdminData groupAdminData) throws DataException {
|
public void save(GroupAdminData groupAdminData) throws DataException {
|
||||||
HSQLDBSaver saveHelper = new HSQLDBSaver("GroupAdmins");
|
HSQLDBSaver saveHelper = new HSQLDBSaver("GroupAdmins");
|
||||||
@ -336,10 +367,15 @@ public class HSQLDBGroupRepository implements GroupRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<GroupMemberData> getGroupMembers(int groupId) throws DataException {
|
public List<GroupMemberData> getGroupMembers(int groupId, Integer limit, Integer offset, Boolean reverse) throws DataException {
|
||||||
|
String sql = "SELECT address, joined, reference FROM GroupMembers WHERE group_id = ? ORDER BY address";
|
||||||
|
if (reverse != null && reverse)
|
||||||
|
sql += " DESC";
|
||||||
|
sql += HSQLDBRepository.limitOffsetSql(limit, offset);
|
||||||
|
|
||||||
List<GroupMemberData> members = new ArrayList<>();
|
List<GroupMemberData> members = new ArrayList<>();
|
||||||
|
|
||||||
try (ResultSet resultSet = this.repository.checkedExecute("SELECT address, joined, reference FROM GroupMembers WHERE group_id = ?", groupId)) {
|
try (ResultSet resultSet = this.repository.checkedExecute(sql, groupId)) {
|
||||||
if (resultSet == null)
|
if (resultSet == null)
|
||||||
return members;
|
return members;
|
||||||
|
|
||||||
@ -359,13 +395,14 @@ public class HSQLDBGroupRepository implements GroupRepository {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Integer countGroupMembers(int groupId) throws DataException {
|
public Integer countGroupMembers(int groupId) throws DataException {
|
||||||
// "GROUP BY" clause required to avoid error "expression not in aggregate or GROUP BY columns: PUBLIC.GROUPS.GROUP_ID"
|
try (ResultSet resultSet = this.repository.checkedExecute("SELECT COUNT(*) FROM GroupMembers WHERE group_id = ?", groupId)) {
|
||||||
try (ResultSet resultSet = this.repository.checkedExecute("SELECT group_id, COUNT(*) FROM GroupMembers WHERE group_id = ? GROUP BY group_id",
|
int count = resultSet.getInt(1);
|
||||||
groupId)) {
|
|
||||||
if (resultSet == null)
|
if (count == 0)
|
||||||
|
// There must be at least one member: the group owner
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return resultSet.getInt(2);
|
return count;
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
throw new DataException("Unable to fetch group member count from repository", e);
|
throw new DataException("Unable to fetch group member count from repository", e);
|
||||||
}
|
}
|
||||||
@ -425,10 +462,15 @@ public class HSQLDBGroupRepository implements GroupRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<GroupInviteData> getInvitesByGroupId(int groupId) throws DataException {
|
public List<GroupInviteData> getInvitesByGroupId(int groupId, Integer limit, Integer offset, Boolean reverse) throws DataException {
|
||||||
|
String sql = "SELECT inviter, invitee, expiry, reference FROM GroupInvites WHERE group_id = ? ORDER BY invitee";
|
||||||
|
if (reverse != null && reverse)
|
||||||
|
sql += " DESC";
|
||||||
|
sql += HSQLDBRepository.limitOffsetSql(limit, offset);
|
||||||
|
|
||||||
List<GroupInviteData> invites = new ArrayList<>();
|
List<GroupInviteData> invites = new ArrayList<>();
|
||||||
|
|
||||||
try (ResultSet resultSet = this.repository.checkedExecute("SELECT inviter, invitee, expiry, reference FROM GroupInvites WHERE group_id = ?", groupId)) {
|
try (ResultSet resultSet = this.repository.checkedExecute(sql, groupId)) {
|
||||||
if (resultSet == null)
|
if (resultSet == null)
|
||||||
return invites;
|
return invites;
|
||||||
|
|
||||||
@ -451,10 +493,15 @@ public class HSQLDBGroupRepository implements GroupRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<GroupInviteData> getInvitesByInvitee(String invitee) throws DataException {
|
public List<GroupInviteData> getInvitesByInvitee(String invitee, Integer limit, Integer offset, Boolean reverse) throws DataException {
|
||||||
|
String sql = "SELECT group_id, inviter, expiry, reference FROM GroupInvites WHERE invitee = ? ORDER BY group_id";
|
||||||
|
if (reverse != null && reverse)
|
||||||
|
sql += " DESC";
|
||||||
|
sql += HSQLDBRepository.limitOffsetSql(limit, offset);
|
||||||
|
|
||||||
List<GroupInviteData> invites = new ArrayList<>();
|
List<GroupInviteData> invites = new ArrayList<>();
|
||||||
|
|
||||||
try (ResultSet resultSet = this.repository.checkedExecute("SELECT group_id, inviter, expiry, reference FROM GroupInvites WHERE invitee = ?", invitee)) {
|
try (ResultSet resultSet = this.repository.checkedExecute(sql, invitee)) {
|
||||||
if (resultSet == null)
|
if (resultSet == null)
|
||||||
return invites;
|
return invites;
|
||||||
|
|
||||||
@ -530,10 +577,15 @@ public class HSQLDBGroupRepository implements GroupRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<GroupJoinRequestData> getGroupJoinRequests(int groupId) throws DataException {
|
public List<GroupJoinRequestData> getGroupJoinRequests(int groupId, Integer limit, Integer offset, Boolean reverse) throws DataException {
|
||||||
|
String sql = "SELECT joiner, reference FROM GroupJoinRequests WHERE group_id = ? ORDER BY joiner";
|
||||||
|
if (reverse != null && reverse)
|
||||||
|
sql += " DESC";
|
||||||
|
sql += HSQLDBRepository.limitOffsetSql(limit, offset);
|
||||||
|
|
||||||
List<GroupJoinRequestData> joinRequests = new ArrayList<>();
|
List<GroupJoinRequestData> joinRequests = new ArrayList<>();
|
||||||
|
|
||||||
try (ResultSet resultSet = this.repository.checkedExecute("SELECT joiner, reference FROM GroupJoinRequests WHERE group_id = ?", groupId)) {
|
try (ResultSet resultSet = this.repository.checkedExecute(sql, groupId)) {
|
||||||
if (resultSet == null)
|
if (resultSet == null)
|
||||||
return joinRequests;
|
return joinRequests;
|
||||||
|
|
||||||
@ -604,11 +656,15 @@ public class HSQLDBGroupRepository implements GroupRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<GroupBanData> getGroupBans(int groupId) throws DataException {
|
public List<GroupBanData> getGroupBans(int groupId, Integer limit, Integer offset, Boolean reverse) throws DataException {
|
||||||
|
String sql = "SELECT offender, admin, banned, reason, expiry, reference FROM GroupBans WHERE group_id = ? ORDER BY offender";
|
||||||
|
if (reverse != null && reverse)
|
||||||
|
sql += " DESC";
|
||||||
|
sql += HSQLDBRepository.limitOffsetSql(limit, offset);
|
||||||
|
|
||||||
List<GroupBanData> bans = new ArrayList<>();
|
List<GroupBanData> bans = new ArrayList<>();
|
||||||
|
|
||||||
try (ResultSet resultSet = this.repository.checkedExecute("SELECT offender, admin, banned, reason, expiry, reference FROM GroupBans WHERE group_id = ?",
|
try (ResultSet resultSet = this.repository.checkedExecute(sql, groupId)) {
|
||||||
groupId)) {
|
|
||||||
if (resultSet == null)
|
if (resultSet == null)
|
||||||
return bans;
|
return bans;
|
||||||
|
|
||||||
|
@ -55,11 +55,15 @@ public class HSQLDBNameRepository implements NameRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<NameData> getAllNames() throws DataException {
|
public List<NameData> getAllNames(Integer limit, Integer offset, Boolean reverse) throws DataException {
|
||||||
|
String sql = "SELECT name, data, owner, registered, updated, reference, is_for_sale, sale_price FROM Names ORDER BY name";
|
||||||
|
if (reverse != null && reverse)
|
||||||
|
sql += " DESC";
|
||||||
|
sql += HSQLDBRepository.limitOffsetSql(limit, offset);
|
||||||
|
|
||||||
List<NameData> names = new ArrayList<>();
|
List<NameData> names = new ArrayList<>();
|
||||||
|
|
||||||
try (ResultSet resultSet = this.repository
|
try (ResultSet resultSet = this.repository.checkedExecute(sql)) {
|
||||||
.checkedExecute("SELECT name, data, owner, registered, updated, reference, is_for_sale, sale_price FROM Names")) {
|
|
||||||
if (resultSet == null)
|
if (resultSet == null)
|
||||||
return names;
|
return names;
|
||||||
|
|
||||||
@ -87,11 +91,15 @@ public class HSQLDBNameRepository implements NameRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<NameData> getNamesForSale() throws DataException {
|
public List<NameData> getNamesForSale(Integer limit, Integer offset, Boolean reverse) throws DataException {
|
||||||
|
String sql = "SELECT name, data, owner, registered, updated, reference, sale_price FROM Names WHERE is_for_sale = TRUE ORDER BY name";
|
||||||
|
if (reverse != null && reverse)
|
||||||
|
sql += " DESC";
|
||||||
|
sql += HSQLDBRepository.limitOffsetSql(limit, offset);
|
||||||
|
|
||||||
List<NameData> names = new ArrayList<>();
|
List<NameData> names = new ArrayList<>();
|
||||||
|
|
||||||
try (ResultSet resultSet = this.repository
|
try (ResultSet resultSet = this.repository.checkedExecute(sql)) {
|
||||||
.checkedExecute("SELECT name, data, owner, registered, updated, reference, sale_price FROM Names WHERE is_for_sale = TRUE")) {
|
|
||||||
if (resultSet == null)
|
if (resultSet == null)
|
||||||
return names;
|
return names;
|
||||||
|
|
||||||
@ -119,11 +127,15 @@ public class HSQLDBNameRepository implements NameRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<NameData> getNamesByOwner(String owner) throws DataException {
|
public List<NameData> getNamesByOwner(String owner, Integer limit, Integer offset, Boolean reverse) throws DataException {
|
||||||
|
String sql = "SELECT name, data, registered, updated, reference, is_for_sale, sale_price FROM Names WHERE owner = ? ORDER BY name";
|
||||||
|
if (reverse != null && reverse)
|
||||||
|
sql += " DESC";
|
||||||
|
sql += HSQLDBRepository.limitOffsetSql(limit, offset);
|
||||||
|
|
||||||
List<NameData> names = new ArrayList<>();
|
List<NameData> names = new ArrayList<>();
|
||||||
|
|
||||||
try (ResultSet resultSet = this.repository
|
try (ResultSet resultSet = this.repository.checkedExecute(sql, owner)) {
|
||||||
.checkedExecute("SELECT name, data, registered, updated, reference, is_for_sale, sale_price FROM Names WHERE owner = ?", owner)) {
|
|
||||||
if (resultSet == null)
|
if (resultSet == null)
|
||||||
return names;
|
return names;
|
||||||
|
|
||||||
@ -157,9 +169,9 @@ public class HSQLDBNameRepository implements NameRepository {
|
|||||||
Long updated = nameData.getUpdated();
|
Long updated = nameData.getUpdated();
|
||||||
Timestamp updatedTimestamp = updated == null ? null : new Timestamp(updated);
|
Timestamp updatedTimestamp = updated == null ? null : new Timestamp(updated);
|
||||||
|
|
||||||
saveHelper.bind("owner", nameData.getOwner()).bind("name", nameData.getName())
|
saveHelper.bind("owner", nameData.getOwner()).bind("name", nameData.getName()).bind("data", nameData.getData())
|
||||||
.bind("data", nameData.getData()).bind("registered", new Timestamp(nameData.getRegistered())).bind("updated", updatedTimestamp)
|
.bind("registered", new Timestamp(nameData.getRegistered())).bind("updated", updatedTimestamp).bind("reference", nameData.getReference())
|
||||||
.bind("reference", nameData.getReference()).bind("is_for_sale", nameData.getIsForSale()).bind("sale_price", nameData.getSalePrice());
|
.bind("is_for_sale", nameData.getIsForSale()).bind("sale_price", nameData.getSalePrice());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
saveHelper.execute(this.repository);
|
saveHelper.execute(this.repository);
|
||||||
|
@ -287,4 +287,25 @@ public class HSQLDBRepository implements Repository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns additional SQL "LIMIT" and "OFFSET" clauses.
|
||||||
|
* <p>
|
||||||
|
* (Convenience method for HSQLDB repository subclasses).
|
||||||
|
*
|
||||||
|
* @param limit
|
||||||
|
* @param offset
|
||||||
|
* @return SQL string, potentially empty but never null
|
||||||
|
*/
|
||||||
|
public static String limitOffsetSql(Integer limit, Integer offset) {
|
||||||
|
String sql = "";
|
||||||
|
|
||||||
|
if (limit != null && limit > 0)
|
||||||
|
sql += " LIMIT " + limit;
|
||||||
|
|
||||||
|
if (offset != null)
|
||||||
|
sql += " OFFSET " + offset;
|
||||||
|
|
||||||
|
return sql;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ import java.util.List;
|
|||||||
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.qora.api.resource.TransactionsResource.ConfirmationStatus;
|
||||||
import org.qora.data.PaymentData;
|
import org.qora.data.PaymentData;
|
||||||
import org.qora.data.transaction.TransactionData;
|
import org.qora.data.transaction.TransactionData;
|
||||||
import org.qora.repository.DataException;
|
import org.qora.repository.DataException;
|
||||||
@ -53,7 +54,8 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
|||||||
|
|
||||||
private static Class<?> getClassByTxType(TransactionType txType) {
|
private static Class<?> getClassByTxType(TransactionType txType) {
|
||||||
try {
|
try {
|
||||||
return Class.forName(String.join("", HSQLDBTransactionRepository.class.getPackage().getName(), ".", "HSQLDB", txType.className, "TransactionRepository"));
|
return Class.forName(
|
||||||
|
String.join("", HSQLDBTransactionRepository.class.getPackage().getName(), ".", "HSQLDB", txType.className, "TransactionRepository"));
|
||||||
} catch (ClassNotFoundException e) {
|
} catch (ClassNotFoundException e) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -204,7 +206,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<byte[]> getAllSignaturesInvolvingAddress(String address) throws DataException {
|
public List<byte[]> getSignaturesInvolvingAddress(String address) throws DataException {
|
||||||
List<byte[]> signatures = new ArrayList<byte[]>();
|
List<byte[]> signatures = new ArrayList<byte[]>();
|
||||||
|
|
||||||
try (ResultSet resultSet = this.repository.checkedExecute("SELECT signature FROM TransactionRecipients WHERE participant = ?", address)) {
|
try (ResultSet resultSet = this.repository.checkedExecute("SELECT signature FROM TransactionRecipients WHERE participant = ?", address)) {
|
||||||
@ -250,7 +252,8 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<byte[]> getAllSignaturesMatchingCriteria(Integer startBlock, Integer blockLimit, TransactionType txType, String address) throws DataException {
|
public List<byte[]> getSignaturesMatchingCriteria(Integer startBlock, Integer blockLimit, TransactionType txType, String address,
|
||||||
|
ConfirmationStatus confirmationStatus, Integer limit, Integer offset, Boolean reverse) throws DataException {
|
||||||
List<byte[]> signatures = new ArrayList<byte[]>();
|
List<byte[]> signatures = new ArrayList<byte[]>();
|
||||||
|
|
||||||
boolean hasAddress = address != null && !address.isEmpty();
|
boolean hasAddress = address != null && !address.isEmpty();
|
||||||
@ -258,33 +261,42 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
|||||||
boolean hasHeightRange = startBlock != null || blockLimit != null;
|
boolean hasHeightRange = startBlock != null || blockLimit != null;
|
||||||
|
|
||||||
if (hasHeightRange && startBlock == null)
|
if (hasHeightRange && startBlock == null)
|
||||||
startBlock = 1;
|
startBlock = (reverse == null || !reverse) ? 1 : this.repository.getBlockRepository().getBlockchainHeight() - blockLimit;
|
||||||
|
|
||||||
String signatureColumn = "NULL";
|
String signatureColumn = "Transactions.signature";
|
||||||
List<Object> bindParams = new ArrayList<Object>();
|
List<String> whereClauses = new ArrayList<String>();
|
||||||
String groupBy = "";
|
String groupBy = "";
|
||||||
|
List<Object> bindParams = new ArrayList<Object>();
|
||||||
|
|
||||||
// Table JOINs first
|
// Tables, starting with Transactions
|
||||||
List<String> tableJoins = new ArrayList<String>();
|
String tables = "Transactions";
|
||||||
|
|
||||||
// Always JOIN BlockTransactions as we only ever want confirmed transactions
|
// BlockTransactions if we want confirmed transactions
|
||||||
tableJoins.add("Blocks");
|
switch (confirmationStatus) {
|
||||||
tableJoins.add("BlockTransactions ON BlockTransactions.block_signature = Blocks.signature");
|
case BOTH:
|
||||||
signatureColumn = "BlockTransactions.transaction_signature";
|
break;
|
||||||
|
|
||||||
// Always JOIN Transactions as we want to order by timestamp
|
case CONFIRMED:
|
||||||
tableJoins.add("Transactions ON Transactions.signature = BlockTransactions.transaction_signature");
|
tables += " JOIN BlockTransactions ON BlockTransactions.transaction_signature = Transactions.signature";
|
||||||
signatureColumn = "Transactions.signature";
|
|
||||||
|
if (hasHeightRange)
|
||||||
|
tables += " JOIN Blocks ON Blocks.signature = BlockTransactions.block_signature";
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case UNCONFIRMED:
|
||||||
|
tables += " LEFT OUTER JOIN BlockTransactions ON BlockTransactions.transaction_signature = Transactions.signature";
|
||||||
|
whereClauses.add("BlockTransactions.transaction_signature IS NULL");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (hasAddress) {
|
if (hasAddress) {
|
||||||
tableJoins.add("TransactionParticipants ON TransactionParticipants.signature = Transactions.signature");
|
tables += " JOIN TransactionParticipants ON TransactionParticipants.signature = Transactions.signature";
|
||||||
signatureColumn = "TransactionParticipants.signature";
|
|
||||||
groupBy = " GROUP BY TransactionParticipants.signature, Transactions.creation";
|
groupBy = " GROUP BY TransactionParticipants.signature, Transactions.creation";
|
||||||
|
signatureColumn = "TransactionParticipants.signature";
|
||||||
}
|
}
|
||||||
|
|
||||||
// WHERE clauses next
|
// WHERE clauses next
|
||||||
List<String> whereClauses = new ArrayList<String>();
|
|
||||||
|
|
||||||
if (hasHeightRange) {
|
if (hasHeightRange) {
|
||||||
whereClauses.add("Blocks.height >= " + startBlock);
|
whereClauses.add("Blocks.height >= " + startBlock);
|
||||||
|
|
||||||
@ -300,7 +312,19 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
|||||||
bindParams.add(address);
|
bindParams.add(address);
|
||||||
}
|
}
|
||||||
|
|
||||||
String sql = "SELECT " + signatureColumn + " FROM " + String.join(" JOIN ", tableJoins) + " WHERE " + String.join(" AND ", whereClauses) + groupBy + " ORDER BY Transactions.creation ASC";
|
String sql = "SELECT " + signatureColumn + " FROM " + tables;
|
||||||
|
|
||||||
|
if (!whereClauses.isEmpty())
|
||||||
|
sql += " WHERE " + String.join(" AND ", whereClauses);
|
||||||
|
|
||||||
|
if (!groupBy.isEmpty())
|
||||||
|
sql += groupBy;
|
||||||
|
|
||||||
|
sql += " ORDER BY Transactions.creation";
|
||||||
|
sql += (reverse == null || !reverse) ? " ASC" : " DESC";
|
||||||
|
|
||||||
|
sql += HSQLDBRepository.limitOffsetSql(limit, offset);
|
||||||
|
|
||||||
LOGGER.trace(sql);
|
LOGGER.trace(sql);
|
||||||
|
|
||||||
try (ResultSet resultSet = this.repository.checkedExecute(sql, bindParams.toArray())) {
|
try (ResultSet resultSet = this.repository.checkedExecute(sql, bindParams.toArray())) {
|
||||||
@ -320,11 +344,19 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<TransactionData> getAllUnconfirmedTransactions() throws DataException {
|
public List<TransactionData> getUnconfirmedTransactions(Integer limit, Integer offset, Boolean reverse) throws DataException {
|
||||||
|
String sql = "SELECT signature FROM UnconfirmedTransactions ORDER BY creation";
|
||||||
|
if (reverse != null && reverse)
|
||||||
|
sql += " DESC";
|
||||||
|
sql += ", signature";
|
||||||
|
if (reverse != null && reverse)
|
||||||
|
sql += " DESC";
|
||||||
|
sql += HSQLDBRepository.limitOffsetSql(limit, offset);
|
||||||
|
|
||||||
List<TransactionData> transactions = new ArrayList<TransactionData>();
|
List<TransactionData> transactions = new ArrayList<TransactionData>();
|
||||||
|
|
||||||
// Find transactions with no corresponding row in BlockTransactions
|
// Find transactions with no corresponding row in BlockTransactions
|
||||||
try (ResultSet resultSet = this.repository.checkedExecute("SELECT signature FROM UnconfirmedTransactions ORDER BY creation ASC, signature ASC")) {
|
try (ResultSet resultSet = this.repository.checkedExecute(sql)) {
|
||||||
if (resultSet == null)
|
if (resultSet == null)
|
||||||
return transactions;
|
return transactions;
|
||||||
|
|
||||||
|
@ -468,7 +468,7 @@ public abstract class Transaction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private int countUnconfirmedByCreator(PublicKeyAccount creator) throws DataException {
|
private int countUnconfirmedByCreator(PublicKeyAccount creator) throws DataException {
|
||||||
List<TransactionData> unconfirmedTransactions = repository.getTransactionRepository().getAllUnconfirmedTransactions();
|
List<TransactionData> unconfirmedTransactions = repository.getTransactionRepository().getUnconfirmedTransactions();
|
||||||
|
|
||||||
int count = 0;
|
int count = 0;
|
||||||
for (TransactionData transactionData : unconfirmedTransactions) {
|
for (TransactionData transactionData : unconfirmedTransactions) {
|
||||||
@ -495,7 +495,7 @@ public abstract class Transaction {
|
|||||||
public static List<TransactionData> getUnconfirmedTransactions(Repository repository) throws DataException {
|
public static List<TransactionData> getUnconfirmedTransactions(Repository repository) throws DataException {
|
||||||
BlockData latestBlockData = repository.getBlockRepository().getLastBlock();
|
BlockData latestBlockData = repository.getBlockRepository().getLastBlock();
|
||||||
|
|
||||||
List<TransactionData> unconfirmedTransactions = repository.getTransactionRepository().getAllUnconfirmedTransactions();
|
List<TransactionData> unconfirmedTransactions = repository.getTransactionRepository().getUnconfirmedTransactions();
|
||||||
List<TransactionData> invalidTransactions = new ArrayList<>();
|
List<TransactionData> invalidTransactions = new ArrayList<>();
|
||||||
|
|
||||||
unconfirmedTransactions.sort(getDataComparator());
|
unconfirmedTransactions.sort(getDataComparator());
|
||||||
|
Loading…
x
Reference in New Issue
Block a user