From b2ca63ce887756f0b9f90ffd7f297a952c2fc595 Mon Sep 17 00:00:00 2001 From: catbref Date: Mon, 7 Jan 2019 09:31:27 +0000 Subject: [PATCH] Add cancel order and get orders by address API support. Added getAccountsOrders asset repository call to facilitate the above. --- .../qora/api/resource/AddressesResource.java | 36 ++++++++++++++++ .../org/qora/api/resource/AssetsResource.java | 43 +++++++++++++++++++ .../CancelOrderTransactionData.java | 17 ++++++++ .../CreateOrderTransactionData.java | 5 +-- .../org/qora/repository/AssetRepository.java | 2 + .../hsqldb/HSQLDBAssetRepository.java | 38 ++++++++++++++++ 6 files changed, 138 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/qora/api/resource/AddressesResource.java b/src/main/java/org/qora/api/resource/AddressesResource.java index 777dc57b..e3f774d4 100644 --- a/src/main/java/org/qora/api/resource/AddressesResource.java +++ b/src/main/java/org/qora/api/resource/AddressesResource.java @@ -15,6 +15,7 @@ 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; @@ -27,6 +28,7 @@ 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; @@ -281,4 +283,38 @@ 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 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); + } + } + } diff --git a/src/main/java/org/qora/api/resource/AssetsResource.java b/src/main/java/org/qora/api/resource/AssetsResource.java index 9c0df73a..b168247d 100644 --- a/src/main/java/org/qora/api/resource/AssetsResource.java +++ b/src/main/java/org/qora/api/resource/AssetsResource.java @@ -32,6 +32,7 @@ import org.qora.data.account.AccountBalanceData; import org.qora.data.asset.AssetData; import org.qora.data.asset.OrderData; import org.qora.data.asset.TradeData; +import org.qora.data.transaction.CancelOrderTransactionData; import org.qora.data.transaction.CreateOrderTransactionData; import org.qora.data.transaction.IssueAssetTransactionData; import org.qora.repository.DataException; @@ -40,6 +41,7 @@ import org.qora.repository.RepositoryManager; import org.qora.transaction.Transaction; import org.qora.transaction.Transaction.ValidationResult; import org.qora.transform.TransformationException; +import org.qora.transform.transaction.CancelOrderTransactionTransformer; import org.qora.transform.transaction.CreateOrderTransactionTransformer; import org.qora.transform.transaction.IssueAssetTransactionTransformer; import org.qora.utils.Base58; @@ -232,6 +234,47 @@ public class AssetsResource { } } + @POST + @Path("/order/delete") + @Operation( + summary = "Cancel existing asset order", + requestBody = @RequestBody( + required = true, + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema(implementation = CancelOrderTransactionData.class) + ) + ), + responses = { + @ApiResponse( + description = "raw, unsigned, CANCEL_ORDER transaction encoded in Base58", + content = @Content( + mediaType = MediaType.TEXT_PLAIN, + schema = @Schema( + type = "string" + ) + ) + ) + } + ) + @ApiErrors({ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE, ApiError.TRANSACTION_INVALID}) + public String cancelOrder(CancelOrderTransactionData transactionData) { + try (final Repository repository = RepositoryManager.getRepository()) { + Transaction transaction = Transaction.fromData(repository, transactionData); + + ValidationResult result = transaction.isValidUnconfirmed(); + if (result != ValidationResult.OK) + throw TransactionsResource.createTransactionInvalidException(request, result); + + byte[] bytes = CancelOrderTransactionTransformer.toBytes(transactionData); + return Base58.encode(bytes); + } catch (TransformationException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.TRANSFORMATION_ERROR, e); + } catch (DataException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e); + } + } + @POST @Path("/issue") @Operation( diff --git a/src/main/java/org/qora/data/transaction/CancelOrderTransactionData.java b/src/main/java/org/qora/data/transaction/CancelOrderTransactionData.java index 75136d4f..27d0a02a 100644 --- a/src/main/java/org/qora/data/transaction/CancelOrderTransactionData.java +++ b/src/main/java/org/qora/data/transaction/CancelOrderTransactionData.java @@ -4,8 +4,10 @@ import java.math.BigDecimal; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; import org.qora.transaction.Transaction; +import org.qora.transaction.Transaction.TransactionType; import io.swagger.v3.oas.annotations.media.Schema; @@ -15,12 +17,14 @@ import io.swagger.v3.oas.annotations.media.Schema; public class CancelOrderTransactionData extends TransactionData { // Properties + @Schema(description = "order ID to cancel", example = "2zYCM8P3PSzUxFNPAKFsSdwg9dWQcYTPCuKkuQbx3GVxTUVjXAUwEmEnvUUss11SZ3p38C16UfYb3cbXP9sRuqFx") private byte[] orderId; // Constructors // For JAX-RS protected CancelOrderTransactionData() { + super(TransactionType.CANCEL_ASSET_ORDER); } public CancelOrderTransactionData(byte[] creatorPublicKey, byte[] orderId, BigDecimal fee, long timestamp, byte[] reference, byte[] signature) { @@ -39,4 +43,17 @@ public class CancelOrderTransactionData extends TransactionData { return this.orderId; } + // Re-expose creatorPublicKey for this transaction type for JAXB + @XmlElement(name = "creatorPublicKey") + @Schema(name = "creatorPublicKey", description = "order creator's public key", example = "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP") + public byte[] getOrderCreatorPublicKey() { + return this.creatorPublicKey; + } + + @XmlElement(name = "creatorPublicKey") + @Schema(name = "creatorPublicKey", description = "order creator's public key", example = "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP") + public void setOrderCreatorPublicKey(byte[] creatorPublicKey) { + this.creatorPublicKey = creatorPublicKey; + } + } diff --git a/src/main/java/org/qora/data/transaction/CreateOrderTransactionData.java b/src/main/java/org/qora/data/transaction/CreateOrderTransactionData.java index 324cb85b..83c4417a 100644 --- a/src/main/java/org/qora/data/transaction/CreateOrderTransactionData.java +++ b/src/main/java/org/qora/data/transaction/CreateOrderTransactionData.java @@ -9,7 +9,6 @@ import javax.xml.bind.annotation.XmlElement; import org.qora.transaction.Transaction.TransactionType; import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.media.Schema.AccessMode; // All properties to be converted to JSON via JAX-RS @XmlAccessorType(XmlAccessType.FIELD) @@ -17,9 +16,9 @@ import io.swagger.v3.oas.annotations.media.Schema.AccessMode; public class CreateOrderTransactionData extends TransactionData { // Properties - @Schema(description = "asset on offer to give by order creator") + @Schema(description = "ID of asset on offer to give by order creator", example = "1") private long haveAssetId; - @Schema(description = "asset wanted to receive by order creator") + @Schema(description = "ID of asset wanted to receive by order creator", example = "0") private long wantAssetId; @Schema(description = "amount of \"have\" asset to trade") private BigDecimal amount; diff --git a/src/main/java/org/qora/repository/AssetRepository.java b/src/main/java/org/qora/repository/AssetRepository.java index f0a0392a..ec4b7ece 100644 --- a/src/main/java/org/qora/repository/AssetRepository.java +++ b/src/main/java/org/qora/repository/AssetRepository.java @@ -32,6 +32,8 @@ public interface AssetRepository { public List getOpenOrders(long haveAssetId, long wantAssetId) throws DataException; + public List getAccountsOrders(byte[] publicKey, boolean includeClosed, boolean includeFulfilled) throws DataException; + public void save(OrderData orderData) throws DataException; public void delete(byte[] orderId) throws DataException; diff --git a/src/main/java/org/qora/repository/hsqldb/HSQLDBAssetRepository.java b/src/main/java/org/qora/repository/hsqldb/HSQLDBAssetRepository.java index 8e1faa7b..dca24911 100644 --- a/src/main/java/org/qora/repository/hsqldb/HSQLDBAssetRepository.java +++ b/src/main/java/org/qora/repository/hsqldb/HSQLDBAssetRepository.java @@ -201,6 +201,44 @@ public class HSQLDBAssetRepository implements AssetRepository { } } + @Override + public List getAccountsOrders(byte[] publicKey, boolean includeClosed, boolean includeFulfilled) throws DataException { + List orders = new ArrayList(); + + 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"; + + try (ResultSet resultSet = this.repository.checkedExecute(sql, publicKey)) { + if (resultSet == null) + return orders; + + do { + byte[] orderId = resultSet.getBytes(1); + long haveAssetId = resultSet.getLong(2); + long wantAssetId = resultSet.getLong(3); + BigDecimal amount = resultSet.getBigDecimal(4); + BigDecimal fulfilled = resultSet.getBigDecimal(5); + BigDecimal price = resultSet.getBigDecimal(6); + long timestamp = resultSet.getTimestamp(7, Calendar.getInstance(HSQLDBRepository.UTC)).getTime(); + boolean isClosed = resultSet.getBoolean(8); + boolean isFulfilled = resultSet.getBoolean(9); + + OrderData order = new OrderData(orderId, publicKey, haveAssetId, wantAssetId, amount, fulfilled, price, timestamp, isClosed, + isFulfilled); + orders.add(order); + } while (resultSet.next()); + + return orders; + } catch (SQLException e) { + throw new DataException("Unable to fetch account's asset orders from repository", e); + } + } + @Override public void save(OrderData orderData) throws DataException { HSQLDBSaver saveHelper = new HSQLDBSaver("AssetOrders");