1. Add to Page changes : In case the sample response is null, don't set the cached response for the action.
2. Add to Page changes : Documentation object has been added in Action to handle the extra Template documentation for actions that have been imported from 3p marketplace 3. Added basic structure for rapid api plugin by copy pasting the rest api plugin
This commit is contained in:
parent
bfea59f354
commit
b17fae8e44
|
|
@ -18,6 +18,7 @@
|
|||
<module>postgresPlugin</module>
|
||||
<module>restApiPlugin</module>
|
||||
<module>mongoPlugin</module>
|
||||
<module>rapidApiPlugin</module>
|
||||
</modules>
|
||||
|
||||
</project>
|
||||
85
app/server/appsmith-plugins/rapidApiPlugin/pom.xml
Normal file
85
app/server/appsmith-plugins/rapidApiPlugin/pom.xml
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.external.plugins</groupId>
|
||||
<artifactId>rapidApiPlugin</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
|
||||
<name>rapidApiPlugin</name>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<java.version>11</java.version>
|
||||
<maven.compiler.source>${java.version}</maven.compiler.source>
|
||||
<maven.compiler.target>${java.version}</maven.compiler.target>
|
||||
<plugin.id>rapidapi-plugin</plugin.id>
|
||||
<plugin.class>com.external.plugins.RapidApiPlugin</plugin.class>
|
||||
<plugin.version>1.0-SNAPSHOT</plugin.version>
|
||||
<plugin.provider>tech@appsmith.com</plugin.provider>
|
||||
<plugin.dependencies/>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.11</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.pf4j</groupId>
|
||||
<artifactId>pf4j-spring</artifactId>
|
||||
<version>0.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.appsmith</groupId>
|
||||
<artifactId>interfaces</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-webflux</artifactId>
|
||||
<version>5.1.5.RELEASE</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.8</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>3.1.2</version>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifestEntries>
|
||||
<Plugin-Id>${plugin.id}</Plugin-Id>
|
||||
<Plugin-Class>${plugin.class}</Plugin-Class>
|
||||
<Plugin-Version>${plugin.version}</Plugin-Version>
|
||||
<Plugin-Provider>${plugin.provider}</Plugin-Provider>
|
||||
<Plugin-Dependencies>${plugin.dependencies}</Plugin-Dependencies>
|
||||
</manifestEntries>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
266
app/server/appsmith-plugins/rapidApiPlugin/src/main/java/com/external/plugins/RapidApiPlugin.java
vendored
Normal file
266
app/server/appsmith-plugins/rapidApiPlugin/src/main/java/com/external/plugins/RapidApiPlugin.java
vendored
Normal file
|
|
@ -0,0 +1,266 @@
|
|||
package com.external.plugins;
|
||||
|
||||
import com.appsmith.external.models.ActionConfiguration;
|
||||
import com.appsmith.external.models.ActionExecutionResult;
|
||||
import com.appsmith.external.models.DatasourceConfiguration;
|
||||
import com.appsmith.external.models.Property;
|
||||
import com.appsmith.external.pluginExceptions.AppsmithPluginError;
|
||||
import com.appsmith.external.pluginExceptions.AppsmithPluginException;
|
||||
import com.appsmith.external.plugins.BasePlugin;
|
||||
import com.appsmith.external.plugins.PluginExecutor;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.bson.internal.Base64;
|
||||
import org.json.JSONObject;
|
||||
import org.pf4j.Extension;
|
||||
import org.pf4j.PluginWrapper;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.reactive.function.BodyInserters;
|
||||
import org.springframework.web.reactive.function.client.ClientResponse;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class RapidApiPlugin extends BasePlugin {
|
||||
private static int MAX_REDIRECTS = 5;
|
||||
private static ObjectMapper objectMapper;
|
||||
private static String rapidApiKeyName = "X-RapidAPI-Key";
|
||||
private static String rapidApiKeyValue = "f2a61def63msh9d6582090d01286p157197jsnade6f31fcae8";
|
||||
|
||||
public RapidApiPlugin(PluginWrapper wrapper) {
|
||||
super(wrapper);
|
||||
this.objectMapper = new ObjectMapper();
|
||||
}
|
||||
|
||||
@Slf4j
|
||||
@Extension
|
||||
public static class RapidApiPluginExecutor implements PluginExecutor {
|
||||
|
||||
@Override
|
||||
public Mono<Object> execute(Object connection,
|
||||
DatasourceConfiguration datasourceConfiguration,
|
||||
ActionConfiguration actionConfiguration) {
|
||||
|
||||
String requestBody = (actionConfiguration.getBody() == null) ? "" : actionConfiguration.getBody();
|
||||
String path = (actionConfiguration.getPath() == null) ? "" : actionConfiguration.getPath();
|
||||
String url = datasourceConfiguration.getUrl() + path;
|
||||
|
||||
HttpMethod httpMethod = actionConfiguration.getHttpMethod();
|
||||
if (httpMethod == null) {
|
||||
return Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, "HTTPMethod must be set."));
|
||||
}
|
||||
|
||||
WebClient.Builder webClientBuilder = WebClient.builder();
|
||||
|
||||
if (datasourceConfiguration.getHeaders() != null) {
|
||||
addHeadersToRequest(webClientBuilder, datasourceConfiguration.getHeaders());
|
||||
}
|
||||
|
||||
if (actionConfiguration.getHeaders() != null) {
|
||||
addHeadersToRequest(webClientBuilder, actionConfiguration.getHeaders());
|
||||
}
|
||||
|
||||
// Add the rapid api headers
|
||||
webClientBuilder.defaultHeader(rapidApiKeyName, rapidApiKeyValue);
|
||||
|
||||
URI uri = null;
|
||||
try {
|
||||
uri = createFinalUriWithQueryParams(url, actionConfiguration.getQueryParameters());
|
||||
System.out.println("Final URL is : " + uri.toString());
|
||||
} catch (URISyntaxException e) {
|
||||
e.printStackTrace();
|
||||
return Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, e));
|
||||
}
|
||||
|
||||
// Build the body of the request in case of bodyFormData is not null
|
||||
if (actionConfiguration.getBodyFormData() != null) {
|
||||
// First set the header to specify the content type
|
||||
webClientBuilder.defaultHeader("Content-Type", "application/json");
|
||||
|
||||
Map<String, String> strStrMap = new HashMap<String, String>();
|
||||
|
||||
List<Property> bodyFormData = actionConfiguration.getBodyFormData();
|
||||
|
||||
bodyFormData
|
||||
.stream()
|
||||
.map(property -> strStrMap.put(property.getKey(), property.getValue()));
|
||||
|
||||
log.debug("str str map from body form data is : {}", strStrMap);
|
||||
|
||||
JSONObject bodyJson = new JSONObject(strStrMap);
|
||||
String jsonString = bodyJson.toString();
|
||||
log.debug("Json string from body form data is : {}", jsonString);
|
||||
|
||||
}
|
||||
|
||||
WebClient client = webClientBuilder.build();
|
||||
return httpCall(client, httpMethod, uri, requestBody, 0)
|
||||
.flatMap(clientResponse -> clientResponse.toEntity(byte[].class))
|
||||
.map(stringResponseEntity -> {
|
||||
HttpHeaders headers = stringResponseEntity.getHeaders();
|
||||
// Find the media type of the response to parse the body as required.
|
||||
MediaType contentType = headers.getContentType();
|
||||
byte[] body = stringResponseEntity.getBody();
|
||||
HttpStatus statusCode = stringResponseEntity.getStatusCode();
|
||||
|
||||
ActionExecutionResult result = new ActionExecutionResult();
|
||||
result.setStatusCode(statusCode.toString());
|
||||
// If the HTTP response is 200, only then cache the response.
|
||||
// We shouldn't cache the response even for other 2xx statuses like 201, 204 etc.
|
||||
if (statusCode.equals(HttpStatus.OK)) {
|
||||
result.setShouldCacheResponse(true);
|
||||
}
|
||||
|
||||
if (headers != null) {
|
||||
// Convert the headers into json tree to store in the results
|
||||
String headerInJsonString;
|
||||
try {
|
||||
headerInJsonString = objectMapper.writeValueAsString(headers);
|
||||
} catch (JsonProcessingException e) {
|
||||
e.printStackTrace();
|
||||
return Mono.defer(() -> Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, e)));
|
||||
}
|
||||
try {
|
||||
// Set headers in the result now
|
||||
result.setHeaders(objectMapper.readTree(headerInJsonString));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return Mono.defer(() -> Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, e)));
|
||||
}
|
||||
}
|
||||
|
||||
if (body != null) {
|
||||
/**TODO
|
||||
* Handle XML response. Currently we only handle JSON & Image responses. The other kind of responses
|
||||
* are kept as is and returned as a string.
|
||||
*/
|
||||
if (MediaType.APPLICATION_JSON.equals(contentType) ||
|
||||
MediaType.APPLICATION_JSON_UTF8.equals(contentType) ||
|
||||
MediaType.APPLICATION_JSON_UTF8_VALUE.equals(contentType)) {
|
||||
try {
|
||||
String jsonBody = new String(body);
|
||||
result.setBody(objectMapper.readTree(jsonBody));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return Mono.defer(() -> Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, e)));
|
||||
}
|
||||
} else if (MediaType.IMAGE_GIF.equals(contentType) ||
|
||||
MediaType.IMAGE_JPEG.equals(contentType) ||
|
||||
MediaType.IMAGE_PNG.equals(contentType)) {
|
||||
String encode = Base64.encode(body);
|
||||
result.setBody(encode);
|
||||
} else {
|
||||
// If the body is not of JSON type, just set it as is.
|
||||
String bodyString = new String(body);
|
||||
result.setBody(bodyString.trim());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
})
|
||||
.doOnError(e -> Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, e)));
|
||||
}
|
||||
|
||||
private Mono<ClientResponse> httpCall(WebClient webClient, HttpMethod httpMethod, URI uri, String requestBody, int iteration) {
|
||||
if (iteration == MAX_REDIRECTS) {
|
||||
System.out.println("Exceeded the http redirect limits. Returning error");
|
||||
return Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, "Exceeded the HTTO redirect limits of " + MAX_REDIRECTS));
|
||||
}
|
||||
return webClient
|
||||
.method(httpMethod)
|
||||
.uri(uri)
|
||||
.body(BodyInserters.fromObject(requestBody))
|
||||
.exchange()
|
||||
.doOnError(e -> Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, e)))
|
||||
.flatMap(res -> {
|
||||
ClientResponse response = (ClientResponse) res;
|
||||
if (response.statusCode().is3xxRedirection()) {
|
||||
String redirectUrl = response.headers().header("Location").get(0);
|
||||
/**
|
||||
* TODO
|
||||
* In case the redirected URL is not absolute (complete), create the new URL using the relative path
|
||||
* This particular scenario is seen in the URL : https://rickandmortyapi.com/api/character
|
||||
* It redirects to partial URI : /api/character/
|
||||
* In this scenario we should convert the partial URI to complete URI
|
||||
*/
|
||||
URI redirectUri = null;
|
||||
try {
|
||||
redirectUri = new URI(redirectUrl);
|
||||
} catch (URISyntaxException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return httpCall(webClient, httpMethod, redirectUri, requestBody, iteration + 1);
|
||||
}
|
||||
return Mono.just(response);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object datasourceCreate(DatasourceConfiguration datasourceConfiguration) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void datasourceDestroy(Object connection) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean isDatasourceValid(DatasourceConfiguration datasourceConfiguration) {
|
||||
if (datasourceConfiguration.getUrl() == null) {
|
||||
System.out.println("URL is null. Data validation failed");
|
||||
return false;
|
||||
}
|
||||
// Check for URL validity
|
||||
try {
|
||||
new URL(datasourceConfiguration.getUrl()).toURI();
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
System.out.println("URL is invalid. Data validation failed");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void addHeadersToRequest(WebClient.Builder webClientBuilder, List<Property> headers) {
|
||||
for (Property header : headers) {
|
||||
if (header.getKey() != null && !header.getKey().isEmpty()) {
|
||||
webClientBuilder.defaultHeader(header.getKey(), header.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private URI createFinalUriWithQueryParams(String url, List<Property> queryParams) throws URISyntaxException {
|
||||
UriComponentsBuilder uriBuilder = UriComponentsBuilder.newInstance();
|
||||
uriBuilder.uri(new URI(url));
|
||||
|
||||
if (queryParams != null) {
|
||||
for (Property queryParam : queryParams) {
|
||||
if (queryParam.getKey() != null && !queryParam.getKey().isEmpty()) {
|
||||
uriBuilder.queryParam(queryParam.getKey(), queryParam.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
return uriBuilder.build(true).toUri();
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO :
|
||||
* Add a function which is called during import of a template to an action. As part of that do the following :
|
||||
* 1. Get the provider and the template
|
||||
* 2. Check if the provider is subscribed to, and if not, subscribe.
|
||||
* 3. Set Property field isRedacted for fields like host, etc. These fields in turn would not be displayed to
|
||||
* the user during GET Actions.
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.external.plugins;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* Unit test for simple App.
|
||||
*/
|
||||
public class RapidApiPluginTest {
|
||||
/**
|
||||
* Rigorous Test :-)
|
||||
*/
|
||||
@Test
|
||||
public void shouldAnswerWithTrue() {
|
||||
assertTrue(true);
|
||||
}
|
||||
}
|
||||
|
|
@ -50,6 +50,8 @@ public class Action extends BaseDomain {
|
|||
|
||||
String templateId; //If action is created via a template, store the id here.
|
||||
|
||||
Documentation documentation;
|
||||
|
||||
/**
|
||||
* If the Datasource is null, create one and set the autoGenerated flag to true. This is required because spring-data
|
||||
* cannot add the createdAt and updatedAt properties for null embedded objects. At this juncture, we couldn't find
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
package com.appsmith.server.domains;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public class Documentation {
|
||||
String text;
|
||||
String url;
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import com.appsmith.external.models.ApiTemplate;
|
|||
import com.appsmith.server.constants.FieldName;
|
||||
import com.appsmith.server.domains.Action;
|
||||
import com.appsmith.server.domains.Datasource;
|
||||
import com.appsmith.server.domains.Documentation;
|
||||
import com.appsmith.server.dtos.AddItemToPageDTO;
|
||||
import com.appsmith.server.dtos.ItemDTO;
|
||||
import com.appsmith.server.dtos.ItemType;
|
||||
|
|
@ -66,13 +67,20 @@ public class ItemServiceImpl implements ItemService {
|
|||
action.setName(addItemToPageDTO.getName());
|
||||
action.setPageId(addItemToPageDTO.getPageId());
|
||||
action.setTemplateId(apiTemplate.getId());
|
||||
|
||||
Documentation documentation = new Documentation();
|
||||
documentation.setText(apiTemplate.getApiTemplateConfiguration().getDocumentation());
|
||||
documentation.setUrl(apiTemplate.getApiTemplateConfiguration().getDocumentationUrl());
|
||||
action.setDocumentation(documentation);
|
||||
/** TODO
|
||||
* Also hit the Marketplace to update the number of imports.
|
||||
*/
|
||||
|
||||
// Set Action Fields
|
||||
action.setActionConfiguration(apiTemplate.getActionConfiguration());
|
||||
action.setCacheResponse(apiTemplate.getApiTemplateConfiguration().getSampleResponse().getBody().toString());
|
||||
if (apiTemplate.getApiTemplateConfiguration().getSampleResponse() != null) {
|
||||
action.setCacheResponse(apiTemplate.getApiTemplateConfiguration().getSampleResponse().getBody().toString());
|
||||
}
|
||||
|
||||
return pluginService
|
||||
.findByPackageName(apiTemplate.getPackageName())
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user