Merge release branch
This commit is contained in:
commit
a892ee90b5
|
|
@ -9,7 +9,6 @@ import org.springframework.data.annotation.CreatedDate;
|
|||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.annotation.LastModifiedBy;
|
||||
import org.springframework.data.annotation.LastModifiedDate;
|
||||
import org.springframework.data.annotation.Version;
|
||||
import org.springframework.data.domain.Persistable;
|
||||
import org.springframework.data.mongodb.core.index.Indexed;
|
||||
|
||||
|
|
@ -48,14 +47,9 @@ public abstract class BaseDomain implements Persistable<String> {
|
|||
|
||||
protected Boolean deleted = false;
|
||||
|
||||
@JsonIgnore
|
||||
@Version
|
||||
protected Long documentVersion;
|
||||
|
||||
@JsonIgnore
|
||||
protected Set<Policy> policies;
|
||||
|
||||
@JsonIgnore
|
||||
@Override
|
||||
public boolean isNew() {
|
||||
return this.getId() == null;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -19,6 +19,7 @@ import org.springframework.web.bind.annotation.PathVariable;
|
|||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
|
@ -45,7 +46,8 @@ public class ActionController extends BaseController<ActionService, Action, Stri
|
|||
|
||||
@PostMapping
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
public Mono<ResponseDTO<Action>> create(@Valid @RequestBody Action resource) throws AppsmithException {
|
||||
public Mono<ResponseDTO<Action>> create(@Valid @RequestBody Action resource,
|
||||
@RequestHeader(name = "Origin", required = false) String originHeader) {
|
||||
log.debug("Going to create resource {}", resource.getClass().getName());
|
||||
return actionCollectionService.createAction(resource)
|
||||
.map(created -> new ResponseDTO<>(HttpStatus.CREATED.value(), created, null));
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import org.springframework.web.bind.annotation.PathVariable;
|
|||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
|
@ -35,7 +36,8 @@ public class ApplicationController extends BaseController<ApplicationService, Ap
|
|||
|
||||
@PostMapping
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
public Mono<ResponseDTO<Application>> create(@Valid @RequestBody Application resource) throws AppsmithException {
|
||||
public Mono<ResponseDTO<Application>> create(@Valid @RequestBody Application resource,
|
||||
@RequestHeader(name = "Origin", required = false) String originHeader) {
|
||||
log.debug("Going to create resource {}", resource.getClass().getName());
|
||||
return applicationPageService.createApplication(resource)
|
||||
.map(created -> new ResponseDTO<>(HttpStatus.CREATED.value(), created, null));
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import org.springframework.web.bind.annotation.PathVariable;
|
|||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
|
@ -29,7 +30,8 @@ public abstract class BaseController<S extends CrudService, T extends BaseDomain
|
|||
|
||||
@PostMapping
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
public Mono<ResponseDTO<T>> create(@Valid @RequestBody T resource) throws AppsmithException {
|
||||
public Mono<ResponseDTO<T>> create(@Valid @RequestBody T resource,
|
||||
@RequestHeader(name = "Origin", required = false) String originHeader) {
|
||||
log.debug("Going to create resource {}", resource.getClass().getName());
|
||||
return service.create(resource)
|
||||
.map(created -> new ResponseDTO<>(HttpStatus.CREATED.value(), created, null));
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
|
@ -32,7 +33,8 @@ public class CollectionController extends BaseController<CollectionService, Coll
|
|||
|
||||
@PostMapping
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
public Mono<ResponseDTO<Collection>> create(@Valid @RequestBody Collection resource) throws AppsmithException {
|
||||
public Mono<ResponseDTO<Collection>> create(@Valid @RequestBody Collection resource,
|
||||
@RequestHeader(name = "Origin", required = false) String originHeader) {
|
||||
log.debug("Going to create resource {}", resource.getClass().getName());
|
||||
return actionCollectionService.createCollection(resource)
|
||||
.map(created -> new ResponseDTO<>(HttpStatus.CREATED.value(), created, null));
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import org.springframework.web.bind.annotation.GetMapping;
|
|||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
|
@ -37,7 +38,8 @@ public class PageController extends BaseController<PageService, Page, String> {
|
|||
|
||||
@PostMapping
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
public Mono<ResponseDTO<Page>> create(@Valid @RequestBody Page resource) throws AppsmithException {
|
||||
public Mono<ResponseDTO<Page>> create(@Valid @RequestBody Page resource,
|
||||
@RequestHeader(name = "Origin", required = false) String originHeader) {
|
||||
log.debug("Going to create resource {}", resource.getClass().getName());
|
||||
return applicationPageService.createPage(resource)
|
||||
.map(created -> new ResponseDTO<>(HttpStatus.CREATED.value(), created, null));
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import org.springframework.util.MultiValueMap;
|
|||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
|
|
|
|||
|
|
@ -2,9 +2,15 @@ package com.appsmith.server.controllers;
|
|||
|
||||
import com.appsmith.external.models.Provider;
|
||||
import com.appsmith.server.constants.Url;
|
||||
import com.appsmith.server.dtos.ResponseDTO;
|
||||
import com.appsmith.server.services.ProviderService;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping(Url.PROVIDER_URL)
|
||||
|
|
@ -13,4 +19,10 @@ public class ProviderController extends BaseController<ProviderService, Provider
|
|||
public ProviderController(ProviderService service) {
|
||||
super(service);
|
||||
}
|
||||
|
||||
@GetMapping("/categories")
|
||||
public Mono<ResponseDTO<List<String>>> getAllCategories() {
|
||||
return service.getAllCategories()
|
||||
.map(resources -> new ResponseDTO<>(HttpStatus.OK.value(), resources, null));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import org.springframework.web.bind.annotation.GetMapping;
|
|||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
|
|
@ -43,7 +44,9 @@ public class RestApiImportController {
|
|||
public Mono<ResponseDTO<Action>> create(@Valid @RequestBody Object input,
|
||||
@RequestParam RestApiImporterType type,
|
||||
@RequestParam String pageId,
|
||||
@RequestParam String name) {
|
||||
@RequestParam String name,
|
||||
@RequestHeader(name = "Origin", required = false) String originHeader
|
||||
) {
|
||||
log.debug("Going to import API");
|
||||
ApiImporter service;
|
||||
|
||||
|
|
@ -61,7 +64,8 @@ public class RestApiImportController {
|
|||
|
||||
@PostMapping("/postman")
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
public Mono<ResponseDTO<TemplateCollection>> importPostmanCollection(@RequestBody Object input, @RequestParam String type) {
|
||||
public Mono<ResponseDTO<TemplateCollection>> importPostmanCollection(@RequestBody Object input,
|
||||
@RequestParam String type) {
|
||||
return Mono.just(postmanImporterService.importPostmanCollection(input))
|
||||
.map(created -> new ResponseDTO<>(HttpStatus.CREATED.value(), created, null));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,33 +0,0 @@
|
|||
package com.appsmith.server.controllers;
|
||||
|
||||
import com.appsmith.server.constants.Url;
|
||||
import com.appsmith.server.domains.Organization;
|
||||
import com.appsmith.server.dtos.ResponseDTO;
|
||||
import com.appsmith.server.services.SignupService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@RestController
|
||||
@RequestMapping(Url.SIGNUP_URL)
|
||||
public class SignupController {
|
||||
private final SignupService signupService;
|
||||
|
||||
@Autowired
|
||||
public SignupController(SignupService signupService) {
|
||||
this.signupService = signupService;
|
||||
}
|
||||
|
||||
@PostMapping("/organization")
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
public Mono<ResponseDTO<Organization>> signupOrganization(@RequestBody Organization organization) {
|
||||
return signupService.createOrganization(organization)
|
||||
.map(org -> new ResponseDTO<>(HttpStatus.CREATED.value(), org, null));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -5,9 +5,11 @@ import com.appsmith.server.domains.InviteUser;
|
|||
import com.appsmith.server.domains.User;
|
||||
import com.appsmith.server.dtos.ResetUserPasswordDTO;
|
||||
import com.appsmith.server.dtos.ResponseDTO;
|
||||
import com.appsmith.server.dtos.UserProfileDTO;
|
||||
import com.appsmith.server.services.SessionUserService;
|
||||
import com.appsmith.server.services.UserOrganizationService;
|
||||
import com.appsmith.server.services.UserService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
|
@ -18,11 +20,15 @@ import org.springframework.web.bind.annotation.RequestBody;
|
|||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import javax.validation.Valid;
|
||||
|
||||
@RestController
|
||||
@RequestMapping(Url.USER_URL)
|
||||
@Slf4j
|
||||
public class UserController extends BaseController<UserService, User, String> {
|
||||
|
||||
private final SessionUserService sessionUserService;
|
||||
|
|
@ -37,6 +43,14 @@ public class UserController extends BaseController<UserService, User, String> {
|
|||
this.userOrganizationService = userOrganizationService;
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
public Mono<ResponseDTO<User>> create(@Valid @RequestBody User resource,
|
||||
@RequestHeader(name = "Origin", required = false) String originHeader) {
|
||||
return service.createUser(resource, originHeader)
|
||||
.map(created -> new ResponseDTO<>(HttpStatus.CREATED.value(), created, null));
|
||||
}
|
||||
|
||||
@PutMapping("/switchOrganization/{orgId}")
|
||||
public Mono<ResponseDTO<User>> setCurrentOrganization(@PathVariable String orgId) {
|
||||
return service.switchCurrentOrganization(orgId)
|
||||
|
|
@ -77,12 +91,19 @@ public class UserController extends BaseController<UserService, User, String> {
|
|||
.map(result -> new ResponseDTO<>(HttpStatus.OK.value(), result, null));
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@GetMapping("/me")
|
||||
public Mono<ResponseDTO<User>> getUserProfile() {
|
||||
return sessionUserService.getCurrentUser()
|
||||
.map(user -> new ResponseDTO<>(HttpStatus.OK.value(), user, null));
|
||||
}
|
||||
|
||||
@GetMapping("/profile")
|
||||
public Mono<ResponseDTO<UserProfileDTO>> getEnhancedUserProfile() {
|
||||
return service.getUserProfile()
|
||||
.map(user -> new ResponseDTO<>(HttpStatus.OK.value(), user, null));
|
||||
}
|
||||
|
||||
/**
|
||||
* This function creates an invite for a new user to join the Appsmith platform. We require the Origin header
|
||||
* in order to construct client facing URLs that will be sent to the user via email.
|
||||
|
|
@ -104,8 +125,9 @@ public class UserController extends BaseController<UserService, User, String> {
|
|||
}
|
||||
|
||||
@PutMapping("/invite/confirm")
|
||||
public Mono<ResponseDTO<Boolean>> confirmInviteUser(@RequestBody InviteUser inviteUser) {
|
||||
return service.confirmInviteUser(inviteUser)
|
||||
public Mono<ResponseDTO<Boolean>> confirmInviteUser(@RequestBody InviteUser inviteUser,
|
||||
@RequestHeader("Origin") String originHeader) {
|
||||
return service.confirmInviteUser(inviteUser, originHeader)
|
||||
.map(result -> new ResponseDTO<>(HttpStatus.OK.value(), result, null));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import lombok.Getter;
|
|||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import org.springframework.data.annotation.Transient;
|
||||
import org.springframework.data.mongodb.core.index.CompoundIndex;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
|
||||
|
|
@ -50,6 +51,13 @@ public class Action extends BaseDomain {
|
|||
|
||||
String templateId; //If action is created via a template, store the id here.
|
||||
|
||||
String providerId; //If action is created via a template, store the template's provider id here.
|
||||
|
||||
@Transient
|
||||
ActionProvider provider;
|
||||
|
||||
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,20 @@
|
|||
package com.appsmith.server.domains;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public class ActionProvider {
|
||||
String name;
|
||||
|
||||
String imageUrl;
|
||||
|
||||
String url;
|
||||
|
||||
String description;
|
||||
|
||||
String credentialSteps;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -1,11 +1,13 @@
|
|||
package com.appsmith.server.domains;
|
||||
|
||||
import com.appsmith.external.models.BaseDomain;
|
||||
import com.appsmith.server.dtos.ApplicationNameIdDTO;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import org.springframework.data.annotation.Transient;
|
||||
import org.springframework.data.mongodb.core.index.Indexed;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
|
|
@ -13,6 +15,7 @@ import org.springframework.security.core.userdetails.UserDetails;
|
|||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
package com.appsmith.server.dtos;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class ApplicationNameIdDTO {
|
||||
|
||||
String id;
|
||||
|
||||
String name;
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package com.appsmith.server.dtos;
|
||||
|
||||
import com.appsmith.server.domains.Organization;
|
||||
import com.appsmith.server.domains.User;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class UserProfileDTO {
|
||||
|
||||
User user;
|
||||
|
||||
Organization currentOrganization;
|
||||
|
||||
List<ApplicationNameIdDTO> applications;
|
||||
}
|
||||
|
|
@ -48,7 +48,7 @@ public class EmailSender {
|
|||
* @throws MailException
|
||||
*/
|
||||
public void sendMail(String to, String subject, String text) {
|
||||
log.debug("Got request to send email to: {} with subject: {} and text: {}", to, subject, text);
|
||||
log.debug("Got request to send email to: {} with subject: {}", to, subject);
|
||||
// Don't send an email for local, dev or test environments
|
||||
if (!emailConfig.isEmailEnabled()) {
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import com.appsmith.external.plugins.PluginExecutor;
|
|||
import com.appsmith.server.constants.AnalyticsEvents;
|
||||
import com.appsmith.server.constants.FieldName;
|
||||
import com.appsmith.server.domains.Action;
|
||||
import com.appsmith.server.domains.ActionProvider;
|
||||
import com.appsmith.server.domains.Datasource;
|
||||
import com.appsmith.server.domains.Plugin;
|
||||
import com.appsmith.server.domains.PluginType;
|
||||
|
|
@ -68,6 +69,7 @@ public class ActionServiceImpl extends BaseService<ActionRepository, Action, Str
|
|||
private final DatasourceContextService datasourceContextService;
|
||||
private final PluginExecutorHelper pluginExecutorHelper;
|
||||
private final SessionUserService sessionUserService;
|
||||
private final ProviderService providerService;
|
||||
|
||||
@Autowired
|
||||
public ActionServiceImpl(Scheduler scheduler,
|
||||
|
|
@ -82,7 +84,8 @@ public class ActionServiceImpl extends BaseService<ActionRepository, Action, Str
|
|||
ObjectMapper objectMapper,
|
||||
DatasourceContextService datasourceContextService,
|
||||
PluginExecutorHelper pluginExecutorHelper,
|
||||
SessionUserService sessionUserService) {
|
||||
SessionUserService sessionUserService,
|
||||
ProviderService providerService) {
|
||||
super(scheduler, validator, mongoConverter, reactiveMongoTemplate, repository, analyticsService);
|
||||
this.repository = repository;
|
||||
this.datasourceService = datasourceService;
|
||||
|
|
@ -92,6 +95,7 @@ public class ActionServiceImpl extends BaseService<ActionRepository, Action, Str
|
|||
this.datasourceContextService = datasourceContextService;
|
||||
this.pluginExecutorHelper = pluginExecutorHelper;
|
||||
this.sessionUserService = sessionUserService;
|
||||
this.providerService = providerService;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -271,6 +275,7 @@ public class ActionServiceImpl extends BaseService<ActionRepository, Action, Str
|
|||
return action;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Mono<ActionExecutionResult> executeAction(ExecuteActionDTO executeActionDTO) {
|
||||
Action actionFromDto = executeActionDTO.getAction();
|
||||
|
|
@ -478,13 +483,8 @@ public class ActionServiceImpl extends BaseService<ActionRepository, Action, Str
|
|||
Mono<Action> actionMono = repository.findById(id)
|
||||
.switchIfEmpty(Mono.error(new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, "action", id)));
|
||||
return actionMono
|
||||
.flatMap(toDelete ->
|
||||
repository.delete(toDelete)
|
||||
.thenReturn(toDelete))
|
||||
.map(deletedObj -> {
|
||||
analyticsService.sendEvent(AnalyticsEvents.DELETE + "_" + deletedObj.getClass().getSimpleName().toUpperCase(), (Action) deletedObj);
|
||||
return (Action) deletedObj;
|
||||
});
|
||||
.flatMap(toDelete -> repository.delete(toDelete).thenReturn(toDelete))
|
||||
.flatMap(deletedObj -> analyticsService.sendEvent(AnalyticsEvents.DELETE + "_" + deletedObj.getClass().getSimpleName().toUpperCase(), (Action) deletedObj));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -523,6 +523,27 @@ public class ActionServiceImpl extends BaseService<ActionRepository, Action, Str
|
|||
.flatMapMany(orgId -> {
|
||||
actionExample.setOrganizationId(orgId);
|
||||
return repository.findAll(Example.of(actionExample), sort);
|
||||
})
|
||||
.flatMap(action -> {
|
||||
if ((action.getTemplateId()!=null) && (action.getProviderId() != null)) {
|
||||
// In case of an action which was imported from a 3P API, fill in the extra information of the
|
||||
// provider required by the front end UI.
|
||||
return providerService
|
||||
.getById(action.getProviderId())
|
||||
.switchIfEmpty(Mono.error(new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, "Provider")))
|
||||
.map(provider -> {
|
||||
ActionProvider actionProvider = new ActionProvider();
|
||||
actionProvider.setName(provider.getName());
|
||||
actionProvider.setCredentialSteps(provider.getCredentialSteps());
|
||||
actionProvider.setDescription(provider.getDescription());
|
||||
actionProvider.setImageUrl(provider.getImageUrl());
|
||||
actionProvider.setUrl(provider.getUrl());
|
||||
|
||||
action.setProvider(actionProvider);
|
||||
return action;
|
||||
});
|
||||
}
|
||||
return Mono.just(action);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -43,8 +43,17 @@ public class AnalyticsService<T extends BaseDomain> {
|
|||
});
|
||||
}
|
||||
|
||||
private User createAnonymousUser() {
|
||||
User user = new User();
|
||||
user.setId("anonymousUser");
|
||||
return user;
|
||||
}
|
||||
|
||||
public Mono<T> sendEvent(String eventTag, T object) {
|
||||
Mono<User> userMono = sessionUserService.getCurrentUser();
|
||||
// We will create an anonymous user object for event tracking if no user is present
|
||||
// Without this, a lot of flows meant for anonymous users will error out
|
||||
Mono<User> userMono = sessionUserService.getCurrentUser()
|
||||
.defaultIfEmpty(createAnonymousUser());
|
||||
return userMono
|
||||
.map(user -> {
|
||||
HashMap<String, String> analyticsProperties = new HashMap<>();
|
||||
|
|
|
|||
|
|
@ -261,10 +261,7 @@ public class ApplicationPageServiceImpl implements ApplicationPageService {
|
|||
.flatMap(application -> applicationService.archive(application));
|
||||
|
||||
return applicationMono
|
||||
.map(deletedObj -> {
|
||||
analyticsService.sendEvent(AnalyticsEvents.DELETE + "_" + deletedObj.getClass().getSimpleName().toUpperCase(), (Application) deletedObj);
|
||||
return (Application) deletedObj;
|
||||
});
|
||||
.flatMap(deletedObj -> analyticsService.sendEvent(AnalyticsEvents.DELETE + "_" + deletedObj.getClass().getSimpleName().toUpperCase(), (Application) deletedObj));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import com.appsmith.server.exceptions.AppsmithException;
|
|||
import com.appsmith.server.repositories.BaseRepository;
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.DBObject;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.mongodb.core.ReactiveMongoTemplate;
|
||||
import org.springframework.data.mongodb.core.convert.MongoConverter;
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
|
|
@ -22,6 +23,7 @@ import reactor.core.scheduler.Scheduler;
|
|||
import javax.validation.Validator;
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
public abstract class BaseService<R extends BaseRepository, T extends BaseDomain, ID> implements CrudService<T, ID> {
|
||||
|
||||
final Scheduler scheduler;
|
||||
|
|
@ -90,10 +92,7 @@ public abstract class BaseService<R extends BaseRepository, T extends BaseDomain
|
|||
return Mono.just(object)
|
||||
.flatMap(this::validateObject)
|
||||
.flatMap(repository::save)
|
||||
.map(savedObj -> {
|
||||
analyticsService.sendEvent(AnalyticsEvents.CREATE + "_" + savedObj.getClass().getSimpleName().toUpperCase(), (T) savedObj);
|
||||
return savedObj;
|
||||
});
|
||||
.flatMap(savedObj -> analyticsService.sendEvent(AnalyticsEvents.CREATE + "_" + savedObj.getClass().getSimpleName().toUpperCase(), (T) savedObj));
|
||||
}
|
||||
|
||||
protected DBObject getDbObject(Object o) {
|
||||
|
|
|
|||
|
|
@ -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,22 @@ public class ItemServiceImpl implements ItemService {
|
|||
action.setName(addItemToPageDTO.getName());
|
||||
action.setPageId(addItemToPageDTO.getPageId());
|
||||
action.setTemplateId(apiTemplate.getId());
|
||||
action.setProviderId(apiTemplate.getProviderId());
|
||||
|
||||
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 &&
|
||||
apiTemplate.getApiTemplateConfiguration().getSampleResponse().getBody() != null ) {
|
||||
action.setCacheResponse(apiTemplate.getApiTemplateConfiguration().getSampleResponse().getBody().toString());
|
||||
}
|
||||
|
||||
return pluginService
|
||||
.findByPackageName(apiTemplate.getPackageName())
|
||||
|
|
|
|||
|
|
@ -118,10 +118,8 @@ public class PageServiceImpl extends BaseService<PageRepository, Page, String> i
|
|||
});
|
||||
});
|
||||
|
||||
return pageMono.map(deletedObj -> {
|
||||
analyticsService.sendEvent(AnalyticsEvents.DELETE + "_" + deletedObj.getClass().getSimpleName().toUpperCase(), (Page) deletedObj);
|
||||
return (Page) deletedObj;
|
||||
});
|
||||
return pageMono
|
||||
.flatMap(deletedObj -> analyticsService.sendEvent(AnalyticsEvents.DELETE + "_" + deletedObj.getClass().getSimpleName().toUpperCase(), (Page) deletedObj));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
package com.appsmith.server.services;
|
||||
|
||||
import com.appsmith.external.models.Provider;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface ProviderService extends CrudService<Provider, String> {
|
||||
public Mono<List<String>> getAllCategories();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,16 +11,25 @@ import org.springframework.data.mongodb.core.convert.MongoConverter;
|
|||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.scheduler.Scheduler;
|
||||
|
||||
import javax.validation.Validator;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
public class ProviderServiceImpl extends BaseService<ProviderRepository, Provider, String> implements ProviderService {
|
||||
|
||||
private static final List<String> CATEGORIES = Arrays.asList("Business","Visual Recognition","Location","Science",
|
||||
"Food","Travel, Transportation","Music","Tools","Text Analysis","Weather","Gaming","SMS","Events","Health, Fitness",
|
||||
"Payments","Financial","Translation","Storage","Logistics","Database","Search","Reward","Mapping","Machine Learning",
|
||||
"Email","News, Media","Video, Images","eCommerce","Medical","Devices","Business Software","Advertising","Education",
|
||||
"Media","Social","Commerce","Communication","Other","Monitoring","Energy");
|
||||
|
||||
private static final String DEFAULT_CATEGORY = "Business Software";
|
||||
public ProviderServiceImpl(Scheduler scheduler,
|
||||
Validator validator,
|
||||
MongoConverter mongoConverter,
|
||||
|
|
@ -39,12 +48,21 @@ public class ProviderServiceImpl extends BaseService<ProviderRepository, Provide
|
|||
providerExample.setName(params.getFirst(FieldName.NAME));
|
||||
}
|
||||
|
||||
List<String> categories = new ArrayList<>();
|
||||
if (params.getFirst(FieldName.CATEGORY) != null) {
|
||||
List<String> categories = new ArrayList<>();
|
||||
categories.add(params.getFirst(FieldName.CATEGORY));
|
||||
providerExample.setCategories(categories);
|
||||
|
||||
} else {
|
||||
// No category has been provided. Set the default category.
|
||||
categories.add(DEFAULT_CATEGORY);
|
||||
}
|
||||
providerExample.setCategories(categories);
|
||||
|
||||
return repository.findAll(Example.of(providerExample), sort);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<List<String>> getAllCategories() {
|
||||
return Mono.just(CATEGORIES);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.appsmith.server.services;
|
|||
import com.appsmith.server.domains.InviteUser;
|
||||
import com.appsmith.server.domains.User;
|
||||
import com.appsmith.server.dtos.ResetUserPasswordDTO;
|
||||
import com.appsmith.server.dtos.UserProfileDTO;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
|
|
@ -24,7 +25,11 @@ public interface UserService extends CrudService<User, String> {
|
|||
|
||||
Mono<Boolean> verifyInviteToken(String email, String token);
|
||||
|
||||
Mono<Boolean> confirmInviteUser(InviteUser inviteUser);
|
||||
Mono<Boolean> confirmInviteUser(InviteUser inviteUser, String originHeader);
|
||||
|
||||
Mono<Collection<GrantedAuthority>> getAnonymousAuthorities();
|
||||
|
||||
Mono<UserProfileDTO> getUserProfile();
|
||||
|
||||
Mono<User> createUser(User user, String originHeader);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,22 +1,26 @@
|
|||
package com.appsmith.server.services;
|
||||
|
||||
import com.appsmith.server.constants.FieldName;
|
||||
import com.appsmith.server.domains.Application;
|
||||
import com.appsmith.server.domains.InviteUser;
|
||||
import com.appsmith.server.domains.LoginSource;
|
||||
import com.appsmith.server.domains.Organization;
|
||||
import com.appsmith.server.domains.PasswordResetToken;
|
||||
import com.appsmith.server.domains.User;
|
||||
import com.appsmith.server.dtos.ApplicationNameIdDTO;
|
||||
import com.appsmith.server.dtos.ResetUserPasswordDTO;
|
||||
import com.appsmith.server.dtos.UserProfileDTO;
|
||||
import com.appsmith.server.exceptions.AppsmithError;
|
||||
import com.appsmith.server.exceptions.AppsmithException;
|
||||
import com.appsmith.server.helpers.BeanCopyUtils;
|
||||
import com.appsmith.server.notifications.EmailSender;
|
||||
import com.appsmith.server.repositories.GroupRepository;
|
||||
import com.appsmith.server.repositories.ApplicationRepository;
|
||||
import com.appsmith.server.repositories.InviteUserRepository;
|
||||
import com.appsmith.server.repositories.PasswordResetTokenRepository;
|
||||
import com.appsmith.server.repositories.UserRepository;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Example;
|
||||
import org.springframework.data.mongodb.core.ReactiveMongoTemplate;
|
||||
import org.springframework.data.mongodb.core.convert.MongoConverter;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
|
|
@ -25,6 +29,7 @@ import org.springframework.security.core.userdetails.UserDetails;
|
|||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.scheduler.Scheduler;
|
||||
|
||||
|
|
@ -33,6 +38,7 @@ import java.io.IOException;
|
|||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
|
@ -49,15 +55,17 @@ public class UserServiceImpl extends BaseService<UserRepository, User, String> i
|
|||
private final PasswordResetTokenRepository passwordResetTokenRepository;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
private final EmailSender emailSender;
|
||||
private final GroupRepository groupRepository;
|
||||
private final InviteUserRepository inviteUserRepository;
|
||||
private final UserOrganizationService userOrganizationService;
|
||||
private final ApplicationRepository applicationRepository;
|
||||
|
||||
private static final String WELCOME_USER_EMAIL_TEMPLATE = "email/welcomeUserTemplate.html";
|
||||
private static final String INVITE_USER_EMAIL_TEMPLATE = "email/inviteUserTemplate.html";
|
||||
private static final String INVITE_USER_EMAIL_TEMPLATE = "email/inviteUserCreatorTemplate.html";
|
||||
private static final String FORGOT_PASSWORD_EMAIL_TEMPLATE = "email/forgotPasswordTemplate.html";
|
||||
private static final String INVITE_USER_CLIENT_URL_FORMAT = "%s/user/createPassword?token=%s&email=%s";
|
||||
private static final String FORGOT_PASSWORD_CLIENT_URL_FORMAT = "%s/user/resetPassword?token=%s&email=%s";
|
||||
// We default the origin header to the production deployment of the client's URL
|
||||
private static final String DEFAULT_ORIGIN_HEADER = "https://app.appsmith.com";
|
||||
|
||||
@Autowired
|
||||
public UserServiceImpl(Scheduler scheduler,
|
||||
|
|
@ -71,9 +79,9 @@ public class UserServiceImpl extends BaseService<UserRepository, User, String> i
|
|||
PasswordResetTokenRepository passwordResetTokenRepository,
|
||||
PasswordEncoder passwordEncoder,
|
||||
EmailSender emailSender,
|
||||
GroupRepository groupRepository,
|
||||
InviteUserRepository inviteUserRepository,
|
||||
UserOrganizationService userOrganizationService) {
|
||||
UserOrganizationService userOrganizationService,
|
||||
ApplicationRepository applicationRepository) {
|
||||
super(scheduler, validator, mongoConverter, reactiveMongoTemplate, repository, analyticsService);
|
||||
this.repository = repository;
|
||||
this.organizationService = organizationService;
|
||||
|
|
@ -82,9 +90,9 @@ public class UserServiceImpl extends BaseService<UserRepository, User, String> i
|
|||
this.passwordResetTokenRepository = passwordResetTokenRepository;
|
||||
this.passwordEncoder = passwordEncoder;
|
||||
this.emailSender = emailSender;
|
||||
this.groupRepository = groupRepository;
|
||||
this.inviteUserRepository = inviteUserRepository;
|
||||
this.userOrganizationService = userOrganizationService;
|
||||
this.applicationRepository = applicationRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -274,35 +282,52 @@ public class UserServiceImpl extends BaseService<UserRepository, User, String> i
|
|||
return Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, FieldName.ORIGIN));
|
||||
}
|
||||
|
||||
// Create an invite token for the user. This token is linked to the email ID and the organization to which the user was invited.
|
||||
// Create an invite token for the user. This token is linked to the email ID and the organization to which the
|
||||
// user was invited.
|
||||
String token = UUID.randomUUID().toString();
|
||||
|
||||
return sessionUserService.getCurrentUser()
|
||||
.map(reqUser -> {
|
||||
// Caching the response from sessionUserService because it's re-used multiple times in this flow
|
||||
Mono<User> currentUserMono = sessionUserService.getCurrentUser().cache();
|
||||
Mono<InviteUser> inviteUserMono = currentUserMono
|
||||
.map(currentUser -> {
|
||||
log.debug("Got request to invite user {} by user: {} for org: {}",
|
||||
user.getEmail(), reqUser.getEmail(), reqUser.getCurrentOrganizationId());
|
||||
user.getEmail(), currentUser.getEmail(), currentUser.getCurrentOrganizationId());
|
||||
|
||||
InviteUser inviteUser = new InviteUser();
|
||||
inviteUser.setEmail(user.getEmail());
|
||||
inviteUser.setCurrentOrganizationId(reqUser.getCurrentOrganizationId());
|
||||
inviteUser.setCurrentOrganizationId(currentUser.getCurrentOrganizationId());
|
||||
inviteUser.setToken(passwordEncoder.encode(token));
|
||||
inviteUser.setGroupIds(user.getGroupIds());
|
||||
inviteUser.setPermissions(user.getPermissions());
|
||||
inviteUser.setInviterUserId(reqUser.getId());
|
||||
inviteUser.setInviterUserId(currentUser.getId());
|
||||
return inviteUser;
|
||||
})
|
||||
// Save the invited user in the DB
|
||||
.flatMap(inviteUserRepository::save)
|
||||
// Send an email to the invited user with the token
|
||||
.map(inviteUser -> {
|
||||
.flatMap(inviteUserRepository::save);
|
||||
|
||||
Mono<Organization> currentOrgMono = currentUserMono
|
||||
.flatMap(currentUser -> organizationService.findById(currentUser.getCurrentOrganizationId()));
|
||||
|
||||
// Send an email to the invited user with the token
|
||||
return Mono.zip(currentUserMono, inviteUserMono, currentOrgMono)
|
||||
.map(tuple -> {
|
||||
User currentUser = tuple.getT1();
|
||||
InviteUser inviteUser = tuple.getT2();
|
||||
Organization currentUserOrg = tuple.getT3();
|
||||
log.debug("Going to send email for invite user to {} with token {}", inviteUser.getEmail(), token);
|
||||
try {
|
||||
String inviteUrl = String.format(INVITE_USER_CLIENT_URL_FORMAT, originHeader,
|
||||
URLEncoder.encode(token, StandardCharsets.UTF_8),
|
||||
URLEncoder.encode(inviteUser.getEmail(), StandardCharsets.UTF_8));
|
||||
Map<String, String> params = Map.of(
|
||||
"token", token,
|
||||
"inviteUrl", inviteUrl);
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("token", token);
|
||||
params.put("inviteUrl", inviteUrl);
|
||||
if (!StringUtils.isEmpty(currentUser.getName())) {
|
||||
params.put("Inviter_First_Name", currentUser.getName());
|
||||
} else {
|
||||
params.put("Inviter_First_Name", currentUser.getEmail());
|
||||
}
|
||||
params.put("inviter_org_name", currentUserOrg.getName());
|
||||
String emailBody = emailSender.replaceEmailTemplate(INVITE_USER_EMAIL_TEMPLATE, params);
|
||||
emailSender.sendMail(inviteUser.getEmail(), "Invite for Appsmith", emailBody);
|
||||
} catch (IOException e) {
|
||||
|
|
@ -310,7 +335,6 @@ public class UserServiceImpl extends BaseService<UserRepository, User, String> i
|
|||
}
|
||||
return inviteUser;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -338,7 +362,7 @@ public class UserServiceImpl extends BaseService<UserRepository, User, String> i
|
|||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Mono<Boolean> confirmInviteUser(InviteUser inviteUser) {
|
||||
public Mono<Boolean> confirmInviteUser(InviteUser inviteUser, String originHeader) {
|
||||
if (inviteUser.getToken() == null || inviteUser.getToken().isEmpty()) {
|
||||
return Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, "token"));
|
||||
}
|
||||
|
|
@ -376,7 +400,7 @@ public class UserServiceImpl extends BaseService<UserRepository, User, String> i
|
|||
log.debug("The invited user {} doesn't exist in the system. Creating a new record", inviteUser.getEmail());
|
||||
// The user doesn't exist in the system. Create a new user object
|
||||
newUser.setPassword(inviteUser.getPassword());
|
||||
return this.create(newUser)
|
||||
return this.createUser(newUser, originHeader)
|
||||
.flatMap(createdUser -> userOrganizationService.addUserToOrganization(newUser.getCurrentOrganizationId(), createdUser))
|
||||
.thenReturn(newUser)
|
||||
.flatMap(userToDelete -> inviteUserRepository.delete(userToDelete))
|
||||
|
|
@ -390,6 +414,11 @@ public class UserServiceImpl extends BaseService<UserRepository, User, String> i
|
|||
.map(user -> user.getAuthorities());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<User> create(User user) {
|
||||
return createUser(user, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function creates a new user in the system. Primarily used by new users signing up for the first time on the
|
||||
* platform. This flow also ensures that a personal workspace name is created for the user. The new user is then
|
||||
|
|
@ -401,7 +430,12 @@ public class UserServiceImpl extends BaseService<UserRepository, User, String> i
|
|||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Mono<User> create(User user) {
|
||||
public Mono<User> createUser(User user, String originHeader) {
|
||||
if (originHeader == null || originHeader.isBlank()) {
|
||||
// Default to the production link
|
||||
originHeader = DEFAULT_ORIGIN_HEADER;
|
||||
}
|
||||
final String finalOriginHeader = originHeader;
|
||||
|
||||
// Only encode the password if it's a form signup. For OAuth signups, we don't need password
|
||||
if (LoginSource.FORM.equals(user.getSource())) {
|
||||
|
|
@ -418,8 +452,8 @@ public class UserServiceImpl extends BaseService<UserRepository, User, String> i
|
|||
firstName = user.getEmail().split("@")[0];
|
||||
}
|
||||
|
||||
String personalWorkspaceName = firstName + "'s Personal Workspace";
|
||||
personalOrg.setName(personalWorkspaceName);
|
||||
String personalOrganizationName = firstName + "'s Personal Organization";
|
||||
personalOrg.setName(personalOrganizationName);
|
||||
|
||||
// Save the new user
|
||||
Mono<User> savedUserMono = super.create(user);
|
||||
|
|
@ -434,7 +468,10 @@ public class UserServiceImpl extends BaseService<UserRepository, User, String> i
|
|||
.map(savedUser -> {
|
||||
// Send an email to the user welcoming them to the Appsmith platform
|
||||
try {
|
||||
Map<String, String> params = Map.of("personalWorkspaceName", personalWorkspaceName);
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("personalOrganizationName", personalOrganizationName);
|
||||
params.put("firstName", savedUser.getName());
|
||||
params.put("appsmithLink", finalOriginHeader);
|
||||
String emailBody = emailSender.replaceEmailTemplate(WELCOME_USER_EMAIL_TEMPLATE, params);
|
||||
emailSender.sendMail(savedUser.getEmail(), "Welcome to Appsmith", emailBody);
|
||||
} catch (IOException e) {
|
||||
|
|
@ -496,4 +533,35 @@ public class UserServiceImpl extends BaseService<UserRepository, User, String> i
|
|||
// Doesn't work without this.
|
||||
.map(user -> (UserDetails) user);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<UserProfileDTO> getUserProfile() {
|
||||
return sessionUserService.getCurrentUser()
|
||||
.flatMap(user -> {
|
||||
String currentOrganizationId = user.getCurrentOrganizationId();
|
||||
UserProfileDTO userProfile = new UserProfileDTO();
|
||||
userProfile.setUser(user);
|
||||
|
||||
Mono<UserProfileDTO> userProfileDTOMono = organizationService.findById(currentOrganizationId)
|
||||
.flatMap(org -> {
|
||||
userProfile.setCurrentOrganization(org);
|
||||
|
||||
Application applicationExample = new Application();
|
||||
applicationExample.setOrganizationId(org.getId());
|
||||
return applicationRepository.findAll(Example.of(applicationExample))
|
||||
.map(application -> {
|
||||
ApplicationNameIdDTO dto = new ApplicationNameIdDTO();
|
||||
dto.setId(application.getId());
|
||||
dto.setName(application.getName());
|
||||
return dto;
|
||||
}).collectList()
|
||||
.map(dtos -> {
|
||||
userProfile.setApplications(dtos);
|
||||
return userProfile;
|
||||
|
||||
});
|
||||
});
|
||||
return userProfileDTOMono;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ spring.security.oauth2.client.provider.github.userNameAttribute=login
|
|||
oauth2.allowed-domains=
|
||||
|
||||
# Segment & Rollbar Properties
|
||||
segment.writeKey=FIRLqUgMYuTlyS6yqOE2hBGZs5umkWhr
|
||||
segment.writeKey=B3UBOacfOky4l6dfk5xyR6Dh8vUZYizW
|
||||
com.rollbar.access-token=b91c4d5b9cac444088f4db9216ed6f42
|
||||
com.rollbar.environment=development
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,189 @@
|
|||
<html>
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html data-editor-version="2" class="sg-campaigns" xmlns="http://www.w3.org/1999/xhtml"><head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1">
|
||||
<!--[if !mso]><!-->
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
|
||||
<!--<![endif]-->
|
||||
<!--[if (gte mso 9)|(IE)]>
|
||||
<xml>
|
||||
<o:OfficeDocumentSettings>
|
||||
<o:AllowPNG/>
|
||||
<o:PixelsPerInch>96</o:PixelsPerInch>
|
||||
</o:OfficeDocumentSettings>
|
||||
</xml>
|
||||
<![endif]-->
|
||||
<!--[if (gte mso 9)|(IE)]>
|
||||
<style type="text/css">
|
||||
body {width: 600px;margin: 0 auto;}
|
||||
table {border-collapse: collapse;}
|
||||
table, td {mso-table-lspace: 0pt;mso-table-rspace: 0pt;}
|
||||
img {-ms-interpolation-mode: bicubic;}
|
||||
</style>
|
||||
<![endif]-->
|
||||
<style type="text/css">
|
||||
body, p, div {
|
||||
font-family: arial,helvetica,sans-serif;
|
||||
font-size: 14px;
|
||||
}
|
||||
body {
|
||||
color: #000000;
|
||||
}
|
||||
body a {
|
||||
color: #1188E6;
|
||||
text-decoration: none;
|
||||
}
|
||||
p { margin: 0; padding: 0; }
|
||||
table.wrapper {
|
||||
width:100% !important;
|
||||
table-layout: fixed;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-moz-text-size-adjust: 100%;
|
||||
-ms-text-size-adjust: 100%;
|
||||
}
|
||||
img.max-width {
|
||||
max-width: 100% !important;
|
||||
}
|
||||
.column.of-2 {
|
||||
width: 50%;
|
||||
}
|
||||
.column.of-3 {
|
||||
width: 33.333%;
|
||||
}
|
||||
.column.of-4 {
|
||||
width: 25%;
|
||||
}
|
||||
@media screen and (max-width:480px) {
|
||||
.preheader .rightColumnContent,
|
||||
.footer .rightColumnContent {
|
||||
text-align: left !important;
|
||||
}
|
||||
.preheader .rightColumnContent div,
|
||||
.preheader .rightColumnContent span,
|
||||
.footer .rightColumnContent div,
|
||||
.footer .rightColumnContent span {
|
||||
text-align: left !important;
|
||||
}
|
||||
.preheader .rightColumnContent,
|
||||
.preheader .leftColumnContent {
|
||||
font-size: 80% !important;
|
||||
padding: 5px 0;
|
||||
}
|
||||
table.wrapper-mobile {
|
||||
width: 100% !important;
|
||||
table-layout: fixed;
|
||||
}
|
||||
img.max-width {
|
||||
height: auto !important;
|
||||
max-width: 100% !important;
|
||||
}
|
||||
a.bulletproof-button {
|
||||
display: block !important;
|
||||
width: auto !important;
|
||||
font-size: 80%;
|
||||
padding-left: 0 !important;
|
||||
padding-right: 0 !important;
|
||||
}
|
||||
.columns {
|
||||
width: 100% !important;
|
||||
}
|
||||
.column {
|
||||
display: block !important;
|
||||
width: 100% !important;
|
||||
padding-left: 0 !important;
|
||||
padding-right: 0 !important;
|
||||
margin-left: 0 !important;
|
||||
margin-right: 0 !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<!--user entered Head Start--><!--End Head user entered-->
|
||||
</head>
|
||||
<body>
|
||||
You can reset your password by clicking <a href={{resetUrl}}>this link</a>.
|
||||
<br /><br />
|
||||
Alternatively, you can copy paste the following URL in your browser: {{resetUrl}}
|
||||
<br /><br />
|
||||
Cheers, <br />
|
||||
Appsmith
|
||||
</body>
|
||||
</html>
|
||||
<center class="wrapper" data-link-color="#1188E6" data-body-style="font-size:14px; font-family:arial,helvetica,sans-serif; color:#000000; background-color:#FFFFFF;">
|
||||
<div class="webkit">
|
||||
<table cellpadding="0" cellspacing="0" border="0" width="100%" class="wrapper" bgcolor="#FFFFFF">
|
||||
<tbody><tr>
|
||||
<td valign="top" bgcolor="#FFFFFF" width="100%">
|
||||
<table width="100%" role="content-container" class="outer" align="center" cellpadding="0" cellspacing="0" border="0">
|
||||
<tbody><tr>
|
||||
<td width="100%">
|
||||
<table width="100%" cellpadding="0" cellspacing="0" border="0">
|
||||
<tbody><tr>
|
||||
<td>
|
||||
<!--[if mso]>
|
||||
<center>
|
||||
<table><tr><td width="600">
|
||||
<![endif]-->
|
||||
<table width="100%" cellpadding="0" cellspacing="0" border="0" style="width:100%; max-width:600px;" align="center">
|
||||
<tbody><tr>
|
||||
<td role="modules-container" style="padding:0px 0px 0px 0px; color:#000000; text-align:left;" bgcolor="#ffffff" width="100%" align="left"><table class="module preheader preheader-hide" role="module" data-type="preheader" border="0" cellpadding="0" cellspacing="0" width="100%" style="display: none !important; mso-hide: all; visibility: hidden; opacity: 0; color: transparent; height: 0; width: 0;">
|
||||
<tbody><tr>
|
||||
<td role="module-content">
|
||||
<p></p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table><table class="wrapper" role="module" data-type="image" border="0" cellpadding="0" cellspacing="0" width="100%" style="table-layout: fixed;" data-muid="40dbb7f1-8428-4188-86b0-1b0245659a17">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="font-size:6px; line-height:10px; padding:0px 0px 0px 0px;" valign="top" align="center">
|
||||
<a href="https://www.appsmith.com/"><img class="max-width" border="0" style="display:block; color:#000000; text-decoration:none; font-family:Helvetica, arial, sans-serif; font-size:16px; max-width:25% !important; width:25%; height:auto !important;" width="150" alt="" data-proportionally-constrained="true" data-responsive="true" src="http://cdn.mcauto-images-production.sendgrid.net/4bbae2fffe647858/b21738f2-3a49-4774-aae9-c8e80ad9c26e/924x284.png"></a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table><table class="module" role="module" data-type="text" border="0" cellpadding="0" cellspacing="0" width="100%" style="table-layout: fixed;" data-muid="71d7e9fb-0f3b-43f4-97e1-994b33bfc82a" data-mc-module-version="2019-10-22">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="padding:0px 0px 10px 0px; line-height:22px; text-align:inherit; background-color:#ffffff;" height="100%" valign="top" bgcolor="#ffffff" role="module-content"><div><div style="font-family: inherit; text-align: inherit; margin-left: 0px"><span style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; font-variant-numeric: normal; font-variant-east-asian: normal; font-stretch: normal; line-height: normal; font-family: "lucida sans unicode", "lucida grande", sans-serif; color: #5c5959">Hello,</span></div>
|
||||
<div style="font-family: inherit; text-align: inherit; margin-left: 0px"><br></div>
|
||||
<div style="font-family: inherit; text-align: start"><span style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; font-variant-numeric: normal; font-variant-east-asian: normal; font-stretch: normal; line-height: normal; font-family: "lucida sans unicode", "lucida grande", sans-serif; color: #5c5959">Forgot the password to your Appsmith account? No worries, we've got you covered.</span></div><div></div></div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table><table border="0" cellpadding="0" cellspacing="0" class="module" data-role="module-button" data-type="button" role="module" style="table-layout:fixed;" width="100%" data-muid="f00155e0-813d-4e9b-b61d-384e6f99e5b7">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" bgcolor="" class="outer-td" style="padding:0px 0px 0px 0px;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" class="wrapper-mobile" style="text-align:center;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" bgcolor="#ff6d2d" class="inner-td" style="border-radius:6px; font-size:16px; text-align:center; background-color:inherit;">
|
||||
<a href="{{resetUrl}}" style="background-color:#ff6d2d; border:1px solid #ff6d2d; border-color:#ff6d2d; border-radius:6px; border-width:1px; color:#ffffff; display:inline-block; font-weight:400; letter-spacing:0px; line-height:6px; padding:12px 18px 12px 18px; text-align:center; text-decoration:none; border-style:solid; font-family:tahoma,geneva,sans-serif; font-size:16px;" target="_blank">Reset Password</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table><table class="module" role="module" data-type="text" border="0" cellpadding="0" cellspacing="0" width="100%" style="table-layout: fixed;" data-muid="cab2544f-5a6c-49a0-b246-efe5ac8c5208" data-mc-module-version="2019-10-22">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="padding:18px 0px 18px 0px; line-height:22px; text-align:inherit;" height="100%" valign="top" bgcolor="" role="module-content"><div><div style="font-family: inherit; text-align: start"><span style="box-sizing: border-box; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; font-style: inherit; font-variant-ligatures: inherit; font-variant-caps: inherit; font-variant-numeric: normal; font-variant-east-asian: normal; font-weight: inherit; font-stretch: normal; line-height: normal; font-family: "lucida sans unicode", "lucida grande", sans-serif; vertical-align: baseline; border-top-width: 0px; border-right-width: 0px; border-bottom-width: 0px; border-left-width: 0px; border-top-style: initial; border-right-style: initial; border-bottom-style: initial; border-left-style: initial; border-top-color: initial; border-right-color: initial; border-bottom-color: initial; border-left-color: initial; border-image-source: initial; border-image-slice: initial; border-image-width: initial; border-image-outset: initial; border-image-repeat: initial; color: #5c5959; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: pre-wrap; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; font-size: 14px">The link will expire in 48 hours. If you didn't request a password reset, you can safely ignore this email.</span></div>
|
||||
<div style="font-family: inherit; text-align: start"><br></div>
|
||||
<div style="font-family: inherit; text-align: inherit; margin-left: 0px"><span style="box-sizing: border-box; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; font-style: inherit; font-variant-ligatures: inherit; font-variant-caps: inherit; font-variant-numeric: normal; font-variant-east-asian: normal; font-weight: inherit; font-stretch: normal; line-height: normal; font-family: "lucida sans unicode", "lucida grande", sans-serif; font-size: 14px; vertical-align: baseline; border-top-width: 0px; border-right-width: 0px; border-bottom-width: 0px; border-left-width: 0px; border-top-style: initial; border-right-style: initial; border-bottom-style: initial; border-left-style: initial; border-top-color: initial; border-right-color: initial; border-bottom-color: initial; border-left-color: initial; border-image-source: initial; border-image-slice: initial; border-image-width: initial; border-image-outset: initial; border-image-repeat: initial; color: #5c5959; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: pre-wrap; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial">Cheers</span></div>
|
||||
<div style="font-family: inherit; text-align: inherit; margin-left: 0px"><span style="box-sizing: border-box; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; font-style: inherit; font-variant-ligatures: inherit; font-variant-caps: inherit; font-variant-numeric: normal; font-variant-east-asian: normal; font-weight: inherit; font-stretch: normal; line-height: normal; font-family: "lucida sans unicode", "lucida grande", sans-serif; font-size: 14px; vertical-align: baseline; border-top-width: 0px; border-right-width: 0px; border-bottom-width: 0px; border-left-width: 0px; border-top-style: initial; border-right-style: initial; border-bottom-style: initial; border-left-style: initial; border-top-color: initial; border-right-color: initial; border-bottom-color: initial; border-left-color: initial; border-image-source: initial; border-image-slice: initial; border-image-width: initial; border-image-outset: initial; border-image-repeat: initial; color: #5c5959; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: pre-wrap; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial">Devs at Appsmith</span></div><div></div></div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table></td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
<!--[if mso]>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</center>
|
||||
<![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
</div>
|
||||
</center>
|
||||
|
||||
|
||||
</body></html>
|
||||
|
|
@ -0,0 +1,187 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html data-editor-version="2" class="sg-campaigns" xmlns="http://www.w3.org/1999/xhtml"><head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1">
|
||||
<!--[if !mso]><!-->
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
|
||||
<!--<![endif]-->
|
||||
<!--[if (gte mso 9)|(IE)]>
|
||||
<xml>
|
||||
<o:OfficeDocumentSettings>
|
||||
<o:AllowPNG/>
|
||||
<o:PixelsPerInch>96</o:PixelsPerInch>
|
||||
</o:OfficeDocumentSettings>
|
||||
</xml>
|
||||
<![endif]-->
|
||||
<!--[if (gte mso 9)|(IE)]>
|
||||
<style type="text/css">
|
||||
body {width: 600px;margin: 0 auto;}
|
||||
table {border-collapse: collapse;}
|
||||
table, td {mso-table-lspace: 0pt;mso-table-rspace: 0pt;}
|
||||
img {-ms-interpolation-mode: bicubic;}
|
||||
</style>
|
||||
<![endif]-->
|
||||
<style type="text/css">
|
||||
body, p, div {
|
||||
font-family: arial,helvetica,sans-serif;
|
||||
font-size: 14px;
|
||||
}
|
||||
body {
|
||||
color: #000000;
|
||||
}
|
||||
body a {
|
||||
color: #1188E6;
|
||||
text-decoration: none;
|
||||
}
|
||||
p { margin: 0; padding: 0; }
|
||||
table.wrapper {
|
||||
width:100% !important;
|
||||
table-layout: fixed;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-moz-text-size-adjust: 100%;
|
||||
-ms-text-size-adjust: 100%;
|
||||
}
|
||||
img.max-width {
|
||||
max-width: 100% !important;
|
||||
}
|
||||
.column.of-2 {
|
||||
width: 50%;
|
||||
}
|
||||
.column.of-3 {
|
||||
width: 33.333%;
|
||||
}
|
||||
.column.of-4 {
|
||||
width: 25%;
|
||||
}
|
||||
@media screen and (max-width:480px) {
|
||||
.preheader .rightColumnContent,
|
||||
.footer .rightColumnContent {
|
||||
text-align: left !important;
|
||||
}
|
||||
.preheader .rightColumnContent div,
|
||||
.preheader .rightColumnContent span,
|
||||
.footer .rightColumnContent div,
|
||||
.footer .rightColumnContent span {
|
||||
text-align: left !important;
|
||||
}
|
||||
.preheader .rightColumnContent,
|
||||
.preheader .leftColumnContent {
|
||||
font-size: 80% !important;
|
||||
padding: 5px 0;
|
||||
}
|
||||
table.wrapper-mobile {
|
||||
width: 100% !important;
|
||||
table-layout: fixed;
|
||||
}
|
||||
img.max-width {
|
||||
height: auto !important;
|
||||
max-width: 100% !important;
|
||||
}
|
||||
a.bulletproof-button {
|
||||
display: block !important;
|
||||
width: auto !important;
|
||||
font-size: 80%;
|
||||
padding-left: 0 !important;
|
||||
padding-right: 0 !important;
|
||||
}
|
||||
.columns {
|
||||
width: 100% !important;
|
||||
}
|
||||
.column {
|
||||
display: block !important;
|
||||
width: 100% !important;
|
||||
padding-left: 0 !important;
|
||||
padding-right: 0 !important;
|
||||
margin-left: 0 !important;
|
||||
margin-right: 0 !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<!--user entered Head Start--><!--End Head user entered-->
|
||||
</head>
|
||||
<body>
|
||||
<center class="wrapper" data-link-color="#1188E6" data-body-style="font-size:14px; font-family:arial,helvetica,sans-serif; color:#000000; background-color:#FFFFFF;">
|
||||
<div class="webkit">
|
||||
<table cellpadding="0" cellspacing="0" border="0" width="100%" class="wrapper" bgcolor="#FFFFFF">
|
||||
<tbody><tr>
|
||||
<td valign="top" bgcolor="#FFFFFF" width="100%">
|
||||
<table width="100%" role="content-container" class="outer" align="center" cellpadding="0" cellspacing="0" border="0">
|
||||
<tbody><tr>
|
||||
<td width="100%">
|
||||
<table width="100%" cellpadding="0" cellspacing="0" border="0">
|
||||
<tbody><tr>
|
||||
<td>
|
||||
<!--[if mso]>
|
||||
<center>
|
||||
<table><tr><td width="600">
|
||||
<![endif]-->
|
||||
<table width="100%" cellpadding="0" cellspacing="0" border="0" style="width:100%; max-width:600px;" align="center">
|
||||
<tbody><tr>
|
||||
<td role="modules-container" style="padding:0px 0px 0px 0px; color:#000000; text-align:left;" bgcolor="#ffffff" width="100%" align="left"><table class="module preheader preheader-hide" role="module" data-type="preheader" border="0" cellpadding="0" cellspacing="0" width="100%" style="display: none !important; mso-hide: all; visibility: hidden; opacity: 0; color: transparent; height: 0; width: 0;">
|
||||
<tbody><tr>
|
||||
<td role="module-content">
|
||||
<p></p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table><table class="wrapper" role="module" data-type="image" border="0" cellpadding="0" cellspacing="0" width="100%" style="table-layout: fixed;" data-muid="40dbb7f1-8428-4188-86b0-1b0245659a17">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="font-size:6px; line-height:10px; padding:0px 0px 0px 0px;" valign="top" align="center">
|
||||
<a href="https://www.appsmith.com/"><img class="max-width" border="0" style="display:block; color:#000000; text-decoration:none; font-family:Helvetica, arial, sans-serif; font-size:16px; max-width:25% !important; width:25%; height:auto !important;" width="150" alt="" data-proportionally-constrained="true" data-responsive="true" src="http://cdn.mcauto-images-production.sendgrid.net/4bbae2fffe647858/b21738f2-3a49-4774-aae9-c8e80ad9c26e/924x284.png"></a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table><table class="module" role="module" data-type="text" border="0" cellpadding="0" cellspacing="0" width="100%" style="table-layout: fixed;" data-muid="71d7e9fb-0f3b-43f4-97e1-994b33bfc82a" data-mc-module-version="2019-10-22">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="padding:0px 0px 18px 0px; line-height:22px; text-align:inherit; background-color:#ffffff;" height="100%" valign="top" bgcolor="#ffffff" role="module-content"><div><div style="font-family: inherit; text-align: center"><span style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; font-variant-numeric: normal; font-variant-east-asian: normal; font-stretch: normal; line-height: normal; font-family: "lucida sans unicode", "lucida grande", sans-serif; color: #5c5959"><strong>You've been invited to collaborate.</strong></span></div>
|
||||
<div style="font-family: inherit; text-align: center"><br></div>
|
||||
<div style="font-family: inherit; text-align: inherit; margin-left: 0px"><span style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; font-variant-numeric: normal; font-variant-east-asian: normal; font-stretch: normal; line-height: normal; font-family: "lucida sans unicode", "lucida grande", sans-serif; color: #5c5959">{{Inviter_First_Name}} has invited you to collaborate on the organization "<strong>{{inviter_org_name}}</strong>" in Appsmith.</span></div><div></div></div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table><table border="0" cellpadding="0" cellspacing="0" class="module" data-role="module-button" data-type="button" role="module" style="table-layout:fixed;" width="100%" data-muid="f00155e0-813d-4e9b-b61d-384e6f99e5b7">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" bgcolor="" class="outer-td" style="padding:0px 0px 0px 0px;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" class="wrapper-mobile" style="text-align:center;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" bgcolor="#ff6d2d" class="inner-td" style="border-radius:6px; font-size:16px; text-align:center; background-color:inherit;">
|
||||
<a href="{{inviteUrl}}" style="background-color:#ff6d2d; border:1px solid #ff6d2d; border-color:#ff6d2d; border-radius:6px; border-width:1px; color:#ffffff; display:inline-block; font-weight:400; letter-spacing:0px; line-height:6px; padding:12px 18px 12px 18px; text-align:center; text-decoration:none; border-style:solid; font-family:tahoma,geneva,sans-serif; font-size:16px;" target="_blank">Accept invite</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table><table class="module" role="module" data-type="text" border="0" cellpadding="0" cellspacing="0" width="100%" style="table-layout: fixed;" data-muid="cab2544f-5a6c-49a0-b246-efe5ac8c5208" data-mc-module-version="2019-10-22">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="padding:0px 0px 0px 0px; line-height:22px; text-align:inherit;" height="100%" valign="top" bgcolor="" role="module-content"><div><div style="font-family: inherit; text-align: start"><span style="box-sizing: border-box; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; font-style: inherit; font-variant-ligatures: inherit; font-variant-caps: inherit; font-variant-numeric: normal; font-variant-east-asian: normal; font-weight: inherit; font-stretch: normal; line-height: normal; font-family: "lucida sans unicode", "lucida grande", sans-serif; vertical-align: baseline; border-top-width: 0px; border-right-width: 0px; border-bottom-width: 0px; border-left-width: 0px; border-top-style: initial; border-right-style: initial; border-bottom-style: initial; border-left-style: initial; border-top-color: initial; border-right-color: initial; border-bottom-color: initial; border-left-color: initial; border-image-source: initial; border-image-slice: initial; border-image-width: initial; border-image-outset: initial; border-image-repeat: initial; color: #5c5959; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: pre-wrap; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; font-size: 14px">Cheers</span></div>
|
||||
<div style="font-family: inherit; text-align: start"><span style="box-sizing: border-box; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; font-style: inherit; font-variant-ligatures: inherit; font-variant-caps: inherit; font-variant-numeric: normal; font-variant-east-asian: normal; font-weight: inherit; font-stretch: normal; line-height: normal; font-family: "lucida sans unicode", "lucida grande", sans-serif; vertical-align: baseline; border-top-width: 0px; border-right-width: 0px; border-bottom-width: 0px; border-left-width: 0px; border-top-style: initial; border-right-style: initial; border-bottom-style: initial; border-left-style: initial; border-top-color: initial; border-right-color: initial; border-bottom-color: initial; border-left-color: initial; border-image-source: initial; border-image-slice: initial; border-image-width: initial; border-image-outset: initial; border-image-repeat: initial; color: #5c5959; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: pre-wrap; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; font-size: 14px">Devs at Appsmith</span></div><div></div></div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table></td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
<!--[if mso]>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</center>
|
||||
<![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
</div>
|
||||
</center>
|
||||
|
||||
|
||||
</body></html>
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
<html>
|
||||
<body>
|
||||
You've been invited to the Appsmith platform. Please complete your sign up by clicking <a href={{inviteUrl}}>this link</a><br /><br />
|
||||
Alternatively, you can copy & paste the following url in your browser: {{inviteUrl}}
|
||||
<br /><br />
|
||||
For reference, your invite token is: {{token}}
|
||||
<br /><br />
|
||||
Cheers,
|
||||
Appsmith
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,9 +1,203 @@
|
|||
<html>
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html data-editor-version="2" class="sg-campaigns" xmlns="http://www.w3.org/1999/xhtml"><head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1">
|
||||
<!--[if !mso]><!-->
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
|
||||
<!--<![endif]-->
|
||||
<!--[if (gte mso 9)|(IE)]>
|
||||
<xml>
|
||||
<o:OfficeDocumentSettings>
|
||||
<o:AllowPNG/>
|
||||
<o:PixelsPerInch>96</o:PixelsPerInch>
|
||||
</o:OfficeDocumentSettings>
|
||||
</xml>
|
||||
<![endif]-->
|
||||
<!--[if (gte mso 9)|(IE)]>
|
||||
<style type="text/css">
|
||||
body {width: 600px;margin: 0 auto;}
|
||||
table {border-collapse: collapse;}
|
||||
table, td {mso-table-lspace: 0pt;mso-table-rspace: 0pt;}
|
||||
img {-ms-interpolation-mode: bicubic;}
|
||||
</style>
|
||||
<![endif]-->
|
||||
<style type="text/css">
|
||||
body, p, div {
|
||||
font-family: arial,helvetica,sans-serif;
|
||||
font-size: 14px;
|
||||
}
|
||||
body {
|
||||
color: #000000;
|
||||
}
|
||||
body a {
|
||||
color: #1188E6;
|
||||
text-decoration: none;
|
||||
}
|
||||
p { margin: 0; padding: 0; }
|
||||
table.wrapper {
|
||||
width:100% !important;
|
||||
table-layout: fixed;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-moz-text-size-adjust: 100%;
|
||||
-ms-text-size-adjust: 100%;
|
||||
}
|
||||
img.max-width {
|
||||
max-width: 100% !important;
|
||||
}
|
||||
.column.of-2 {
|
||||
width: 50%;
|
||||
}
|
||||
.column.of-3 {
|
||||
width: 33.333%;
|
||||
}
|
||||
.column.of-4 {
|
||||
width: 25%;
|
||||
}
|
||||
@media screen and (max-width:480px) {
|
||||
.preheader .rightColumnContent,
|
||||
.footer .rightColumnContent {
|
||||
text-align: left !important;
|
||||
}
|
||||
.preheader .rightColumnContent div,
|
||||
.preheader .rightColumnContent span,
|
||||
.footer .rightColumnContent div,
|
||||
.footer .rightColumnContent span {
|
||||
text-align: left !important;
|
||||
}
|
||||
.preheader .rightColumnContent,
|
||||
.preheader .leftColumnContent {
|
||||
font-size: 80% !important;
|
||||
padding: 5px 0;
|
||||
}
|
||||
table.wrapper-mobile {
|
||||
width: 100% !important;
|
||||
table-layout: fixed;
|
||||
}
|
||||
img.max-width {
|
||||
height: auto !important;
|
||||
max-width: 100% !important;
|
||||
}
|
||||
a.bulletproof-button {
|
||||
display: block !important;
|
||||
width: auto !important;
|
||||
font-size: 80%;
|
||||
padding-left: 0 !important;
|
||||
padding-right: 0 !important;
|
||||
}
|
||||
.columns {
|
||||
width: 100% !important;
|
||||
}
|
||||
.column {
|
||||
display: block !important;
|
||||
width: 100% !important;
|
||||
padding-left: 0 !important;
|
||||
padding-right: 0 !important;
|
||||
margin-left: 0 !important;
|
||||
margin-right: 0 !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<!--user entered Head Start--><!--End Head user entered-->
|
||||
</head>
|
||||
<body>
|
||||
Thank you for signing up for the Appsmith platform.<br /><br />
|
||||
Your personal workspace is: {{personalWorkspaceName}}
|
||||
<br /><br />
|
||||
Cheers,<br />
|
||||
Appsmith
|
||||
</body>
|
||||
</html>
|
||||
<center class="wrapper" data-link-color="#1188E6" data-body-style="font-size:14px; font-family:arial,helvetica,sans-serif; color:#000000; background-color:#FFFFFF;">
|
||||
<div class="webkit">
|
||||
<table cellpadding="0" cellspacing="0" border="0" width="100%" class="wrapper" bgcolor="#FFFFFF">
|
||||
<tbody><tr>
|
||||
<td valign="top" bgcolor="#FFFFFF" width="100%">
|
||||
<table width="100%" role="content-container" class="outer" align="center" cellpadding="0" cellspacing="0" border="0">
|
||||
<tbody><tr>
|
||||
<td width="100%">
|
||||
<table width="100%" cellpadding="0" cellspacing="0" border="0">
|
||||
<tbody><tr>
|
||||
<td>
|
||||
<!--[if mso]>
|
||||
<center>
|
||||
<table><tr><td width="600">
|
||||
<![endif]-->
|
||||
<table width="100%" cellpadding="0" cellspacing="0" border="0" style="width:100%; max-width:600px;" align="center">
|
||||
<tbody><tr>
|
||||
<td role="modules-container" style="padding:0px 0px 0px 0px; color:#000000; text-align:left;" bgcolor="#ffffff" width="100%" align="left"><table class="module preheader preheader-hide" role="module" data-type="preheader" border="0" cellpadding="0" cellspacing="0" width="100%" style="display: none !important; mso-hide: all; visibility: hidden; opacity: 0; color: transparent; height: 0; width: 0;">
|
||||
<tbody><tr>
|
||||
<td role="module-content">
|
||||
<p></p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table><table class="wrapper" role="module" data-type="image" border="0" cellpadding="0" cellspacing="0" width="100%" style="table-layout: fixed;" data-muid="40dbb7f1-8428-4188-86b0-1b0245659a17">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="font-size:6px; line-height:10px; padding:0px 0px 0px 0px;" valign="top" align="center">
|
||||
<a href="https://www.appsmith.com/"><img class="max-width" border="0" style="display:block; color:#000000; text-decoration:none; font-family:Helvetica, arial, sans-serif; font-size:16px; max-width:25% !important; width:25%; height:auto !important;" width="150" alt="" data-proportionally-constrained="true" data-responsive="true" src="http://cdn.mcauto-images-production.sendgrid.net/4bbae2fffe647858/b21738f2-3a49-4774-aae9-c8e80ad9c26e/924x284.png"></a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table><table class="module" role="module" data-type="text" border="0" cellpadding="0" cellspacing="0" width="100%" style="table-layout: fixed;" data-muid="71d7e9fb-0f3b-43f4-97e1-994b33bfc82a" data-mc-module-version="2019-10-22">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="padding:0px 0px 18px 0px; line-height:22px; text-align:inherit; background-color:#ffffff;" height="100%" valign="top" bgcolor="#ffffff" role="module-content"><div><div style="font-family: inherit; text-align: inherit; margin-left: 0px"><span style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; font-variant-numeric: normal; font-variant-east-asian: normal; font-stretch: normal; line-height: normal; font-family: "lucida sans unicode", "lucida grande", sans-serif; color: #5c5959">Hi {{firstName}},</span></div>
|
||||
<div style="font-family: inherit; text-align: inherit; margin-left: 0px"><br></div>
|
||||
<div style="font-family: inherit; text-align: start"><span style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; font-variant-numeric: normal; font-variant-east-asian: normal; font-stretch: normal; line-height: normal; font-family: "lucida sans unicode", "lucida grande", sans-serif; color: #5c5959">I am really excited you signed up for Appsmith and wanted to personally reach out to welcome you.</span></div>
|
||||
<div style="font-family: inherit; text-align: inherit; margin-left: 0px"><br></div>
|
||||
<div style="font-family: inherit; text-align: inherit"><span style="color: #e8e7e3; font-family: "apple color emoji", "segoe ui emoji", "noto color emoji", "android emoji", emojisymbols, "emojione mozilla", "twemoji mozilla", "segoe ui symbol"; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; float: none; display: inline; font-size: 14px; background-color: rgb(239, 239, 239)">🚀</span><span style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; font-variant-numeric: normal; font-variant-east-asian: normal; font-stretch: normal; line-height: normal; font-family: "lucida sans unicode", "lucida grande", sans-serif; color: #5c5959">Hope you started creating your first app already. In case you didn't and are looking for a little inspiration, you may want to </span><a href="https://docs.appsmith.com/tutorials/"><span style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; font-variant-numeric: normal; font-variant-east-asian: normal; font-stretch: normal; line-height: normal; font-family: "lucida sans unicode", "lucida grande", sans-serif">check out a few use cases</span></a><span style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; font-variant-numeric: normal; font-variant-east-asian: normal; font-stretch: normal; line-height: normal; font-family: "lucida sans unicode", "lucida grande", sans-serif; color: #5c5959"> other developers are building.</span></div>
|
||||
<div style="font-family: inherit; text-align: inherit"><br></div>
|
||||
<div style="font-family: inherit; text-align: left"><span style="box-sizing: border-box; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; font-style: inherit; font-variant-ligatures: inherit; font-variant-caps: inherit; font-variant-numeric: inherit; font-variant-east-asian: inherit; font-weight: inherit; font-stretch: inherit; line-height: inherit; font-family: inherit; vertical-align: baseline; border-top-width: 0px; border-right-width: 0px; border-bottom-width: 0px; border-left-width: 0px; border-top-style: initial; border-right-style: initial; border-bottom-style: initial; border-left-style: initial; border-top-color: initial; border-right-color: initial; border-bottom-color: initial; border-left-color: initial; border-image-source: initial; border-image-slice: initial; border-image-width: initial; border-image-outset: initial; border-image-repeat: initial; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-image: initial; background-position-x: 0px; background-position-y: 0px; background-size: initial; background-repeat-x: initial; background-repeat-y: initial; background-attachment: initial; background-origin: initial; background-clip: initial; background-color: initial; text-decoration-style: initial; text-decoration-color: initial; outline-color: initial; outline-style: initial; outline-width: 0px; color: #e8e7e3; white-space: normal; font-size: 14px">📅</span><span style="font-family: "lucida sans unicode", "lucida grande", sans-serif; color: #5c5959">If you would like to explore the product together or collaborate while creating your first app, </span><a href="https://calendly.com/arpit-appsmith/15min"><span style="font-family: "lucida sans unicode", "lucida grande", sans-serif">schedule a time here</span></a><span style="font-family: "lucida sans unicode", "lucida grande", sans-serif"> </span><span style="font-family: "lucida sans unicode", "lucida grande", sans-serif; color: #5c5959">and let's get building.</span></div>
|
||||
<div style="font-family: inherit; text-align: left"><br></div>
|
||||
<div style="font-family: inherit; text-align: left"><span style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; border-top-width: 0px; border-right-width: 0px; border-bottom-width: 0px; border-left-width: 0px; border-top-style: initial; border-right-style: initial; border-bottom-style: initial; border-left-style: initial; border-top-color: initial; border-right-color: initial; border-bottom-color: initial; border-left-color: initial; border-image-source: initial; border-image-slice: initial; border-image-width: initial; border-image-outset: initial; border-image-repeat: initial; outline-color: initial; outline-style: initial; outline-width: 0px; vertical-align: baseline; background-image: initial; background-position-x: 0px; background-position-y: 0px; background-size: initial; background-repeat-x: initial; background-repeat-y: initial; background-attachment: initial; background-origin: initial; background-clip: initial; background-color: initial; color: #e8e7e3; font-family: "apple color emoji", "segoe ui emoji", "noto color emoji", "android emoji", emojisymbols, "emojione mozilla", "twemoji mozilla", "segoe ui symbol"; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; font-weight: 400; font-size: 14px">🙋</span><span style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; font-variant-numeric: normal; font-variant-east-asian: normal; font-stretch: normal; line-height: normal; font-family: "lucida sans unicode", "lucida grande", sans-serif; color: #5c5959"> If you need anything at all, I'm here to help. Reach out anytime you want to chat - all thoughts, questions, and feedback are welcome.</span></div><div></div></div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table><table border="0" cellpadding="0" cellspacing="0" class="module" data-role="module-button" data-type="button" role="module" style="table-layout:fixed;" width="100%" data-muid="f00155e0-813d-4e9b-b61d-384e6f99e5b7">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" bgcolor="" class="outer-td" style="padding:0px 0px 0px 0px;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" class="wrapper-mobile" style="text-align:center;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" bgcolor="#ff6d2d" class="inner-td" style="border-radius:6px; font-size:16px; text-align:center; background-color:inherit;">
|
||||
<a href="{{appsmithLink}}" style="background-color:#ff6d2d; border:1px solid #ff6d2d; border-color:#ff6d2d; border-radius:6px; border-width:1px; color:#ffffff; display:inline-block; font-weight:400; letter-spacing:0px; line-height:6px; padding:12px 18px 12px 18px; text-align:center; text-decoration:none; border-style:solid; font-family:tahoma,geneva,sans-serif; font-size:16px;" target="_blank">Go to Appsmith</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table><table class="module" role="module" data-type="text" border="0" cellpadding="0" cellspacing="0" width="100%" style="table-layout: fixed;" data-muid="cab2544f-5a6c-49a0-b246-efe5ac8c5208" data-mc-module-version="2019-10-22">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="padding:0px 0px 0px 0px; line-height:22px; text-align:inherit;" height="100%" valign="top" bgcolor="" role="module-content"><div><div style="font-family: inherit; text-align: start"><span style="box-sizing: border-box; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; font-style: inherit; font-variant-ligatures: inherit; font-variant-caps: inherit; font-variant-numeric: normal; font-variant-east-asian: normal; font-weight: inherit; font-stretch: normal; line-height: normal; font-family: "lucida sans unicode", "lucida grande", sans-serif; vertical-align: baseline; border-top-width: 0px; border-right-width: 0px; border-bottom-width: 0px; border-left-width: 0px; border-top-style: initial; border-right-style: initial; border-bottom-style: initial; border-left-style: initial; border-top-color: initial; border-right-color: initial; border-bottom-color: initial; border-left-color: initial; border-image-source: initial; border-image-slice: initial; border-image-width: initial; border-image-outset: initial; border-image-repeat: initial; color: #5c5959; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: pre-wrap; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; font-size: 14px">Cheers</span></div>
|
||||
<div style="font-family: inherit; text-align: start"><span style="box-sizing: border-box; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; font-style: inherit; font-variant-ligatures: inherit; font-variant-caps: inherit; font-variant-numeric: normal; font-variant-east-asian: normal; font-weight: inherit; font-stretch: normal; line-height: normal; font-family: "lucida sans unicode", "lucida grande", sans-serif; vertical-align: baseline; border-top-width: 0px; border-right-width: 0px; border-bottom-width: 0px; border-left-width: 0px; border-top-style: initial; border-right-style: initial; border-bottom-style: initial; border-left-style: initial; border-top-color: initial; border-right-color: initial; border-bottom-color: initial; border-left-color: initial; border-image-source: initial; border-image-slice: initial; border-image-width: initial; border-image-outset: initial; border-image-repeat: initial; color: #5c5959; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: pre-wrap; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; font-size: 14px">Arpit Mohan</span></div>
|
||||
<div style="font-family: inherit; text-align: start"><br></div>
|
||||
|
||||
<!-- <div style="font-family: inherit; text-align: inherit; margin-left: 0px"><span style="box-sizing: border-box; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; font-style: inherit; font-variant-ligatures: inherit; font-variant-caps: inherit; font-variant-numeric: normal; font-variant-east-asian: normal; font-weight: inherit; font-stretch: normal; line-height: normal; font-family: "lucida sans unicode", "lucida grande", sans-serif; vertical-align: baseline; border-top-width: 0px; border-right-width: 0px; border-bottom-width: 0px; border-left-width: 0px; border-top-style: initial; border-right-style: initial; border-bottom-style: initial; border-left-style: initial; border-top-color: initial; border-right-color: initial; border-bottom-color: initial; border-left-color: initial; border-image-source: initial; border-image-slice: initial; border-image-width: initial; border-image-outset: initial; border-image-repeat: initial; color: #5c5959; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: pre-wrap; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; font-size: 14px">P.S:</span><span style="font-size: 14px"> </span><span style="box-sizing: border-box; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; font-style: inherit; font-variant-ligatures: inherit; font-variant-caps: inherit; font-variant-numeric: inherit; font-variant-east-asian: inherit; font-weight: inherit; font-stretch: inherit; line-height: inherit; font-family: "lucida sans unicode", "lucida grande", sans-serif; vertical-align: baseline; border-top-width: 0px; border-right-width: 0px; border-bottom-width: 0px; border-left-width: 0px; border-top-style: initial; border-right-style: initial; border-bottom-style: initial; border-left-style: initial; border-top-color: initial; border-right-color: initial; border-bottom-color: initial; border-left-color: initial; border-image-source: initial; border-image-slice: initial; border-image-width: initial; border-image-outset: initial; border-image-repeat: initial; color: #5c5959; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: pre-wrap; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; float: none; display: inline; font-size: 14px">I'll send you a few more emails in the coming days to help you make the most of Appsmith. If you don't want to receive these, hit unsubscribe.</span></div> -->
|
||||
<div></div></div></td>
|
||||
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<!-- <div data-role="module-unsubscribe" class="module" role="module" data-type="unsubscribe" style="color:#444444; font-size:10px; line-height:20px; padding:0px 16px 0px 16px; text-align:center;" data-muid="1c219d7b-fb60-4317-8a55-f9d8bbd8592d">
|
||||
<div class="Unsubscribe--addressLine"></div>
|
||||
<p style="font-family:tahoma,geneva,sans-serif; font-size:10px; line-height:20px;"><a class="Unsubscribe--unsubscribeLink" href="{{{unsubscribe}}}" target="_blank" style="">Unsubscribe</a> - <a href="{{{unsubscribe_preferences}}}" target="_blank" class="Unsubscribe--unsubscribePreferences" style="">Unsubscribe Preferences</a></p>
|
||||
</div> -->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
<!--[if mso]>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</center>
|
||||
<![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
</div>
|
||||
</center>
|
||||
|
||||
|
||||
</body></html>
|
||||
Loading…
Reference in New Issue
Block a user