organization controller
All checks were successful
Deploy API / deploy (ubuntu-latest, 2.44.0) (push) Successful in 44s

This commit is contained in:
Braydon 2024-09-18 20:34:35 -04:00
parent a394ecc0e3
commit b458e24f29
9 changed files with 156 additions and 6 deletions

@ -7,7 +7,6 @@ import lombok.NonNull;
import lombok.experimental.UtilityClass; import lombok.experimental.UtilityClass;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.regex.Pattern;
/** /**
* @author Braydon * @author Braydon

@ -1,6 +1,7 @@
package cc.pulseapp.api.controller.v1; package cc.pulseapp.api.controller.v1;
import cc.pulseapp.api.exception.impl.BadRequestException; 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.UserLoginInput;
import cc.pulseapp.api.model.user.input.UserRegistrationInput; import cc.pulseapp.api.model.user.input.UserRegistrationInput;
import cc.pulseapp.api.model.user.response.UserAuthResponse; 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; import org.springframework.web.bind.annotation.RestController;
/** /**
* This controller is responsible for * This controller is responsible for handling
* handling user authentication requests. * {@link User} authentication requests.
* *
* @author Braydon * @author Braydon
*/ */

@ -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<OrganizationResponse> getOrganizations() {
return ResponseEntity.ok(orgService.getOrganizations());
}
}

@ -1,6 +1,7 @@
package cc.pulseapp.api.controller.v1; package cc.pulseapp.api.controller.v1;
import cc.pulseapp.api.exception.impl.BadRequestException; 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.UserDTO;
import cc.pulseapp.api.model.user.input.CompleteOnboardingInput; import cc.pulseapp.api.model.user.input.CompleteOnboardingInput;
import cc.pulseapp.api.service.UserService; import cc.pulseapp.api.service.UserService;
@ -14,7 +15,7 @@ import java.util.Map;
/** /**
* This controller is responsible for * This controller is responsible for
* handling user authentication requests. * handling {@link User} related requests.
* *
* @author Braydon * @author Braydon
*/ */
@ -42,11 +43,26 @@ public final class UserController {
return ResponseEntity.ok(userService.getUser()); 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 @GetMapping("/exists") @ResponseBody @NonNull
public ResponseEntity<Map<String, Object>> doesUserExist(@RequestParam @NonNull String email) { public ResponseEntity<Map<String, Object>> doesUserExist(@RequestParam @NonNull String email) {
return ResponseEntity.ok(Map.of("exists", userService.doesUserExist(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 @PostMapping("/complete-onboarding") @ResponseBody @NonNull
public ResponseEntity<Map<String, Object>> completeOnboarding(CompleteOnboardingInput input) throws BadRequestException { public ResponseEntity<Map<String, Object>> completeOnboarding(CompleteOnboardingInput input) throws BadRequestException {
userService.completeOnboarding(input); userService.completeOnboarding(input);

@ -16,7 +16,7 @@ import java.util.regex.Pattern;
@AllArgsConstructor @Getter @AllArgsConstructor @Getter
@EqualsAndHashCode(onlyExplicitlyIncluded = true) @ToString @EqualsAndHashCode(onlyExplicitlyIncluded = true) @ToString
@Document("organizations") @Document("organizations")
public final class Organization { public class Organization {
public static final Pattern SLUG_PATTERN = Pattern.compile("^[a-z0-9]+(?:-[a-z0-9]+)*$"); public static final Pattern SLUG_PATTERN = Pattern.compile("^[a-z0-9]+(?:-[a-z0-9]+)*$");
/** /**

@ -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<Organization> 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<StatusPage> statusPages;
public Organization(@NonNull cc.pulseapp.api.model.org.Organization origin, @NonNull List<StatusPage> statusPages) {
super(origin.getSnowflake(), origin.getName(), origin.getSlug(), origin.getOwnerSnowflake());
this.statusPages = statusPages;
}
}
}

@ -4,6 +4,8 @@ import cc.pulseapp.api.model.org.Organization;
import lombok.NonNull; import lombok.NonNull;
import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.data.mongodb.repository.MongoRepository;
import java.util.List;
/** /**
* The repository for interacting with {@link Organization}'s. * The repository for interacting with {@link Organization}'s.
* *
@ -17,4 +19,13 @@ public interface OrganizationRepository extends MongoRepository<Organization, Lo
* @return the org with the name * @return the org with the name
*/ */
Organization findByNameIgnoreCase(@NonNull String name); Organization findByNameIgnoreCase(@NonNull String name);
/**
* Get the organizations that
* are owned by the given user.
*
* @param ownerSnowflake the user snowflake
* @return the owned organizations
*/
List<Organization> findByOwnerSnowflake(long ownerSnowflake);
} }

@ -4,6 +4,8 @@ import cc.pulseapp.api.model.page.StatusPage;
import lombok.NonNull; import lombok.NonNull;
import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.data.mongodb.repository.MongoRepository;
import java.util.List;
/** /**
* The repository for interacting with {@link StatusPage}'s. * The repository for interacting with {@link StatusPage}'s.
* *
@ -17,4 +19,13 @@ public interface StatusPageRepository extends MongoRepository<StatusPage, Long>
* @return the status page with the name * @return the status page with the name
*/ */
StatusPage findByNameIgnoreCase(@NonNull String 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<StatusPage> findByOrgSnowflake(long orgSnowflake);
} }

@ -4,18 +4,28 @@ import cc.pulseapp.api.exception.impl.BadRequestException;
import cc.pulseapp.api.model.Feature; import cc.pulseapp.api.model.Feature;
import cc.pulseapp.api.model.IGenericResponse; import cc.pulseapp.api.model.IGenericResponse;
import cc.pulseapp.api.model.org.Organization; 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.model.user.User;
import cc.pulseapp.api.repository.OrganizationRepository; import cc.pulseapp.api.repository.OrganizationRepository;
import cc.pulseapp.api.repository.StatusPageRepository;
import jakarta.annotation.Nonnull; import jakarta.annotation.Nonnull;
import lombok.NonNull; import lombok.NonNull;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
/** /**
* @author Braydon * @author Braydon
*/ */
@Service @Service
public final class OrganizationService { 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. * The service to use for snowflake generation.
*/ */
@ -26,10 +36,18 @@ public final class OrganizationService {
*/ */
@NonNull private final OrganizationRepository orgRepository; @NonNull private final OrganizationRepository orgRepository;
/**
* The repository to retrieve status pages from.
*/
@Nonnull private final StatusPageRepository statusPageRepository;
@Autowired @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.snowflakeService = snowflakeService;
this.orgRepository = orgRepository; 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())); return orgRepository.save(new Organization(snowflakeService.generateSnowflake(), name, slug, owner.getSnowflake()));
} }
@NonNull
public OrganizationResponse getOrganizations() {
User user = authService.getAuthenticatedUser();
List<OrganizationResponse.Organization> 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. * Organization errors.
*/ */