add custom slugs to orgs
All checks were successful
Deploy API / deploy (ubuntu-latest, 2.44.0) (push) Successful in 43s

This commit is contained in:
Braydon 2024-09-18 19:16:54 -04:00
parent a929a4ee48
commit a394ecc0e3
6 changed files with 50 additions and 9 deletions

@ -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();
}
/**

@ -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.

@ -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.
*/

@ -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());
}
}

@ -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()));
}
/**

@ -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,
}
}