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 571831d121..ae299be0c5 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.ResetUserPasswordDTO; import com.appsmith.server.dtos.ResponseDTO; import com.appsmith.server.dtos.UserProfileDTO; import com.appsmith.server.dtos.UserSignupRequestDTO; +import com.appsmith.server.dtos.UserUpdateDTO; import com.appsmith.server.services.SessionUserService; import com.appsmith.server.services.UserDataService; import com.appsmith.server.services.UserOrganizationService; @@ -92,8 +93,8 @@ public class UserController extends BaseController { } @PutMapping() - public Mono> update(@RequestBody User resource, ServerWebExchange exchange) { - return service.updateCurrentUser(resource, exchange) + public Mono> update(@RequestBody UserUpdateDTO updates, ServerWebExchange exchange) { + return service.updateCurrentUser(updates, exchange) .map(updatedUser -> new ResponseDTO<>(HttpStatus.OK.value(), updatedUser, null)); } 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 index eb8ffab8a6..4c18fcedda 100644 --- 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 @@ -32,6 +32,10 @@ public class UserProfileDTO { CommentOnboardingState commentOnboardingState; + String role; + + String useCase; + public boolean isAccountNonExpired() { return this.isEnabled; } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/UserUpdateDTO.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/UserUpdateDTO.java new file mode 100644 index 0000000000..dcb6516afb --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/UserUpdateDTO.java @@ -0,0 +1,25 @@ +package com.appsmith.server.dtos; + +import lombok.Data; + +/** + * Includes **only** those fields that can be updated for a user, via an API call. + */ +@Data +public class UserUpdateDTO { + + private String name; + + private String role; + + private String useCase; + + public boolean hasUserUpdates() { + return name != null; + } + + public boolean hasUserDataUpdates() { + return role != null || useCase != null; + } + +} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserDataService.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserDataService.java index 44ad2a72d8..19d3c70955 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserDataService.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserDataService.java @@ -18,6 +18,8 @@ public interface UserDataService { Mono getForUserEmail(String email); + Mono updateForCurrentUser(UserData updates); + Mono updateForUser(User user, UserData updates); Mono setViewedCurrentVersionReleaseNotes(User user); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserDataServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserDataServiceImpl.java index b2d29887ff..70245e8130 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserDataServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserDataServiceImpl.java @@ -100,20 +100,16 @@ public class UserDataServiceImpl extends BaseService updateForCurrentUser(UserData updates) { + @Override + public Mono updateForCurrentUser(UserData updates) { return sessionUserService.getCurrentUser() .flatMap(user -> userRepository.findByEmail(user.getEmail())) - .flatMap(user -> { - // If a UserData document exists for this user, update it. If not, create one. - updates.setUserId(user.getId()); - final Mono updaterMono = update(user.getId(), updates); - final Mono creatorMono = Mono.just(updates).flatMap(this::create); - return updaterMono.switchIfEmpty(creatorMono); - }); + .flatMap(user -> updateForUser(user, updates)); } @Override public Mono updateForUser(User user, UserData updates) { + // If a UserData document exists for this user, update it. If not, create one. updates.setUserId(user.getId()); final Mono updaterMono = update(user.getId(), updates); final Mono creatorMono = Mono.just(updates).flatMap(this::create); 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 c5383bbdcd..391b6de969 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 @@ -7,6 +7,7 @@ import com.appsmith.server.dtos.InviteUsersDTO; import com.appsmith.server.dtos.ResetUserPasswordDTO; import com.appsmith.server.dtos.UserProfileDTO; import com.appsmith.server.dtos.UserSignupDTO; +import com.appsmith.server.dtos.UserUpdateDTO; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; @@ -33,7 +34,7 @@ public interface UserService extends CrudService { Mono> inviteUsers(InviteUsersDTO inviteUsersDTO, String originHeader); - Mono updateCurrentUser(User updates, ServerWebExchange exchange); + Mono updateCurrentUser(UserUpdateDTO updates, ServerWebExchange exchange); Map getEmailParams(Organization organization, User inviterUser, String inviteUrl, boolean isNewUser); 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 0e7d633c31..a2a3749d9a 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 @@ -24,6 +24,7 @@ import com.appsmith.server.dtos.InviteUsersDTO; import com.appsmith.server.dtos.ResetUserPasswordDTO; import com.appsmith.server.dtos.UserProfileDTO; import com.appsmith.server.dtos.UserSignupDTO; +import com.appsmith.server.dtos.UserUpdateDTO; import com.appsmith.server.exceptions.AppsmithError; import com.appsmith.server.exceptions.AppsmithException; import com.appsmith.server.helpers.PolicyUtils; @@ -818,19 +819,37 @@ public class UserServiceImpl extends BaseService i } @Override - public Mono updateCurrentUser(User allUpdates, ServerWebExchange exchange) { - // Not all fields can be updated. We only let a few fields of a User object be updated with this method. - final User allowedUpdates = new User(); - allowedUpdates.setName(allUpdates.getName()); + public Mono updateCurrentUser(final UserUpdateDTO allUpdates, ServerWebExchange exchange) { + List> monos = new ArrayList<>(); - return sessionUserService.getCurrentUser() - .flatMap(user -> - update(user.getEmail(), allowedUpdates, fieldName(QUser.user.email)) - .then(exchange == null - ? repository.findByEmail(user.getEmail()) - : sessionUserService.refreshCurrentUser(exchange)) - ) - .map(userChangedHandler::publish); + Mono updatedUserMono; + + if (allUpdates.hasUserUpdates()) { + final User updates = new User(); + updates.setName(allUpdates.getName()); + updatedUserMono = sessionUserService.getCurrentUser() + .flatMap(user -> + update(user.getEmail(), updates, fieldName(QUser.user.email)) + .then(exchange == null + ? repository.findByEmail(user.getEmail()) + : sessionUserService.refreshCurrentUser(exchange)) + ) + .map(userChangedHandler::publish) + .cache(); + monos.add(updatedUserMono.then()); + } else { + updatedUserMono = sessionUserService.getCurrentUser() + .flatMap(user -> findByEmail(user.getEmail())); + } + + if (allUpdates.hasUserDataUpdates()) { + final UserData updates = new UserData(); + updates.setRole(allUpdates.getRole()); + updates.setUseCase(allUpdates.getUseCase()); + monos.add(userDataService.updateForCurrentUser(updates).then()); + } + + return Mono.whenDelayError(monos).then(updatedUserMono); } public Map getEmailParams(Organization organization, User inviter, String inviteUrl, boolean isNewUser) { @@ -882,6 +901,8 @@ public class UserServiceImpl extends BaseService i profile.setAnonymous(user.isAnonymous()); profile.setEnabled(user.isEnabled()); profile.setCommentOnboardingState(userData.getCommentOnboardingState()); + profile.setRole(userData.getRole()); + profile.setUseCase(userData.getUseCase()); profile.setSuperUser(policyUtils.isPermissionPresentForUser( userFromDb.getPolicies(), diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/UserServiceTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/UserServiceTest.java index fa80d84d79..27fb39ea13 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/UserServiceTest.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/UserServiceTest.java @@ -11,9 +11,11 @@ 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.domains.UserData; import com.appsmith.server.dtos.InviteUsersDTO; import com.appsmith.server.dtos.ResetUserPasswordDTO; import com.appsmith.server.dtos.UserSignupDTO; +import com.appsmith.server.dtos.UserUpdateDTO; import com.appsmith.server.exceptions.AppsmithError; import com.appsmith.server.exceptions.AppsmithException; import com.appsmith.server.repositories.PasswordResetTokenRepository; @@ -39,6 +41,7 @@ import org.springframework.util.LinkedCaseInsensitiveMap; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; +import reactor.util.function.Tuple2; import java.nio.charset.StandardCharsets; import java.time.Instant; @@ -83,6 +86,9 @@ public class UserServiceTest { @Autowired EncryptionService encryptionService; + @Autowired + UserDataService userDataService; + @MockBean PasswordResetTokenRepository passwordResetTokenRepository; @@ -457,10 +463,8 @@ public class UserServiceTest { @Test @WithUserDetails(value = "api_user") public void updateNameOfUser() { - User updateUser = new User(); - updateUser.setEmail("api_user"); + UserUpdateDTO updateUser = new UserUpdateDTO(); updateUser.setName("New name of api_user"); - StepVerifier.create(userService.updateCurrentUser(updateUser, null)) .assertNext(user -> { assertNotNull(user); @@ -470,6 +474,46 @@ public class UserServiceTest { .verifyComplete(); } + @Test + @WithUserDetails(value = "api_user") + public void updateRoleOfUser() { + UserUpdateDTO updateUser = new UserUpdateDTO(); + updateUser.setRole("New role of user"); + final Mono resultMono = userService.updateCurrentUser(updateUser, null) + .then(userDataService.getForUserEmail("api_user")); + StepVerifier.create(resultMono) + .assertNext(userData -> { + assertNotNull(userData); + assertThat(userData.getRole()).isEqualTo("New role of user"); + }) + .verifyComplete(); + } + + @Test + @WithUserDetails(value = "api_user") + public void updateNameRoleAndUseCaseOfUser() { + UserUpdateDTO updateUser = new UserUpdateDTO(); + updateUser.setName("New name of user here"); + updateUser.setRole("New role of user"); + updateUser.setUseCase("New use case"); + final Mono> resultMono = userService.updateCurrentUser(updateUser, null) + .flatMap(user -> Mono.zip( + Mono.just(user), + userDataService.getForUserEmail("api_user") + )); + StepVerifier.create(resultMono) + .assertNext(tuple -> { + final User user = tuple.getT1(); + final UserData userData = tuple.getT2(); + assertNotNull(user); + assertNotNull(userData); + assertThat(user.getName()).isEqualTo("New name of user here"); + assertThat(userData.getRole()).isEqualTo("New role of user"); + assertThat(userData.getUseCase()).isEqualTo("New use case"); + }) + .verifyComplete(); + } + @Test public void createUserAndSendEmail_WhenUserExistsWithEmailInOtherCase_ThrowsException() { User existingUser = new User();