mirror of
				https://github.com/Qortal/qortal.git
				synced 2025-11-04 08:57:04 +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:
		@@ -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.");
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										35
									
								
								tools/qdata
									
									
									
									
									
								
							
							
						
						
									
										35
									
								
								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
 | 
				
			||||||
 | 
					    if [[ "${type}" == "PATH" ]]; then
 | 
				
			||||||
      echo "Error: missing directory"; exit
 | 
					      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..."
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user