From 22ff1a2efb6b1e7d7c3bfe2dd661147115bed1c2 Mon Sep 17 00:00:00 2001 From: Rainnny7 Date: Wed, 18 Sep 2024 00:33:50 -0400 Subject: [PATCH] status page creation for onboarding --- .../java/cc/pulseapp/api/model/Feature.java | 3 +- .../api/repository/StatusPageRepository.java | 20 ++++++ .../cc/pulseapp/api/service/AuthService.java | 2 +- .../api/service/OrganizationService.java | 4 +- .../api/service/StatusPageService.java | 72 +++++++++++++++++++ .../cc/pulseapp/api/service/UserService.java | 13 +++- 6 files changed, 109 insertions(+), 5 deletions(-) create mode 100644 src/main/java/cc/pulseapp/api/repository/StatusPageRepository.java create mode 100644 src/main/java/cc/pulseapp/api/service/StatusPageService.java diff --git a/src/main/java/cc/pulseapp/api/model/Feature.java b/src/main/java/cc/pulseapp/api/model/Feature.java index ea91a1a..783a24e 100644 --- a/src/main/java/cc/pulseapp/api/model/Feature.java +++ b/src/main/java/cc/pulseapp/api/model/Feature.java @@ -10,7 +10,8 @@ import lombok.*; @RequiredArgsConstructor @Getter @ToString public enum Feature { USER_REGISTRATION_ENABLED("user-registration"), - ORG_CREATION_ENABLED("org-creation"); + ORG_CREATION_ENABLED("org-creation"), + STATUS_PAGE_CREATION_ENABLED("status-page-creation"); public static final Feature[] VALUES = values(); diff --git a/src/main/java/cc/pulseapp/api/repository/StatusPageRepository.java b/src/main/java/cc/pulseapp/api/repository/StatusPageRepository.java new file mode 100644 index 0000000..8ec3ecf --- /dev/null +++ b/src/main/java/cc/pulseapp/api/repository/StatusPageRepository.java @@ -0,0 +1,20 @@ +package cc.pulseapp.api.repository; + +import cc.pulseapp.api.model.page.StatusPage; +import lombok.NonNull; +import org.springframework.data.mongodb.repository.MongoRepository; + +/** + * The repository for interacting with {@link StatusPage}'s. + * + * @author Braydon + */ +public interface StatusPageRepository extends MongoRepository { + /** + * Find a status page by its name. + * + * @param name the name of the status page + * @return the status page with the name + */ + StatusPage findByNameIgnoreCase(@NonNull String name); +} \ No newline at end of file diff --git a/src/main/java/cc/pulseapp/api/service/AuthService.java b/src/main/java/cc/pulseapp/api/service/AuthService.java index 35ce6ce..8406500 100644 --- a/src/main/java/cc/pulseapp/api/service/AuthService.java +++ b/src/main/java/cc/pulseapp/api/service/AuthService.java @@ -84,7 +84,7 @@ public final class AuthService { byte[] salt = HashUtils.generateSalt(); Date now = new Date(); return generateSession(request, userRepository.save(new User( - snowflakeService.generateSnowflake(), input.getEmail(), input.getUsername(), + snowflakeService.generateSnowflake(), input.getEmail(), input.getUsername().toLowerCase(), HashUtils.hash(salt, input.getPassword()), Base64.getEncoder().encodeToString(salt), null, UserTier.FREE, 0, now ))); diff --git a/src/main/java/cc/pulseapp/api/service/OrganizationService.java b/src/main/java/cc/pulseapp/api/service/OrganizationService.java index 02e31e6..47e56ec 100644 --- a/src/main/java/cc/pulseapp/api/service/OrganizationService.java +++ b/src/main/java/cc/pulseapp/api/service/OrganizationService.java @@ -8,6 +8,7 @@ import cc.pulseapp.api.model.user.User; import cc.pulseapp.api.repository.OrganizationRepository; import jakarta.annotation.Nonnull; import lombok.NonNull; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; /** @@ -25,7 +26,8 @@ public final class OrganizationService { */ @NonNull private final OrganizationRepository orgRepository; - private OrganizationService(@NonNull SnowflakeService snowflakeService, @NonNull OrganizationRepository orgRepository) { + @Autowired + public OrganizationService(@NonNull SnowflakeService snowflakeService, @NonNull OrganizationRepository orgRepository) { this.snowflakeService = snowflakeService; this.orgRepository = orgRepository; } diff --git a/src/main/java/cc/pulseapp/api/service/StatusPageService.java b/src/main/java/cc/pulseapp/api/service/StatusPageService.java new file mode 100644 index 0000000..86b2c8c --- /dev/null +++ b/src/main/java/cc/pulseapp/api/service/StatusPageService.java @@ -0,0 +1,72 @@ +package cc.pulseapp.api.service; + +import cc.pulseapp.api.exception.impl.BadRequestException; +import cc.pulseapp.api.model.Feature; +import cc.pulseapp.api.model.IGenericResponse; +import cc.pulseapp.api.model.org.Organization; +import cc.pulseapp.api.model.page.StatusPage; +import cc.pulseapp.api.model.page.StatusPageTheme; +import cc.pulseapp.api.repository.StatusPageRepository; +import jakarta.annotation.Nonnull; +import lombok.NonNull; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.concurrent.ThreadLocalRandom; + +/** + * @author Braydon + */ +@Service +public final class StatusPageService { + /** + * The service to use for snowflake generation. + */ + @NonNull private final SnowflakeService snowflakeService; + + /** + * The repository to store and retrieve status pages. + */ + @NonNull private final StatusPageRepository pageRepository; + + @Autowired + public StatusPageService(@NonNull SnowflakeService snowflakeService, @NonNull StatusPageRepository pageRepository) { + this.snowflakeService = snowflakeService; + this.pageRepository = pageRepository; + } + + /** + * Create a new status page. + * + * @param name the status page name + * @param owner the owner of the status page + * @return the created status page + * @throws BadRequestException if the status page creation fails + */ + @NonNull + public StatusPage createStatusPage(@Nonnull String name, @NonNull Organization owner) throws BadRequestException { + // Ensure status page creation is enabled + if (!Feature.STATUS_PAGE_CREATION_ENABLED.isEnabled()) { + throw new BadRequestException(Error.STATUS_PAGE_CREATION_DISABLED); + } + // Ensure the status page name isn't taken + if (pageRepository.findByNameIgnoreCase(name) != null) { + throw new BadRequestException(Error.STATUS_PAGE_NAME_TAKEN); + } + // Create the status page and return it + String slug = name.replace(" ", "-") + + "-" + ThreadLocalRandom.current().nextInt(10000, 99999); + return pageRepository.save(new StatusPage( + snowflakeService.generateSnowflake(), name, null, slug, null, + null, StatusPageTheme.AUTO, true, owner.getSnowflake()) + ); + } + + /** + * Organization errors. + */ + private enum Error implements IGenericResponse { + STATUS_PAGE_CREATION_DISABLED, + STATUS_PAGE_NAME_TAKEN + } +} \ No newline at end of file diff --git a/src/main/java/cc/pulseapp/api/service/UserService.java b/src/main/java/cc/pulseapp/api/service/UserService.java index dce5ac7..1026664 100644 --- a/src/main/java/cc/pulseapp/api/service/UserService.java +++ b/src/main/java/cc/pulseapp/api/service/UserService.java @@ -3,6 +3,7 @@ package cc.pulseapp.api.service; import cc.pulseapp.api.common.StringUtils; import cc.pulseapp.api.exception.impl.BadRequestException; import cc.pulseapp.api.model.IGenericResponse; +import cc.pulseapp.api.model.org.Organization; import cc.pulseapp.api.model.user.User; import cc.pulseapp.api.model.user.UserDTO; import cc.pulseapp.api.model.user.UserFlag; @@ -34,6 +35,11 @@ public final class UserService { */ @NonNull private final OrganizationService orgService; + /** + * The status page service to use. + */ + @NonNull private final StatusPageService statusPageService; + /** * The user repository to use. */ @@ -41,10 +47,12 @@ public final class UserService { @Autowired public UserService(@NonNull AuthService authService, @NonNull SnowflakeService snowflakeService, - @NonNull OrganizationService orgService, @NonNull UserRepository userRepository) { + @NonNull OrganizationService orgService, @NonNull StatusPageService statusPageService, + @NonNull UserRepository userRepository) { this.authService = authService; this.snowflakeService = snowflakeService; this.orgService = orgService; + this.statusPageService = statusPageService; this.userRepository = userRepository; } @@ -78,7 +86,8 @@ public final class UserService { if (user.hasFlag(UserFlag.COMPLETED_ONBOARDING)) { // Already completed throw new BadRequestException(Error.ALREADY_ONBOARDED); } - orgService.createOrganization(input.getOrganizationName(), user); // Create the org + Organization org = orgService.createOrganization(input.getOrganizationName(), user); // Create the org + statusPageService.createStatusPage(input.getStatusPageName(), org); // Create the status page user.addFlag(UserFlag.COMPLETED_ONBOARDING); // Flag completed onboarding userRepository.save(user); }