forked from Qortal/qortal
API: basic asset info
Added repository support for asset API calls Added /utils/seed for returning server-generated 32-byte seed
This commit is contained in:
parent
3829630b29
commit
2aaa199c86
12
pom.xml
12
pom.xml
@ -71,6 +71,18 @@
|
||||
<token>https://petstore.swagger.io/v2/swagger.json</token>
|
||||
<value>/openapi.json</value>
|
||||
</replacement>
|
||||
<replacement>
|
||||
<token>Swagger UI</token>
|
||||
<value>API Documentation</value>
|
||||
</replacement>
|
||||
<replacement>
|
||||
<token>deepLinking: true,</token>
|
||||
<value>
|
||||
deepLinking: true,
|
||||
tagsSorter: "alpha",
|
||||
operationsSorter: "alpha",
|
||||
</value>
|
||||
</replacement>
|
||||
</replacements>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
@ -370,7 +370,7 @@ public class AddressesResource {
|
||||
@GET
|
||||
@Path("/publickey/{address}")
|
||||
@Operation(
|
||||
summary = "Address' public key",
|
||||
summary = "Get public key of address",
|
||||
description = "Returns the base64-encoded account public key of the given address, or \"false\" if address not known or has no public key.",
|
||||
extensions = {
|
||||
@Extension(name = "translation", properties = {
|
||||
|
@ -38,6 +38,7 @@ public class AdminResource {
|
||||
@Parameter(in = ParameterIn.QUERY, name = "limit", description = "Maximum number of entries to return, 0 means unlimited", schema = @Schema(type = "integer", defaultValue = "10"))
|
||||
@Parameter(in = ParameterIn.QUERY, name = "offset", description = "Starting entry in results", 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"))
|
||||
public String globalParameters() {
|
||||
return "";
|
||||
}
|
||||
|
@ -7,10 +7,11 @@ import io.swagger.v3.oas.annotations.info.Info;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
@OpenAPIDefinition(
|
||||
info = @Info( title = "Qora API", description = "NOTE: byte-arrays currently returned as Base64 but this is likely to change to Base58" ),
|
||||
info = @Info( title = "Qora API", description = "NOTE: byte-arrays are encoded in Base64" ),
|
||||
tags = {
|
||||
@Tag(name = "Addresses"),
|
||||
@Tag(name = "Admin"),
|
||||
@Tag(name = "Assets"),
|
||||
@Tag(name = "Blocks"),
|
||||
@Tag(name = "Transactions"),
|
||||
@Tag(name = "Utilities")
|
||||
|
@ -29,6 +29,7 @@ public class ApiService {
|
||||
this.resources = new HashSet<Class<?>>();
|
||||
this.resources.add(AddressesResource.class);
|
||||
this.resources.add(AdminResource.class);
|
||||
this.resources.add(AssetsResource.class);
|
||||
this.resources.add(BlocksResource.class);
|
||||
this.resources.add(TransactionsResource.class);
|
||||
this.resources.add(UtilsResource.class);
|
||||
|
86
src/api/AssetsResource.java
Normal file
86
src/api/AssetsResource.java
Normal file
@ -0,0 +1,86 @@
|
||||
package api;
|
||||
|
||||
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;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import repository.DataException;
|
||||
import repository.Repository;
|
||||
import repository.RepositoryManager;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.core.Context;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
|
||||
import api.models.AssetWithHolders;
|
||||
import data.assets.AssetData;
|
||||
|
||||
@Path("/assets")
|
||||
@Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN})
|
||||
@Tag(name = "Assets")
|
||||
public class AssetsResource {
|
||||
|
||||
@Context
|
||||
HttpServletRequest request;
|
||||
|
||||
@GET
|
||||
@Path("/all")
|
||||
@Operation(
|
||||
summary = "List all known assets",
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
description = "asset info",
|
||||
content = @Content(array = @ArraySchema(schema = @Schema(implementation = AssetData.class)))
|
||||
)
|
||||
}
|
||||
)
|
||||
public List<AssetData> getAllAssets() {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
return repository.getAssetRepository().getAllAssets();
|
||||
} catch (DataException e) {
|
||||
throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e);
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/info")
|
||||
@Operation(
|
||||
summary = "Info on specific asset",
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
description = "asset info",
|
||||
content = @Content(array = @ArraySchema(schema = @Schema(implementation = AssetData.class)))
|
||||
)
|
||||
}
|
||||
)
|
||||
public AssetWithHolders getAssetInfo(@QueryParam("key") Integer key, @QueryParam("name") String name, @Parameter(ref = "includeHolders") @QueryParam("withHolders") boolean includeHolders) {
|
||||
if (key == null && (name == null || name.isEmpty()))
|
||||
throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_CRITERIA);
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
AssetData assetData = null;
|
||||
|
||||
if (key != null)
|
||||
assetData = repository.getAssetRepository().fromAssetId(key);
|
||||
else
|
||||
assetData = repository.getAssetRepository().fromAssetName(name);
|
||||
|
||||
if (assetData == null)
|
||||
throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_ASSET_ID);
|
||||
|
||||
return new AssetWithHolders(repository, assetData, includeHolders);
|
||||
} catch (DataException e) {
|
||||
throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -7,6 +7,7 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import utils.Base58;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Base64;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
@ -63,4 +64,21 @@ public class UtilsResource {
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/seed")
|
||||
@Operation(
|
||||
summary = "Generate random 32-byte seed",
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
description = "base64 data",
|
||||
content = @Content(schema = @Schema(implementation = String.class))
|
||||
)
|
||||
}
|
||||
)
|
||||
public String seed() {
|
||||
byte[] seed = new byte[32];
|
||||
new SecureRandom().nextBytes(seed);
|
||||
return Base64.getEncoder().encodeToString(seed);
|
||||
}
|
||||
|
||||
}
|
||||
|
43
src/api/models/AssetWithHolders.java
Normal file
43
src/api/models/AssetWithHolders.java
Normal file
@ -0,0 +1,43 @@
|
||||
package api.models;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.xml.bind.annotation.XmlElement;
|
||||
|
||||
import api.ApiError;
|
||||
import api.ApiErrorFactory;
|
||||
import data.account.AccountBalanceData;
|
||||
import data.assets.AssetData;
|
||||
import data.block.BlockData;
|
||||
import data.transaction.TransactionData;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import qora.block.Block;
|
||||
import repository.DataException;
|
||||
import repository.Repository;
|
||||
|
||||
@Schema(description = "Asset with (optional) asset holders")
|
||||
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
|
||||
@SuppressWarnings("unused")
|
||||
private AssetWithHolders() {
|
||||
}
|
||||
|
||||
public AssetWithHolders(Repository repository, AssetData assetData, boolean includeHolders) throws DataException {
|
||||
if (assetData == null)
|
||||
throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_ASSET_ID);
|
||||
|
||||
this.assetData = assetData;
|
||||
|
||||
if (includeHolders)
|
||||
this.holders = repository.getAccountRepository().getAssetBalances(assetData.getAssetId());
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,10 @@
|
||||
package data.assets;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
|
||||
//All properties to be converted to JSON via JAX-RS
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
public class AssetData {
|
||||
|
||||
// Properties
|
||||
@ -11,6 +16,10 @@ public class AssetData {
|
||||
private boolean isDivisible;
|
||||
private byte[] reference;
|
||||
|
||||
// necessary for JAX-RS serialization
|
||||
protected AssetData() {
|
||||
}
|
||||
|
||||
// NOTE: key is Long, not long, because it can be null if asset ID/key not yet assigned.
|
||||
public AssetData(Long assetId, String owner, String name, String description, long quantity, boolean isDivisible, byte[] reference) {
|
||||
this.assetId = assetId;
|
||||
|
@ -23,6 +23,8 @@ public interface AccountRepository {
|
||||
|
||||
public List<AccountBalanceData> getAllBalances(String address) throws DataException;
|
||||
|
||||
public List<AccountBalanceData> getAssetBalances(long assetId) throws DataException;
|
||||
|
||||
public void save(AccountBalanceData accountBalanceData) throws DataException;
|
||||
|
||||
public void delete(String address, long assetId) throws DataException;
|
||||
|
@ -2,6 +2,7 @@ package repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import data.account.AccountBalanceData;
|
||||
import data.assets.AssetData;
|
||||
import data.assets.OrderData;
|
||||
import data.assets.TradeData;
|
||||
@ -18,6 +19,10 @@ public interface AssetRepository {
|
||||
|
||||
public boolean assetExists(String assetName) throws DataException;
|
||||
|
||||
public List<AssetData> getAllAssets() throws DataException;
|
||||
|
||||
// For a list of asset holders, see AccountRepository.getAssetBalances
|
||||
|
||||
public void save(AssetData assetData) throws DataException;
|
||||
|
||||
public void delete(long assetId) throws DataException;
|
||||
|
@ -114,6 +114,27 @@ public class HSQLDBAccountRepository implements AccountRepository {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AccountBalanceData> getAssetBalances(long assetId) throws DataException {
|
||||
List<AccountBalanceData> balances = new ArrayList<AccountBalanceData>();
|
||||
|
||||
try (ResultSet resultSet = this.repository.checkedExecute("SELECT account, balance FROM AccountBalances WHERE asset_id = ?", assetId)) {
|
||||
if (resultSet == null)
|
||||
return balances;
|
||||
|
||||
do {
|
||||
String address = resultSet.getString(1);
|
||||
BigDecimal balance = resultSet.getBigDecimal(2).setScale(8);
|
||||
|
||||
balances.add(new AccountBalanceData(address, assetId, balance));
|
||||
} while (resultSet.next());
|
||||
|
||||
return balances;
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to fetch asset account balances from repository", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(AccountBalanceData accountBalanceData) throws DataException {
|
||||
HSQLDBSaver saveHelper = new HSQLDBSaver("AccountBalances");
|
||||
|
@ -82,6 +82,33 @@ public class HSQLDBAssetRepository implements AssetRepository {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AssetData> getAllAssets() throws DataException {
|
||||
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")) {
|
||||
if (resultSet == null)
|
||||
return assets;
|
||||
|
||||
do {
|
||||
String owner = resultSet.getString(1);
|
||||
long assetId = resultSet.getLong(2);
|
||||
String description = resultSet.getString(3);
|
||||
long quantity = resultSet.getLong(4);
|
||||
boolean isDivisible = resultSet.getBoolean(5);
|
||||
byte[] reference = resultSet.getBytes(6);
|
||||
String assetName = resultSet.getString(7);
|
||||
|
||||
assets.add(new AssetData(assetId, owner, assetName, description, quantity, isDivisible, reference));
|
||||
} while (resultSet.next());
|
||||
|
||||
return assets;
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to fetch all assets from repository", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(AssetData assetData) throws DataException {
|
||||
HSQLDBSaver saveHelper = new HSQLDBSaver("Assets");
|
||||
@ -148,8 +175,7 @@ public class HSQLDBAssetRepository implements AssetRepository {
|
||||
|
||||
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",
|
||||
+ "WHERE have_asset_id = ? AND want_asset_id = ? AND is_closed = FALSE AND is_fulfilled = FALSE ORDER BY price ASC, ordered ASC",
|
||||
haveAssetId, wantAssetId)) {
|
||||
if (resultSet == null)
|
||||
return orders;
|
||||
|
@ -152,6 +152,8 @@ public class HSQLDBDatabaseUpdates {
|
||||
stmt.execute("CREATE INDEX UnconfirmedTransactionsIndex ON UnconfirmedTransactions (creation, signature)");
|
||||
|
||||
// Transaction recipients
|
||||
// XXX This should be transaction "participants" to allow lookup of all activity by an address!
|
||||
// Could add "is_recipient" boolean flag
|
||||
stmt.execute("CREATE TABLE TransactionRecipients (signature Signature, recipient QoraAddress NOT NULL, "
|
||||
+ "FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
|
||||
// Use a separate table space as this table will be very large.
|
||||
|
Loading…
Reference in New Issue
Block a user