From c17e28ebd83c0f0cb5dbe8ef1f49a2628af9f888 Mon Sep 17 00:00:00 2001 From: Trisha Anand Date: Thu, 3 Apr 2025 14:25:24 +0530 Subject: [PATCH] chore: Minor refactor for sign up workspace create flow. (#40046) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description > [!TIP] > _Add a TL;DR when the description is longer than 500 words or extremely technical (helps the content, marketing, and DevRel team)._ > > _Please also include relevant motivation and context. List any dependencies that are required for this change. Add links to Notion, Figma or any other documents that might be relevant to the PR._ Fixes #`Issue Number` _or_ Fixes `Issue URL` > [!WARNING] > _If no issue exists, please create an issue first, and check with the maintainers if the issue is valid._ ## Automation /test sanity ### :mag: Cypress test results > [!TIP] > 🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉 > Workflow run: > Commit: c173b0401d3f62b07f1a28d7139cc6930ffb9ca0 > Cypress dashboard. > Tags: `@tag.Sanity` > Spec: >
Thu, 03 Apr 2025 08:32:21 UTC ## Communication Should the DevRel and Marketing teams inform users about this change? - [ ] Yes - [ ] No ## Summary by CodeRabbit - **New Features** - Enhanced the sign-up process with a new `UserSignupHelper` for automated default workspace and application provisioning. - Improved the email verification redirection for a smoother onboarding experience. - **Refactor** - Streamlined backend workflows by consolidating workspace and application creation logic, enhancing modularity and maintainability. --- .../AuthenticationSuccessHandler.java | 16 +-- .../ce/AuthenticationSuccessHandlerCE.java | 53 +------- .../server/helpers/UserSignupHelper.java | 30 +++++ .../server/helpers/ce/UserSignupHelperCE.java | 122 ++++++++++++++++++ .../server/services/ce/UserServiceCEImpl.java | 68 ++++++---- 5 files changed, 208 insertions(+), 81 deletions(-) create mode 100644 app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/UserSignupHelper.java create mode 100644 app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/ce/UserSignupHelperCE.java diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/authentication/handlers/AuthenticationSuccessHandler.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/authentication/handlers/AuthenticationSuccessHandler.java index 55e4a8b040..624cff6a4e 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/authentication/handlers/AuthenticationSuccessHandler.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/authentication/handlers/AuthenticationSuccessHandler.java @@ -2,19 +2,17 @@ package com.appsmith.server.authentication.handlers; import com.appsmith.server.authentication.handlers.ce.AuthenticationSuccessHandlerCE; import com.appsmith.server.helpers.RedirectHelper; +import com.appsmith.server.helpers.UserSignupHelper; import com.appsmith.server.helpers.WorkspaceServiceHelper; import com.appsmith.server.instanceconfigs.helpers.InstanceVariablesHelper; import com.appsmith.server.ratelimiting.RateLimitService; import com.appsmith.server.repositories.UserRepository; -import com.appsmith.server.repositories.WorkspaceRepository; import com.appsmith.server.services.AnalyticsService; import com.appsmith.server.services.ApplicationPageService; import com.appsmith.server.services.OrganizationService; import com.appsmith.server.services.SessionUserService; import com.appsmith.server.services.UserDataService; import com.appsmith.server.services.UserService; -import com.appsmith.server.services.WorkspaceService; -import com.appsmith.server.solutions.WorkspacePermission; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; @@ -28,29 +26,25 @@ public class AuthenticationSuccessHandler extends AuthenticationSuccessHandlerCE AnalyticsService analyticsService, UserDataService userDataService, UserRepository userRepository, - WorkspaceRepository workspaceRepository, - WorkspaceService workspaceService, ApplicationPageService applicationPageService, - WorkspacePermission workspacePermission, RateLimitService rateLimitService, OrganizationService organizationService, UserService userService, WorkspaceServiceHelper workspaceServiceHelper, - InstanceVariablesHelper instanceVariablesHelper) { + InstanceVariablesHelper instanceVariablesHelper, + UserSignupHelper userSignupHelper) { super( redirectHelper, sessionUserService, analyticsService, userDataService, userRepository, - workspaceRepository, - workspaceService, applicationPageService, - workspacePermission, rateLimitService, organizationService, userService, workspaceServiceHelper, - instanceVariablesHelper); + instanceVariablesHelper, + userSignupHelper); } } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/authentication/handlers/ce/AuthenticationSuccessHandlerCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/authentication/handlers/ce/AuthenticationSuccessHandlerCE.java index 3b9645ad65..cd4322eff1 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/authentication/handlers/ce/AuthenticationSuccessHandlerCE.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/authentication/handlers/ce/AuthenticationSuccessHandlerCE.java @@ -8,22 +8,19 @@ import com.appsmith.server.constants.Security; import com.appsmith.server.domains.Application; import com.appsmith.server.domains.LoginSource; import com.appsmith.server.domains.User; -import com.appsmith.server.domains.Workspace; import com.appsmith.server.dtos.ResendEmailVerificationDTO; import com.appsmith.server.helpers.RedirectHelper; +import com.appsmith.server.helpers.UserSignupHelper; import com.appsmith.server.helpers.WorkspaceServiceHelper; import com.appsmith.server.instanceconfigs.helpers.InstanceVariablesHelper; import com.appsmith.server.ratelimiting.RateLimitService; import com.appsmith.server.repositories.UserRepository; -import com.appsmith.server.repositories.WorkspaceRepository; import com.appsmith.server.services.AnalyticsService; import com.appsmith.server.services.ApplicationPageService; import com.appsmith.server.services.OrganizationService; import com.appsmith.server.services.SessionUserService; import com.appsmith.server.services.UserDataService; import com.appsmith.server.services.UserService; -import com.appsmith.server.services.WorkspaceService; -import com.appsmith.server.solutions.WorkspacePermission; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpHeaders; @@ -60,15 +57,13 @@ public class AuthenticationSuccessHandlerCE implements ServerAuthenticationSucce private final AnalyticsService analyticsService; private final UserDataService userDataService; private final UserRepository userRepository; - private final WorkspaceRepository workspaceRepository; - private final WorkspaceService workspaceService; private final ApplicationPageService applicationPageService; - private final WorkspacePermission workspacePermission; private final RateLimitService rateLimitService; private final OrganizationService organizationService; private final UserService userService; private final WorkspaceServiceHelper workspaceServiceHelper; private final InstanceVariablesHelper instanceVariablesHelper; + private final UserSignupHelper userSignupHelper; private Mono isVerificationRequired(String userEmail, String method) { Mono emailVerificationEnabledMono = @@ -194,7 +189,7 @@ public class AuthenticationSuccessHandlerCE implements ServerAuthenticationSucce * then redirects the user to /verificationPending and sends the magic link with the user's redirectUrl * in the email. */ - private Mono formEmailVerificationRedirectionHandler( + public Mono formEmailVerificationRedirectionHandler( WebFilterExchange webFilterExchange, String defaultWorkspaceId, Authentication authentication, @@ -350,45 +345,9 @@ public class AuthenticationSuccessHandlerCE implements ServerAuthenticationSucce protected Mono createDefaultApplication(String defaultWorkspaceId, Authentication authentication) { // need to create default application - return createWorkspaceIfNotExistsAndGetId(defaultWorkspaceId, authentication) - .flatMap(this::createFirstApplication); - } - - protected Mono createWorkspaceIfNotExistsAndGetId( - String defaultWorkspaceId, Authentication authentication) { - if (defaultWorkspaceId == null) { - return workspaceRepository - .findAll(workspacePermission.getEditPermission()) - .take(1, true) - .collectList() - .flatMap(workspaces -> { - // Since this is the first application creation, the first workspace would be the only - // workspace user has access to, and would be user's default workspace. Hence, we use this - // workspace to create the application. - if (workspaces.size() == 1) { - return Mono.just(workspaces.get(0)); - } - - // In case no workspaces are found for the user, create a new default workspace - String email = ((User) authentication.getPrincipal()).getEmail(); - - return organizationService - .getCurrentUserOrganizationId() - .flatMap(orgId -> userRepository.findByEmailAndOrganizationId(email, orgId)) - .flatMap(user -> workspaceService.createDefault(new Workspace(), user)); - }) - .map(Workspace::getId); - } - - return Mono.just(defaultWorkspaceId); - } - - protected Mono createFirstApplication(String workspaceId) { - // need to create default application - Application application = new Application(); - application.setWorkspaceId(workspaceId); - application.setName("My first application"); - return applicationPageService.createApplication(application); + return userSignupHelper + .createWorkspaceIfNotExistsAndGetId(defaultWorkspaceId, authentication) + .flatMap(userSignupHelper::createDefaultApplication); } /** diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/UserSignupHelper.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/UserSignupHelper.java new file mode 100644 index 0000000000..2a9e050156 --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/UserSignupHelper.java @@ -0,0 +1,30 @@ +package com.appsmith.server.helpers; + +import com.appsmith.server.helpers.ce.UserSignupHelperCE; +import com.appsmith.server.repositories.UserRepository; +import com.appsmith.server.repositories.WorkspaceRepository; +import com.appsmith.server.services.ApplicationPageService; +import com.appsmith.server.services.OrganizationService; +import com.appsmith.server.services.WorkspaceService; +import com.appsmith.server.solutions.WorkspacePermission; +import org.springframework.stereotype.Component; + +@Component +public class UserSignupHelper extends UserSignupHelperCE { + + public UserSignupHelper( + WorkspaceRepository workspaceRepository, + WorkspaceService workspaceService, + ApplicationPageService applicationPageService, + UserRepository userRepository, + OrganizationService organizationService, + WorkspacePermission workspacePermission) { + super( + workspaceRepository, + workspaceService, + applicationPageService, + userRepository, + organizationService, + workspacePermission); + } +} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/ce/UserSignupHelperCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/ce/UserSignupHelperCE.java new file mode 100644 index 0000000000..6d9364c8dd --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/ce/UserSignupHelperCE.java @@ -0,0 +1,122 @@ +package com.appsmith.server.helpers.ce; + +import com.appsmith.server.domains.Application; +import com.appsmith.server.domains.User; +import com.appsmith.server.domains.Workspace; +import com.appsmith.server.repositories.UserRepository; +import com.appsmith.server.repositories.WorkspaceRepository; +import com.appsmith.server.services.ApplicationPageService; +import com.appsmith.server.services.OrganizationService; +import com.appsmith.server.services.WorkspaceService; +import com.appsmith.server.solutions.WorkspacePermission; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.Authentication; +import reactor.core.publisher.Mono; + +@Slf4j +public class UserSignupHelperCE { + private final WorkspaceRepository workspaceRepository; + private final WorkspaceService workspaceService; + private final ApplicationPageService applicationPageService; + private final UserRepository userRepository; + private final OrganizationService organizationService; + private final WorkspacePermission workspacePermission; + + public UserSignupHelperCE( + WorkspaceRepository workspaceRepository, + WorkspaceService workspaceService, + ApplicationPageService applicationPageService, + UserRepository userRepository, + OrganizationService organizationService, + WorkspacePermission workspacePermission) { + this.workspaceRepository = workspaceRepository; + this.workspaceService = workspaceService; + this.applicationPageService = applicationPageService; + this.userRepository = userRepository; + this.organizationService = organizationService; + this.workspacePermission = workspacePermission; + } + + /** + * Creates a default workspace and application for a user. + * Basic CE implementation that creates a simple workspace and application. + * + * @param user The user for whom to create the workspace and application + * @return A Mono that completes when the workspace and application are created + */ + public Mono createDefaultWorkspaceAndApplication(User user) { + log.debug("Creating default workspace and application for user: {}", user.getEmail()); + + // Create a default workspace + Workspace workspace = new Workspace(); + workspace.setName(user.getName() != null ? user.getName() + "'s workspace" : "Default workspace"); + + return workspaceService + .create(workspace, user, Boolean.FALSE) + .flatMap(createdWorkspace -> + createDefaultApplication(createdWorkspace.getId()).then()) + .doOnError(error -> log.error("Error creating workspace or application: {}", error.getMessage())) + .then(); + } + + /** + * Creates a default application in the specified workspace. + * + * @param workspaceId ID of the workspace to create the application in + * @return A Mono containing the created Application + */ + public Mono createDefaultApplication(String workspaceId) { + log.debug("Creating default application in workspace: {}", workspaceId); + Application application = new Application(); + application.setWorkspaceId(workspaceId); + application.setName("My first application"); + return applicationPageService.createApplication(application); + } + + /** + * Gets an existing workspace ID or creates a new workspace if needed. + * + * @param defaultWorkspaceId The workspace ID to use if provided + * @param authentication The authentication object containing the user principal + * @return A Mono containing the workspace ID to use + */ + public Mono createWorkspaceIfNotExistsAndGetId(String defaultWorkspaceId, Authentication authentication) { + if (defaultWorkspaceId != null) { + return Mono.just(defaultWorkspaceId); + } + + return workspaceRepository + .findAll(workspacePermission.getEditPermission()) + .take(1, true) + .collectList() + .flatMap(workspaces -> { + // Since this is the first application creation, the first workspace would be the only + // workspace user has access to, and would be user's default workspace. Hence, we use this + // workspace to create the application. + if (workspaces.size() == 1) { + return Mono.just(workspaces.get(0)); + } + + // In case no workspaces are found for the user, create a new default workspace + User user = (User) authentication.getPrincipal(); + + // Use the protected method that can be overridden in EE version + return createDefaultWorkspaceForUser(user); + }) + .map(Workspace::getId); + } + + /** + * Creates a default workspace for a user. This method can be overridden in the EE version + * to add additional checks like multi-org feature flag. + * + * @param user User for whom to create the workspace + * @return Mono containing the created workspace + */ + public Mono createDefaultWorkspaceForUser(User user) { + return organizationService + .getCurrentUserOrganizationId() + .flatMap(orgId -> userRepository.findByEmailAndOrganizationId(user.getEmail(), orgId)) + .flatMap(user1 -> workspaceService.createDefault(new Workspace(), user1)); + } +} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/UserServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/UserServiceCEImpl.java index 26ebe51ac7..6c96296822 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/UserServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/UserServiceCEImpl.java @@ -445,6 +445,18 @@ public class UserServiceCEImpl extends BaseService return Mono.just(TRUE); } + /** + * Checks if a workspace should be created for a user during signup. + * This method can be overridden in EE to add checks for multi-org settings. + * + * @param user The user for whom to check workspace creation + * @return Mono true if workspace should be created, false otherwise + */ + protected Mono shouldCreateWorkspaceForUser(User user) { + // In CE, always create workspace + return Mono.just(TRUE); + } + @Override public Mono createUser(User user) { // Only encode the password if it's a form signup. For OAuth signups, we don't need password @@ -491,29 +503,39 @@ public class UserServiceCEImpl extends BaseService final UserSignupDTO userSignupDTO = new UserSignupDTO(); userSignupDTO.setUser(savedUser); - return workspaceService - .createDefault(new Workspace(), savedUser) - .elapsed() - .map(pair -> { - log.debug( - "UserServiceCEImpl::Time taken to create default workspace: {} ms", - pair.getT1()); - return pair.getT2(); - }) - .map(workspace -> { - log.debug( - "Created blank default workspace for user '{}'.", - savedUser.getEmail()); - userSignupDTO.setDefaultWorkspaceId(workspace.getId()); - return userSignupDTO; - }) - .onErrorResume(e -> { - log.debug( - "Error creating default workspace for user '{}'.", - savedUser.getEmail(), - e); - return Mono.just(userSignupDTO); - }); + // Check if we should create a workspace for this user + return shouldCreateWorkspaceForUser(savedUser).flatMap(shouldCreateWorkspace -> { + if (Boolean.TRUE.equals(shouldCreateWorkspace)) { + // Create workspace as normal + return workspaceService + .createDefault(new Workspace(), savedUser) + .elapsed() + .map(pair -> { + log.debug( + "UserServiceCEImpl::Time taken to create default workspace: {} ms", + pair.getT1()); + return pair.getT2(); + }) + .map(workspace -> { + log.debug( + "Created blank default workspace for user '{}'.", + savedUser.getEmail()); + userSignupDTO.setDefaultWorkspaceId(workspace.getId()); + return userSignupDTO; + }) + .onErrorResume(e -> { + log.debug( + "Error creating default workspace for user '{}'.", + savedUser.getEmail(), + e); + return Mono.just(userSignupDTO); + }); + } else { + // Skip workspace creation + log.debug("Skipping workspace creation for user: {}", savedUser.getEmail()); + return Mono.just(userSignupDTO); + } + }); }) .flatMap(userSignupDTO -> findByEmail( userSignupDTO.getUser().getEmail())