status page creation for onboarding

This commit is contained in:
Braydon 2024-09-18 00:33:50 -04:00
parent 1bd167d0ec
commit 22ff1a2efb
6 changed files with 109 additions and 5 deletions

@ -10,7 +10,8 @@ import lombok.*;
@RequiredArgsConstructor @Getter @ToString @RequiredArgsConstructor @Getter @ToString
public enum Feature { public enum Feature {
USER_REGISTRATION_ENABLED("user-registration"), 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(); public static final Feature[] VALUES = values();

@ -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<StatusPage, Long> {
/**
* 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);
}

@ -84,7 +84,7 @@ public final class AuthService {
byte[] salt = HashUtils.generateSalt(); byte[] salt = HashUtils.generateSalt();
Date now = new Date(); Date now = new Date();
return generateSession(request, userRepository.save(new User( 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), HashUtils.hash(salt, input.getPassword()), Base64.getEncoder().encodeToString(salt),
null, UserTier.FREE, 0, now null, UserTier.FREE, 0, now
))); )));

@ -8,6 +8,7 @@ import cc.pulseapp.api.model.user.User;
import cc.pulseapp.api.repository.OrganizationRepository; import cc.pulseapp.api.repository.OrganizationRepository;
import jakarta.annotation.Nonnull; import jakarta.annotation.Nonnull;
import lombok.NonNull; import lombok.NonNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
/** /**
@ -25,7 +26,8 @@ public final class OrganizationService {
*/ */
@NonNull private final OrganizationRepository orgRepository; @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.snowflakeService = snowflakeService;
this.orgRepository = orgRepository; this.orgRepository = orgRepository;
} }

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

@ -3,6 +3,7 @@ package cc.pulseapp.api.service;
import cc.pulseapp.api.common.StringUtils; import cc.pulseapp.api.common.StringUtils;
import cc.pulseapp.api.exception.impl.BadRequestException; import cc.pulseapp.api.exception.impl.BadRequestException;
import cc.pulseapp.api.model.IGenericResponse; 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.User;
import cc.pulseapp.api.model.user.UserDTO; import cc.pulseapp.api.model.user.UserDTO;
import cc.pulseapp.api.model.user.UserFlag; import cc.pulseapp.api.model.user.UserFlag;
@ -34,6 +35,11 @@ public final class UserService {
*/ */
@NonNull private final OrganizationService orgService; @NonNull private final OrganizationService orgService;
/**
* The status page service to use.
*/
@NonNull private final StatusPageService statusPageService;
/** /**
* The user repository to use. * The user repository to use.
*/ */
@ -41,10 +47,12 @@ public final class UserService {
@Autowired @Autowired
public UserService(@NonNull AuthService authService, @NonNull SnowflakeService snowflakeService, 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.authService = authService;
this.snowflakeService = snowflakeService; this.snowflakeService = snowflakeService;
this.orgService = orgService; this.orgService = orgService;
this.statusPageService = statusPageService;
this.userRepository = userRepository; this.userRepository = userRepository;
} }
@ -78,7 +86,8 @@ public final class UserService {
if (user.hasFlag(UserFlag.COMPLETED_ONBOARDING)) { // Already completed if (user.hasFlag(UserFlag.COMPLETED_ONBOARDING)) { // Already completed
throw new BadRequestException(Error.ALREADY_ONBOARDED); 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 user.addFlag(UserFlag.COMPLETED_ONBOARDING); // Flag completed onboarding
userRepository.save(user); userRepository.save(user);
} }