mirror of
https://github.com/Qortal/qortal.git
synced 2025-02-14 11:15: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/resources=UTF-8
|
||||
encoding//src/test/java=UTF-8
|
||||
encoding//target/generated-sources/package-info=UTF-8
|
||||
encoding/<project>=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"
|
||||
logger.hsqldb.name = hsqldb.db
|
||||
logger.hsqldb.level = warn
|
||||
logger.hsqldb.appenderRef.rolling.ref = FILE
|
||||
|
||||
# Override logging level for this class
|
||||
logger.voting.name = qora.transaction.VoteOnPollTransaction
|
||||
logger.voting.level = trace
|
||||
logger.voting.appenderRef.rolling.ref = FILE
|
||||
# Suppress extraneous Jersey warning
|
||||
logger.jerseyInject.name = org.glassfish.jersey.internal.inject.Providers
|
||||
logger.jerseyInject.level = error
|
||||
|
||||
# Override logging level for this class
|
||||
logger.assets.name = qora.assets.Order
|
||||
logger.assets.level = trace
|
||||
logger.assets.appenderRef.rolling.ref = FILE
|
||||
# Debugging transaction searches
|
||||
logger.txSearch.name = org.qora.repository.hsqldb.transaction.HSQLDBTransactionRepository
|
||||
logger.txSearch.level = trace
|
||||
|
||||
appender.console.type = Console
|
||||
appender.console.name = stdout
|
||||
|
19
pom.xml
19
pom.xml
@ -305,17 +305,24 @@
|
||||
<artifactId>log4j-api</artifactId>
|
||||
<version>${log4j.version}</version>
|
||||
</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>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>${slf4j.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-simple</artifactId>
|
||||
<version>${slf4j.version}</version>
|
||||
</dependency>
|
||||
<!-- Servlet related -->
|
||||
<dependency>
|
||||
<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.XmlElement;
|
||||
|
||||
import org.qora.data.group.GroupData;
|
||||
import org.qora.data.group.GroupMemberData;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
@Schema(description = "Group info, maybe including members")
|
||||
// All properties to be converted to JSON via JAX-RS
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
public class GroupWithMemberInfo {
|
||||
|
||||
@Schema(implementation = GroupData.class, name = "group", title = "group info")
|
||||
@XmlElement(name = "group")
|
||||
public GroupData groupData;
|
||||
public class GroupMembers {
|
||||
|
||||
Integer memberCount;
|
||||
|
||||
@XmlElement(name = "admins")
|
||||
public List<String> groupAdminAddresses;
|
||||
Integer adminCount;
|
||||
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
@Schema(description = "Member info")
|
||||
public static class MemberInfo {
|
||||
public String member;
|
||||
public long joined;
|
||||
public Long joined;
|
||||
public Boolean isAdmin;
|
||||
|
||||
// For JAX-RS
|
||||
protected MemberInfo() {
|
||||
}
|
||||
|
||||
public MemberInfo(GroupMemberData groupMemberData) {
|
||||
this.member = groupMemberData.getMember();
|
||||
this.joined = groupMemberData.getJoined();
|
||||
public MemberInfo(String member, Long joined, boolean isAdmin) {
|
||||
this.member = member;
|
||||
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;
|
||||
|
||||
// For JAX-RS
|
||||
protected GroupWithMemberInfo() {
|
||||
protected GroupMembers() {
|
||||
}
|
||||
|
||||
public GroupWithMemberInfo(GroupData groupData, List<String> groupAdminAddresses, List<MemberInfo> groupMembers, Integer memberCount) {
|
||||
this.groupData = groupData;
|
||||
this.groupAdminAddresses = groupAdminAddresses;
|
||||
public GroupMembers(List<MemberInfo> groupMembers, Integer memberCount, Integer adminCount) {
|
||||
this.groupMembers = groupMembers;
|
||||
this.memberCount = memberCount;
|
||||
this.adminCount = adminCount;
|
||||
}
|
||||
|
||||
}
|
@ -1,21 +1,18 @@
|
||||
package org.qora.api.resource;
|
||||
|
||||
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.Schema;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.core.Context;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
|
||||
@ -26,9 +23,7 @@ import org.qora.api.ApiException;
|
||||
import org.qora.api.ApiExceptionFactory;
|
||||
import org.qora.asset.Asset;
|
||||
import org.qora.crypto.Crypto;
|
||||
import org.qora.data.account.AccountBalanceData;
|
||||
import org.qora.data.account.AccountData;
|
||||
import org.qora.data.asset.OrderData;
|
||||
import org.qora.repository.DataException;
|
||||
import org.qora.repository.Repository;
|
||||
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
|
||||
@Path("/balance/{address}/{confirmations}")
|
||||
@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
|
||||
@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 = "string", format = "byte"))
|
||||
@Parameter(in = ParameterIn.PATH, name = "otherAssetId", description = "Asset ID, 0 is native coin", schema = @Schema(type = "string", format = "byte"))
|
||||
@Parameter(in = ParameterIn.PATH, name = "assetid", 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 = "integer"))
|
||||
@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 = "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 = "includeTransactions", description = "Include associated transactions in 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"))
|
||||
@Parameter(in = ParameterIn.QUERY, name = "reverse", description = "Reverse results", schema = @Schema(type = "boolean"))
|
||||
public String globalParameters() {
|
||||
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.tags.Tag;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@ -22,13 +23,15 @@ import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.core.Context;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
|
||||
import org.qora.account.Account;
|
||||
import org.qora.api.ApiError;
|
||||
import org.qora.api.ApiErrors;
|
||||
import org.qora.api.ApiException;
|
||||
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.crypto.Crypto;
|
||||
import org.qora.data.account.AccountBalanceData;
|
||||
import org.qora.data.account.AccountData;
|
||||
import org.qora.data.asset.AssetData;
|
||||
import org.qora.data.asset.OrderData;
|
||||
import org.qora.data.asset.TradeData;
|
||||
@ -47,8 +50,12 @@ import org.qora.transform.transaction.IssueAssetTransactionTransformer;
|
||||
import org.qora.utils.Base58;
|
||||
|
||||
@Path("/assets")
|
||||
@Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN})
|
||||
@Tag(name = "Assets")
|
||||
@Produces({
|
||||
MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN
|
||||
})
|
||||
@Tag(
|
||||
name = "Assets"
|
||||
)
|
||||
public class AssetsResource {
|
||||
|
||||
@Context
|
||||
@ -60,21 +67,28 @@ public class AssetsResource {
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
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})
|
||||
public List<AssetData> getAllAssets(@Parameter(ref = "limit") @QueryParam("limit") int limit, @Parameter(ref = "offset") @QueryParam("offset") int offset) {
|
||||
@ApiErrors({
|
||||
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()) {
|
||||
List<AssetData> assets = repository.getAssetRepository().getAllAssets();
|
||||
|
||||
// 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;
|
||||
return repository.getAssetRepository().getAllAssets(limit, offset, reverse);
|
||||
} catch (DataException e) {
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||
}
|
||||
@ -88,12 +102,18 @@ public class AssetsResource {
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
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})
|
||||
public AssetWithHolders getAssetInfo(@QueryParam("assetId") Integer assetId, @QueryParam("assetName") String assetName, @Parameter(ref = "includeHolders") @QueryParam("includeHolders") boolean includeHolders) {
|
||||
@ApiErrors({
|
||||
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()))
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
|
||||
|
||||
@ -108,31 +128,81 @@ public class AssetsResource {
|
||||
if (assetData == null)
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ASSET_ID);
|
||||
|
||||
List<AccountBalanceData> holders = null;
|
||||
if (includeHolders)
|
||||
holders = repository.getAccountRepository().getAssetBalances(assetData.getAssetId());
|
||||
|
||||
return new AssetWithHolders(assetData, holders);
|
||||
return assetData;
|
||||
} catch (DataException e) {
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||
}
|
||||
}
|
||||
|
||||
@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(
|
||||
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 = {
|
||||
@ApiResponse(
|
||||
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})
|
||||
public List<OrderData> getAssetOrders(@Parameter(ref = "assetId") @PathParam("assetId") int assetId, @Parameter(ref = "otherAssetId") @PathParam("otherAssetId") int otherAssetId,
|
||||
@Parameter(ref = "limit") @QueryParam("limit") int limit, @Parameter(ref = "offset") @QueryParam("offset") int offset) {
|
||||
@ApiErrors({
|
||||
ApiError.INVALID_ASSET_ID, ApiError.REPOSITORY_ISSUE
|
||||
})
|
||||
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()) {
|
||||
if (!repository.getAssetRepository().assetExists(assetId))
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ASSET_ID);
|
||||
@ -140,36 +210,45 @@ public class AssetsResource {
|
||||
if (!repository.getAssetRepository().assetExists(otherAssetId))
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ASSET_ID);
|
||||
|
||||
List<OrderData> orders = repository.getAssetRepository().getOpenOrders(assetId, otherAssetId);
|
||||
|
||||
// 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;
|
||||
return repository.getAssetRepository().getOpenOrders(assetId, otherAssetId, limit, offset, reverse);
|
||||
} catch (DataException e) {
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/trades/{assetId}/{otherAssetId}")
|
||||
@Path("/trades/{assetid}/{otherassetid}")
|
||||
@Operation(
|
||||
summary = "Asset trades",
|
||||
description = "Returns successful trades of {assetId} for {otherAssetId}.<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.",
|
||||
description = "Returns successful trades of {assetid} for {otherassetid}.<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.",
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
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})
|
||||
public List<TradeWithOrderInfo> getAssetTrades(@Parameter(ref = "assetId") @PathParam("assetId") int assetId, @Parameter(ref = "otherAssetId") @PathParam("otherAssetId") int otherAssetId,
|
||||
@Parameter(ref = "limit") @QueryParam("limit") int limit, @Parameter(ref = "offset") @QueryParam("offset") int offset) {
|
||||
@ApiErrors({
|
||||
ApiError.INVALID_ASSET_ID, ApiError.REPOSITORY_ISSUE
|
||||
})
|
||||
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()) {
|
||||
if (!repository.getAssetRepository().assetExists(assetId))
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ASSET_ID);
|
||||
@ -177,12 +256,7 @@ public class AssetsResource {
|
||||
if (!repository.getAssetRepository().assetExists(otherAssetId))
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ASSET_ID);
|
||||
|
||||
List<TradeData> trades = repository.getAssetRepository().getTrades(assetId, otherAssetId);
|
||||
|
||||
// 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);
|
||||
List<TradeData> trades = repository.getAssetRepository().getTrades(assetId, otherAssetId, limit, offset, reverse);
|
||||
|
||||
// Expanding remaining entries
|
||||
List<TradeWithOrderInfo> fullTrades = new ArrayList<>();
|
||||
@ -199,19 +273,25 @@ public class AssetsResource {
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/order/{orderId}")
|
||||
@Path("/order/{orderid}")
|
||||
@Operation(
|
||||
summary = "Fetch asset order",
|
||||
description = "Returns asset order info.",
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
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})
|
||||
public OrderWithTrades getAssetOrder(@PathParam("orderId") String orderId58) {
|
||||
@ApiErrors({
|
||||
ApiError.INVALID_ORDER_ID, ApiError.ORDER_NO_EXISTS, ApiError.REPOSITORY_ISSUE
|
||||
})
|
||||
public OrderData getAssetOrder(@PathParam("orderid") String orderId58) {
|
||||
// Decode orderID
|
||||
byte[] orderId;
|
||||
try {
|
||||
@ -223,11 +303,180 @@ public class AssetsResource {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
OrderData orderData = repository.getAssetRepository().fromOrderId(orderId);
|
||||
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) {
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||
}
|
||||
@ -237,11 +486,13 @@ public class AssetsResource {
|
||||
@Path("/order/delete")
|
||||
@Operation(
|
||||
summary = "Cancel existing asset order",
|
||||
requestBody = @RequestBody(
|
||||
requestBody = @RequestBody(
|
||||
required = true,
|
||||
content = @Content(
|
||||
mediaType = MediaType.APPLICATION_JSON,
|
||||
schema = @Schema(implementation = CancelAssetOrderTransactionData.class)
|
||||
schema = @Schema(
|
||||
implementation = CancelAssetOrderTransactionData.class
|
||||
)
|
||||
)
|
||||
),
|
||||
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) {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
Transaction transaction = Transaction.fromData(repository, transactionData);
|
||||
@ -282,7 +535,9 @@ public class AssetsResource {
|
||||
required = true,
|
||||
content = @Content(
|
||||
mediaType = MediaType.APPLICATION_JSON,
|
||||
schema = @Schema(implementation = IssueAssetTransactionData.class)
|
||||
schema = @Schema(
|
||||
implementation = IssueAssetTransactionData.class
|
||||
)
|
||||
)
|
||||
),
|
||||
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) {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
Transaction transaction = Transaction.fromData(repository, transactionData);
|
||||
@ -323,7 +580,9 @@ public class AssetsResource {
|
||||
required = true,
|
||||
content = @Content(
|
||||
mediaType = MediaType.APPLICATION_JSON,
|
||||
schema = @Schema(implementation = CreateAssetOrderTransactionData.class)
|
||||
schema = @Schema(
|
||||
implementation = CreateAssetOrderTransactionData.class
|
||||
)
|
||||
)
|
||||
),
|
||||
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) {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
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.Parameter;
|
||||
import io.swagger.v3.oas.annotations.media.ArraySchema;
|
||||
import io.swagger.v3.oas.annotations.media.Content;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
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.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
@ -26,7 +26,6 @@ import org.qora.api.ApiError;
|
||||
import org.qora.api.ApiErrors;
|
||||
import org.qora.api.ApiException;
|
||||
import org.qora.api.ApiExceptionFactory;
|
||||
import org.qora.api.model.BlockWithTransactions;
|
||||
import org.qora.block.Block;
|
||||
import org.qora.data.block.BlockData;
|
||||
import org.qora.data.transaction.TransactionData;
|
||||
@ -36,8 +35,12 @@ import org.qora.repository.RepositoryManager;
|
||||
import org.qora.utils.Base58;
|
||||
|
||||
@Path("/blocks")
|
||||
@Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN})
|
||||
@Tag(name = "Blocks")
|
||||
@Produces({
|
||||
MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN
|
||||
})
|
||||
@Tag(
|
||||
name = "Blocks"
|
||||
)
|
||||
public class BlocksResource {
|
||||
|
||||
@Context
|
||||
@ -53,14 +56,16 @@ public class BlocksResource {
|
||||
description = "the block",
|
||||
content = @Content(
|
||||
schema = @Schema(
|
||||
implementation = BlockWithTransactions.class
|
||||
implementation = BlockData.class
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
@ApiErrors({ApiError.INVALID_SIGNATURE, ApiError.BLOCK_NO_EXISTS, ApiError.REPOSITORY_ISSUE})
|
||||
public BlockWithTransactions getBlock(@PathParam("signature") String signature58, @Parameter(ref = "includeTransactions") @QueryParam("includeTransactions") boolean includeTransactions) {
|
||||
@ApiErrors({
|
||||
ApiError.INVALID_SIGNATURE, ApiError.BLOCK_NO_EXISTS, ApiError.REPOSITORY_ISSUE
|
||||
})
|
||||
public BlockData getBlock(@PathParam("signature") String signature58) {
|
||||
// Decode signature
|
||||
byte[] signature;
|
||||
try {
|
||||
@ -70,8 +75,55 @@ public class BlocksResource {
|
||||
}
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
BlockData blockData = repository.getBlockRepository().fromSignature(signature);
|
||||
return packageBlockData(repository, blockData, includeTransactions);
|
||||
return repository.getBlockRepository().fromSignature(signature);
|
||||
} 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) {
|
||||
throw e;
|
||||
} catch (DataException e) {
|
||||
@ -89,17 +141,18 @@ public class BlocksResource {
|
||||
description = "the block",
|
||||
content = @Content(
|
||||
schema = @Schema(
|
||||
implementation = BlockWithTransactions.class
|
||||
implementation = BlockData.class
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
@ApiErrors({ApiError.BLOCK_NO_EXISTS, ApiError.REPOSITORY_ISSUE})
|
||||
public BlockWithTransactions getFirstBlock(@Parameter(ref = "includeTransactions") @QueryParam("includeTransactions") boolean includeTransactions) {
|
||||
@ApiErrors({
|
||||
ApiError.BLOCK_NO_EXISTS, ApiError.REPOSITORY_ISSUE
|
||||
})
|
||||
public BlockData getFirstBlock() {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
BlockData blockData = repository.getBlockRepository().fromHeight(1);
|
||||
return packageBlockData(repository, blockData, includeTransactions);
|
||||
return repository.getBlockRepository().fromHeight(1);
|
||||
} catch (ApiException e) {
|
||||
throw e;
|
||||
} catch (DataException e) {
|
||||
@ -117,17 +170,18 @@ public class BlocksResource {
|
||||
description = "the block",
|
||||
content = @Content(
|
||||
schema = @Schema(
|
||||
implementation = BlockWithTransactions.class
|
||||
implementation = BlockData.class
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
@ApiErrors({ApiError.BLOCK_NO_EXISTS, ApiError.REPOSITORY_ISSUE})
|
||||
public BlockWithTransactions getLastBlock(@Parameter(ref = "includeTransactions") @QueryParam("includeTransactions") boolean includeTransactions) {
|
||||
@ApiErrors({
|
||||
ApiError.BLOCK_NO_EXISTS, ApiError.REPOSITORY_ISSUE
|
||||
})
|
||||
public BlockData getLastBlock() {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
BlockData blockData = repository.getBlockRepository().getLastBlock();
|
||||
return packageBlockData(repository, blockData, includeTransactions);
|
||||
return repository.getBlockRepository().getLastBlock();
|
||||
} catch (ApiException e) {
|
||||
throw e;
|
||||
} catch (DataException e) {
|
||||
@ -145,14 +199,16 @@ public class BlocksResource {
|
||||
description = "the block",
|
||||
content = @Content(
|
||||
schema = @Schema(
|
||||
implementation = BlockWithTransactions.class
|
||||
implementation = BlockData.class
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
@ApiErrors({ApiError.INVALID_SIGNATURE, ApiError.BLOCK_NO_EXISTS, ApiError.REPOSITORY_ISSUE})
|
||||
public BlockWithTransactions getChild(@PathParam("signature") String signature58, @Parameter(ref = "includeTransactions") @QueryParam("includeTransactions") boolean includeTransactions) {
|
||||
@ApiErrors({
|
||||
ApiError.INVALID_SIGNATURE, ApiError.BLOCK_NO_EXISTS, ApiError.REPOSITORY_ISSUE
|
||||
})
|
||||
public BlockData getChild(@PathParam("signature") String signature58) {
|
||||
// Decode signature
|
||||
byte[] signature;
|
||||
try {
|
||||
@ -170,8 +226,11 @@ public class BlocksResource {
|
||||
|
||||
BlockData childBlockData = repository.getBlockRepository().fromReference(signature);
|
||||
|
||||
// Checking child exists is handled by packageBlockData()
|
||||
return packageBlockData(repository, childBlockData, includeTransactions);
|
||||
// Check child block exists
|
||||
if (childBlockData == null)
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.BLOCK_NO_EXISTS);
|
||||
|
||||
return childBlockData;
|
||||
} catch (ApiException e) {
|
||||
throw e;
|
||||
} catch (DataException e) {
|
||||
@ -196,7 +255,9 @@ public class BlocksResource {
|
||||
)
|
||||
}
|
||||
)
|
||||
@ApiErrors({ApiError.REPOSITORY_ISSUE})
|
||||
@ApiErrors({
|
||||
ApiError.REPOSITORY_ISSUE
|
||||
})
|
||||
public BigDecimal getGeneratingBalance() {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
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) {
|
||||
// Decode signature
|
||||
byte[] signature;
|
||||
@ -269,7 +332,9 @@ public class BlocksResource {
|
||||
)
|
||||
}
|
||||
)
|
||||
@ApiErrors({ApiError.REPOSITORY_ISSUE})
|
||||
@ApiErrors({
|
||||
ApiError.REPOSITORY_ISSUE
|
||||
})
|
||||
public long getTimePerBlock() {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
BlockData blockData = repository.getBlockRepository().getLastBlock();
|
||||
@ -319,7 +384,9 @@ public class BlocksResource {
|
||||
)
|
||||
}
|
||||
)
|
||||
@ApiErrors({ApiError.REPOSITORY_ISSUE})
|
||||
@ApiErrors({
|
||||
ApiError.REPOSITORY_ISSUE
|
||||
})
|
||||
public int getHeight() {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
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) {
|
||||
// Decode signature
|
||||
byte[] signature;
|
||||
@ -382,17 +451,22 @@ public class BlocksResource {
|
||||
description = "the block",
|
||||
content = @Content(
|
||||
schema = @Schema(
|
||||
implementation = BlockWithTransactions.class
|
||||
implementation = BlockData.class
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
@ApiErrors({ApiError.BLOCK_NO_EXISTS, ApiError.REPOSITORY_ISSUE})
|
||||
public BlockWithTransactions getByHeight(@PathParam("height") int height, @Parameter(ref = "includeTransactions") @QueryParam("includeTransactions") boolean includeTransactions) {
|
||||
@ApiErrors({
|
||||
ApiError.BLOCK_NO_EXISTS, ApiError.REPOSITORY_ISSUE
|
||||
})
|
||||
public BlockData getByHeight(@PathParam("height") int height) {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
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) {
|
||||
throw e;
|
||||
} catch (DataException e) {
|
||||
@ -409,19 +483,23 @@ public class BlocksResource {
|
||||
@ApiResponse(
|
||||
description = "blocks",
|
||||
content = @Content(
|
||||
schema = @Schema(
|
||||
implementation = BlockWithTransactions.class
|
||||
array = @ArraySchema(
|
||||
schema = @Schema(
|
||||
implementation = BlockData.class
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
@ApiErrors({ApiError.BLOCK_NO_EXISTS, ApiError.REPOSITORY_ISSUE})
|
||||
public List<BlockWithTransactions> getBlockRange(@PathParam("height") int height, @Parameter(ref = "count") @QueryParam("count") int count) {
|
||||
boolean includeTransactions = false;
|
||||
|
||||
@ApiErrors({
|
||||
ApiError.BLOCK_NO_EXISTS, ApiError.REPOSITORY_ISSUE
|
||||
})
|
||||
public List<BlockData> getBlockRange(@PathParam("height") int height, @Parameter(
|
||||
ref = "count"
|
||||
) @QueryParam("count") int count) {
|
||||
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) {
|
||||
BlockData blockData = repository.getBlockRepository().fromHeight(height);
|
||||
@ -429,7 +507,7 @@ public class BlocksResource {
|
||||
// Run out of blocks!
|
||||
break;
|
||||
|
||||
blocks.add(packageBlockData(repository, blockData, includeTransactions));
|
||||
blocks.add(blockData);
|
||||
}
|
||||
|
||||
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 java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
@ -25,8 +26,8 @@ import javax.ws.rs.core.MediaType;
|
||||
import org.qora.api.ApiError;
|
||||
import org.qora.api.ApiErrors;
|
||||
import org.qora.api.ApiExceptionFactory;
|
||||
import org.qora.api.model.GroupWithMemberInfo;
|
||||
import org.qora.api.model.GroupWithMemberInfo.MemberInfo;
|
||||
import org.qora.api.model.GroupMembers;
|
||||
import org.qora.api.model.GroupMembers.MemberInfo;
|
||||
import org.qora.crypto.Crypto;
|
||||
import org.qora.data.group.GroupAdminData;
|
||||
import org.qora.data.group.GroupBanData;
|
||||
@ -86,14 +87,15 @@ public class GroupsResource {
|
||||
}
|
||||
)
|
||||
@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()) {
|
||||
List<GroupData> groups = repository.getGroupRepository().getAllGroups();
|
||||
|
||||
// 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);
|
||||
List<GroupData> groups = repository.getGroupRepository().getAllGroups(limit, offset, reverse);
|
||||
|
||||
return groups;
|
||||
} catch (DataException e) {
|
||||
@ -158,7 +160,7 @@ public class GroupsResource {
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/{groupname}")
|
||||
@Path("/{groupid}")
|
||||
@Operation(
|
||||
summary = "Info on group",
|
||||
responses = {
|
||||
@ -166,45 +168,72 @@ public class GroupsResource {
|
||||
description = "group info",
|
||||
content = @Content(
|
||||
mediaType = MediaType.APPLICATION_JSON,
|
||||
schema = @Schema(implementation = GroupWithMemberInfo.class)
|
||||
schema = @Schema(implementation = GroupData.class)
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
@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()) {
|
||||
GroupData groupData = repository.getGroupRepository().fromGroupName(groupName);
|
||||
GroupData groupData = repository.getGroupRepository().fromGroupId(groupId);
|
||||
if (groupData == null)
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.GROUP_UNKNOWN);
|
||||
|
||||
List<MemberInfo> members = null;
|
||||
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);
|
||||
return groupData;
|
||||
} catch (DataException 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
|
||||
@Path("/create")
|
||||
|
@ -68,15 +68,12 @@ public class NamesResource {
|
||||
}
|
||||
)
|
||||
@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()) {
|
||||
List<NameData> names = repository.getNameRepository().getAllNames();
|
||||
|
||||
// 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);
|
||||
List<NameData> names = repository.getNameRepository().getAllNames(limit, offset, reverse);
|
||||
|
||||
// Convert to summary
|
||||
return names.stream().map(nameData -> new NameSummary(nameData)).collect(Collectors.toList());
|
||||
} catch (DataException e) {
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||
@ -98,12 +95,13 @@ public class NamesResource {
|
||||
}
|
||||
)
|
||||
@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))
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS);
|
||||
|
||||
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());
|
||||
} catch (DataException e) {
|
||||
@ -365,16 +363,10 @@ public class NamesResource {
|
||||
}
|
||||
)
|
||||
@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()) {
|
||||
List<NameData> names = repository.getNameRepository().getNamesForSale();
|
||||
|
||||
// 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;
|
||||
return repository.getNameRepository().getNamesForSale(limit, offset, reverse);
|
||||
} catch (DataException 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.ApiExceptionFactory;
|
||||
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.globalization.Translator;
|
||||
import org.qora.repository.DataException;
|
||||
@ -45,8 +43,12 @@ import org.qora.utils.Base58;
|
||||
import com.google.common.primitives.Bytes;
|
||||
|
||||
@Path("/transactions")
|
||||
@Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN})
|
||||
@Tag(name = "Transactions")
|
||||
@Produces({
|
||||
MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN
|
||||
})
|
||||
@Tag(
|
||||
name = "Transactions"
|
||||
)
|
||||
public class TransactionsResource {
|
||||
|
||||
@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) {
|
||||
byte[] signature;
|
||||
try {
|
||||
@ -100,12 +104,16 @@ public class TransactionsResource {
|
||||
description = "raw transaction encoded in Base58",
|
||||
content = @Content(
|
||||
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) {
|
||||
byte[] signature;
|
||||
try {
|
||||
@ -138,21 +146,28 @@ public class TransactionsResource {
|
||||
description = "Returns list of transactions",
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
description = "list of transactions",
|
||||
description = "the block",
|
||||
content = @Content(
|
||||
array = @ArraySchema(
|
||||
schema = @Schema(
|
||||
oneOf = {
|
||||
GenesisTransactionData.class, PaymentTransactionData.class
|
||||
}
|
||||
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") int limit, @Parameter(ref = "offset") @QueryParam("offset") int offset) {
|
||||
@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);
|
||||
@ -161,18 +176,10 @@ public class TransactionsResource {
|
||||
}
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
List<TransactionData> transactions = repository.getBlockRepository().getTransactionsFromSignature(signature);
|
||||
|
||||
// check if block exists
|
||||
if (transactions == null)
|
||||
if (repository.getBlockRepository().getHeightFromSignature(signature) == 0)
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.BLOCK_NO_EXISTS);
|
||||
|
||||
// Pagination would take effect here (or as part of the repository access)
|
||||
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;
|
||||
return repository.getBlockRepository().getTransactionsFromSignature(signature, limit, offset, reverse);
|
||||
} catch (ApiException e) {
|
||||
throw e;
|
||||
} catch (DataException e) {
|
||||
@ -198,10 +205,18 @@ public class TransactionsResource {
|
||||
)
|
||||
}
|
||||
)
|
||||
@ApiErrors({ApiError.REPOSITORY_ISSUE})
|
||||
public List<TransactionData> getUnconfirmedTransactions() {
|
||||
@ApiErrors({
|
||||
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()) {
|
||||
return repository.getTransactionRepository().getAllUnconfirmedTransactions();
|
||||
return repository.getTransactionRepository().getUnconfirmedTransactions(limit, offset, reverse);
|
||||
} catch (ApiException e) {
|
||||
throw e;
|
||||
} catch (DataException e) {
|
||||
@ -209,23 +224,17 @@ public class TransactionsResource {
|
||||
}
|
||||
}
|
||||
|
||||
public enum ConfirmationStatus {
|
||||
CONFIRMED,
|
||||
UNCONFIRMED,
|
||||
BOTH;
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/search")
|
||||
@Operation(
|
||||
summary = "Find matching transactions",
|
||||
description = "Returns transactions that match criteria. At least either txType or address must be provided.",
|
||||
/*
|
||||
* 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"))
|
||||
* },
|
||||
*/
|
||||
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.",
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
description = "transactions",
|
||||
@ -239,30 +248,31 @@ public class TransactionsResource {
|
||||
)
|
||||
}
|
||||
)
|
||||
@ApiErrors({ApiError.INVALID_CRITERIA, ApiError.REPOSITORY_ISSUE})
|
||||
public List<TransactionData> searchTransactions(@QueryParam("startBlock") Integer startBlock, @QueryParam("blockLimit") Integer blockLimit,
|
||||
@QueryParam("txType") Integer txTypeNum, @QueryParam("address") String address, @Parameter(
|
||||
@ApiErrors({
|
||||
ApiError.INVALID_CRITERIA, ApiError.REPOSITORY_ISSUE
|
||||
})
|
||||
public List<TransactionData> searchTransactions(@QueryParam("startBlock") Integer startBlock, @QueryParam("blockLimit") Integer blockLimit,
|
||||
@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"
|
||||
) @QueryParam("limit") int limit, @Parameter(
|
||||
) @QueryParam("limit") Integer limit, @Parameter(
|
||||
ref = "offset"
|
||||
) @QueryParam("offset") int offset) {
|
||||
if ((txTypeNum == null || txTypeNum == 0) && (address == null || address.isEmpty()))
|
||||
) @QueryParam("offset") Integer offset, @Parameter(
|
||||
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);
|
||||
|
||||
TransactionType txType = null;
|
||||
if (txTypeNum != null) {
|
||||
txType = TransactionType.valueOf(txTypeNum);
|
||||
if (txType == null)
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
|
||||
}
|
||||
// You can't ask for unconfirmed and impose a block height range
|
||||
if (confirmationStatus != ConfirmationStatus.CONFIRMED && (startBlock != null || blockLimit != null))
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
List<byte[]> signatures = repository.getTransactionRepository().getAllSignaturesMatchingCriteria(startBlock, blockLimit, txType, address);
|
||||
|
||||
// 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);
|
||||
List<byte[]> signatures = repository.getTransactionRepository().getSignaturesMatchingCriteria(startBlock, blockLimit, txType, address,
|
||||
confirmationStatus, limit, offset, reverse);
|
||||
|
||||
// Expand signatures to transactions
|
||||
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) {
|
||||
if (signRequest.transactionBytes.length == 0)
|
||||
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) {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
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) {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
byte[] rawBytes = Base58.decode(rawBytes58);
|
||||
|
@ -50,7 +50,7 @@ public class BlockGenerator extends Thread {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
if (Settings.getInstance().getWipeUnconfirmedOnStart()) {
|
||||
// Wipe existing unconfirmed transactions
|
||||
List<TransactionData> unconfirmedTransactions = repository.getTransactionRepository().getAllUnconfirmedTransactions();
|
||||
List<TransactionData> unconfirmedTransactions = repository.getTransactionRepository().getUnconfirmedTransactions();
|
||||
|
||||
for (TransactionData transactionData : unconfirmedTransactions) {
|
||||
LOGGER.trace(String.format("Deleting unconfirmed transaction %s", Base58.encode(transactionData.getSignature())));
|
||||
|
@ -17,6 +17,11 @@ import org.qora.utils.Base58;
|
||||
|
||||
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);
|
||||
|
||||
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.XmlTransient;
|
||||
|
||||
import org.eclipse.persistence.oxm.annotations.XmlClassExtractor;
|
||||
import org.qora.api.TransactionClassExtractor;
|
||||
// XXX are this still needed? see below
|
||||
// import org.eclipse.persistence.oxm.annotations.XmlClassExtractor;
|
||||
// import org.qora.api.TransactionClassExtractor;
|
||||
import org.qora.crypto.Crypto;
|
||||
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!
|
||||
*/
|
||||
|
||||
@XmlClassExtractor(TransactionClassExtractor.class)
|
||||
// XXX is this still in use?
|
||||
// @XmlClassExtractor(TransactionClassExtractor.class)
|
||||
@XmlSeeAlso({GenesisTransactionData.class, PaymentTransactionData.class, RegisterNameTransactionData.class, UpdateNameTransactionData.class,
|
||||
SellNameTransactionData.class, CancelSellNameTransactionData.class, BuyNameTransactionData.class,
|
||||
CreatePollTransactionData.class, VoteOnPollTransactionData.class, ArbitraryTransactionData.class,
|
||||
|
@ -21,9 +21,17 @@ public interface AccountRepository {
|
||||
|
||||
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;
|
||||
|
||||
|
@ -18,7 +18,11 @@ public interface AssetRepository {
|
||||
|
||||
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
|
||||
|
||||
@ -30,9 +34,18 @@ public interface AssetRepository {
|
||||
|
||||
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;
|
||||
|
||||
@ -40,10 +53,18 @@ public interface AssetRepository {
|
||||
|
||||
// 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 */
|
||||
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;
|
||||
|
||||
|
@ -59,6 +59,17 @@ public interface BlockRepository {
|
||||
*/
|
||||
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.
|
||||
* <p>
|
||||
@ -68,7 +79,9 @@ public interface BlockRepository {
|
||||
* @return list of transactions, or null if block not found in blockchain.
|
||||
* @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.
|
||||
|
@ -21,11 +21,23 @@ public interface GroupRepository {
|
||||
|
||||
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;
|
||||
|
||||
@ -39,7 +51,14 @@ public interface GroupRepository {
|
||||
|
||||
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;
|
||||
|
||||
@ -51,7 +70,11 @@ public interface GroupRepository {
|
||||
|
||||
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 */
|
||||
public Integer countGroupMembers(int groupId) throws DataException;
|
||||
@ -66,9 +89,17 @@ public interface GroupRepository {
|
||||
|
||||
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;
|
||||
|
||||
@ -80,7 +111,11 @@ public interface GroupRepository {
|
||||
|
||||
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;
|
||||
|
||||
@ -92,7 +127,11 @@ public interface GroupRepository {
|
||||
|
||||
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;
|
||||
|
||||
|
@ -10,11 +10,23 @@ public interface NameRepository {
|
||||
|
||||
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;
|
||||
|
||||
|
@ -2,6 +2,7 @@ package org.qora.repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.qora.api.resource.TransactionsResource.ConfirmationStatus;
|
||||
import org.qora.data.transaction.TransactionData;
|
||||
import org.qora.transaction.Transaction.TransactionType;
|
||||
|
||||
@ -20,7 +21,7 @@ public interface TransactionRepository {
|
||||
|
||||
// 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;
|
||||
|
||||
@ -28,7 +29,21 @@ public interface TransactionRepository {
|
||||
|
||||
// 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.
|
||||
@ -36,7 +51,9 @@ public interface TransactionRepository {
|
||||
* @return list of transactions, or empty if none.
|
||||
* @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.
|
||||
|
@ -94,10 +94,15 @@ public class HSQLDBAccountRepository implements AccountRepository {
|
||||
}
|
||||
|
||||
@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>();
|
||||
|
||||
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)
|
||||
return balances;
|
||||
|
||||
@ -115,10 +120,15 @@ public class HSQLDBAccountRepository implements AccountRepository {
|
||||
}
|
||||
|
||||
@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>();
|
||||
|
||||
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)
|
||||
return balances;
|
||||
|
||||
|
@ -83,11 +83,15 @@ public class HSQLDBAssetRepository implements AssetRepository {
|
||||
}
|
||||
|
||||
@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>();
|
||||
|
||||
try (ResultSet resultSet = this.repository
|
||||
.checkedExecute("SELECT owner, asset_id, description, quantity, is_divisible, reference, asset_name FROM Assets ORDER BY asset_id ASC")) {
|
||||
try (ResultSet resultSet = this.repository.checkedExecute(sql)) {
|
||||
if (resultSet == null)
|
||||
return assets;
|
||||
|
||||
@ -170,13 +174,19 @@ public class HSQLDBAssetRepository implements AssetRepository {
|
||||
}
|
||||
|
||||
@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>();
|
||||
|
||||
try (ResultSet resultSet = this.repository.checkedExecute(
|
||||
"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)) {
|
||||
try (ResultSet resultSet = this.repository.checkedExecute(sql, haveAssetId, wantAssetId)) {
|
||||
if (resultSet == null)
|
||||
return orders;
|
||||
|
||||
@ -202,16 +212,18 @@ public class HSQLDBAssetRepository implements AssetRepository {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<OrderData> getAccountsOrders(byte[] publicKey, boolean includeClosed, boolean includeFulfilled) 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 = ?";
|
||||
public List<OrderData> getAccountsOrders(byte[] publicKey, boolean includeClosed, boolean includeFulfilled, Integer limit, Integer offset, Boolean reverse) throws DataException {
|
||||
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)
|
||||
sql += " AND is_closed = FALSE";
|
||||
if (!includeFulfilled)
|
||||
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)) {
|
||||
if (resultSet == null)
|
||||
@ -267,13 +279,16 @@ public class HSQLDBAssetRepository implements AssetRepository {
|
||||
// Trades
|
||||
|
||||
@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>();
|
||||
|
||||
try (ResultSet resultSet = this.repository.checkedExecute(
|
||||
"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)) {
|
||||
try (ResultSet resultSet = this.repository.checkedExecute(sql, haveAssetId, wantAssetId)) {
|
||||
if (resultSet == null)
|
||||
return trades;
|
||||
|
||||
@ -295,12 +310,15 @@ public class HSQLDBAssetRepository implements AssetRepository {
|
||||
}
|
||||
|
||||
@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>();
|
||||
|
||||
try (ResultSet resultSet = this.repository.checkedExecute(
|
||||
"SELECT initiating_order_id, target_order_id, amount, price, traded FROM AssetTrades WHERE initiating_order_id = ? OR target_order_id = ?",
|
||||
orderId, orderId)) {
|
||||
try (ResultSet resultSet = this.repository.checkedExecute(sql, orderId, orderId)) {
|
||||
if (resultSet == null)
|
||||
return trades;
|
||||
|
||||
|
@ -108,10 +108,15 @@ public class HSQLDBBlockRepository implements BlockRepository {
|
||||
}
|
||||
|
||||
@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>();
|
||||
|
||||
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)
|
||||
return transactions; // No transactions in this block
|
||||
|
||||
|
@ -95,11 +95,15 @@ public class HSQLDBGroupRepository implements GroupRepository {
|
||||
}
|
||||
|
||||
@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<>();
|
||||
|
||||
try (ResultSet resultSet = this.repository
|
||||
.checkedExecute("SELECT group_id, owner, group_name, description, created, updated, reference, is_open FROM Groups")) {
|
||||
try (ResultSet resultSet = this.repository.checkedExecute(sql)) {
|
||||
if (resultSet == null)
|
||||
return groups;
|
||||
|
||||
@ -127,11 +131,15 @@ public class HSQLDBGroupRepository implements GroupRepository {
|
||||
}
|
||||
|
||||
@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<>();
|
||||
|
||||
try (ResultSet resultSet = this.repository
|
||||
.checkedExecute("SELECT group_id, group_name, description, created, updated, reference, is_open FROM Groups WHERE owner = ?", owner)) {
|
||||
try (ResultSet resultSet = this.repository.checkedExecute(sql, owner)) {
|
||||
if (resultSet == null)
|
||||
return groups;
|
||||
|
||||
@ -158,12 +166,15 @@ public class HSQLDBGroupRepository implements GroupRepository {
|
||||
}
|
||||
|
||||
@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<>();
|
||||
|
||||
try (ResultSet resultSet = this.repository.checkedExecute(
|
||||
"SELECT group_id, owner, group_name, description, created, updated, reference, is_open FROM Groups JOIN GroupMembers USING (group_id) WHERE address = ?",
|
||||
member)) {
|
||||
try (ResultSet resultSet = this.repository.checkedExecute(sql, member)) {
|
||||
if (resultSet == null)
|
||||
return groups;
|
||||
|
||||
@ -266,10 +277,15 @@ public class HSQLDBGroupRepository implements GroupRepository {
|
||||
}
|
||||
|
||||
@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<>();
|
||||
|
||||
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)
|
||||
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
|
||||
public void save(GroupAdminData groupAdminData) throws DataException {
|
||||
HSQLDBSaver saveHelper = new HSQLDBSaver("GroupAdmins");
|
||||
@ -336,10 +367,15 @@ public class HSQLDBGroupRepository implements GroupRepository {
|
||||
}
|
||||
|
||||
@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<>();
|
||||
|
||||
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)
|
||||
return members;
|
||||
|
||||
@ -359,13 +395,14 @@ public class HSQLDBGroupRepository implements GroupRepository {
|
||||
|
||||
@Override
|
||||
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 group_id, COUNT(*) FROM GroupMembers WHERE group_id = ? GROUP BY group_id",
|
||||
groupId)) {
|
||||
if (resultSet == null)
|
||||
try (ResultSet resultSet = this.repository.checkedExecute("SELECT COUNT(*) FROM GroupMembers WHERE group_id = ?", groupId)) {
|
||||
int count = resultSet.getInt(1);
|
||||
|
||||
if (count == 0)
|
||||
// There must be at least one member: the group owner
|
||||
return null;
|
||||
|
||||
return resultSet.getInt(2);
|
||||
return count;
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to fetch group member count from repository", e);
|
||||
}
|
||||
@ -425,10 +462,15 @@ public class HSQLDBGroupRepository implements GroupRepository {
|
||||
}
|
||||
|
||||
@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<>();
|
||||
|
||||
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)
|
||||
return invites;
|
||||
|
||||
@ -451,10 +493,15 @@ public class HSQLDBGroupRepository implements GroupRepository {
|
||||
}
|
||||
|
||||
@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<>();
|
||||
|
||||
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)
|
||||
return invites;
|
||||
|
||||
@ -530,10 +577,15 @@ public class HSQLDBGroupRepository implements GroupRepository {
|
||||
}
|
||||
|
||||
@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<>();
|
||||
|
||||
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)
|
||||
return joinRequests;
|
||||
|
||||
@ -604,11 +656,15 @@ public class HSQLDBGroupRepository implements GroupRepository {
|
||||
}
|
||||
|
||||
@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<>();
|
||||
|
||||
try (ResultSet resultSet = this.repository.checkedExecute("SELECT offender, admin, banned, reason, expiry, reference FROM GroupBans WHERE group_id = ?",
|
||||
groupId)) {
|
||||
try (ResultSet resultSet = this.repository.checkedExecute(sql, groupId)) {
|
||||
if (resultSet == null)
|
||||
return bans;
|
||||
|
||||
|
@ -55,11 +55,15 @@ public class HSQLDBNameRepository implements NameRepository {
|
||||
}
|
||||
|
||||
@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<>();
|
||||
|
||||
try (ResultSet resultSet = this.repository
|
||||
.checkedExecute("SELECT name, data, owner, registered, updated, reference, is_for_sale, sale_price FROM Names")) {
|
||||
try (ResultSet resultSet = this.repository.checkedExecute(sql)) {
|
||||
if (resultSet == null)
|
||||
return names;
|
||||
|
||||
@ -87,11 +91,15 @@ public class HSQLDBNameRepository implements NameRepository {
|
||||
}
|
||||
|
||||
@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<>();
|
||||
|
||||
try (ResultSet resultSet = this.repository
|
||||
.checkedExecute("SELECT name, data, owner, registered, updated, reference, sale_price FROM Names WHERE is_for_sale = TRUE")) {
|
||||
try (ResultSet resultSet = this.repository.checkedExecute(sql)) {
|
||||
if (resultSet == null)
|
||||
return names;
|
||||
|
||||
@ -119,11 +127,15 @@ public class HSQLDBNameRepository implements NameRepository {
|
||||
}
|
||||
|
||||
@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<>();
|
||||
|
||||
try (ResultSet resultSet = this.repository
|
||||
.checkedExecute("SELECT name, data, registered, updated, reference, is_for_sale, sale_price FROM Names WHERE owner = ?", owner)) {
|
||||
try (ResultSet resultSet = this.repository.checkedExecute(sql, owner)) {
|
||||
if (resultSet == null)
|
||||
return names;
|
||||
|
||||
@ -157,9 +169,9 @@ public class HSQLDBNameRepository implements NameRepository {
|
||||
Long updated = nameData.getUpdated();
|
||||
Timestamp updatedTimestamp = updated == null ? null : new Timestamp(updated);
|
||||
|
||||
saveHelper.bind("owner", nameData.getOwner()).bind("name", nameData.getName())
|
||||
.bind("data", nameData.getData()).bind("registered", new Timestamp(nameData.getRegistered())).bind("updated", updatedTimestamp)
|
||||
.bind("reference", nameData.getReference()).bind("is_for_sale", nameData.getIsForSale()).bind("sale_price", nameData.getSalePrice());
|
||||
saveHelper.bind("owner", nameData.getOwner()).bind("name", nameData.getName()).bind("data", nameData.getData())
|
||||
.bind("registered", new Timestamp(nameData.getRegistered())).bind("updated", updatedTimestamp).bind("reference", nameData.getReference())
|
||||
.bind("is_for_sale", nameData.getIsForSale()).bind("sale_price", nameData.getSalePrice());
|
||||
|
||||
try {
|
||||
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.Logger;
|
||||
import org.qora.api.resource.TransactionsResource.ConfirmationStatus;
|
||||
import org.qora.data.PaymentData;
|
||||
import org.qora.data.transaction.TransactionData;
|
||||
import org.qora.repository.DataException;
|
||||
@ -53,7 +54,8 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
||||
|
||||
private static Class<?> getClassByTxType(TransactionType txType) {
|
||||
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) {
|
||||
return null;
|
||||
}
|
||||
@ -204,7 +206,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<byte[]> getAllSignaturesInvolvingAddress(String address) throws DataException {
|
||||
public List<byte[]> getSignaturesInvolvingAddress(String address) throws DataException {
|
||||
List<byte[]> signatures = new ArrayList<byte[]>();
|
||||
|
||||
try (ResultSet resultSet = this.repository.checkedExecute("SELECT signature FROM TransactionRecipients WHERE participant = ?", address)) {
|
||||
@ -250,7 +252,8 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
||||
}
|
||||
|
||||
@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[]>();
|
||||
|
||||
boolean hasAddress = address != null && !address.isEmpty();
|
||||
@ -258,33 +261,42 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
||||
boolean hasHeightRange = startBlock != null || blockLimit != null;
|
||||
|
||||
if (hasHeightRange && startBlock == null)
|
||||
startBlock = 1;
|
||||
startBlock = (reverse == null || !reverse) ? 1 : this.repository.getBlockRepository().getBlockchainHeight() - blockLimit;
|
||||
|
||||
String signatureColumn = "NULL";
|
||||
List<Object> bindParams = new ArrayList<Object>();
|
||||
String signatureColumn = "Transactions.signature";
|
||||
List<String> whereClauses = new ArrayList<String>();
|
||||
String groupBy = "";
|
||||
List<Object> bindParams = new ArrayList<Object>();
|
||||
|
||||
// Table JOINs first
|
||||
List<String> tableJoins = new ArrayList<String>();
|
||||
// Tables, starting with Transactions
|
||||
String tables = "Transactions";
|
||||
|
||||
// Always JOIN BlockTransactions as we only ever want confirmed transactions
|
||||
tableJoins.add("Blocks");
|
||||
tableJoins.add("BlockTransactions ON BlockTransactions.block_signature = Blocks.signature");
|
||||
signatureColumn = "BlockTransactions.transaction_signature";
|
||||
// BlockTransactions if we want confirmed transactions
|
||||
switch (confirmationStatus) {
|
||||
case BOTH:
|
||||
break;
|
||||
|
||||
// Always JOIN Transactions as we want to order by timestamp
|
||||
tableJoins.add("Transactions ON Transactions.signature = BlockTransactions.transaction_signature");
|
||||
signatureColumn = "Transactions.signature";
|
||||
case CONFIRMED:
|
||||
tables += " JOIN BlockTransactions ON BlockTransactions.transaction_signature = 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) {
|
||||
tableJoins.add("TransactionParticipants ON TransactionParticipants.signature = Transactions.signature");
|
||||
signatureColumn = "TransactionParticipants.signature";
|
||||
tables += " JOIN TransactionParticipants ON TransactionParticipants.signature = Transactions.signature";
|
||||
groupBy = " GROUP BY TransactionParticipants.signature, Transactions.creation";
|
||||
signatureColumn = "TransactionParticipants.signature";
|
||||
}
|
||||
|
||||
// WHERE clauses next
|
||||
List<String> whereClauses = new ArrayList<String>();
|
||||
|
||||
if (hasHeightRange) {
|
||||
whereClauses.add("Blocks.height >= " + startBlock);
|
||||
|
||||
@ -300,7 +312,19 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
||||
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);
|
||||
|
||||
try (ResultSet resultSet = this.repository.checkedExecute(sql, bindParams.toArray())) {
|
||||
@ -320,11 +344,19 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
||||
}
|
||||
|
||||
@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>();
|
||||
|
||||
// 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)
|
||||
return transactions;
|
||||
|
||||
|
@ -468,7 +468,7 @@ public abstract class Transaction {
|
||||
}
|
||||
|
||||
private int countUnconfirmedByCreator(PublicKeyAccount creator) throws DataException {
|
||||
List<TransactionData> unconfirmedTransactions = repository.getTransactionRepository().getAllUnconfirmedTransactions();
|
||||
List<TransactionData> unconfirmedTransactions = repository.getTransactionRepository().getUnconfirmedTransactions();
|
||||
|
||||
int count = 0;
|
||||
for (TransactionData transactionData : unconfirmedTransactions) {
|
||||
@ -495,7 +495,7 @@ public abstract class Transaction {
|
||||
public static List<TransactionData> getUnconfirmedTransactions(Repository repository) throws DataException {
|
||||
BlockData latestBlockData = repository.getBlockRepository().getLastBlock();
|
||||
|
||||
List<TransactionData> unconfirmedTransactions = repository.getTransactionRepository().getAllUnconfirmedTransactions();
|
||||
List<TransactionData> unconfirmedTransactions = repository.getTransactionRepository().getUnconfirmedTransactions();
|
||||
List<TransactionData> invalidTransactions = new ArrayList<>();
|
||||
|
||||
unconfirmedTransactions.sort(getDataComparator());
|
||||
|
Loading…
x
Reference in New Issue
Block a user