From 35b0c223345eb4f0e36aa00e5cb7d819bcd3bbc7 Mon Sep 17 00:00:00 2001 From: Arpit Mohan Date: Fri, 6 Mar 2020 06:17:00 +0000 Subject: [PATCH 01/11] Adding /profile endpoint to return enhanced user profile to the client. --- .../server/controllers/UserController.java | 8 ++++ .../com/appsmith/server/domains/User.java | 3 ++ .../server/dtos/ApplicationNameIdDTO.java | 13 ++++++ .../appsmith/server/dtos/UserProfileDTO.java | 19 +++++++++ .../appsmith/server/services/UserService.java | 3 ++ .../server/services/UserServiceImpl.java | 41 ++++++++++++++++++- 6 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ApplicationNameIdDTO.java create mode 100644 app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/UserProfileDTO.java diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/UserController.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/UserController.java index 347459d6fa..18744c7550 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/UserController.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/UserController.java @@ -5,6 +5,7 @@ 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; @@ -77,12 +78,19 @@ public class UserController extends BaseController { .map(result -> new ResponseDTO<>(HttpStatus.OK.value(), result, null)); } + @Deprecated @GetMapping("/me") public Mono> getUserProfile() { return sessionUserService.getCurrentUser() .map(user -> new ResponseDTO<>(HttpStatus.OK.value(), user, null)); } + @GetMapping("/profile") + public Mono> 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. diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/User.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/User.java index 234e38bf5d..83fbbff986 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/User.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/User.java @@ -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; @@ -14,6 +16,7 @@ import org.springframework.security.core.userdetails.UserDetails; import java.util.Collection; import java.util.HashSet; +import java.util.List; import java.util.Set; import java.util.stream.Collectors; diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ApplicationNameIdDTO.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ApplicationNameIdDTO.java new file mode 100644 index 0000000000..42d6b1b5b9 --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ApplicationNameIdDTO.java @@ -0,0 +1,13 @@ +package com.appsmith.server.dtos; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class ApplicationNameIdDTO { + + String id; + + String name; +} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/UserProfileDTO.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/UserProfileDTO.java new file mode 100644 index 0000000000..7733221ffc --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/UserProfileDTO.java @@ -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 applications; +} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserService.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserService.java index f8b4103621..e0f88ade7d 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserService.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserService.java @@ -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 reactor.core.publisher.Mono; public interface UserService extends CrudService { @@ -22,4 +23,6 @@ public interface UserService extends CrudService { Mono verifyInviteToken(String email, String token); Mono confirmInviteUser(InviteUser inviteUser); + + Mono getUserProfile(); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserServiceImpl.java index 1174000ee7..b2b6fbf63e 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserServiceImpl.java @@ -1,21 +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.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.userdetails.ReactiveUserDetailsService; @@ -49,6 +54,7 @@ public class UserServiceImpl extends BaseService i private final GroupService groupService; 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"; @@ -70,7 +76,8 @@ public class UserServiceImpl extends BaseService i EmailSender emailSender, GroupService groupService, InviteUserRepository inviteUserRepository, - UserOrganizationService userOrganizationService) { + UserOrganizationService userOrganizationService, + ApplicationRepository applicationRepository) { super(scheduler, validator, mongoConverter, reactiveMongoTemplate, repository, analyticsService); this.repository = repository; this.organizationService = organizationService; @@ -82,6 +89,7 @@ public class UserServiceImpl extends BaseService i this.groupService = groupService; this.inviteUserRepository = inviteUserRepository; this.userOrganizationService = userOrganizationService; + this.applicationRepository = applicationRepository; } @Override @@ -487,4 +495,35 @@ public class UserServiceImpl extends BaseService i // Doesn't work without this. .map(user -> (UserDetails) user); } + + @Override + public Mono getUserProfile() { + return sessionUserService.getCurrentUser() + .flatMap(user -> { + String currentOrganizationId = user.getCurrentOrganizationId(); + UserProfileDTO userProfile = new UserProfileDTO(); + userProfile.setUser(user); + + Mono 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; + }); + } } From e5f8f2bea94c1fc4797097a8fe37d81ee84a33c1 Mon Sep 17 00:00:00 2001 From: Trisha Anand Date: Fri, 6 Mar 2020 12:49:51 +0000 Subject: [PATCH 02/11] Segment Events Fixed + Soft delete actions --- .../appsmith/server/services/ActionServiceImpl.java | 10 +++------- .../server/services/ApplicationPageServiceImpl.java | 5 +---- .../java/com/appsmith/server/services/BaseService.java | 7 +++---- .../com/appsmith/server/services/PageServiceImpl.java | 6 ++---- 4 files changed, 9 insertions(+), 19 deletions(-) diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ActionServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ActionServiceImpl.java index 38f1434667..4e51a4af8c 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ActionServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ActionServiceImpl.java @@ -271,6 +271,7 @@ public class ActionServiceImpl extends BaseService executeAction(ExecuteActionDTO executeActionDTO) { Action actionFromDto = executeActionDTO.getAction(); @@ -478,13 +479,8 @@ public class ActionServiceImpl extends BaseService 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.archive(toDelete)) + .flatMap(deletedObj -> analyticsService.sendEvent(AnalyticsEvents.DELETE + "_" + deletedObj.getClass().getSimpleName().toUpperCase(), (Action) deletedObj)); } @Override diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ApplicationPageServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ApplicationPageServiceImpl.java index 7f06890f2e..510a5fa2c7 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ApplicationPageServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ApplicationPageServiceImpl.java @@ -219,10 +219,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)); } } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/BaseService.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/BaseService.java index 6df89faf67..33f41d8118 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/BaseService.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/BaseService.java @@ -8,6 +8,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; @@ -21,6 +22,7 @@ import reactor.core.scheduler.Scheduler; import javax.validation.Validator; import java.util.Map; +@Slf4j public abstract class BaseService implements CrudService { final Scheduler scheduler; @@ -89,10 +91,7 @@ public abstract class BaseService { - analyticsService.sendEvent(AnalyticsEvents.CREATE + "_" + savedObj.getClass().getSimpleName().toUpperCase(), (T) savedObj); - return savedObj; - }); + .flatMap(savedObj -> analyticsService.sendEvent(AnalyticsEvents.CREATE + "_" + savedObj.getClass().getSimpleName().toUpperCase(), (T) savedObj)); } private DBObject getDbObject(Object o) { diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/PageServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/PageServiceImpl.java index f6048dd19d..62069a2165 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/PageServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/PageServiceImpl.java @@ -116,10 +116,8 @@ public class PageServiceImpl extends BaseService 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 From 580bfc76c7a1eb273ac687fbe24d1534d9d9dfe8 Mon Sep 17 00:00:00 2001 From: Trisha Anand Date: Fri, 6 Mar 2020 13:31:00 +0000 Subject: [PATCH 03/11] Removing the soft delete right now. Due to the indexing this would lead to duplicate key errors. The index needs to be removed and the allowed naming should be done at application level. --- .../java/com/appsmith/server/services/ActionServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ActionServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ActionServiceImpl.java index 4e51a4af8c..08b1e1c8c7 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ActionServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ActionServiceImpl.java @@ -479,7 +479,7 @@ public class ActionServiceImpl extends BaseService actionMono = repository.findById(id) .switchIfEmpty(Mono.error(new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, "action", id))); return actionMono - .flatMap(toDelete -> repository.archive(toDelete)) + .flatMap(toDelete -> repository.delete(toDelete).thenReturn(toDelete)) .flatMap(deletedObj -> analyticsService.sendEvent(AnalyticsEvents.DELETE + "_" + deletedObj.getClass().getSimpleName().toUpperCase(), (Action) deletedObj)); } From 68fd2f21d98cd457eb15f7e753223baf3416ded8 Mon Sep 17 00:00:00 2001 From: Arpit Mohan Date: Tue, 10 Mar 2020 18:58:28 +0530 Subject: [PATCH 04/11] Updating the communication emails to users for signup, forgot password and invite user flows Also adding the Origin header to the BaseController create function. This is required by the user creation flow in order to customize the links in the email. For most of the controllers overriding the BaseController, the request header parameter is non-mandatory and can be skipped for testing or otherwise. --- .../server/controllers/ActionController.java | 4 +- .../controllers/ApplicationController.java | 4 +- .../server/controllers/BaseController.java | 4 +- .../controllers/CollectionController.java | 4 +- .../server/controllers/PageController.java | 4 +- .../server/controllers/PluginController.java | 1 + .../controllers/RestApiImportController.java | 8 +- .../server/controllers/SignupController.java | 33 --- .../server/controllers/UserController.java | 13 ++ .../server/services/AnalyticsService.java | 11 +- .../appsmith/server/services/UserService.java | 2 + .../server/services/UserServiceImpl.java | 64 ++++-- .../email/forgotPasswordTemplate.html | 197 +++++++++++++++- .../email/inviteUserCreatorTemplate.html | 187 ++++++++++++++++ .../resources/email/inviteUserTemplate.html | 11 - .../resources/email/welcomeUserTemplate.html | 210 +++++++++++++++++- 16 files changed, 671 insertions(+), 86 deletions(-) delete mode 100644 app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/SignupController.java create mode 100644 app/server/appsmith-server/src/main/resources/email/inviteUserCreatorTemplate.html delete mode 100644 app/server/appsmith-server/src/main/resources/email/inviteUserTemplate.html diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ActionController.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ActionController.java index 499cba64ad..a212b84e1c 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ActionController.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ActionController.java @@ -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> create(@Valid @RequestBody Action resource) throws AppsmithException { + public Mono> 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)); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ApplicationController.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ApplicationController.java index 8b919f479c..039842bc56 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ApplicationController.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ApplicationController.java @@ -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> create(@Valid @RequestBody Application resource) throws AppsmithException { + public Mono> 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)); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/BaseController.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/BaseController.java index 8b54ff09a9..d9989d6d67 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/BaseController.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/BaseController.java @@ -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> create(@Valid @RequestBody T resource) throws AppsmithException { + public Mono> 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)); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/CollectionController.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/CollectionController.java index 0710edc1f8..f6488defbc 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/CollectionController.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/CollectionController.java @@ -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> create(@Valid @RequestBody Collection resource) throws AppsmithException { + public Mono> 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)); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/PageController.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/PageController.java index 9378db06a7..e45745f6cd 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/PageController.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/PageController.java @@ -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 { @PostMapping @ResponseStatus(HttpStatus.CREATED) - public Mono> create(@Valid @RequestBody Page resource) throws AppsmithException { + public Mono> 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)); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/PluginController.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/PluginController.java index 828b3c30af..2284278bd2 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/PluginController.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/PluginController.java @@ -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; diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/RestApiImportController.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/RestApiImportController.java index 23b0d60866..3ccf8f7e8c 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/RestApiImportController.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/RestApiImportController.java @@ -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> 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> importPostmanCollection(@RequestBody Object input, @RequestParam String type) { + public Mono> importPostmanCollection(@RequestBody Object input, + @RequestParam String type) { return Mono.just(postmanImporterService.importPostmanCollection(input)) .map(created -> new ResponseDTO<>(HttpStatus.CREATED.value(), created, null)); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/SignupController.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/SignupController.java deleted file mode 100644 index f266071fe6..0000000000 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/SignupController.java +++ /dev/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> signupOrganization(@RequestBody Organization organization) { - return signupService.createOrganization(organization) - .map(org -> new ResponseDTO<>(HttpStatus.CREATED.value(), org, null)); - } - -} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/UserController.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/UserController.java index 18744c7550..7c85d1de85 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/UserController.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/UserController.java @@ -9,6 +9,7 @@ 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; @@ -19,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 { private final SessionUserService sessionUserService; @@ -38,6 +43,14 @@ public class UserController extends BaseController { this.userOrganizationService = userOrganizationService; } + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public Mono> 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> setCurrentOrganization(@PathVariable String orgId) { return service.switchCurrentOrganization(orgId) diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/AnalyticsService.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/AnalyticsService.java index 195b6e6477..11de1af389 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/AnalyticsService.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/AnalyticsService.java @@ -43,8 +43,17 @@ public class AnalyticsService { }); } + private User createAnonymousUser() { + User user = new User(); + user.setId("anonymousUser"); + return user; + } + public Mono sendEvent(String eventTag, T object) { - Mono 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 userMono = sessionUserService.getCurrentUser() + .defaultIfEmpty(createAnonymousUser()); return userMono .map(user -> { HashMap analyticsProperties = new HashMap<>(); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserService.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserService.java index e0f88ade7d..4c0fac546b 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserService.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserService.java @@ -25,4 +25,6 @@ public interface UserService extends CrudService { Mono confirmInviteUser(InviteUser inviteUser); Mono getUserProfile(); + + Mono createUser(User user, String originHeader); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserServiceImpl.java index b2b6fbf63e..bd87fec217 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserServiceImpl.java @@ -28,6 +28,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; @@ -35,6 +36,7 @@ import javax.validation.Validator; import java.io.IOException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; +import java.util.HashMap; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -57,7 +59,7 @@ public class UserServiceImpl extends BaseService i 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"; @@ -282,32 +284,47 @@ public class UserServiceImpl extends BaseService i // 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 -> { + Mono currentUserMono = sessionUserService.getCurrentUser(); + Mono 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 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 params = Map.of( - "token", token, - "inviteUrl", inviteUrl); + Map 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) { @@ -315,7 +332,6 @@ public class UserServiceImpl extends BaseService i } return inviteUser; }); - } /** @@ -389,6 +405,11 @@ public class UserServiceImpl extends BaseService i }).flatMap(result -> result); } + @Override + public Mono 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 @@ -400,7 +421,12 @@ public class UserServiceImpl extends BaseService i * @return */ @Override - public Mono create(User user) { + public Mono createUser(User user, String originHeader) { + if (originHeader == null || originHeader.isBlank()) { + // Default to the production link + originHeader = "https://app.appsmith.com"; + } + 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())) { @@ -417,8 +443,8 @@ public class UserServiceImpl extends BaseService 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 savedUserMono = super.create(user); @@ -433,7 +459,11 @@ public class UserServiceImpl extends BaseService i .map(savedUser -> { // Send an email to the user welcoming them to the Appsmith platform try { - Map params = Map.of("personalWorkspaceName", personalWorkspaceName); + Map params = new HashMap<>(); + params.put("personalOrganizationName", personalOrganizationName); + params.put("firstName", savedUser.getName()); + // TODO: Configure this link for each environment. For now, hard-coding it to app.appsmith.com for production + params.put("appsmithLink", finalOriginHeader); String emailBody = emailSender.replaceEmailTemplate(WELCOME_USER_EMAIL_TEMPLATE, params); emailSender.sendMail(savedUser.getEmail(), "Welcome to Appsmith", emailBody); } catch (IOException e) { diff --git a/app/server/appsmith-server/src/main/resources/email/forgotPasswordTemplate.html b/app/server/appsmith-server/src/main/resources/email/forgotPasswordTemplate.html index dff342e15d..046d9a0d19 100644 --- a/app/server/appsmith-server/src/main/resources/email/forgotPasswordTemplate.html +++ b/app/server/appsmith-server/src/main/resources/email/forgotPasswordTemplate.html @@ -1,10 +1,189 @@ - + + + + + + + + + + + - You can reset your password by clicking this link. -

- Alternatively, you can copy paste the following URL in your browser: {{resetUrl}} -

- Cheers,
- Appsmith - - \ No newline at end of file +
+
+ + + + +
+ + + + +
+ + + + +
+ + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
Hello,
+

+
Forgot the password to your Appsmith account? No worries, we've got you covered.
+ + + + + +
+ + + + + + +
+ Reset Password +
+
+ + + + + +
The link will expire in 48 hours. If you didn't request a password reset, you can safely ignore this email.
+

+
Cheers
+
Devs at Appsmith
+ +
+
+
+
+
+ + + \ No newline at end of file diff --git a/app/server/appsmith-server/src/main/resources/email/inviteUserCreatorTemplate.html b/app/server/appsmith-server/src/main/resources/email/inviteUserCreatorTemplate.html new file mode 100644 index 0000000000..33232d5156 --- /dev/null +++ b/app/server/appsmith-server/src/main/resources/email/inviteUserCreatorTemplate.html @@ -0,0 +1,187 @@ + + + + + + + + + + + + +
+
+ + + + +
+ + + + +
+ + + + +
+ + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
You've been invited to collaborate.
+

+
{{Inviter_First_Name}} has invited you to collaborate on the organization "{{inviter_org_name}}" in Appsmith.
+ + + + + +
+ + + + + + +
+ Accept invite +
+
+ + + + + +
Cheers
+
Devs at Appsmith
+ +
+
+
+
+
+ + + \ No newline at end of file diff --git a/app/server/appsmith-server/src/main/resources/email/inviteUserTemplate.html b/app/server/appsmith-server/src/main/resources/email/inviteUserTemplate.html deleted file mode 100644 index 63eebfbe5c..0000000000 --- a/app/server/appsmith-server/src/main/resources/email/inviteUserTemplate.html +++ /dev/null @@ -1,11 +0,0 @@ - - - You've been invited to the Appsmith platform. Please complete your sign up by clicking this link

- Alternatively, you can copy & paste the following url in your browser: {{inviteUrl}} -

- For reference, your invite token is: {{token}} -

- Cheers, - Appsmith - - \ No newline at end of file diff --git a/app/server/appsmith-server/src/main/resources/email/welcomeUserTemplate.html b/app/server/appsmith-server/src/main/resources/email/welcomeUserTemplate.html index 40772c94a5..0441063bcc 100644 --- a/app/server/appsmith-server/src/main/resources/email/welcomeUserTemplate.html +++ b/app/server/appsmith-server/src/main/resources/email/welcomeUserTemplate.html @@ -1,9 +1,203 @@ - + + + + + + + + + + + - Thank you for signing up for the Appsmith platform.

- Your personal workspace is: {{personalWorkspaceName}} -

- Cheers,
- Appsmith - - +
+
+ + + + +
+ + + + +
+ + + + +
+ + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
Hi {{firstName}},
+

+
I am really excited you signed up for Appsmith and wanted to personally reach out to welcome you.
+

+
🚀Hope you started creating your first app already. In case you didn't and are looking for a little inspiration, you may want to check out a few use cases other developers are building.
+

+
📅If you would like to explore the product together or collaborate while creating your first app, schedule a time here and let's get building.
+

+
🙋 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.
+ + + + + +
+ + + + + + +
+ Go to Appsmith +
+
+ + + + + + +
Cheers
+
Arpit Mohan
+

+ + +
+ +
+ +
+
+
+
+
+ + + \ No newline at end of file From 2da4727cbd15baffedab11b858b4f3d7520f6473 Mon Sep 17 00:00:00 2001 From: Arpit Mohan Date: Wed, 11 Mar 2020 18:14:13 +0530 Subject: [PATCH 05/11] Removing document version from all the Mongo documents. Also adding Origin header to the user invite flow so that we can send the correct links to the user --- .../main/java/com/appsmith/external/models/BaseDomain.java | 5 ----- .../java/com/appsmith/server/controllers/UserController.java | 5 +++-- .../main/java/com/appsmith/server/services/UserService.java | 2 +- .../java/com/appsmith/server/services/UserServiceImpl.java | 4 ++-- 4 files changed, 6 insertions(+), 10 deletions(-) diff --git a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/BaseDomain.java b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/BaseDomain.java index 0685ab09dd..96452fd421 100644 --- a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/BaseDomain.java +++ b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/BaseDomain.java @@ -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; @@ -47,10 +46,6 @@ public abstract class BaseDomain implements Persistable { protected Boolean deleted = false; - @JsonIgnore - @Version - protected Long documentVersion; - @JsonIgnore @Override public boolean isNew() { diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/UserController.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/UserController.java index 7c85d1de85..6904686f12 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/UserController.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/UserController.java @@ -125,8 +125,9 @@ public class UserController extends BaseController { } @PutMapping("/invite/confirm") - public Mono> confirmInviteUser(@RequestBody InviteUser inviteUser) { - return service.confirmInviteUser(inviteUser) + public Mono> confirmInviteUser(@RequestBody InviteUser inviteUser, + @RequestHeader("Origin") String originHeader) { + return service.confirmInviteUser(inviteUser, originHeader) .map(result -> new ResponseDTO<>(HttpStatus.OK.value(), result, null)); } } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserService.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserService.java index 4c0fac546b..be71441f19 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserService.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserService.java @@ -22,7 +22,7 @@ public interface UserService extends CrudService { Mono verifyInviteToken(String email, String token); - Mono confirmInviteUser(InviteUser inviteUser); + Mono confirmInviteUser(InviteUser inviteUser, String originHeader); Mono getUserProfile(); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserServiceImpl.java index bd87fec217..5e7ce19c11 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserServiceImpl.java @@ -359,7 +359,7 @@ public class UserServiceImpl extends BaseService i * @return */ @Override - public Mono confirmInviteUser(InviteUser inviteUser) { + public Mono confirmInviteUser(InviteUser inviteUser, String originHeader) { if (inviteUser.getToken() == null || inviteUser.getToken().isEmpty()) { return Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, "token")); } @@ -397,7 +397,7 @@ public class UserServiceImpl extends BaseService 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)) From 668b91665d25a9b02a8b7c24e879177665ba69b3 Mon Sep 17 00:00:00 2001 From: Trisha Anand Date: Wed, 11 Mar 2020 13:03:48 +0000 Subject: [PATCH 06/11] 1. Added an endpoint to get all categories. 2. When provider is being fetched without a category, only Business Software category providers are returned. --- .../controllers/ProviderController.java | 10 ++++++++ .../server/services/ProviderService.java | 2 ++ .../server/services/ProviderServiceImpl.java | 23 +++++++++++++++++-- 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ProviderController.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ProviderController.java index 7a422469dc..14e012086c 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ProviderController.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ProviderController.java @@ -2,9 +2,13 @@ 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.Flux; @RestController @RequestMapping(Url.PROVIDER_URL) @@ -13,4 +17,10 @@ public class ProviderController extends BaseController> getAllCategories() { + return service.getAllCategories() + .map(resources -> new ResponseDTO<>(HttpStatus.OK.value(), resources, null)); + } } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ProviderService.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ProviderService.java index b33acb0bbf..7d03655d9e 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ProviderService.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ProviderService.java @@ -1,6 +1,8 @@ package com.appsmith.server.services; import com.appsmith.external.models.Provider; +import reactor.core.publisher.Flux; public interface ProviderService extends CrudService { + public Flux getAllCategories(); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ProviderServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ProviderServiceImpl.java index bb826f27ea..c09bb9cc75 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ProviderServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ProviderServiceImpl.java @@ -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 implements ProviderService { + private static final List 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,22 @@ public class ProviderServiceImpl extends BaseService categories = new ArrayList<>(); if (params.getFirst(FieldName.CATEGORY) != null) { - List 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 Flux getAllCategories() { + return Mono.just(CATEGORIES) + .flatMapMany(Flux::fromIterable); + } } From b17fae8e44679f85a3b0708a6dc72ee80b0848bd Mon Sep 17 00:00:00 2001 From: Trisha Anand Date: Wed, 11 Mar 2020 17:16:15 +0000 Subject: [PATCH 07/11] 1. Add to Page changes : In case the sample response is null, don't set the cached response for the action. 2. Add to Page changes : Documentation object has been added in Action to handle the extra Template documentation for actions that have been imported from 3p marketplace 3. Added basic structure for rapid api plugin by copy pasting the rest api plugin --- app/server/appsmith-plugins/pom.xml | 1 + .../appsmith-plugins/rapidApiPlugin/pom.xml | 85 ++++++ .../com/external/plugins/RapidApiPlugin.java | 266 ++++++++++++++++++ .../external/plugins/RapidApiPluginTest.java | 18 ++ .../com/appsmith/server/domains/Action.java | 2 + .../server/domains/Documentation.java | 13 + .../server/services/ItemServiceImpl.java | 10 +- 7 files changed, 394 insertions(+), 1 deletion(-) create mode 100644 app/server/appsmith-plugins/rapidApiPlugin/pom.xml create mode 100644 app/server/appsmith-plugins/rapidApiPlugin/src/main/java/com/external/plugins/RapidApiPlugin.java create mode 100644 app/server/appsmith-plugins/rapidApiPlugin/src/test/java/com/external/plugins/RapidApiPluginTest.java create mode 100644 app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Documentation.java diff --git a/app/server/appsmith-plugins/pom.xml b/app/server/appsmith-plugins/pom.xml index 11ebe088e9..eb1160af2c 100644 --- a/app/server/appsmith-plugins/pom.xml +++ b/app/server/appsmith-plugins/pom.xml @@ -18,6 +18,7 @@ postgresPlugin restApiPlugin mongoPlugin + rapidApiPlugin \ No newline at end of file diff --git a/app/server/appsmith-plugins/rapidApiPlugin/pom.xml b/app/server/appsmith-plugins/rapidApiPlugin/pom.xml new file mode 100644 index 0000000000..32621c6e67 --- /dev/null +++ b/app/server/appsmith-plugins/rapidApiPlugin/pom.xml @@ -0,0 +1,85 @@ + + + + 4.0.0 + + com.external.plugins + rapidApiPlugin + 1.0-SNAPSHOT + + rapidApiPlugin + + + UTF-8 + 11 + ${java.version} + ${java.version} + rapidapi-plugin + com.external.plugins.RapidApiPlugin + 1.0-SNAPSHOT + tech@appsmith.com + + + + + + junit + junit + 4.11 + test + + + + org.pf4j + pf4j-spring + 0.5.0 + + + + com.appsmith + interfaces + 1.0-SNAPSHOT + + + + org.springframework + spring-webflux + 5.1.5.RELEASE + + + + org.projectlombok + lombok + 1.18.8 + provided + + + + + + + + maven-compiler-plugin + 3.8.1 + + + org.apache.maven.plugins + maven-jar-plugin + 3.1.2 + + + + ${plugin.id} + ${plugin.class} + ${plugin.version} + ${plugin.provider} + ${plugin.dependencies} + + + + + + + + diff --git a/app/server/appsmith-plugins/rapidApiPlugin/src/main/java/com/external/plugins/RapidApiPlugin.java b/app/server/appsmith-plugins/rapidApiPlugin/src/main/java/com/external/plugins/RapidApiPlugin.java new file mode 100644 index 0000000000..420f4e3cc8 --- /dev/null +++ b/app/server/appsmith-plugins/rapidApiPlugin/src/main/java/com/external/plugins/RapidApiPlugin.java @@ -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 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 strStrMap = new HashMap(); + + List 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 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 headers) { + for (Property header : headers) { + if (header.getKey() != null && !header.getKey().isEmpty()) { + webClientBuilder.defaultHeader(header.getKey(), header.getValue()); + } + } + } + + private URI createFinalUriWithQueryParams(String url, List 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. + */ + } +} diff --git a/app/server/appsmith-plugins/rapidApiPlugin/src/test/java/com/external/plugins/RapidApiPluginTest.java b/app/server/appsmith-plugins/rapidApiPlugin/src/test/java/com/external/plugins/RapidApiPluginTest.java new file mode 100644 index 0000000000..6436bda5ab --- /dev/null +++ b/app/server/appsmith-plugins/rapidApiPlugin/src/test/java/com/external/plugins/RapidApiPluginTest.java @@ -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); + } +} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Action.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Action.java index 9af67ec1e5..3cf56314ac 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Action.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Action.java @@ -50,6 +50,8 @@ public class Action extends BaseDomain { String templateId; //If action is created via a template, store the id here. + Documentation documentation; + /** * If the Datasource is null, create one and set the autoGenerated flag to true. This is required because spring-data * cannot add the createdAt and updatedAt properties for null embedded objects. At this juncture, we couldn't find diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Documentation.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Documentation.java new file mode 100644 index 0000000000..b4f39fe3b2 --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Documentation.java @@ -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; +} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ItemServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ItemServiceImpl.java index ea23434000..e8855557dc 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ItemServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ItemServiceImpl.java @@ -4,6 +4,7 @@ import com.appsmith.external.models.ApiTemplate; import com.appsmith.server.constants.FieldName; import com.appsmith.server.domains.Action; import com.appsmith.server.domains.Datasource; +import com.appsmith.server.domains.Documentation; import com.appsmith.server.dtos.AddItemToPageDTO; import com.appsmith.server.dtos.ItemDTO; import com.appsmith.server.dtos.ItemType; @@ -66,13 +67,20 @@ public class ItemServiceImpl implements ItemService { action.setName(addItemToPageDTO.getName()); action.setPageId(addItemToPageDTO.getPageId()); action.setTemplateId(apiTemplate.getId()); + + Documentation documentation = new Documentation(); + documentation.setText(apiTemplate.getApiTemplateConfiguration().getDocumentation()); + documentation.setUrl(apiTemplate.getApiTemplateConfiguration().getDocumentationUrl()); + action.setDocumentation(documentation); /** TODO * Also hit the Marketplace to update the number of imports. */ // Set Action Fields action.setActionConfiguration(apiTemplate.getActionConfiguration()); - action.setCacheResponse(apiTemplate.getApiTemplateConfiguration().getSampleResponse().getBody().toString()); + if (apiTemplate.getApiTemplateConfiguration().getSampleResponse() != null) { + action.setCacheResponse(apiTemplate.getApiTemplateConfiguration().getSampleResponse().getBody().toString()); + } return pluginService .findByPackageName(apiTemplate.getPackageName()) From 6050b370d4ac7c86ef53ec8e8e1fb145db523f2a Mon Sep 17 00:00:00 2001 From: Trisha Anand Date: Wed, 11 Mar 2020 19:31:21 +0000 Subject: [PATCH 08/11] 1. Add to Page changes : In case the sample response is null, don't set the cached response for the action. 2. Add to Page changes : Documentation object has been added in Action to handle the extra Template documentation for actions that have been imported from 3p marketplace 3. Added basic structure for rapid api plugin by copy pasting the rest api plugin --- .../java/com/appsmith/server/services/ItemServiceImpl.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ItemServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ItemServiceImpl.java index e8855557dc..46630dea3a 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ItemServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ItemServiceImpl.java @@ -78,7 +78,8 @@ public class ItemServiceImpl implements ItemService { // Set Action Fields action.setActionConfiguration(apiTemplate.getActionConfiguration()); - if (apiTemplate.getApiTemplateConfiguration().getSampleResponse() != null) { + if (apiTemplate.getApiTemplateConfiguration().getSampleResponse() != null && + apiTemplate.getApiTemplateConfiguration().getSampleResponse().getBody() != null ) { action.setCacheResponse(apiTemplate.getApiTemplateConfiguration().getSampleResponse().getBody().toString()); } From 0c5fc27450272223a8e9508bfa181594f4804bb2 Mon Sep 17 00:00:00 2001 From: Arpit Mohan Date: Thu, 12 Mar 2020 16:44:31 +0530 Subject: [PATCH 09/11] Correcting the writeKey for Segment in Staging Also adding Mono.cache in the user invite flow to ensure that the same Mono is not executed multiple times. --- .../appsmith/server/notifications/EmailSender.java | 2 +- .../appsmith/server/services/UserServiceImpl.java | 14 +++++++------- .../main/resources/application-staging.properties | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/notifications/EmailSender.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/notifications/EmailSender.java index 260069f5d0..70400ce9d4 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/notifications/EmailSender.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/notifications/EmailSender.java @@ -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; diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserServiceImpl.java index 5e7ce19c11..283ca7a7d8 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserServiceImpl.java @@ -53,7 +53,6 @@ public class UserServiceImpl extends BaseService i private final PasswordResetTokenRepository passwordResetTokenRepository; private final PasswordEncoder passwordEncoder; private final EmailSender emailSender; - private final GroupService groupService; private final InviteUserRepository inviteUserRepository; private final UserOrganizationService userOrganizationService; private final ApplicationRepository applicationRepository; @@ -63,6 +62,8 @@ public class UserServiceImpl extends BaseService i 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, @@ -76,7 +77,6 @@ public class UserServiceImpl extends BaseService i PasswordResetTokenRepository passwordResetTokenRepository, PasswordEncoder passwordEncoder, EmailSender emailSender, - GroupService groupService, InviteUserRepository inviteUserRepository, UserOrganizationService userOrganizationService, ApplicationRepository applicationRepository) { @@ -88,7 +88,6 @@ public class UserServiceImpl extends BaseService i this.passwordResetTokenRepository = passwordResetTokenRepository; this.passwordEncoder = passwordEncoder; this.emailSender = emailSender; - this.groupService = groupService; this.inviteUserRepository = inviteUserRepository; this.userOrganizationService = userOrganizationService; this.applicationRepository = applicationRepository; @@ -281,10 +280,12 @@ public class UserServiceImpl extends BaseService 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(); - Mono currentUserMono = sessionUserService.getCurrentUser(); + // Caching the response from sessionUserService because it's re-used multiple times in this flow + Mono currentUserMono = sessionUserService.getCurrentUser().cache(); Mono inviteUserMono = currentUserMono .map(currentUser -> { log.debug("Got request to invite user {} by user: {} for org: {}", @@ -424,7 +425,7 @@ public class UserServiceImpl extends BaseService i public Mono createUser(User user, String originHeader) { if (originHeader == null || originHeader.isBlank()) { // Default to the production link - originHeader = "https://app.appsmith.com"; + originHeader = DEFAULT_ORIGIN_HEADER; } final String finalOriginHeader = originHeader; @@ -462,7 +463,6 @@ public class UserServiceImpl extends BaseService i Map params = new HashMap<>(); params.put("personalOrganizationName", personalOrganizationName); params.put("firstName", savedUser.getName()); - // TODO: Configure this link for each environment. For now, hard-coding it to app.appsmith.com for production params.put("appsmithLink", finalOriginHeader); String emailBody = emailSender.replaceEmailTemplate(WELCOME_USER_EMAIL_TEMPLATE, params); emailSender.sendMail(savedUser.getEmail(), "Welcome to Appsmith", emailBody); diff --git a/app/server/appsmith-server/src/main/resources/application-staging.properties b/app/server/appsmith-server/src/main/resources/application-staging.properties index 84cf71fbb4..9c6c8af343 100644 --- a/app/server/appsmith-server/src/main/resources/application-staging.properties +++ b/app/server/appsmith-server/src/main/resources/application-staging.properties @@ -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 From 2543be8668d54ee245871aee0b2439f0c42f10ee Mon Sep 17 00:00:00 2001 From: Trisha Anand Date: Thu, 12 Mar 2020 18:46:30 +0530 Subject: [PATCH 10/11] Provider fields added with the transient property which is set for Actions when they are read. --- .../com/appsmith/server/domains/Action.java | 6 +++++ .../server/domains/ActionProvider.java | 20 ++++++++++++++ .../server/services/ActionServiceImpl.java | 27 ++++++++++++++++++- .../server/services/ItemServiceImpl.java | 1 + 4 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 app/server/appsmith-server/src/main/java/com/appsmith/server/domains/ActionProvider.java diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Action.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Action.java index 3cf56314ac..1a74211b99 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Action.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Action.java @@ -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,11 @@ 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; /** diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/ActionProvider.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/ActionProvider.java new file mode 100644 index 0000000000..2984dc9df6 --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/ActionProvider.java @@ -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; +} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ActionServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ActionServiceImpl.java index 08b1e1c8c7..53c6283acc 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ActionServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ActionServiceImpl.java @@ -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 { 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); }); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ItemServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ItemServiceImpl.java index 46630dea3a..ca856c2bd8 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ItemServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ItemServiceImpl.java @@ -67,6 +67,7 @@ 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()); From 1d404cb9bf46b923efdeabd43cc2dd8d3dfd07d2 Mon Sep 17 00:00:00 2001 From: Trisha Anand Date: Thu, 12 Mar 2020 20:01:43 +0530 Subject: [PATCH 11/11] Get all categories now return Mono of List instead of Flux. --- .../com/appsmith/server/controllers/ProviderController.java | 6 ++++-- .../java/com/appsmith/server/services/ProviderService.java | 6 ++++-- .../com/appsmith/server/services/ProviderServiceImpl.java | 5 ++--- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ProviderController.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ProviderController.java index 14e012086c..999a2652bb 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ProviderController.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ProviderController.java @@ -8,7 +8,9 @@ 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.Flux; +import reactor.core.publisher.Mono; + +import java.util.List; @RestController @RequestMapping(Url.PROVIDER_URL) @@ -19,7 +21,7 @@ public class ProviderController extends BaseController> getAllCategories() { + public Mono>> getAllCategories() { return service.getAllCategories() .map(resources -> new ResponseDTO<>(HttpStatus.OK.value(), resources, null)); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ProviderService.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ProviderService.java index 7d03655d9e..0b06159fa0 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ProviderService.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ProviderService.java @@ -1,8 +1,10 @@ package com.appsmith.server.services; import com.appsmith.external.models.Provider; -import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.util.List; public interface ProviderService extends CrudService { - public Flux getAllCategories(); + public Mono> getAllCategories(); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ProviderServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ProviderServiceImpl.java index c09bb9cc75..7f5c4623b8 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ProviderServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ProviderServiceImpl.java @@ -62,8 +62,7 @@ public class ProviderServiceImpl extends BaseService getAllCategories() { - return Mono.just(CATEGORIES) - .flatMapMany(Flux::fromIterable); + public Mono> getAllCategories() { + return Mono.just(CATEGORIES); } }