forked from Qortal/qortal
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.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.Paths;
|
||||
import java.util.ArrayList;
|
||||
@ -265,7 +269,6 @@ public class ArbitraryResource {
|
||||
@Path("/{service}/{name}")
|
||||
@Operation(
|
||||
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(
|
||||
required = true,
|
||||
content = @Content(
|
||||
@ -293,14 +296,48 @@ public class ArbitraryResource {
|
||||
String path) {
|
||||
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
|
||||
@Path("/{service}/{name}/{identifier}")
|
||||
@Operation(
|
||||
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(
|
||||
required = true,
|
||||
content = @Content(
|
||||
@ -329,10 +366,45 @@ public class ArbitraryResource {
|
||||
String path) {
|
||||
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
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
NameData nameData = repository.getNameRepository().fromName(name);
|
||||
@ -348,6 +420,22 @@ public class ArbitraryResource {
|
||||
byte[] publicKey = accountData.getPublicKey();
|
||||
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 {
|
||||
ArbitraryDataTransactionBuilder transactionBuilder = new ArbitraryDataTransactionBuilder(
|
||||
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());
|
||||
}
|
||||
|
||||
} catch (DataException e) {
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE);
|
||||
} catch (DataException | IOException e) {
|
||||
throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.REPOSITORY_ISSUE, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -125,7 +125,7 @@ public class ArbitraryDataTransactionBuilder {
|
||||
catch (IOException | DataException | MissingDataException | IllegalStateException e) {
|
||||
// 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.")) {
|
||||
throw new DataException(e);
|
||||
throw new DataException(e.getMessage());
|
||||
}
|
||||
LOGGER.info("Caught exception: {}", e.getMessage());
|
||||
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
|
||||
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 "Fetch data:"
|
||||
echo "qdata GET [service] [name] <identifier-or-default> <filepath-or-default> <rebuild>"
|
||||
@ -37,20 +38,44 @@ fi
|
||||
|
||||
|
||||
if [[ "${method}" == "POST" ]]; then
|
||||
directory=$4
|
||||
identifier=$5
|
||||
type=$4
|
||||
data=$5
|
||||
identifier=$6
|
||||
|
||||
if [ -z "${directory}" ]; then
|
||||
echo "Error: missing directory"; exit
|
||||
if [ -z "${data}" ]; then
|
||||
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
|
||||
if [ -z "${QORTAL_PRIVKEY}" ]; then
|
||||
echo "Error: missing private key. Set it by running: export QORTAL_PRIVKEY=privkeyhere"; exit
|
||||
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..."
|
||||
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
|
||||
echo "${tx_data}"; exit
|
||||
elif [ -z "${tx_data}" ]; then
|
||||
echo "Error: no transaction data returned"; exit
|
||||
fi
|
||||
|
||||
echo "Signing..."
|
||||
|
Loading…
Reference in New Issue
Block a user