fix: remove RapidAPI plugin src code from API server (#16501)
* remove RapidApi plugin related code from API server * mark RapidApi plugin related data as deleted in DB (soft delete)
This commit is contained in:
parent
995847cdef
commit
b8f30a8f10
|
|
@ -64,7 +64,6 @@
|
|||
<module>postgresPlugin</module>
|
||||
<module>restApiPlugin</module>
|
||||
<module>mongoPlugin</module>
|
||||
<module>rapidApiPlugin</module>
|
||||
<module>mysqlPlugin</module>
|
||||
<module>elasticSearchPlugin</module>
|
||||
<module>dynamoPlugin</module>
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
plugin.id=rapidapi-plugin
|
||||
plugin.class=com.external.plugins.RapidApiPlugin
|
||||
plugin.version=1.0-SNAPSHOT
|
||||
plugin.provider=tech@appsmith.com
|
||||
plugin.dependencies=
|
||||
|
|
@ -1,92 +0,0 @@
|
|||
<?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>
|
||||
|
||||
<parent>
|
||||
<groupId>com.appsmith</groupId>
|
||||
<artifactId>appsmith-plugins</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<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>org.springframework</groupId>
|
||||
<artifactId>spring-web</artifactId>
|
||||
<version>5.3.20</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-webflux</artifactId>
|
||||
<version>5.3.20</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>io.projectreactor</groupId>
|
||||
<artifactId>reactor-core</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-core</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-web</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<!--
|
||||
This doesn't use the maven-shade-plugin because it inherits from spring-boot-starter-parent
|
||||
for it's dependency on webclient. This is a normal compile.
|
||||
-->
|
||||
<build>
|
||||
<plugins>
|
||||
<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>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>${java.version}</source>
|
||||
<target>${java.version}</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
|
|
@ -1,327 +0,0 @@
|
|||
package com.external.plugins;
|
||||
|
||||
import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginError;
|
||||
import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginException;
|
||||
import com.appsmith.external.models.ActionConfiguration;
|
||||
import com.appsmith.external.models.ActionExecutionResult;
|
||||
import com.appsmith.external.models.DatasourceConfiguration;
|
||||
import com.appsmith.external.models.DatasourceTestResult;
|
||||
import com.appsmith.external.models.Property;
|
||||
import com.appsmith.external.plugins.BasePlugin;
|
||||
import com.appsmith.external.plugins.PluginExecutor;
|
||||
import com.appsmith.util.WebClientUtils;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
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.util.StringUtils;
|
||||
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.Exceptions;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@Slf4j
|
||||
public class RapidApiPlugin extends BasePlugin {
|
||||
private static final int MAX_REDIRECTS = 5;
|
||||
|
||||
private static final String JSON_TYPE = "apipayload";
|
||||
|
||||
public RapidApiPlugin(PluginWrapper wrapper) {
|
||||
super(wrapper);
|
||||
}
|
||||
|
||||
@Extension
|
||||
public static class RapidApiPluginExecutor implements PluginExecutor<Void> {
|
||||
|
||||
private static final String RAPID_API_KEY_NAME = "X-RapidAPI-Key";
|
||||
private static final String RAPID_API_KEY_VALUE = System.getenv("APPSMITH_RAPID_API_KEY_VALUE");
|
||||
|
||||
@Override
|
||||
public Mono<ActionExecutionResult> execute(Void ignored,
|
||||
DatasourceConfiguration datasourceConfiguration,
|
||||
ActionConfiguration actionConfiguration) {
|
||||
|
||||
if (StringUtils.isEmpty(RAPID_API_KEY_VALUE)) {
|
||||
return Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_EXECUTE_ARGUMENT_ERROR, "RapidAPI Key value " +
|
||||
"not set."));
|
||||
}
|
||||
|
||||
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_EXECUTE_ARGUMENT_ERROR, "HTTPMethod must be " +
|
||||
"set."));
|
||||
}
|
||||
|
||||
WebClient.Builder webClientBuilder = WebClientUtils.builder();
|
||||
|
||||
if (datasourceConfiguration.getHeaders() != null) {
|
||||
addHeadersToRequest(webClientBuilder, datasourceConfiguration.getHeaders());
|
||||
}
|
||||
|
||||
if (actionConfiguration.getHeaders() != null) {
|
||||
addHeadersToRequest(webClientBuilder, actionConfiguration.getHeaders());
|
||||
}
|
||||
|
||||
// Add the rapid api headers
|
||||
webClientBuilder.defaultHeader(RAPID_API_KEY_NAME, RAPID_API_KEY_VALUE);
|
||||
|
||||
//If route parameters exist, update the URL by replacing the key surrounded by '{' and '}'
|
||||
if (actionConfiguration.getRouteParameters() != null && !actionConfiguration.getRouteParameters().isEmpty()) {
|
||||
for (Property property : actionConfiguration.getRouteParameters()) {
|
||||
// If either the key or the value is empty, skip
|
||||
if (property.getKey() != null && !property.getKey().isEmpty() &&
|
||||
property.getValue() != null && !((String) property.getValue()).isEmpty()) {
|
||||
|
||||
Pattern pattern = Pattern.compile("\\{" + property.getKey() + "\\}");
|
||||
Matcher matcher = pattern.matcher(url);
|
||||
url = matcher.replaceAll(URLEncoder.encode((String) property.getValue()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
URI uri;
|
||||
try {
|
||||
String httpUrl = addHttpToUrlWhenPrefixNotPresent(url);
|
||||
uri = createFinalUriWithQueryParams(httpUrl, actionConfiguration.getQueryParameters());
|
||||
log.info("Final URL is : {}", uri);
|
||||
} catch (URISyntaxException e) {
|
||||
return Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_EXECUTE_ARGUMENT_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(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON.toString());
|
||||
|
||||
Map<String, String> keyValueMap = new HashMap<>();
|
||||
|
||||
List<Property> bodyFormData = actionConfiguration.getBodyFormData();
|
||||
String jsonString = null;
|
||||
JSONObject bodyJson;
|
||||
for (Property property : bodyFormData) {
|
||||
|
||||
if (property.getValue() != null) {
|
||||
if (!property.getType().equals(JSON_TYPE)) {
|
||||
keyValueMap.put(property.getKey(), (String) property.getValue());
|
||||
} else {
|
||||
// This is actually supposed to be the body and should not be in key-value format. No need to
|
||||
// convert the same.
|
||||
jsonString = (String) property.getValue();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (jsonString == null) {
|
||||
bodyJson = new JSONObject(keyValueMap);
|
||||
} else {
|
||||
bodyJson = new JSONObject(jsonString);
|
||||
}
|
||||
jsonString = bodyJson.toString();
|
||||
|
||||
// Now reset the request body
|
||||
requestBody = 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.setIsExecutionSuccess(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();
|
||||
throw Exceptions.propagate(new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, e));
|
||||
}
|
||||
try {
|
||||
// Set headers in the result now
|
||||
result.setHeaders(objectMapper.readTree(headerInJsonString));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
throw Exceptions.propagate(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)) {
|
||||
String jsonBody = new String(body);
|
||||
try {
|
||||
result.setBody(objectMapper.readTree(jsonBody));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
throw Exceptions.propagate(new AppsmithPluginException(AppsmithPluginError.PLUGIN_JSON_PARSE_ERROR, jsonBody, 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;
|
||||
})
|
||||
.onErrorMap(throwable -> {
|
||||
final Throwable actualException = Exceptions.unwrap(throwable);
|
||||
if (actualException instanceof AppsmithPluginException) {
|
||||
return actualException;
|
||||
} else {
|
||||
return new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, actualException);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Mono<ClientResponse> httpCall(WebClient webClient, HttpMethod httpMethod, URI uri, String requestBody, int iteration) {
|
||||
if (iteration == MAX_REDIRECTS) {
|
||||
log.debug("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 Mono<Void> datasourceCreate(DatasourceConfiguration datasourceConfiguration) {
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void datasourceDestroy(Void connection) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> validateDatasource(DatasourceConfiguration datasourceConfiguration) {
|
||||
// Since the datasource is created by rapid api & not by the user and it can't be edited.
|
||||
// Assume that everything is good. Return as valid.
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<DatasourceTestResult> testDatasource(DatasourceConfiguration datasourceConfiguration) {
|
||||
return StringUtils.isEmpty(RAPID_API_KEY_VALUE)
|
||||
? Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_DATASOURCE_ARGUMENT_ERROR, "RapidAPI Key value " +
|
||||
"not set."))
|
||||
: Mono.just(new DatasourceTestResult());
|
||||
}
|
||||
|
||||
private void addHeadersToRequest(WebClient.Builder webClientBuilder, List<Property> headers) {
|
||||
for (Property header : headers) {
|
||||
if (header.getKey() != null && !header.getKey().isEmpty()) {
|
||||
webClientBuilder.defaultHeader(header.getKey(), (String) header.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String addHttpToUrlWhenPrefixNotPresent(String url) {
|
||||
if (url == null || url.toLowerCase().startsWith("http") || url.contains("://")) {
|
||||
return url;
|
||||
}
|
||||
return "http://" + url;
|
||||
}
|
||||
|
||||
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 either the key or the value is empty, skip
|
||||
if (queryParam.getKey() != null && !queryParam.getKey().isEmpty() &&
|
||||
queryParam.getValue() != null && !((String) queryParam.getValue()).isEmpty()) {
|
||||
uriBuilder.queryParam(queryParam.getKey(), URLEncoder.encode((String) queryParam.getValue(),
|
||||
StandardCharsets.UTF_8));
|
||||
}
|
||||
}
|
||||
}
|
||||
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.
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
{
|
||||
"form": [
|
||||
{
|
||||
"sectionName": "General",
|
||||
"id": 1,
|
||||
"children": [
|
||||
{
|
||||
"label": "Rapid Api Connection Name",
|
||||
"configProperty": "connectionName",
|
||||
"controlType": "INPUT_TEXT"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -4822,7 +4822,7 @@ public class DatabaseChangelog {
|
|||
* @param action
|
||||
* @return true / false
|
||||
*/
|
||||
private boolean hasUnpublishedActionConfiguration(NewAction action) {
|
||||
public boolean hasUnpublishedActionConfiguration(NewAction action) {
|
||||
ActionDTO unpublishedAction = action.getUnpublishedAction();
|
||||
if (unpublishedAction == null || unpublishedAction.getActionConfiguration() == null) {
|
||||
return false;
|
||||
|
|
@ -4837,7 +4837,7 @@ public class DatabaseChangelog {
|
|||
* @param mongockTemplate
|
||||
* @return action
|
||||
*/
|
||||
private NewAction fetchActionUsingId(String actionId, MongockTemplate mongockTemplate) {
|
||||
public static NewAction fetchActionUsingId(String actionId, MongockTemplate mongockTemplate) {
|
||||
final NewAction action =
|
||||
mongockTemplate.findOne(query(where(fieldName(QNewAction.newAction.id)).is(actionId)), NewAction.class);
|
||||
return action;
|
||||
|
|
@ -4848,7 +4848,7 @@ public class DatabaseChangelog {
|
|||
* @param plugin
|
||||
* @return query
|
||||
*/
|
||||
private Query getQueryToFetchAllPluginActionsWhichAreNotDeleted(Plugin plugin) {
|
||||
public static Query getQueryToFetchAllPluginActionsWhichAreNotDeleted(Plugin plugin) {
|
||||
Criteria pluginIdMatchesSuppliedPluginId = where("pluginId").is(plugin.getId());
|
||||
Criteria isNotDeleted = where("deleted").ne(true);
|
||||
return query((new Criteria()).andOperator(pluginIdMatchesSuppliedPluginId, isNotDeleted));
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ import com.github.cloudyrock.mongock.ChangeLog;
|
|||
import com.github.cloudyrock.mongock.ChangeSet;
|
||||
import com.github.cloudyrock.mongock.driver.mongodb.springdata.v3.decorator.impl.MongockTemplate;
|
||||
import com.google.gson.Gson;
|
||||
import com.querydsl.core.types.Path;
|
||||
import io.changock.migration.api.annotations.NonLockGuarded;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
|
|
@ -2593,4 +2594,109 @@ public class DatabaseChangelog2 {
|
|||
.findOne(query(where("packageName").is("graphql-plugin")), Plugin.class);
|
||||
installPluginToAllWorkspaces(mongoTemplate, graphQLPlugin.getId());
|
||||
}
|
||||
|
||||
public void softDeletePlugin(MongockTemplate mongockTemplate, Plugin plugin) {
|
||||
softDeleteAllPluginActions(plugin, mongockTemplate);
|
||||
softDeleteAllPluginDatasources(plugin, mongockTemplate);
|
||||
softDeletePluginFromAllWorkspaces(plugin, mongockTemplate);
|
||||
softDeleteInPluginCollection(plugin, mongockTemplate);
|
||||
}
|
||||
|
||||
@ChangeSet(order = "038", id = "delete-rapid-api-plugin-related-items", author = "")
|
||||
public void deleteRapidApiPluginRelatedItems(MongockTemplate mongockTemplate) {
|
||||
Plugin rapidApiPlugin = mongockTemplate.findOne(query(where("packageName").is("rapidapi-plugin")),
|
||||
Plugin.class);
|
||||
|
||||
if (rapidApiPlugin == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
softDeletePlugin(mongockTemplate, rapidApiPlugin);
|
||||
}
|
||||
|
||||
private void softDeletePluginFromAllWorkspaces(Plugin plugin, MongockTemplate mongockTemplate) {
|
||||
Query queryToGetNonDeletedWorkspaces = new Query();
|
||||
queryToGetNonDeletedWorkspaces.fields().include(fieldName(QWorkspace.workspace.id));
|
||||
List<Workspace> workspaces = mongockTemplate.find(queryToGetNonDeletedWorkspaces, Workspace.class);
|
||||
workspaces.stream()
|
||||
.map(Workspace::getId)
|
||||
.map(id -> fetchDomainObjectUsingId(id, mongockTemplate, QWorkspace.workspace.id, Workspace.class))
|
||||
.forEachOrdered(workspace -> {
|
||||
workspace.getPlugins().stream()
|
||||
.filter(workspacePlugin -> workspacePlugin != null && workspacePlugin.getPluginId() != null)
|
||||
.filter(workspacePlugin -> workspacePlugin.getPluginId().equals(plugin.getId()))
|
||||
.forEach(workspacePlugin -> {
|
||||
workspacePlugin.setDeleted(true);
|
||||
workspacePlugin.setDeletedAt(Instant.now());
|
||||
});
|
||||
mongockTemplate.save(workspace);
|
||||
});
|
||||
}
|
||||
|
||||
private void softDeleteInPluginCollection(Plugin plugin, MongockTemplate mongockTemplate) {
|
||||
plugin.setDeleted(true);
|
||||
plugin.setDeletedAt(Instant.now());
|
||||
mongockTemplate.save(plugin);
|
||||
}
|
||||
|
||||
private void softDeleteAllPluginDatasources(Plugin plugin, MongockTemplate mongockTemplate) {
|
||||
/* Query to get all plugin datasources which are not deleted */
|
||||
Query queryToGetDatasources = getQueryToFetchAllDomainObjectsWhichAreNotDeletedUsingPluginId(plugin);
|
||||
|
||||
/* Update the previous query to only include id field */
|
||||
queryToGetDatasources.fields().include(fieldName(QDatasource.datasource.id));
|
||||
|
||||
/* Fetch plugin datasources using the previous query */
|
||||
List<Datasource> datasources = mongockTemplate.find(queryToGetDatasources, Datasource.class);
|
||||
|
||||
/* Mark each selected datasource as deleted */
|
||||
updateDeleteAndDeletedAtFieldsForEachDomainObject(datasources, mongockTemplate,
|
||||
QDatasource.datasource.id, Datasource.class);
|
||||
}
|
||||
|
||||
private void softDeleteAllPluginActions(Plugin plugin, MongockTemplate mongockTemplate) {
|
||||
/* Query to get all plugin actions which are not deleted */
|
||||
Query queryToGetActions = getQueryToFetchAllDomainObjectsWhichAreNotDeletedUsingPluginId(plugin);
|
||||
|
||||
/* Update the previous query to only include id field */
|
||||
queryToGetActions.fields().include(fieldName(QNewAction.newAction.id));
|
||||
|
||||
/* Fetch plugin actions using the previous query */
|
||||
List<NewAction> actions = mongockTemplate.find(queryToGetActions, NewAction.class);
|
||||
|
||||
/* Mark each selected action as deleted */
|
||||
updateDeleteAndDeletedAtFieldsForEachDomainObject(actions, mongockTemplate, QNewAction.newAction.id,
|
||||
NewAction.class);
|
||||
}
|
||||
|
||||
private Query getQueryToFetchAllDomainObjectsWhichAreNotDeletedUsingPluginId(Plugin plugin) {
|
||||
Criteria pluginIdMatchesSuppliedPluginId = where("pluginId").is(plugin.getId());
|
||||
Criteria isNotDeleted = where("deleted").ne(true);
|
||||
return query((new Criteria()).andOperator(pluginIdMatchesSuppliedPluginId, isNotDeleted));
|
||||
}
|
||||
|
||||
private <T extends BaseDomain> void updateDeleteAndDeletedAtFieldsForEachDomainObject(List<? extends BaseDomain> domainObjects,
|
||||
MongockTemplate mongockTemplate, Path path,
|
||||
Class<T> type) {
|
||||
domainObjects.stream()
|
||||
.map(BaseDomain::getId) // iterate over id one by one
|
||||
.map(id -> fetchDomainObjectUsingId(id, mongockTemplate, path, type)) // find object using id
|
||||
.forEachOrdered(domainObject -> {
|
||||
domainObject.setDeleted(true);
|
||||
domainObject.setDeletedAt(Instant.now());
|
||||
mongockTemplate.save(domainObject);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Here 'id' refers to the ObjectId which is used to uniquely identify each Mongo document. 'path' refers to the
|
||||
* path in the Query DSL object that indicates which field in a document should be matched against the `id`.
|
||||
* `type` is a POJO class type that indicates which collection we are interested in. eg. path=QNewAction
|
||||
* .newAction.id, type=NewAction.class
|
||||
*/
|
||||
private <T extends BaseDomain> T fetchDomainObjectUsingId(String id, MongockTemplate mongockTemplate, Path path,
|
||||
Class<T> type) {
|
||||
final T domainObject = mongockTemplate.findOne(query(where(fieldName(path)).is(id)), type);
|
||||
return domainObject;
|
||||
}
|
||||
}
|
||||
|
|
@ -133,6 +133,7 @@ public class PluginServiceCEImpl extends BaseService<PluginRepository, Plugin, S
|
|||
|
||||
List<String> pluginIds = org.getPlugins()
|
||||
.stream()
|
||||
.filter(plugin -> plugin.getDeleted() == false)
|
||||
.map(WorkspacePlugin::getPluginId)
|
||||
.collect(Collectors.toList());
|
||||
Query query = new Query();
|
||||
|
|
|
|||
|
|
@ -51,10 +51,6 @@ sentry.send-default-pii=true
|
|||
sentry.debug=off
|
||||
sentry.environment=${APPSMITH_SENTRY_ENVIRONMENT:}
|
||||
|
||||
# RapidAPI
|
||||
rapidapi.key.name = X-RapidAPI-Key
|
||||
rapidapi.key.value = ${APPSMITH_RAPID_API_KEY_VALUE:}
|
||||
|
||||
# Redis Properties
|
||||
spring.redis.url=${APPSMITH_REDIS_URL}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user