From a394ecc0e383c6040ace5114561c75eebce8edb0 Mon Sep 17 00:00:00 2001 From: Rainnny7 Date: Wed, 18 Sep 2024 19:16:54 -0400 Subject: [PATCH] add custom slugs to orgs --- .../cc/pulseapp/api/common/StringUtils.java | 19 ++++++++++++++----- .../pulseapp/api/model/org/Organization.java | 9 +++++++++ .../java/cc/pulseapp/api/model/user/User.java | 4 ++++ .../user/input/CompleteOnboardingInput.java | 6 ++++++ .../api/service/OrganizationService.java | 5 +++-- .../cc/pulseapp/api/service/UserService.java | 16 ++++++++++++++-- 6 files changed, 50 insertions(+), 9 deletions(-) diff --git a/src/main/java/cc/pulseapp/api/common/StringUtils.java b/src/main/java/cc/pulseapp/api/common/StringUtils.java index 43bb2a9..18df966 100644 --- a/src/main/java/cc/pulseapp/api/common/StringUtils.java +++ b/src/main/java/cc/pulseapp/api/common/StringUtils.java @@ -1,6 +1,8 @@ package cc.pulseapp.api.common; import cc.pulseapp.api.model.IGenericResponse; +import cc.pulseapp.api.model.org.Organization; +import cc.pulseapp.api.model.user.User; import lombok.NonNull; import lombok.experimental.UtilityClass; @@ -17,9 +19,6 @@ public final class StringUtils { private static final String SPECIAL_STRING = "!@#$%^&*()_+-=[]{}|;:,.<>?"; private static final SecureRandom RANDOM = new SecureRandom(); - private static final Pattern EMAIL_PATTERN = Pattern.compile("^[A-Za-z0-9+_.-]+@(.+)$"); - private static final Pattern USERNAME_PATTERN = Pattern.compile("^[a-z0-9_.]*$"); - /** * Check if the given email is valid. * @@ -27,7 +26,7 @@ public final class StringUtils { * @return whether the email is valid */ public static boolean isValidEmail(@NonNull String email) { - return !email.isBlank() && EMAIL_PATTERN.matcher(email).matches(); + return !email.isBlank() && User.EMAIL_PATTERN.matcher(email).matches(); } /** @@ -37,7 +36,17 @@ public final class StringUtils { * @return whether the username is valid */ public static boolean isValidUsername(@NonNull String username) { - return USERNAME_PATTERN.matcher(username).matches(); + return User.USERNAME_PATTERN.matcher(username).matches(); + } + + /** + * Check if the given org slug is valid. + * + * @param slug the slug to check + * @return whether the slug is valid + */ + public static boolean isValidOrgSlug(@NonNull String slug) { + return Organization.SLUG_PATTERN.matcher(slug).matches(); } /** diff --git a/src/main/java/cc/pulseapp/api/model/org/Organization.java b/src/main/java/cc/pulseapp/api/model/org/Organization.java index 6f69ca1..115d7f0 100644 --- a/src/main/java/cc/pulseapp/api/model/org/Organization.java +++ b/src/main/java/cc/pulseapp/api/model/org/Organization.java @@ -6,6 +6,8 @@ import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.index.Indexed; import org.springframework.data.mongodb.core.mapping.Document; +import java.util.regex.Pattern; + /** * An organization owned by a {@link User}. * @@ -15,6 +17,8 @@ import org.springframework.data.mongodb.core.mapping.Document; @EqualsAndHashCode(onlyExplicitlyIncluded = true) @ToString @Document("organizations") public final class Organization { + public static final Pattern SLUG_PATTERN = Pattern.compile("^[a-z0-9]+(?:-[a-z0-9]+)*$"); + /** * The snowflake id of this organization. */ @@ -25,6 +29,11 @@ public final class Organization { */ @Indexed @NonNull private final String name; + /** + * The slug of this organization. + */ + @Indexed @NonNull private final String slug; + /** * The snowflake of the {@link User} * that owns this organization. diff --git a/src/main/java/cc/pulseapp/api/model/user/User.java b/src/main/java/cc/pulseapp/api/model/user/User.java index 4a88567..4591a52 100644 --- a/src/main/java/cc/pulseapp/api/model/user/User.java +++ b/src/main/java/cc/pulseapp/api/model/user/User.java @@ -6,6 +6,7 @@ import org.springframework.data.mongodb.core.index.Indexed; import org.springframework.data.mongodb.core.mapping.Document; import java.util.Date; +import java.util.regex.Pattern; /** * @author Braydon @@ -14,6 +15,9 @@ import java.util.Date; @EqualsAndHashCode(onlyExplicitlyIncluded = true) @ToString @Document("users") public final class User { + public static final Pattern EMAIL_PATTERN = Pattern.compile("^[A-Za-z0-9+_.-]+@(.+)$"); + public static final Pattern USERNAME_PATTERN = Pattern.compile("^[a-z0-9_.]*$"); + /** * The snowflake id of this user. */ diff --git a/src/main/java/cc/pulseapp/api/model/user/input/CompleteOnboardingInput.java b/src/main/java/cc/pulseapp/api/model/user/input/CompleteOnboardingInput.java index 3758e8a..41fd7fc 100644 --- a/src/main/java/cc/pulseapp/api/model/user/input/CompleteOnboardingInput.java +++ b/src/main/java/cc/pulseapp/api/model/user/input/CompleteOnboardingInput.java @@ -19,6 +19,11 @@ public final class CompleteOnboardingInput { */ private final String organizationName; + /** + * The slug of the {@link Organization} to create. + */ + private final String organizationSlug; + /** * The name of the {@link StatusPage} to create. */ @@ -31,6 +36,7 @@ public final class CompleteOnboardingInput { */ public boolean isValid() { return organizationName != null && (!organizationName.isBlank()) + && organizationSlug != null && (!organizationSlug.isBlank()) && statusPageName != null && (!statusPageName.isBlank()); } } \ No newline at end of file diff --git a/src/main/java/cc/pulseapp/api/service/OrganizationService.java b/src/main/java/cc/pulseapp/api/service/OrganizationService.java index 47e56ec..f7d200d 100644 --- a/src/main/java/cc/pulseapp/api/service/OrganizationService.java +++ b/src/main/java/cc/pulseapp/api/service/OrganizationService.java @@ -36,12 +36,13 @@ public final class OrganizationService { * Create a new organization. * * @param name the org name + * @param slug the org slug * @param owner the owner of the org * @return the created org * @throws BadRequestException if the org creation fails */ @NonNull - public Organization createOrganization(@Nonnull String name, @NonNull User owner) throws BadRequestException { + public Organization createOrganization(@Nonnull String name, @Nonnull String slug, @NonNull User owner) throws BadRequestException { // Ensure org creation is enabled if (!Feature.ORG_CREATION_ENABLED.isEnabled()) { throw new BadRequestException(Error.ORG_CREATION_DISABLED); @@ -51,7 +52,7 @@ public final class OrganizationService { throw new BadRequestException(Error.ORG_NAME_TAKEN); } // Create the org and return it - return orgRepository.save(new Organization(snowflakeService.generateSnowflake(), name, owner.getSnowflake())); + return orgRepository.save(new Organization(snowflakeService.generateSnowflake(), name, slug, owner.getSnowflake())); } /** diff --git a/src/main/java/cc/pulseapp/api/service/UserService.java b/src/main/java/cc/pulseapp/api/service/UserService.java index 1026664..ca560b0 100644 --- a/src/main/java/cc/pulseapp/api/service/UserService.java +++ b/src/main/java/cc/pulseapp/api/service/UserService.java @@ -77,16 +77,27 @@ public final class UserService { return StringUtils.isValidEmail(email) && userRepository.findByEmailIgnoreCase(email) != null; } - public void completeOnboarding(CompleteOnboardingInput input) { + /** + * Complete the onboarding + * process for a user. + * + * @param input the input to process + * @throws BadRequestException if onboarding fails + */ + public void completeOnboarding(CompleteOnboardingInput input) throws BadRequestException { // Ensure the input was provided if (input == null || (!input.isValid())) { throw new BadRequestException(Error.MALFORMED_ONBOARDING_INPUT); } + // Ensure the org slug is valid + if (!StringUtils.isValidOrgSlug(input.getOrganizationSlug())) { + throw new BadRequestException(Error.ORGANIZATION_SLUG_INVALID); + } User user = authService.getAuthenticatedUser(); if (user.hasFlag(UserFlag.COMPLETED_ONBOARDING)) { // Already completed throw new BadRequestException(Error.ALREADY_ONBOARDED); } - Organization org = orgService.createOrganization(input.getOrganizationName(), user); // Create the org + Organization org = orgService.createOrganization(input.getOrganizationName(), input.getOrganizationSlug(), user); // Create the org statusPageService.createStatusPage(input.getStatusPageName(), org); // Create the status page user.addFlag(UserFlag.COMPLETED_ONBOARDING); // Flag completed onboarding userRepository.save(user); @@ -97,6 +108,7 @@ public final class UserService { */ private enum Error implements IGenericResponse { MALFORMED_ONBOARDING_INPUT, + ORGANIZATION_SLUG_INVALID, ALREADY_ONBOARDED, } } \ No newline at end of file