From 58b7b69bb3d5cc763fb12f8d1685cb0ce852172e Mon Sep 17 00:00:00 2001 From: Rainnny7 Date: Fri, 20 Sep 2024 03:21:27 -0400 Subject: [PATCH] Change up the org backend a bit to support members --- .../api/model/org/DetailedOrganization.java | 2 +- .../pulseapp/api/model/org/Organization.java | 6 +++ .../api/model/org/OrganizationMember.java | 25 +++++++++ .../org/OrganizationMemberPermission.java | 53 +++++++++++++++++++ .../java/cc/pulseapp/api/model/user/User.java | 2 +- .../repository/OrganizationRepository.java | 19 +++++-- .../api/repository/UserRepository.java | 8 --- .../api/service/OrganizationService.java | 18 +++---- 8 files changed, 109 insertions(+), 24 deletions(-) create mode 100644 src/main/java/cc/pulseapp/api/model/org/OrganizationMember.java create mode 100644 src/main/java/cc/pulseapp/api/model/org/OrganizationMemberPermission.java diff --git a/src/main/java/cc/pulseapp/api/model/org/DetailedOrganization.java b/src/main/java/cc/pulseapp/api/model/org/DetailedOrganization.java index 4ba1388..dc9fb1b 100644 --- a/src/main/java/cc/pulseapp/api/model/org/DetailedOrganization.java +++ b/src/main/java/cc/pulseapp/api/model/org/DetailedOrganization.java @@ -21,7 +21,7 @@ public final class DetailedOrganization extends cc.pulseapp.api.model.org.Organi @NonNull private final List statusPages; public DetailedOrganization(@NonNull cc.pulseapp.api.model.org.Organization origin, @NonNull List statusPages) { - super(origin.getSnowflake(), origin.getName(), origin.getSlug(), origin.getLogo(), origin.getOwnerSnowflake()); + super(origin.getSnowflake(), origin.getName(), origin.getSlug(), origin.getLogo(), origin.getMembers(), origin.getOwnerSnowflake()); this.statusPages = statusPages; } } \ No newline at end of file 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 18caaca..c8cbeec 100644 --- a/src/main/java/cc/pulseapp/api/model/org/Organization.java +++ b/src/main/java/cc/pulseapp/api/model/org/Organization.java @@ -6,6 +6,7 @@ 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.List; import java.util.regex.Pattern; /** @@ -39,6 +40,11 @@ public class Organization { */ private final String logo; + /** + * The members of this organization. + */ + @NonNull private final List members; + /** * The snowflake of the {@link User} * that owns this organization. diff --git a/src/main/java/cc/pulseapp/api/model/org/OrganizationMember.java b/src/main/java/cc/pulseapp/api/model/org/OrganizationMember.java new file mode 100644 index 0000000..b2aa6ab --- /dev/null +++ b/src/main/java/cc/pulseapp/api/model/org/OrganizationMember.java @@ -0,0 +1,25 @@ +package cc.pulseapp.api.model.org; + +import cc.pulseapp.api.model.user.User; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.ToString; + +/** + * A member of an {@link Organization}. + * + * @author Braydon + */ +@AllArgsConstructor @Getter @ToString +public final class OrganizationMember { + /** + * The snowflake of the {@link User} + * this member belongs to. + */ + private final long userSnowflake; + + /** + * The bitwise permissions of this member. + */ + private final int permissions; +} \ No newline at end of file diff --git a/src/main/java/cc/pulseapp/api/model/org/OrganizationMemberPermission.java b/src/main/java/cc/pulseapp/api/model/org/OrganizationMemberPermission.java new file mode 100644 index 0000000..f20c6ca --- /dev/null +++ b/src/main/java/cc/pulseapp/api/model/org/OrganizationMemberPermission.java @@ -0,0 +1,53 @@ +package cc.pulseapp.api.model.org; + +import cc.pulseapp.api.model.page.StatusPage; + +/** + * The permissions of a {@link OrganizationMember}. + * + * @author Braydon + */ +public enum OrganizationMemberPermission { + /** + * The member can create, edit, and delete + * {@link StatusPage}s for the organization. + */ + MANAGE_STATUS_PAGES, + + /** + * The member can create, edit, and + * delete automations for the organization. + */ + MANAGE_AUTOMATIONS, + + /** + * The member can create, edit, and + * delete incidents for the organization. + */ + MANAGE_INCIDENTS, + + /** + * The member can view insights of the organization. + */ + VIEW_INSIGHTS, + + /** + * The member can view audit logs of the organization. + */ + VIEW_AUDIT_LOGS, + + /** + * The member can edit the organization. + */ + MANAGE_ORGANIZATION; + + /** + * Get the bitwise value + * of this permission. + * + * @return the bitwise value + */ + public int bitwise() { + return 1 << ordinal(); + } +} \ No newline at end of file diff --git a/src/main/java/cc/pulseapp/api/model/user/User.java b/src/main/java/cc/pulseapp/api/model/user/User.java index 420b1fc..bf961f4 100644 --- a/src/main/java/cc/pulseapp/api/model/user/User.java +++ b/src/main/java/cc/pulseapp/api/model/user/User.java @@ -60,7 +60,7 @@ public final class User { private TFAProfile tfa; /** - * The flags for this user. + * The bitwise flags for this user. */ private int flags; diff --git a/src/main/java/cc/pulseapp/api/repository/OrganizationRepository.java b/src/main/java/cc/pulseapp/api/repository/OrganizationRepository.java index 87322b2..6b40929 100644 --- a/src/main/java/cc/pulseapp/api/repository/OrganizationRepository.java +++ b/src/main/java/cc/pulseapp/api/repository/OrganizationRepository.java @@ -3,6 +3,7 @@ package cc.pulseapp.api.repository; import cc.pulseapp.api.model.org.Organization; import lombok.NonNull; import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.data.mongodb.repository.Query; import org.springframework.stereotype.Repository; import java.util.List; @@ -15,12 +16,12 @@ import java.util.List; @Repository public interface OrganizationRepository extends MongoRepository { /** - * Find an organization by its name. + * Get the organization that has the given slug. * - * @param name the name of the org - * @return the org with the name + * @param slug the organization slug + * @return the organization, null if none */ - Organization findByNameIgnoreCase(@NonNull String name); + Organization findBySlug(@NonNull String slug); /** * Get the organizations that @@ -30,4 +31,14 @@ public interface OrganizationRepository extends MongoRepository findByOwnerSnowflake(long ownerSnowflake); + + /** + * Get the organizations that the user + * either owns or is a member of. + * + * @param userSnowflake the user's snowflake + * @return the organizations the user has access to + */ + @Query("{ '$or': [ { 'ownerSnowflake': ?0 }, { 'members.userSnowflake': ?0 } ] }") + List findByUserAccess(long userSnowflake); } \ No newline at end of file diff --git a/src/main/java/cc/pulseapp/api/repository/UserRepository.java b/src/main/java/cc/pulseapp/api/repository/UserRepository.java index 53fcba4..568509a 100644 --- a/src/main/java/cc/pulseapp/api/repository/UserRepository.java +++ b/src/main/java/cc/pulseapp/api/repository/UserRepository.java @@ -19,12 +19,4 @@ public interface UserRepository extends MongoRepository { * @return the user with the email */ User findByEmailIgnoreCase(@NonNull String email); - - /** - * Find a user by their username. - * - * @param username the username of the user - * @return the user with the username - */ - User findByUsernameIgnoreCase(@NonNull String username); } \ 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 87e0788..5ae7504 100644 --- a/src/main/java/cc/pulseapp/api/service/OrganizationService.java +++ b/src/main/java/cc/pulseapp/api/service/OrganizationService.java @@ -15,6 +15,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** @@ -66,21 +67,18 @@ public final class OrganizationService { if (!Feature.ORG_CREATION_ENABLED.isEnabled()) { throw new BadRequestException(Error.ORG_CREATION_DISABLED); } - List ownedOrgs = orgRepository.findByOwnerSnowflake(owner.getSnowflake()); - - // Ensure the org name isn't taken - if (ownedOrgs.stream().anyMatch(org -> org.getName().equalsIgnoreCase(name))) { - throw new BadRequestException(Error.ORG_NAME_TAKEN); + // Ensure the org slug isn't taken + if (orgRepository.findBySlug(slug) != null) { + throw new BadRequestException(Error.ORG_SLUG_TAKEN); } // Handle cloud environment checks if (EnvironmentUtils.isCloud()) { - if (ownedOrgs.size() >= owner.getTier().getMaxOrganizations()) { + if (orgRepository.findByOwnerSnowflake(owner.getSnowflake()).size() >= owner.getTier().getMaxOrganizations()) { throw new BadRequestException(Error.MAX_ORGS_REACHED); } } // Create the org and return it - slug = slug.trim().replaceAll("-+$", ""); // Trim slug trailing dashes - return orgRepository.save(new Organization(snowflakeService.generateSnowflake(), name, slug, null, owner.getSnowflake())); + return orgRepository.save(new Organization(snowflakeService.generateSnowflake(), name, slug, null, Collections.emptyList(), owner.getSnowflake())); } /** @@ -93,7 +91,7 @@ public final class OrganizationService { public List getOrganizations() { User user = authService.getAuthenticatedUser(); List organizations = new ArrayList<>(); - for (Organization org : orgRepository.findByOwnerSnowflake(user.getSnowflake())) { + for (Organization org : orgRepository.findByUserAccess(user.getSnowflake())) { organizations.add(new DetailedOrganization(org, statusPageRepository.findByOrgSnowflake(org.getSnowflake()))); } return organizations; @@ -104,7 +102,7 @@ public final class OrganizationService { */ private enum Error implements IGenericResponse { ORG_CREATION_DISABLED, - ORG_NAME_TAKEN, + ORG_SLUG_TAKEN, MAX_ORGS_REACHED } } \ No newline at end of file