forked from Qortal/qortal
Work on API
Rejigged pom.xml, extracting common dependency versions as properties. Removed extraneous HSQLDB dependency (v2.4.1) as we're using svn r5836 for now. Removed calls to Security.checkApiCallAllowed() for all API calls EXCEPT /admin/stop. Throws error if remote IP is not localhost. Added 'global' OpenAPI parameters to fake /admin/dud endpoint to save copy&pasting. This will need more tidying in the future, or at least future support from swagger-core. Code added in AnnotationPostProcessor to insert global parameters in top-level OpenAPI components section. /block-explorer.html hidden from API UI BlocksResource now expects Base64 block signatures instead of Base58. Endpoints that return block data also accept optional "includeTransactions" query param which does exactly that. BlockWithTransactions API model added for above. Some attempt to get transaction-specific data returned but no luck as yet. (TransactionData, GenesisTransactionData, PaymentTransactionData touched). See https://github.com/swagger-api/swagger-core/issues/3046 TransactionsResource now has support for optional query params "limit" and "offset" so that only a subset of large results can be requested. UtilsResource added to provide convenient Base64<->Base58 conversions. /admin/uptime fixed to return uptime from application launch instead of instantiation of AdminResource class! Controller improved to detect repository and API startup failures. HSQLDBRepositoryFactory now detects when it can't open database and throws. (Before it would simply hang). Removed extraneous import from qora.account.Account
This commit is contained in:
parent
df2a414cf4
commit
b5c02f49ce
241
pom.xml
241
pom.xml
@ -7,6 +7,13 @@
|
||||
<packaging>jar</packaging>
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<bouncycastle.version>1.60</bouncycastle.version>
|
||||
<hsqldb.version>r5836</hsqldb.version>
|
||||
<jetty.version>9.4.11.v20180605</jetty.version>
|
||||
<jersey.version>2.27</jersey.version>
|
||||
<log4j.version>2.11.0</log4j.version>
|
||||
<slf4j.version>1.7.12</slf4j.version>
|
||||
<swagger-api.version>2.0.6</swagger-api.version>
|
||||
<swagger-ui.version>3.19.0</swagger-ui.version>
|
||||
</properties>
|
||||
<build>
|
||||
@ -156,105 +163,144 @@
|
||||
</repository>
|
||||
</repositories>
|
||||
<dependencies>
|
||||
<!-- HSQLDB for repository -->
|
||||
<dependency>
|
||||
<groupId>org.hsqldb</groupId>
|
||||
<artifactId>hsqldb</artifactId>
|
||||
<version>r5836</version>
|
||||
<version>${hsqldb.version}</version>
|
||||
</dependency>
|
||||
<!-- CIYAM AT (automated transactions) -->
|
||||
<dependency>
|
||||
<groupId>org.ciyam</groupId>
|
||||
<artifactId>at</artifactId>
|
||||
<version>1.0</version>
|
||||
</dependency>
|
||||
<!-- Bitcoin support -->
|
||||
<dependency>
|
||||
<groupId>org.bitcoinj</groupId>
|
||||
<artifactId>bitcoinj-core</artifactId>
|
||||
<version>0.14.7</version>
|
||||
</dependency>
|
||||
<!-- Utilities -->
|
||||
<dependency>
|
||||
<groupId>com.googlecode.json-simple</groupId>
|
||||
<artifactId>json-simple</artifactId>
|
||||
<version>1.1.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<version>25.0-jre</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-core</artifactId>
|
||||
<version>2.11.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-api</artifactId>
|
||||
<version>2.11.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-net</groupId>
|
||||
<artifactId>commons-net</artifactId>
|
||||
<version>3.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.glassfish.jersey.core</groupId>
|
||||
<artifactId>jersey-server</artifactId>
|
||||
<version>2.27</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
<version>4.0.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-server</artifactId>
|
||||
<version>9.4.11.v20180605</version>
|
||||
<classifier>config</classifier>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.glassfish.jersey.containers</groupId>
|
||||
<artifactId>jersey-container-servlet-core</artifactId>
|
||||
<version>2.27</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-servlet</artifactId>
|
||||
<version>9.4.11.v20180605</version>
|
||||
<type>jar</type>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/org.eclipse.jetty/jetty-servlets -->
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-servlets</artifactId>
|
||||
<version>9.4.11.v20180605</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.glassfish.jersey.inject</groupId>
|
||||
<artifactId>jersey-hk2</artifactId>
|
||||
<version>2.27</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.swagger.core.v3</groupId>
|
||||
<artifactId>swagger-jaxrs2</artifactId>
|
||||
<version>2.0.4</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.swagger.core.v3</groupId>
|
||||
<artifactId>swagger-jaxrs2-servlet-initializer</artifactId>
|
||||
<version>2.0.4</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-text</artifactId>
|
||||
<version>1.4</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-net</groupId>
|
||||
<artifactId>commons-net</artifactId>
|
||||
<version>3.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<version>25.0-jre</version>
|
||||
</dependency>
|
||||
<!-- Logging: log4j2 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-core</artifactId>
|
||||
<version>${log4j.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-api</artifactId>
|
||||
<version>${log4j.version}</version>
|
||||
</dependency>
|
||||
<!-- Logging: slf4j used by Jetty -->
|
||||
<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>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
<version>4.0.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.mail</groupId>
|
||||
<artifactId>mail</artifactId>
|
||||
<version>1.5.0-b01</version>
|
||||
</dependency>
|
||||
<!-- Jetty -->
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-server</artifactId>
|
||||
<version>${jetty.version}</version>
|
||||
<classifier>config</classifier>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-servlet</artifactId>
|
||||
<version>${jetty.version}</version>
|
||||
<type>jar</type>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-servlets</artifactId>
|
||||
<version>${jetty.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-rewrite</artifactId>
|
||||
<version>${jetty.version}</version>
|
||||
</dependency>
|
||||
<!-- Jersey -->
|
||||
<dependency>
|
||||
<groupId>org.glassfish.jersey.core</groupId>
|
||||
<artifactId>jersey-server</artifactId>
|
||||
<version>${jersey.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.glassfish.jersey.containers</groupId>
|
||||
<artifactId>jersey-container-servlet-core</artifactId>
|
||||
<version>${jersey.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.glassfish.jersey.inject</groupId>
|
||||
<artifactId>jersey-hk2</artifactId>
|
||||
<version>${jersey.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.glassfish.jersey.media</groupId>
|
||||
<artifactId>jersey-media-moxy</artifactId>
|
||||
<version>2.27</version>
|
||||
<version>${jersey.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.ciyam</groupId>
|
||||
<artifactId>at</artifactId>
|
||||
<version>1.0</version>
|
||||
<groupId>org.glassfish.jersey.media</groupId>
|
||||
<artifactId>jersey-media-multipart</artifactId>
|
||||
<version>${jersey.version}</version>
|
||||
</dependency>
|
||||
<!-- Swagger OpenAPI implementation -->
|
||||
<dependency>
|
||||
<groupId>io.swagger.core.v3</groupId>
|
||||
<artifactId>swagger-jaxrs2</artifactId>
|
||||
<version>${swagger-api.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hsqldb</groupId>
|
||||
<artifactId>sqltool</artifactId>
|
||||
<version>2.4.1</version>
|
||||
<scope>test</scope>
|
||||
<groupId>io.swagger.core.v3</groupId>
|
||||
<artifactId>swagger-jaxrs2-servlet-initializer</artifactId>
|
||||
<version>${swagger-api.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.webjars</groupId>
|
||||
<artifactId>swagger-ui</artifactId>
|
||||
<version>${swagger-ui.version}</version>
|
||||
</dependency>
|
||||
<!-- Testing -->
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-engine</artifactId>
|
||||
@ -265,48 +311,11 @@
|
||||
<artifactId>hamcrest-library</artifactId>
|
||||
<version>1.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.glassfish.jersey.media</groupId>
|
||||
<artifactId>jersey-media-multipart</artifactId>
|
||||
<version>2.27</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.mail</groupId>
|
||||
<artifactId>mail</artifactId>
|
||||
<version>1.5.0-b01</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.webjars</groupId>
|
||||
<artifactId>swagger-ui</artifactId>
|
||||
<version>${swagger-ui.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-rewrite</artifactId>
|
||||
<version>9.4.11.v20180605</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bitcoinj</groupId>
|
||||
<artifactId>bitcoinj-core</artifactId>
|
||||
<version>0.14.7</version>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk15on -->
|
||||
<!-- BouncyCastle for crypto, including TLS secure networking -->
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk15on</artifactId>
|
||||
<version>1.60</version>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>1.7.12</version>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-simple -->
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-simple</artifactId>
|
||||
<version>1.7.12</version>
|
||||
<version>${bouncycastle.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
@ -81,8 +81,6 @@ public class AddressesResource {
|
||||
public String getLastReference(
|
||||
@Parameter(description = "a base58-encoded address", required = true) @PathParam("address") String address
|
||||
) {
|
||||
Security.checkApiCallAllowed("GET addresses/lastreference", request);
|
||||
|
||||
if (!Crypto.isValidAddress(address))
|
||||
throw this.apiErrorFactory.createError(ApiError.INVALID_ADDRESS);
|
||||
|
||||
@ -130,8 +128,6 @@ public class AddressesResource {
|
||||
}
|
||||
)
|
||||
public String getLastReferenceUnconfirmed(@PathParam("address") String address) {
|
||||
Security.checkApiCallAllowed("GET addresses/lastreference", request);
|
||||
|
||||
if (!Crypto.isValidAddress(address))
|
||||
throw this.apiErrorFactory.createError(ApiError.INVALID_ADDRESS);
|
||||
|
||||
@ -177,8 +173,6 @@ public class AddressesResource {
|
||||
}
|
||||
)
|
||||
public boolean validate(@PathParam("address") String address) {
|
||||
Security.checkApiCallAllowed("GET addresses/validate", request);
|
||||
|
||||
return Crypto.isValidAddress(address);
|
||||
}
|
||||
|
||||
@ -208,8 +202,6 @@ public class AddressesResource {
|
||||
}
|
||||
)
|
||||
public BigDecimal getGeneratingBalanceOfAddress(@PathParam("address") String address) {
|
||||
Security.checkApiCallAllowed("GET addresses/generatingbalance", request);
|
||||
|
||||
if (!Crypto.isValidAddress(address))
|
||||
throw this.apiErrorFactory.createError(ApiError.INVALID_ADDRESS);
|
||||
|
||||
@ -250,8 +242,6 @@ public class AddressesResource {
|
||||
}
|
||||
)
|
||||
public BigDecimal getGeneratingBalance(@PathParam("address") String address) {
|
||||
Security.checkApiCallAllowed("GET addresses/balance", request);
|
||||
|
||||
if (!Crypto.isValidAddress(address))
|
||||
throw this.apiErrorFactory.createError(ApiError.INVALID_ADDRESS);
|
||||
|
||||
@ -291,8 +281,6 @@ public class AddressesResource {
|
||||
}
|
||||
)
|
||||
public BigDecimal getAssetBalance(@PathParam("assetid") long assetid, @PathParam("address") String address) {
|
||||
Security.checkApiCallAllowed("GET addresses/assetbalance", request);
|
||||
|
||||
if (!Crypto.isValidAddress(address))
|
||||
throw this.apiErrorFactory.createError(ApiError.INVALID_ADDRESS);
|
||||
|
||||
@ -332,8 +320,6 @@ public class AddressesResource {
|
||||
}
|
||||
)
|
||||
public List<AccountBalanceData> getAssets(@PathParam("address") String address) {
|
||||
Security.checkApiCallAllowed("GET addresses/assets", request);
|
||||
|
||||
if (!Crypto.isValidAddress(address))
|
||||
throw this.apiErrorFactory.createError(ApiError.INVALID_ADDRESS);
|
||||
|
||||
@ -372,8 +358,6 @@ public class AddressesResource {
|
||||
}
|
||||
)
|
||||
public String getGeneratingBalance(@PathParam("address") String address, @PathParam("confirmations") int confirmations) {
|
||||
Security.checkApiCallAllowed("GET addresses/balance", request);
|
||||
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@ -403,8 +387,6 @@ public class AddressesResource {
|
||||
}
|
||||
)
|
||||
public String getPublicKey(@PathParam("address") String address) {
|
||||
Security.checkApiCallAllowed("GET addresses/publickey", request);
|
||||
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,8 @@
|
||||
package api;
|
||||
|
||||
import globalization.Translator;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
||||
import io.swagger.v3.oas.annotations.extensions.Extension;
|
||||
import io.swagger.v3.oas.annotations.extensions.ExtensionProperty;
|
||||
import io.swagger.v3.oas.annotations.media.Content;
|
||||
@ -30,16 +31,14 @@ public class AdminResource {
|
||||
@Context
|
||||
HttpServletRequest request;
|
||||
|
||||
private static final long startTime = System.currentTimeMillis();
|
||||
|
||||
private ApiErrorFactory apiErrorFactory;
|
||||
|
||||
public AdminResource() {
|
||||
this(new ApiErrorFactory(Translator.getInstance()));
|
||||
}
|
||||
|
||||
public AdminResource(ApiErrorFactory apiErrorFactory) {
|
||||
this.apiErrorFactory = apiErrorFactory;
|
||||
@GET
|
||||
@Path("/dud")
|
||||
@Parameter(name = "blockSignature", description = "Block signature", schema = @Schema(type = "string", format = "byte", minLength = 84, maxLength=88))
|
||||
@Parameter(in = ParameterIn.QUERY, name = "limit", description = "Maximum number of entries to return", 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"))
|
||||
public String globalParameters() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@GET
|
||||
@ -65,9 +64,7 @@ public class AdminResource {
|
||||
}
|
||||
)
|
||||
public String uptime() {
|
||||
Security.checkApiCallAllowed("GET admin/uptime", request);
|
||||
|
||||
return Long.toString(System.currentTimeMillis() - startTime);
|
||||
return Long.toString(System.currentTimeMillis() - Controller.startTime);
|
||||
}
|
||||
|
||||
@GET
|
||||
@ -93,16 +90,16 @@ public class AdminResource {
|
||||
}
|
||||
)
|
||||
public String shutdown() {
|
||||
Security.checkApiCallAllowed("GET admin/stop", request);
|
||||
Security.checkApiCallAllowed(request);
|
||||
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Controller.shutdown();
|
||||
}
|
||||
}); // disabled for now: .start();
|
||||
}).start();
|
||||
|
||||
return "false";
|
||||
return "true";
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -6,23 +6,31 @@ import globalization.Translator;
|
||||
import io.swagger.v3.core.converter.ModelConverters;
|
||||
import io.swagger.v3.jaxrs2.Reader;
|
||||
import io.swagger.v3.jaxrs2.ReaderListener;
|
||||
import io.swagger.v3.oas.models.media.Content;
|
||||
import io.swagger.v3.oas.models.Components;
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import io.swagger.v3.oas.models.Operation;
|
||||
import io.swagger.v3.oas.models.PathItem;
|
||||
import io.swagger.v3.oas.models.examples.Example;
|
||||
import io.swagger.v3.oas.models.info.Info;
|
||||
import io.swagger.v3.oas.models.media.Content;
|
||||
import io.swagger.v3.oas.models.media.MediaType;
|
||||
import io.swagger.v3.oas.models.media.Schema;
|
||||
import io.swagger.v3.oas.models.parameters.Parameter;
|
||||
import io.swagger.v3.oas.models.responses.ApiResponse;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
|
||||
public class AnnotationPostProcessor implements ReaderListener {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger(AnnotationPostProcessor.class);
|
||||
|
||||
private class ContextInformation {
|
||||
public String path;
|
||||
public Map<String, String> keys;
|
||||
@ -41,10 +49,22 @@ public class AnnotationPostProcessor implements ReaderListener {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeScan(Reader reader, OpenAPI openAPI) {}
|
||||
public void beforeScan(Reader reader, OpenAPI openAPI) {
|
||||
LOGGER.info("beforeScan");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterScan(Reader reader, OpenAPI openAPI) {
|
||||
LOGGER.info("afterScan");
|
||||
|
||||
// Populate Components section with reusable parameters, like "limit" and "offset"
|
||||
// We take the reusable parameters from AdminResource.globalParameters path "/admin/dud"
|
||||
Components components = openAPI.getComponents();
|
||||
PathItem globalParametersPathItem = openAPI.getPaths().get("/admin/dud");
|
||||
if (globalParametersPathItem != null)
|
||||
for (Parameter parameter : globalParametersPathItem.getGet().getParameters())
|
||||
components.addParameters(parameter.getName(), parameter);
|
||||
|
||||
// use context path and keys from "x-translation" extension annotations
|
||||
// to translate supported annotations and finally remove "x-translation" extensions
|
||||
Info resourceInfo = openAPI.getInfo();
|
||||
|
@ -31,10 +31,12 @@ public class ApiService {
|
||||
this.resources.add(AdminResource.class);
|
||||
this.resources.add(BlocksResource.class);
|
||||
this.resources.add(TransactionsResource.class);
|
||||
this.resources.add(BlockExplorerResource.class);
|
||||
this.resources.add(OpenApiResource.class); // swagger
|
||||
this.resources.add(ApiDefinition.class); // for API definition
|
||||
this.resources.add(AnnotationPostProcessor.class); // for API resource annotations
|
||||
this.resources.add(UtilsResource.class);
|
||||
|
||||
this.resources.add(BlockExplorerResource.class); // block-explorer.html
|
||||
this.resources.add(OpenApiResource.class); // Swagger/OpenAPI
|
||||
this.resources.add(ApiDefinition.class); // API info
|
||||
this.resources.add(AnnotationPostProcessor.class); // For API resource annotations
|
||||
ResourceConfig config = new ResourceConfig(this.resources);
|
||||
|
||||
// Create RPC server
|
||||
|
@ -7,12 +7,11 @@ import java.nio.file.Files;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.core.Context;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
|
||||
@Path("/")
|
||||
@Produces({ MediaType.TEXT_HTML })
|
||||
public class BlockExplorerResource {
|
||||
|
||||
@Context
|
||||
@ -23,6 +22,7 @@ public class BlockExplorerResource {
|
||||
|
||||
@GET
|
||||
@Path("/block-explorer.html")
|
||||
@Operation(hidden = true)
|
||||
public String getBlockExplorer() {
|
||||
try {
|
||||
byte[] htmlBytes = Files.readAllBytes(FileSystems.getDefault().getPath("block-explorer.html"));
|
||||
|
@ -3,6 +3,7 @@ package api;
|
||||
import data.block.BlockData;
|
||||
import globalization.Translator;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.extensions.Extension;
|
||||
import io.swagger.v3.oas.annotations.extensions.ExtensionProperty;
|
||||
import io.swagger.v3.oas.annotations.media.Content;
|
||||
@ -11,19 +12,23 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Base64;
|
||||
|
||||
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;
|
||||
|
||||
import api.models.BlockWithTransactions;
|
||||
import qora.block.Block;
|
||||
import repository.DataException;
|
||||
import repository.Repository;
|
||||
import repository.RepositoryManager;
|
||||
import utils.Base58;
|
||||
|
||||
@Path("blocks")
|
||||
@Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN})
|
||||
@ -50,7 +55,7 @@ public class BlocksResource {
|
||||
@GET
|
||||
@Path("/{signature}")
|
||||
@Operation(
|
||||
summary = "Fetch block using base58 signature",
|
||||
summary = "Fetch block using base64 signature",
|
||||
description = "returns the block that matches the given signature",
|
||||
extensions = {
|
||||
@Extension(name = "translation", properties = {
|
||||
@ -64,7 +69,7 @@ public class BlocksResource {
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
description = "the block",
|
||||
content = @Content(schema = @Schema(implementation = BlockData.class)),
|
||||
content = @Content(schema = @Schema(implementation = BlockWithTransactions.class)),
|
||||
extensions = {
|
||||
@Extension(name = "translation", properties = {
|
||||
@ExtensionProperty(name="description.key", value="success_response:description")
|
||||
@ -73,34 +78,23 @@ public class BlocksResource {
|
||||
)
|
||||
}
|
||||
)
|
||||
public BlockData getBlock(@PathParam("signature") String signature) {
|
||||
Security.checkApiCallAllowed("GET blocks", request);
|
||||
|
||||
// decode signature
|
||||
public BlockWithTransactions getBlock(@PathParam("signature") String signature, @Parameter(ref = "includeTransactions") @QueryParam("includeTransactions") boolean includeTransactions) {
|
||||
// Decode signature
|
||||
byte[] signatureBytes;
|
||||
try
|
||||
{
|
||||
signatureBytes = Base58.decode(signature);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw this.apiErrorFactory.createError(ApiError.INVALID_SIGNATURE, e);
|
||||
try {
|
||||
signatureBytes = Base64.getDecoder().decode(signature);
|
||||
} catch (NumberFormatException e) {
|
||||
throw this.apiErrorFactory.createError(ApiError.INVALID_SIGNATURE, e);
|
||||
}
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
BlockData blockData = repository.getBlockRepository().fromSignature(signatureBytes);
|
||||
|
||||
// check if block exists
|
||||
if(blockData == null)
|
||||
throw this.apiErrorFactory.createError(ApiError.BLOCK_NO_EXISTS);
|
||||
|
||||
return blockData;
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
BlockData blockData = repository.getBlockRepository().fromSignature(signatureBytes);
|
||||
return new BlockWithTransactions(repository, blockData, includeTransactions);
|
||||
} catch (ApiException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw this.apiErrorFactory.createError(ApiError.UNKNOWN, e);
|
||||
}
|
||||
} catch (DataException e) {
|
||||
throw this.apiErrorFactory.createError(ApiError.REPOSITORY_ISSUE, e);
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@ -115,7 +109,7 @@ public class BlocksResource {
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
description = "the block",
|
||||
content = @Content(schema = @Schema(implementation = BlockData.class)),
|
||||
content = @Content(schema = @Schema(implementation = BlockWithTransactions.class)),
|
||||
extensions = {
|
||||
@Extension(name = "translation", properties = {
|
||||
@ExtensionProperty(name="description.key", value="success_response:description")
|
||||
@ -124,18 +118,15 @@ public class BlocksResource {
|
||||
)
|
||||
}
|
||||
)
|
||||
public BlockData getFirstBlock() {
|
||||
Security.checkApiCallAllowed("GET blocks/first", request);
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
BlockData blockData = repository.getBlockRepository().fromHeight(1);
|
||||
return blockData;
|
||||
|
||||
public BlockWithTransactions getFirstBlock(@Parameter(ref = "includeTransactions") @QueryParam("includeTransactions") boolean includeTransactions) {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
BlockData blockData = repository.getBlockRepository().fromHeight(1);
|
||||
return new BlockWithTransactions(repository, blockData, includeTransactions);
|
||||
} catch (ApiException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw this.apiErrorFactory.createError(ApiError.UNKNOWN, e);
|
||||
}
|
||||
} catch (DataException e) {
|
||||
throw this.apiErrorFactory.createError(ApiError.REPOSITORY_ISSUE, e);
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@ -150,7 +141,7 @@ public class BlocksResource {
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
description = "the block",
|
||||
content = @Content(schema = @Schema(implementation = BlockData.class)),
|
||||
content = @Content(schema = @Schema(implementation = BlockWithTransactions.class)),
|
||||
extensions = {
|
||||
@Extension(name = "translation", properties = {
|
||||
@ExtensionProperty(name="description.key", value="success_response:description")
|
||||
@ -159,24 +150,21 @@ public class BlocksResource {
|
||||
)
|
||||
}
|
||||
)
|
||||
public BlockData getLastBlock() {
|
||||
Security.checkApiCallAllowed("GET blocks/last", request);
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
BlockData blockData = repository.getBlockRepository().getLastBlock();
|
||||
return blockData;
|
||||
|
||||
public BlockWithTransactions getLastBlock(@Parameter(ref = "includeTransactions") @QueryParam("includeTransactions") boolean includeTransactions) {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
BlockData blockData = repository.getBlockRepository().getLastBlock();
|
||||
return new BlockWithTransactions(repository, blockData, includeTransactions);
|
||||
} catch (ApiException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw this.apiErrorFactory.createError(ApiError.UNKNOWN, e);
|
||||
}
|
||||
} catch (DataException e) {
|
||||
throw this.apiErrorFactory.createError(ApiError.REPOSITORY_ISSUE, e);
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/child/{signature}")
|
||||
@Operation(
|
||||
summary = "Fetch child block using base58 signature of parent block",
|
||||
summary = "Fetch child block using base64 signature of parent block",
|
||||
description = "returns the child block of the block that matches the given signature",
|
||||
extensions = {
|
||||
@Extension(name = "translation", properties = {
|
||||
@ -190,7 +178,7 @@ public class BlocksResource {
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
description = "the block",
|
||||
content = @Content(schema = @Schema(implementation = BlockData.class)),
|
||||
content = @Content(schema = @Schema(implementation = BlockWithTransactions.class)),
|
||||
extensions = {
|
||||
@Extension(name = "translation", properties = {
|
||||
@ExtensionProperty(name="description.key", value="success_response:description")
|
||||
@ -199,13 +187,11 @@ public class BlocksResource {
|
||||
)
|
||||
}
|
||||
)
|
||||
public BlockData getChild(@PathParam("signature") String signature) {
|
||||
Security.checkApiCallAllowed("GET blocks/child", request);
|
||||
|
||||
// decode signature
|
||||
public BlockWithTransactions getChild(@PathParam("signature") String signature, @Parameter(ref = "includeTransactions") @QueryParam("includeTransactions") boolean includeTransactions) {
|
||||
// Decode signature
|
||||
byte[] signatureBytes;
|
||||
try {
|
||||
signatureBytes = Base58.decode(signature);
|
||||
signatureBytes = Base64.getDecoder().decode(signature);
|
||||
} catch (NumberFormatException e) {
|
||||
throw this.apiErrorFactory.createError(ApiError.INVALID_SIGNATURE, e);
|
||||
}
|
||||
@ -213,17 +199,17 @@ public class BlocksResource {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
BlockData blockData = repository.getBlockRepository().fromSignature(signatureBytes);
|
||||
|
||||
// check if block exists
|
||||
// Check block exists
|
||||
if(blockData == null)
|
||||
throw this.apiErrorFactory.createError(ApiError.BLOCK_NO_EXISTS);
|
||||
|
||||
BlockData childBlockData = repository.getBlockRepository().fromReference(signatureBytes);
|
||||
|
||||
// check if child exists
|
||||
// Check child exists
|
||||
if(childBlockData == null)
|
||||
throw this.apiErrorFactory.createError(ApiError.BLOCK_NO_EXISTS);
|
||||
|
||||
return childBlockData;
|
||||
return new BlockWithTransactions(repository, childBlockData, includeTransactions);
|
||||
} catch (ApiException e) {
|
||||
throw e;
|
||||
} catch (DataException e) {
|
||||
@ -252,18 +238,15 @@ public class BlocksResource {
|
||||
}
|
||||
)
|
||||
public BigDecimal getGeneratingBalance() {
|
||||
Security.checkApiCallAllowed("GET blocks/generatingbalance", request);
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
BlockData blockData = repository.getBlockRepository().getLastBlock();
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
BlockData blockData = repository.getBlockRepository().getLastBlock();
|
||||
Block block = new Block(repository, blockData);
|
||||
return block.calcNextBlockGeneratingBalance();
|
||||
|
||||
} catch (ApiException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw this.apiErrorFactory.createError(ApiError.UNKNOWN, e);
|
||||
}
|
||||
} catch (DataException e) {
|
||||
throw this.apiErrorFactory.createError(ApiError.REPOSITORY_ISSUE, e);
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@ -292,34 +275,28 @@ public class BlocksResource {
|
||||
}
|
||||
)
|
||||
public BigDecimal getGeneratingBalance(@PathParam("signature") String signature) {
|
||||
Security.checkApiCallAllowed("GET blocks/generatingbalance", request);
|
||||
|
||||
// decode signature
|
||||
// Decode signature
|
||||
byte[] signatureBytes;
|
||||
try
|
||||
{
|
||||
signatureBytes = Base58.decode(signature);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw this.apiErrorFactory.createError(ApiError.INVALID_SIGNATURE, e);
|
||||
try {
|
||||
signatureBytes = Base64.getDecoder().decode(signature);
|
||||
} catch (NumberFormatException e) {
|
||||
throw this.apiErrorFactory.createError(ApiError.INVALID_SIGNATURE, e);
|
||||
}
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
BlockData blockData = repository.getBlockRepository().fromSignature(signatureBytes);
|
||||
|
||||
// check if block exists
|
||||
if(blockData == null)
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
BlockData blockData = repository.getBlockRepository().fromSignature(signatureBytes);
|
||||
|
||||
// Check block exists
|
||||
if (blockData == null)
|
||||
throw this.apiErrorFactory.createError(ApiError.BLOCK_NO_EXISTS);
|
||||
|
||||
Block block = new Block(repository, blockData);
|
||||
return block.calcNextBlockGeneratingBalance();
|
||||
|
||||
} catch (ApiException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw this.apiErrorFactory.createError(ApiError.UNKNOWN, e);
|
||||
}
|
||||
} catch (DataException e) {
|
||||
throw this.apiErrorFactory.createError(ApiError.REPOSITORY_ISSUE, e);
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@ -343,17 +320,14 @@ public class BlocksResource {
|
||||
}
|
||||
)
|
||||
public long getTimePerBlock() {
|
||||
Security.checkApiCallAllowed("GET blocks/time", request);
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
BlockData blockData = repository.getBlockRepository().getLastBlock();
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
BlockData blockData = repository.getBlockRepository().getLastBlock();
|
||||
return Block.calcForgingDelay(blockData.getGeneratingBalance());
|
||||
|
||||
} catch (ApiException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw this.apiErrorFactory.createError(ApiError.UNKNOWN, e);
|
||||
}
|
||||
} catch (DataException e) {
|
||||
throw this.apiErrorFactory.createError(ApiError.REPOSITORY_ISSUE, e);
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@ -377,8 +351,6 @@ public class BlocksResource {
|
||||
}
|
||||
)
|
||||
public long getTimePerBlock(@PathParam("generating") BigDecimal generatingbalance) {
|
||||
Security.checkApiCallAllowed("GET blocks/time", request);
|
||||
|
||||
return Block.calcForgingDelay(generatingbalance);
|
||||
}
|
||||
|
||||
@ -403,16 +375,13 @@ public class BlocksResource {
|
||||
}
|
||||
)
|
||||
public int getHeight() {
|
||||
Security.checkApiCallAllowed("GET blocks/height", request);
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
return repository.getBlockRepository().getBlockchainHeight();
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
return repository.getBlockRepository().getBlockchainHeight();
|
||||
} catch (ApiException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw this.apiErrorFactory.createError(ApiError.UNKNOWN, e);
|
||||
}
|
||||
} catch (DataException e) {
|
||||
throw this.apiErrorFactory.createError(ApiError.REPOSITORY_ISSUE, e);
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@ -441,33 +410,27 @@ public class BlocksResource {
|
||||
}
|
||||
)
|
||||
public int getHeight(@PathParam("signature") String signature) {
|
||||
Security.checkApiCallAllowed("GET blocks/height", request);
|
||||
|
||||
// decode signature
|
||||
// Decode signature
|
||||
byte[] signatureBytes;
|
||||
try
|
||||
{
|
||||
signatureBytes = Base58.decode(signature);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw this.apiErrorFactory.createError(ApiError.INVALID_SIGNATURE, e);
|
||||
try {
|
||||
signatureBytes = Base64.getDecoder().decode(signature);
|
||||
} catch (NumberFormatException e) {
|
||||
throw this.apiErrorFactory.createError(ApiError.INVALID_SIGNATURE, e);
|
||||
}
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
BlockData blockData = repository.getBlockRepository().fromSignature(signatureBytes);
|
||||
|
||||
// check if block exists
|
||||
if(blockData == null)
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
BlockData blockData = repository.getBlockRepository().fromSignature(signatureBytes);
|
||||
|
||||
// Check block exists
|
||||
if (blockData == null)
|
||||
throw this.apiErrorFactory.createError(ApiError.BLOCK_NO_EXISTS);
|
||||
|
||||
return blockData.getHeight();
|
||||
|
||||
} catch (ApiException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw this.apiErrorFactory.createError(ApiError.UNKNOWN, e);
|
||||
}
|
||||
} catch (DataException e) {
|
||||
throw this.apiErrorFactory.createError(ApiError.REPOSITORY_ISSUE, e);
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@ -486,7 +449,7 @@ public class BlocksResource {
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
description = "the block",
|
||||
content = @Content(schema = @Schema(implementation = BlockData.class)),
|
||||
content = @Content(schema = @Schema(implementation = BlockWithTransactions.class)),
|
||||
extensions = {
|
||||
@Extension(name = "translation", properties = {
|
||||
@ExtensionProperty(name="description.key", value="success_response:description")
|
||||
@ -495,22 +458,15 @@ public class BlocksResource {
|
||||
)
|
||||
}
|
||||
)
|
||||
public BlockData getbyHeight(@PathParam("height") int height) {
|
||||
Security.checkApiCallAllowed("GET blocks/byheight", request);
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
BlockData blockData = repository.getBlockRepository().fromHeight(height);
|
||||
|
||||
// check if block exists
|
||||
if(blockData == null)
|
||||
throw this.apiErrorFactory.createError(ApiError.BLOCK_NO_EXISTS);
|
||||
|
||||
return blockData;
|
||||
|
||||
public BlockWithTransactions getbyHeight(@PathParam("height") int height, @Parameter(ref = "includeTransactions") @QueryParam("includeTransactions") boolean includeTransactions) {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
BlockData blockData = repository.getBlockRepository().fromHeight(height);
|
||||
return new BlockWithTransactions(repository, blockData, includeTransactions);
|
||||
} catch (ApiException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw this.apiErrorFactory.createError(ApiError.UNKNOWN, e);
|
||||
}
|
||||
} catch (DataException e) {
|
||||
throw this.apiErrorFactory.createError(ApiError.REPOSITORY_ISSUE, e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,12 +1,22 @@
|
||||
package api;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
public class Security {
|
||||
|
||||
public static void checkApiCallAllowed(final String messageToDisplay, HttpServletRequest request) {
|
||||
// TODO
|
||||
// TODO: replace with proper authentication
|
||||
public static void checkApiCallAllowed(HttpServletRequest request) {
|
||||
InetAddress remoteAddr;
|
||||
try {
|
||||
remoteAddr = InetAddress.getByName(request.getRemoteAddr());
|
||||
} catch (UnknownHostException e) {
|
||||
throw ApiErrorFactory.getInstance().createError(ApiError.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
// throw this.apiErrorFactory.createError(ApiError.UNAUTHORIZED);
|
||||
if (!remoteAddr.isLoopbackAddress())
|
||||
throw ApiErrorFactory.getInstance().createError(ApiError.UNAUTHORIZED);
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,8 @@ package api;
|
||||
|
||||
import globalization.Translator;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
||||
import io.swagger.v3.oas.annotations.extensions.Extension;
|
||||
import io.swagger.v3.oas.annotations.extensions.ExtensionProperty;
|
||||
import io.swagger.v3.oas.annotations.media.ArraySchema;
|
||||
@ -19,9 +21,12 @@ 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;
|
||||
|
||||
import data.transaction.GenesisTransactionData;
|
||||
import data.transaction.PaymentTransactionData;
|
||||
import data.transaction.TransactionData;
|
||||
import repository.DataException;
|
||||
import repository.Repository;
|
||||
@ -56,6 +61,9 @@ public class TransactionsResource {
|
||||
@Operation(
|
||||
summary = "Fetch transactions involving address",
|
||||
description = "Returns list of transactions",
|
||||
parameters = {
|
||||
@Parameter(in = ParameterIn.PATH, name = "address", description = "Account's address", schema = @Schema(type = "string"))
|
||||
},
|
||||
extensions = {
|
||||
@Extension(name = "translation", properties = {
|
||||
@ExtensionProperty(name="path", value="GET block:signature"),
|
||||
@ -77,9 +85,7 @@ public class TransactionsResource {
|
||||
)
|
||||
}
|
||||
)
|
||||
public List<TransactionData> getAddressTransactions(@PathParam("address") String address) {
|
||||
Security.checkApiCallAllowed("GET transactions/address", request);
|
||||
|
||||
public List<TransactionData> getAddressTransactions(@PathParam("address") String address, @Parameter(ref = "limit") int limit, @Parameter(ref = "offset") @QueryParam("offset") int offset) {
|
||||
if (!Crypto.isValidAddress(address))
|
||||
throw this.apiErrorFactory.createError(ApiError.INVALID_ADDRESS);
|
||||
|
||||
@ -89,6 +95,9 @@ public class TransactionsResource {
|
||||
List<byte[]> signatures = txRepo.getAllSignaturesInvolvingAddress(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);
|
||||
|
||||
// Expand signatures to transactions
|
||||
List<TransactionData> transactions = new ArrayList<TransactionData>(signatures.size());
|
||||
@ -101,13 +110,12 @@ public class TransactionsResource {
|
||||
} catch (DataException e) {
|
||||
throw this.apiErrorFactory.createError(ApiError.REPOSITORY_ISSUE, e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/block/{signature}")
|
||||
@Operation(
|
||||
summary = "Fetch transactions via block signature",
|
||||
summary = "Fetch transactions using block signature",
|
||||
description = "Returns list of transactions",
|
||||
extensions = {
|
||||
@Extension(name = "translation", properties = {
|
||||
@ -121,7 +129,9 @@ public class TransactionsResource {
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
description = "list of transactions",
|
||||
content = @Content(array = @ArraySchema(schema = @Schema(implementation = TransactionData.class))),
|
||||
content = @Content(array = @ArraySchema(schema = @Schema(
|
||||
oneOf = { GenesisTransactionData.class, PaymentTransactionData.class }
|
||||
))),
|
||||
extensions = {
|
||||
@Extension(name = "translation", properties = {
|
||||
@ExtensionProperty(name="description.key", value="success_response:description")
|
||||
@ -130,9 +140,7 @@ public class TransactionsResource {
|
||||
)
|
||||
}
|
||||
)
|
||||
public List<TransactionData> getBlockTransactions(@PathParam("signature") String signature) {
|
||||
Security.checkApiCallAllowed("GET transactions/block", request);
|
||||
|
||||
public List<TransactionData> getBlockTransactions(@PathParam("signature") String signature, @Parameter(ref = "limit") @QueryParam("limit") int limit, @Parameter(ref = "offset") @QueryParam("offset") int offset) {
|
||||
// decode signature
|
||||
byte[] signatureBytes;
|
||||
try {
|
||||
@ -148,13 +156,17 @@ public class TransactionsResource {
|
||||
if(transactions == null)
|
||||
throw this.apiErrorFactory.createError(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;
|
||||
} catch (ApiException e) {
|
||||
throw e;
|
||||
} catch (DataException e) {
|
||||
throw this.apiErrorFactory.createError(ApiError.REPOSITORY_ISSUE, e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
66
src/api/UtilsResource.java
Normal file
66
src/api/UtilsResource.java
Normal file
@ -0,0 +1,66 @@
|
||||
package api;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
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 utils.Base58;
|
||||
|
||||
import java.util.Base64;
|
||||
|
||||
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.core.Context;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
|
||||
@Path("/utils")
|
||||
@Produces({MediaType.TEXT_PLAIN})
|
||||
@Tag(name = "utils")
|
||||
public class UtilsResource {
|
||||
|
||||
@Context
|
||||
HttpServletRequest request;
|
||||
|
||||
@GET
|
||||
@Path("/base58from64/{base64}")
|
||||
@Operation(
|
||||
summary = "Convert base64 data to base58",
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
description = "base58 data",
|
||||
content = @Content(schema = @Schema(implementation = String.class))
|
||||
)
|
||||
}
|
||||
)
|
||||
public String base58from64(@PathParam("base64") String base64) {
|
||||
try {
|
||||
return Base58.encode(Base64.getDecoder().decode(base64));
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_DATA);
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/base64from58/{base58}")
|
||||
@Operation(
|
||||
summary = "Convert base58 data to base64",
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
description = "base64 data",
|
||||
content = @Content(schema = @Schema(implementation = String.class))
|
||||
)
|
||||
}
|
||||
)
|
||||
public String base64from58(@PathParam("base58") String base58) {
|
||||
try {
|
||||
return Base64.getEncoder().encodeToString(Base58.decode(base58));
|
||||
} catch (NumberFormatException e) {
|
||||
throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_DATA);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
43
src/api/models/BlockWithTransactions.java
Normal file
43
src/api/models/BlockWithTransactions.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.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 = "Block with (optional) transactions")
|
||||
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
|
||||
@SuppressWarnings("unused")
|
||||
private BlockWithTransactions() {
|
||||
}
|
||||
|
||||
public BlockWithTransactions(Repository repository, BlockData blockData, boolean includeTransactions) throws DataException {
|
||||
if (blockData == null)
|
||||
throw ApiErrorFactory.getInstance().createError(ApiError.BLOCK_NO_EXISTS);
|
||||
|
||||
this.blockData = blockData;
|
||||
|
||||
if (includeTransactions) {
|
||||
Block block = new Block(repository, blockData);
|
||||
this.transactions = block.getTransactions().stream().map(transaction -> transaction.getTransactionData()).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -14,19 +14,31 @@ public class Controller {
|
||||
private static final Logger LOGGER = LogManager.getLogger(Controller.class);
|
||||
|
||||
private static final String connectionUrl = "jdbc:hsqldb:file:db/test;create=true";
|
||||
|
||||
public static final long startTime = System.currentTimeMillis();
|
||||
private static final Object shutdownLock = new Object();
|
||||
private static boolean isStopping = false;
|
||||
|
||||
public static void main(String args[]) throws DataException {
|
||||
public static void main(String args[]) {
|
||||
LOGGER.info("Starting up...");
|
||||
|
||||
LOGGER.info("Starting repository");
|
||||
RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(connectionUrl);
|
||||
RepositoryManager.setRepositoryFactory(repositoryFactory);
|
||||
try {
|
||||
RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(connectionUrl);
|
||||
RepositoryManager.setRepositoryFactory(repositoryFactory);
|
||||
} catch (DataException e) {
|
||||
LOGGER.error("Unable to start repository", e);
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
LOGGER.info("Starting API");
|
||||
ApiService apiService = ApiService.getInstance();
|
||||
apiService.start();
|
||||
try {
|
||||
ApiService apiService = ApiService.getInstance();
|
||||
apiService.start();
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Unable to start API", e);
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
Runtime.getRuntime().addShutdownHook(new Thread() {
|
||||
@Override
|
||||
|
@ -29,8 +29,7 @@ public class BlockData implements Serializable {
|
||||
private BigDecimal atFees;
|
||||
|
||||
// necessary for JAX-RS serialization
|
||||
@SuppressWarnings("unused")
|
||||
private BlockData() {
|
||||
protected BlockData() {
|
||||
}
|
||||
|
||||
public BlockData(int version, byte[] reference, int transactionCount, BigDecimal totalFees, byte[] transactionsSignature, Integer height, long timestamp,
|
||||
|
@ -2,9 +2,16 @@ package data.transaction;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import qora.account.GenesisAccount;
|
||||
import qora.transaction.Transaction.TransactionType;
|
||||
|
||||
// All properties to be converted to JSON via JAX-RS
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
@Schema( allOf = { TransactionData.class } )
|
||||
public class GenesisTransactionData extends TransactionData {
|
||||
|
||||
// Properties
|
||||
@ -13,6 +20,10 @@ public class GenesisTransactionData extends TransactionData {
|
||||
|
||||
// Constructors
|
||||
|
||||
// For JAX-RS
|
||||
protected GenesisTransactionData() {
|
||||
}
|
||||
|
||||
public GenesisTransactionData(String recipient, BigDecimal amount, long timestamp, byte[] signature) {
|
||||
// Zero fee
|
||||
super(TransactionType.GENESIS, BigDecimal.ZERO, GenesisAccount.PUBLIC_KEY, timestamp, null, signature);
|
||||
|
@ -2,8 +2,15 @@ package data.transaction;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import qora.transaction.Transaction.TransactionType;
|
||||
|
||||
// All properties to be converted to JSON via JAX-RS
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
@Schema( allOf = { TransactionData.class } )
|
||||
public class PaymentTransactionData extends TransactionData {
|
||||
|
||||
// Properties
|
||||
@ -13,6 +20,10 @@ public class PaymentTransactionData extends TransactionData {
|
||||
|
||||
// Constructors
|
||||
|
||||
// For JAX-RS
|
||||
protected PaymentTransactionData() {
|
||||
}
|
||||
|
||||
public PaymentTransactionData(byte[] senderPublicKey, String recipient, BigDecimal amount, BigDecimal fee, long timestamp, byte[] reference,
|
||||
byte[] signature) {
|
||||
super(TransactionType.PAYMENT, fee, senderPublicKey, timestamp, reference, signature);
|
||||
|
@ -23,6 +23,10 @@ public abstract class TransactionData {
|
||||
|
||||
// Constructors
|
||||
|
||||
// For JAX-RS
|
||||
protected TransactionData() {
|
||||
}
|
||||
|
||||
public TransactionData(TransactionType type, BigDecimal fee, byte[] creatorPublicKey, long timestamp, byte[] reference, byte[] signature) {
|
||||
this.fee = fee;
|
||||
this.type = type;
|
||||
|
@ -1,7 +1,6 @@
|
||||
package qora.account;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
|
@ -20,6 +20,12 @@ public class HSQLDBRepositoryFactory implements RepositoryFactory {
|
||||
// one-time initialization goes in here
|
||||
this.connectionUrl = connectionUrl;
|
||||
|
||||
// Check no-one else is accessing database
|
||||
try (Connection connection = DriverManager.getConnection(this.connectionUrl)) {
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to open repository: " + e.getMessage());
|
||||
}
|
||||
|
||||
this.connectionPool = new JDBCPool();
|
||||
this.connectionPool.setUrl(this.connectionUrl);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user