diff --git a/src/main/java/cc/pulseapp/api/common/StringUtils.java b/src/main/java/cc/pulseapp/api/common/StringUtils.java index 18df966..b54f0b7 100644 --- a/src/main/java/cc/pulseapp/api/common/StringUtils.java +++ b/src/main/java/cc/pulseapp/api/common/StringUtils.java @@ -7,7 +7,6 @@ import lombok.NonNull; import lombok.experimental.UtilityClass; import java.security.SecureRandom; -import java.util.regex.Pattern; /** * @author Braydon diff --git a/src/main/java/cc/pulseapp/api/controller/v1/AuthController.java b/src/main/java/cc/pulseapp/api/controller/v1/AuthController.java index 13beba5..af5f30b 100644 --- a/src/main/java/cc/pulseapp/api/controller/v1/AuthController.java +++ b/src/main/java/cc/pulseapp/api/controller/v1/AuthController.java @@ -1,6 +1,7 @@ package cc.pulseapp.api.controller.v1; import cc.pulseapp.api.exception.impl.BadRequestException; +import cc.pulseapp.api.model.user.User; import cc.pulseapp.api.model.user.input.UserLoginInput; import cc.pulseapp.api.model.user.input.UserRegistrationInput; import cc.pulseapp.api.model.user.response.UserAuthResponse; @@ -16,8 +17,8 @@ import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; /** - * This controller is responsible for - * handling user authentication requests. + * This controller is responsible for handling + * {@link User} authentication requests. * * @author Braydon */ diff --git a/src/main/java/cc/pulseapp/api/controller/v1/OrganizationController.java b/src/main/java/cc/pulseapp/api/controller/v1/OrganizationController.java new file mode 100644 index 0000000..69d7192 --- /dev/null +++ b/src/main/java/cc/pulseapp/api/controller/v1/OrganizationController.java @@ -0,0 +1,44 @@ +package cc.pulseapp.api.controller.v1; + +import cc.pulseapp.api.model.org.Organization; +import cc.pulseapp.api.model.org.response.OrganizationResponse; +import cc.pulseapp.api.service.OrganizationService; +import lombok.NonNull; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +/** + * This controller is responsible for + * handling {@link Organization} requests. + * + * @author Braydon + */ +@RestController +@RequestMapping(value = "/v1/organization", produces = MediaType.APPLICATION_JSON_VALUE) +public final class OrganizationController { + /** + * The organization service to use. + */ + @NonNull private final OrganizationService orgService; + + @Autowired + public OrganizationController(@NonNull OrganizationService orgService) { + this.orgService = orgService; + } + + /** + * A GET endpoint to get the + * organizations of a user. + * + * @return the organizations + */ + @GetMapping("/@me") @ResponseBody @NonNull + public ResponseEntity getOrganizations() { + return ResponseEntity.ok(orgService.getOrganizations()); + } +} \ No newline at end of file diff --git a/src/main/java/cc/pulseapp/api/controller/v1/UserController.java b/src/main/java/cc/pulseapp/api/controller/v1/UserController.java index aa8c5db..fa241a9 100644 --- a/src/main/java/cc/pulseapp/api/controller/v1/UserController.java +++ b/src/main/java/cc/pulseapp/api/controller/v1/UserController.java @@ -1,6 +1,7 @@ package cc.pulseapp.api.controller.v1; import cc.pulseapp.api.exception.impl.BadRequestException; +import cc.pulseapp.api.model.user.User; import cc.pulseapp.api.model.user.UserDTO; import cc.pulseapp.api.model.user.input.CompleteOnboardingInput; import cc.pulseapp.api.service.UserService; @@ -14,7 +15,7 @@ import java.util.Map; /** * This controller is responsible for - * handling user authentication requests. + * handling {@link User} related requests. * * @author Braydon */ @@ -42,11 +43,26 @@ public final class UserController { return ResponseEntity.ok(userService.getUser()); } + /** + * A GET endpoint to check if a + * user exists with the given email. + * + * @param email the email to check + * @return the response + */ @GetMapping("/exists") @ResponseBody @NonNull public ResponseEntity> doesUserExist(@RequestParam @NonNull String email) { return ResponseEntity.ok(Map.of("exists", userService.doesUserExist(email))); } + /** + * A POST endpoint to complete + * the onboarding process. + * + * @param input the completion input + * @return the response + * @throws BadRequestException if the completion fails + */ @PostMapping("/complete-onboarding") @ResponseBody @NonNull public ResponseEntity> completeOnboarding(CompleteOnboardingInput input) throws BadRequestException { userService.completeOnboarding(input); 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 115d7f0..cd3e6c7 100644 --- a/src/main/java/cc/pulseapp/api/model/org/Organization.java +++ b/src/main/java/cc/pulseapp/api/model/org/Organization.java @@ -16,7 +16,7 @@ import java.util.regex.Pattern; @AllArgsConstructor @Getter @EqualsAndHashCode(onlyExplicitlyIncluded = true) @ToString @Document("organizations") -public final class Organization { +public class Organization { public static final Pattern SLUG_PATTERN = Pattern.compile("^[a-z0-9]+(?:-[a-z0-9]+)*$"); /** diff --git a/src/main/java/cc/pulseapp/api/model/org/response/OrganizationResponse.java b/src/main/java/cc/pulseapp/api/model/org/response/OrganizationResponse.java new file mode 100644 index 0000000..dc725e2 --- /dev/null +++ b/src/main/java/cc/pulseapp/api/model/org/response/OrganizationResponse.java @@ -0,0 +1,40 @@ +package cc.pulseapp.api.model.org.response; + +import cc.pulseapp.api.model.page.StatusPage; +import cc.pulseapp.api.model.user.User; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NonNull; +import lombok.ToString; + +import java.util.List; + +/** + * The response to return when fetching + * a {@link User}'s {@link Organization}'s. + * + * @author Braydon + */ +@AllArgsConstructor @Getter @ToString +public final class OrganizationResponse { + /** + * The organizations in this response. + */ + @NonNull private final List organizations; + + /** + * An organization wrapper that includes the owned status pages. + */ + @Getter + public static class Organization extends cc.pulseapp.api.model.org.Organization { + /** + * The status pages owned by this organization. + */ + @NonNull private final List statusPages; + + public Organization(@NonNull cc.pulseapp.api.model.org.Organization origin, @NonNull List statusPages) { + super(origin.getSnowflake(), origin.getName(), origin.getSlug(), origin.getOwnerSnowflake()); + this.statusPages = statusPages; + } + } +} \ No newline at end of file diff --git a/src/main/java/cc/pulseapp/api/repository/OrganizationRepository.java b/src/main/java/cc/pulseapp/api/repository/OrganizationRepository.java index d0e0e7e..907c277 100644 --- a/src/main/java/cc/pulseapp/api/repository/OrganizationRepository.java +++ b/src/main/java/cc/pulseapp/api/repository/OrganizationRepository.java @@ -4,6 +4,8 @@ import cc.pulseapp.api.model.org.Organization; import lombok.NonNull; import org.springframework.data.mongodb.repository.MongoRepository; +import java.util.List; + /** * The repository for interacting with {@link Organization}'s. * @@ -17,4 +19,13 @@ public interface OrganizationRepository extends MongoRepository findByOwnerSnowflake(long ownerSnowflake); } \ No newline at end of file diff --git a/src/main/java/cc/pulseapp/api/repository/StatusPageRepository.java b/src/main/java/cc/pulseapp/api/repository/StatusPageRepository.java index 8ec3ecf..0e5ce62 100644 --- a/src/main/java/cc/pulseapp/api/repository/StatusPageRepository.java +++ b/src/main/java/cc/pulseapp/api/repository/StatusPageRepository.java @@ -4,6 +4,8 @@ import cc.pulseapp.api.model.page.StatusPage; import lombok.NonNull; import org.springframework.data.mongodb.repository.MongoRepository; +import java.util.List; + /** * The repository for interacting with {@link StatusPage}'s. * @@ -17,4 +19,13 @@ public interface StatusPageRepository extends MongoRepository * @return the status page with the name */ StatusPage findByNameIgnoreCase(@NonNull String name); + + /** + * Find the status pages that are + * owned by the given organization. + * + * @param orgSnowflake the org snowflake + * @return the list of status pages + */ + List findByOrgSnowflake(long orgSnowflake); } \ 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 f7d200d..f7ed7f6 100644 --- a/src/main/java/cc/pulseapp/api/service/OrganizationService.java +++ b/src/main/java/cc/pulseapp/api/service/OrganizationService.java @@ -4,18 +4,28 @@ 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.org.response.OrganizationResponse; import cc.pulseapp.api.model.user.User; import cc.pulseapp.api.repository.OrganizationRepository; +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.ArrayList; +import java.util.List; + /** * @author Braydon */ @Service public final class OrganizationService { + /** + * The auth service to use for retrieving the user. + */ + @NonNull private final AuthService authService; + /** * The service to use for snowflake generation. */ @@ -26,10 +36,18 @@ public final class OrganizationService { */ @NonNull private final OrganizationRepository orgRepository; + /** + * The repository to retrieve status pages from. + */ + @Nonnull private final StatusPageRepository statusPageRepository; + @Autowired - public OrganizationService(@NonNull SnowflakeService snowflakeService, @NonNull OrganizationRepository orgRepository) { + public OrganizationService(@NonNull AuthService authService, @NonNull SnowflakeService snowflakeService, + @NonNull OrganizationRepository orgRepository, @Nonnull StatusPageRepository statusPageRepository) { + this.authService = authService; this.snowflakeService = snowflakeService; this.orgRepository = orgRepository; + this.statusPageRepository = statusPageRepository; } /** @@ -55,6 +73,16 @@ public final class OrganizationService { return orgRepository.save(new Organization(snowflakeService.generateSnowflake(), name, slug, owner.getSnowflake())); } + @NonNull + public OrganizationResponse getOrganizations() { + User user = authService.getAuthenticatedUser(); + List organizations = new ArrayList<>(); + for (Organization org : orgRepository.findByOwnerSnowflake(user.getSnowflake())) { + organizations.add(new OrganizationResponse.Organization(org, statusPageRepository.findByOrgSnowflake(org.getSnowflake()))); + } + return new OrganizationResponse(organizations); + } + /** * Organization errors. */