mirror of
https://github.com/Qortal/qortal.git
synced 2025-03-27 07:45:53 +00:00
Added POST /arbitrary/../string API endpoints to allow data to be passed to the core as a string.
This will be useful for metadata, playlists, etc, as well as some types of data published by Qortal apps.
This commit is contained in:
parent
332b874493
commit
d0aafaee60
@ -10,6 +10,10 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
|||||||
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
|
import java.io.BufferedWriter;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileWriter;
|
||||||
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -265,7 +269,6 @@ public class ArbitraryResource {
|
|||||||
@Path("/{service}/{name}")
|
@Path("/{service}/{name}")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Build raw, unsigned, ARBITRARY transaction, based on a user-supplied path",
|
summary = "Build raw, unsigned, ARBITRARY transaction, based on a user-supplied path",
|
||||||
description = "A POST transaction automatically selects a PUT or PATCH method based on the data supplied",
|
|
||||||
requestBody = @RequestBody(
|
requestBody = @RequestBody(
|
||||||
required = true,
|
required = true,
|
||||||
content = @Content(
|
content = @Content(
|
||||||
@ -293,14 +296,48 @@ public class ArbitraryResource {
|
|||||||
String path) {
|
String path) {
|
||||||
Security.checkApiCallAllowed(request);
|
Security.checkApiCallAllowed(request);
|
||||||
|
|
||||||
return this.upload(null, Service.valueOf(serviceString), name, null, path);
|
return this.upload(null, Service.valueOf(serviceString), name, null, path, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("/{service}/{name}/string")
|
||||||
|
@Operation(
|
||||||
|
summary = "Build raw, unsigned, ARBITRARY transaction, based on a user-supplied string",
|
||||||
|
requestBody = @RequestBody(
|
||||||
|
required = true,
|
||||||
|
content = @Content(
|
||||||
|
mediaType = MediaType.TEXT_PLAIN,
|
||||||
|
schema = @Schema(
|
||||||
|
type = "string", example = "{\"title\":\"\", \"description\":\"\", \"tags\":[]}"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(
|
||||||
|
description = "raw, unsigned, ARBITRARY transaction encoded in Base58",
|
||||||
|
content = @Content(
|
||||||
|
mediaType = MediaType.TEXT_PLAIN,
|
||||||
|
schema = @Schema(
|
||||||
|
type = "string"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@SecurityRequirement(name = "apiKey")
|
||||||
|
public String postString(@PathParam("service") String serviceString,
|
||||||
|
@PathParam("name") String name,
|
||||||
|
String string) {
|
||||||
|
Security.checkApiCallAllowed(request);
|
||||||
|
|
||||||
|
return this.upload(null, Service.valueOf(serviceString), name, null, null, string);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@Path("/{service}/{name}/{identifier}")
|
@Path("/{service}/{name}/{identifier}")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Build raw, unsigned, ARBITRARY transaction, based on a user-supplied path",
|
summary = "Build raw, unsigned, ARBITRARY transaction, based on a user-supplied path",
|
||||||
description = "A POST transaction automatically selects a PUT or PATCH method based on the data supplied",
|
|
||||||
requestBody = @RequestBody(
|
requestBody = @RequestBody(
|
||||||
required = true,
|
required = true,
|
||||||
content = @Content(
|
content = @Content(
|
||||||
@ -329,10 +366,45 @@ public class ArbitraryResource {
|
|||||||
String path) {
|
String path) {
|
||||||
Security.checkApiCallAllowed(request);
|
Security.checkApiCallAllowed(request);
|
||||||
|
|
||||||
return this.upload(null, Service.valueOf(serviceString), name, identifier, path);
|
return this.upload(null, Service.valueOf(serviceString), name, identifier, path, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String upload(Method method, Service service, String name, String identifier, String path) {
|
@POST
|
||||||
|
@Path("/{service}/{name}/{identifier}/string")
|
||||||
|
@Operation(
|
||||||
|
summary = "Build raw, unsigned, ARBITRARY transaction, based on user supplied string",
|
||||||
|
requestBody = @RequestBody(
|
||||||
|
required = true,
|
||||||
|
content = @Content(
|
||||||
|
mediaType = MediaType.TEXT_PLAIN,
|
||||||
|
schema = @Schema(
|
||||||
|
type = "string", example = "{\"title\":\"\", \"description\":\"\", \"tags\":[]}"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(
|
||||||
|
description = "raw, unsigned, ARBITRARY transaction encoded in Base58",
|
||||||
|
content = @Content(
|
||||||
|
mediaType = MediaType.TEXT_PLAIN,
|
||||||
|
schema = @Schema(
|
||||||
|
type = "string"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@SecurityRequirement(name = "apiKey")
|
||||||
|
public String postString(@PathParam("service") String serviceString,
|
||||||
|
@PathParam("name") String name,
|
||||||
|
@PathParam("identifier") String identifier,
|
||||||
|
String string) {
|
||||||
|
Security.checkApiCallAllowed(request);
|
||||||
|
|
||||||
|
return this.upload(null, Service.valueOf(serviceString), name, identifier, null, string);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String upload(Method method, Service service, String name, String identifier, String path, String string) {
|
||||||
// Fetch public key from registered name
|
// Fetch public key from registered name
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
NameData nameData = repository.getNameRepository().fromName(name);
|
NameData nameData = repository.getNameRepository().fromName(name);
|
||||||
@ -348,6 +420,22 @@ public class ArbitraryResource {
|
|||||||
byte[] publicKey = accountData.getPublicKey();
|
byte[] publicKey = accountData.getPublicKey();
|
||||||
String publicKey58 = Base58.encode(publicKey);
|
String publicKey58 = Base58.encode(publicKey);
|
||||||
|
|
||||||
|
if (path == null) {
|
||||||
|
// See if we have a string instead
|
||||||
|
if (string != null) {
|
||||||
|
File tempFile = File.createTempFile("qortal-", ".tmp");
|
||||||
|
tempFile.deleteOnExit();
|
||||||
|
BufferedWriter writer = new BufferedWriter(new FileWriter(tempFile.toPath().toString()));
|
||||||
|
writer.write(string);
|
||||||
|
writer.newLine();
|
||||||
|
writer.close();
|
||||||
|
path = tempFile.toPath().toString();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.INVALID_CRITERIA, "Missing path or data string");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
ArbitraryDataTransactionBuilder transactionBuilder = new ArbitraryDataTransactionBuilder(
|
ArbitraryDataTransactionBuilder transactionBuilder = new ArbitraryDataTransactionBuilder(
|
||||||
publicKey58, Paths.get(path), name, method, service, identifier
|
publicKey58, Paths.get(path), name, method, service, identifier
|
||||||
@ -361,8 +449,8 @@ public class ArbitraryResource {
|
|||||||
throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.INVALID_DATA, e.getMessage());
|
throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.INVALID_DATA, e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (DataException e) {
|
} catch (DataException | IOException e) {
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE);
|
throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.REPOSITORY_ISSUE, e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,7 +125,7 @@ public class ArbitraryDataTransactionBuilder {
|
|||||||
catch (IOException | DataException | MissingDataException | IllegalStateException e) {
|
catch (IOException | DataException | MissingDataException | IllegalStateException e) {
|
||||||
// Handle matching states separately, as it's best to block transactions with duplicate states
|
// Handle matching states separately, as it's best to block transactions with duplicate states
|
||||||
if (e.getMessage().equals("Current state matches previous state. Nothing to do.")) {
|
if (e.getMessage().equals("Current state matches previous state. Nothing to do.")) {
|
||||||
throw new DataException(e);
|
throw new DataException(e.getMessage());
|
||||||
}
|
}
|
||||||
LOGGER.info("Caught exception: {}", e.getMessage());
|
LOGGER.info("Caught exception: {}", e.getMessage());
|
||||||
LOGGER.info("Unable to load existing resource - using PUT to overwrite it.");
|
LOGGER.info("Unable to load existing resource - using PUT to overwrite it.");
|
||||||
|
37
tools/qdata
37
tools/qdata
@ -8,7 +8,8 @@ if [ -z "$*" ]; then
|
|||||||
echo "Usage:"
|
echo "Usage:"
|
||||||
echo
|
echo
|
||||||
echo "Host/update data:"
|
echo "Host/update data:"
|
||||||
echo "qdata POST [service] [name] [dirpath] <identifier>"
|
echo "qdata POST [service] [name] PATH [dirpath] <identifier>"
|
||||||
|
echo "qdata POST [service] [name] STRING [data-string] <identifier>"
|
||||||
echo
|
echo
|
||||||
echo "Fetch data:"
|
echo "Fetch data:"
|
||||||
echo "qdata GET [service] [name] <identifier-or-default> <filepath-or-default> <rebuild>"
|
echo "qdata GET [service] [name] <identifier-or-default> <filepath-or-default> <rebuild>"
|
||||||
@ -37,20 +38,44 @@ fi
|
|||||||
|
|
||||||
|
|
||||||
if [[ "${method}" == "POST" ]]; then
|
if [[ "${method}" == "POST" ]]; then
|
||||||
directory=$4
|
type=$4
|
||||||
identifier=$5
|
data=$5
|
||||||
|
identifier=$6
|
||||||
|
|
||||||
if [ -z "${directory}" ]; then
|
if [ -z "${data}" ]; then
|
||||||
echo "Error: missing directory"; exit
|
if [[ "${type}" == "PATH" ]]; then
|
||||||
|
echo "Error: missing directory"; exit
|
||||||
|
elif [[ "${type}" == "STRING" ]]; then
|
||||||
|
echo "Error: missing data string"; exit
|
||||||
|
else
|
||||||
|
echo "Error: unrecognized type"; exit
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
if [ -z "${QORTAL_PRIVKEY}" ]; then
|
if [ -z "${QORTAL_PRIVKEY}" ]; then
|
||||||
echo "Error: missing private key. Set it by running: export QORTAL_PRIVKEY=privkeyhere"; exit
|
echo "Error: missing private key. Set it by running: export QORTAL_PRIVKEY=privkeyhere"; exit
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Create identifier component in URL
|
||||||
|
if [[ -z "${identifier}" || "${identifier}" == "default" ]]; then
|
||||||
|
identifier_component=""
|
||||||
|
else
|
||||||
|
identifier_component="/${identifier}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create type component in URL
|
||||||
|
if [[ "${type}" == "PATH" ]]; then
|
||||||
|
type_component=""
|
||||||
|
elif [[ "${type}" == "STRING" ]]; then
|
||||||
|
type_component="/string"
|
||||||
|
fi
|
||||||
|
|
||||||
echo "Creating transaction - this can take a while..."
|
echo "Creating transaction - this can take a while..."
|
||||||
tx_data=$(curl --silent --insecure -X ${method} "http://${host}:${port}/arbitrary/${service}/${name}/${identifier}" -d "${directory}")
|
tx_data=$(curl --silent --insecure -X ${method} "http://${host}:${port}/arbitrary/${service}/${name}${identifier_component}${type_component}" -d "${data}")
|
||||||
|
|
||||||
if [[ "${tx_data}" == *"error"* || "${tx_data}" == *"ERROR"* ]]; then
|
if [[ "${tx_data}" == *"error"* || "${tx_data}" == *"ERROR"* ]]; then
|
||||||
echo "${tx_data}"; exit
|
echo "${tx_data}"; exit
|
||||||
|
elif [ -z "${tx_data}" ]; then
|
||||||
|
echo "Error: no transaction data returned"; exit
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Signing..."
|
echo "Signing..."
|
||||||
|
Loading…
x
Reference in New Issue
Block a user