diff --git a/backend/src/main/java/net/beetechgroup/beetask/entities/organization/UserOrganization.java b/backend/src/main/java/net/beetechgroup/beetask/entities/organization/UserOrganization.java index 6120b60..dc9950d 100644 --- a/backend/src/main/java/net/beetechgroup/beetask/entities/organization/UserOrganization.java +++ b/backend/src/main/java/net/beetechgroup/beetask/entities/organization/UserOrganization.java @@ -6,6 +6,7 @@ public class UserOrganization { private User user; private Organization organization; private UserOrganizationRole role; + private UserOrganizationStatus status; public User getUser() { return user; @@ -30,4 +31,12 @@ public class UserOrganization { public void setRole(UserOrganizationRole role) { this.role = role; } + + public UserOrganizationStatus getStatus() { + return status; + } + + public void setStatus(UserOrganizationStatus status) { + this.status = status; + } } diff --git a/backend/src/main/java/net/beetechgroup/beetask/entities/organization/UserOrganizationStatus.java b/backend/src/main/java/net/beetechgroup/beetask/entities/organization/UserOrganizationStatus.java new file mode 100644 index 0000000..701890e --- /dev/null +++ b/backend/src/main/java/net/beetechgroup/beetask/entities/organization/UserOrganizationStatus.java @@ -0,0 +1,7 @@ +package net.beetechgroup.beetask.entities.organization; + +public enum UserOrganizationStatus { + PENDING, + ACTIVE, + REJECTED +} diff --git a/backend/src/main/java/net/beetechgroup/beetask/frameworks/config/OrganizationUseCaseConfig.java b/backend/src/main/java/net/beetechgroup/beetask/frameworks/config/OrganizationUseCaseConfig.java index 46ae922..4f8cdd7 100644 --- a/backend/src/main/java/net/beetechgroup/beetask/frameworks/config/OrganizationUseCaseConfig.java +++ b/backend/src/main/java/net/beetechgroup/beetask/frameworks/config/OrganizationUseCaseConfig.java @@ -3,6 +3,11 @@ package net.beetechgroup.beetask.frameworks.config; import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.inject.Produces; import net.beetechgroup.beetask.usecase.organization.create.CreateOrganizationUseCase; +import net.beetechgroup.beetask.usecase.organization.search.SearchOrganizationsUseCase; +import net.beetechgroup.beetask.usecase.organization.join.RequestJoinOrganizationUseCase; +import net.beetechgroup.beetask.usecase.organization.requests.ListPendingRequestsUseCase; +import net.beetechgroup.beetask.usecase.organization.requests.HandleJoinRequestUseCase; +import net.beetechgroup.beetask.usecase.organization.auth.AuthorizeOrganizationAdminUseCase; import net.beetechgroup.beetask.usecase.repository.OrganizationRepository; import net.beetechgroup.beetask.usecase.repository.UserOrganizationRepository; import net.beetechgroup.beetask.usecase.repository.UserRepository; @@ -10,10 +15,40 @@ import net.beetechgroup.beetask.usecase.repository.UserRepository; @ApplicationScoped public class OrganizationUseCaseConfig { + @Produces + public AuthorizeOrganizationAdminUseCase authorizeOrganizationAdminUseCase(UserRepository userRepository, + UserOrganizationRepository userOrganizationRepository) { + return new AuthorizeOrganizationAdminUseCase(userRepository, userOrganizationRepository); + } + @Produces public CreateOrganizationUseCase createOrganizationUseCase(OrganizationRepository organizationRepository, UserRepository userRepository, UserOrganizationRepository userOrganizationRepository) { return new CreateOrganizationUseCase(organizationRepository, userRepository, userOrganizationRepository); } + + @Produces + public SearchOrganizationsUseCase searchOrganizationsUseCase(OrganizationRepository organizationRepository) { + return new SearchOrganizationsUseCase(organizationRepository); + } + + @Produces + public RequestJoinOrganizationUseCase requestJoinOrganizationUseCase(OrganizationRepository organizationRepository, + UserRepository userRepository, + UserOrganizationRepository userOrganizationRepository) { + return new RequestJoinOrganizationUseCase(organizationRepository, userRepository, userOrganizationRepository); + } + + @Produces + public ListPendingRequestsUseCase listPendingRequestsUseCase(AuthorizeOrganizationAdminUseCase authorizer, + UserOrganizationRepository userOrganizationRepository) { + return new ListPendingRequestsUseCase(authorizer, userOrganizationRepository); + } + + @Produces + public HandleJoinRequestUseCase handleJoinRequestUseCase(AuthorizeOrganizationAdminUseCase authorizer, + UserOrganizationRepository userOrganizationRepository) { + return new HandleJoinRequestUseCase(authorizer, userOrganizationRepository); + } } \ No newline at end of file diff --git a/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/entities/UserOrganizationEntity.java b/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/entities/UserOrganizationEntity.java index a0ef122..f4a80bd 100644 --- a/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/entities/UserOrganizationEntity.java +++ b/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/entities/UserOrganizationEntity.java @@ -8,6 +8,9 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; import net.beetechgroup.beetask.entities.organization.UserOrganizationRole; +import net.beetechgroup.beetask.entities.organization.UserOrganizationStatus; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; @Entity @Table(name = "user_organizations") @@ -21,7 +24,10 @@ public class UserOrganizationEntity { @ManyToOne @JoinColumn(name = "organization_id") private OrganizationEntity organization; + @Enumerated(EnumType.STRING) private UserOrganizationRole role; + @Enumerated(EnumType.STRING) + private UserOrganizationStatus status; public UserEntity getUser() { return user; @@ -46,4 +52,12 @@ public class UserOrganizationEntity { public void setRole(UserOrganizationRole role) { this.role = role; } + + public UserOrganizationStatus getStatus() { + return status; + } + + public void setStatus(UserOrganizationStatus status) { + this.status = status; + } } diff --git a/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/mapper/UserOrganizationEntityMapper.java b/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/mapper/UserOrganizationEntityMapper.java index f2636ce..e127b51 100644 --- a/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/mapper/UserOrganizationEntityMapper.java +++ b/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/mapper/UserOrganizationEntityMapper.java @@ -11,6 +11,7 @@ public class UserOrganizationEntityMapper { domain.setUser(UserEntityMapper.toDomain(entity.getUser())); domain.setOrganization(OrganizationEntityMapper.toDomain(entity.getOrganization())); domain.setRole(entity.getRole()); + domain.setStatus(entity.getStatus()); return domain; } @@ -20,6 +21,7 @@ public class UserOrganizationEntityMapper { entity.setUser(UserEntityMapper.toEntity(domain.getUser())); entity.setOrganization(OrganizationEntityMapper.toEntity(domain.getOrganization())); entity.setRole(domain.getRole()); + entity.setStatus(domain.getStatus()); return entity; } } diff --git a/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/repository/OrganizationRepositoryImpl.java b/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/repository/OrganizationRepositoryImpl.java index d982977..5074dec 100644 --- a/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/repository/OrganizationRepositoryImpl.java +++ b/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/repository/OrganizationRepositoryImpl.java @@ -8,6 +8,9 @@ import net.beetechgroup.beetask.frameworks.persistence.entities.OrganizationEnti import net.beetechgroup.beetask.frameworks.persistence.mapper.OrganizationEntityMapper; import net.beetechgroup.beetask.usecase.repository.OrganizationRepository; +import java.util.List; +import java.util.Optional; + @ApplicationScoped public class OrganizationRepositoryImpl implements OrganizationRepository, PanacheRepository { @@ -23,4 +26,16 @@ public class OrganizationRepositoryImpl implements OrganizationRepository, Panac return OrganizationEntityMapper.toDomain(entity); } + @Override + public List search(String query) { + return find("name like ?1", "%" + query + "%").stream() + .map(OrganizationEntityMapper::toDomain) + .toList(); + } + + @Override + public Optional findOrganizationById(Long id) { + return findByIdOptional(id) + .map(OrganizationEntityMapper::toDomain); + } } \ No newline at end of file diff --git a/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/repository/UserOrganizationRepositoryImpl.java b/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/repository/UserOrganizationRepositoryImpl.java index 2343565..1113b12 100644 --- a/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/repository/UserOrganizationRepositoryImpl.java +++ b/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/repository/UserOrganizationRepositoryImpl.java @@ -4,11 +4,13 @@ import io.quarkus.hibernate.orm.panache.PanacheRepository; import jakarta.enterprise.context.ApplicationScoped; import jakarta.transaction.Transactional; import net.beetechgroup.beetask.entities.organization.UserOrganization; +import net.beetechgroup.beetask.entities.organization.UserOrganizationStatus; import net.beetechgroup.beetask.frameworks.persistence.entities.UserOrganizationEntity; import net.beetechgroup.beetask.frameworks.persistence.mapper.UserOrganizationEntityMapper; import net.beetechgroup.beetask.usecase.repository.UserOrganizationRepository; import java.util.List; +import java.util.Optional; @ApplicationScoped public class UserOrganizationRepositoryImpl implements UserOrganizationRepository, PanacheRepository { @@ -20,10 +22,43 @@ public class UserOrganizationRepositoryImpl implements UserOrganizationRepositor .toList(); } + @Override + public List findByUserIdAndStatus(Long userId, UserOrganizationStatus status) { + return find("user.id = ?1 and status = ?2", userId, status).stream() + .map(UserOrganizationEntityMapper::toDomain) + .toList(); + } + + @Override + public List findByOrganizationIdAndStatus(Long organizationId, UserOrganizationStatus status) { + return find("organization.id = ?1 and status = ?2", organizationId, status).stream() + .map(UserOrganizationEntityMapper::toDomain) + .toList(); + } + + @Override + public Optional findByUserAndOrganization(Long userId, Long organizationId) { + return find("user.id = ?1 and organization.id = ?2", userId, organizationId) + .firstResultOptional() + .map(UserOrganizationEntityMapper::toDomain); + } + @Override @Transactional public void save(UserOrganization userOrganization) { UserOrganizationEntity entity = UserOrganizationEntityMapper.toEntity(userOrganization); + if (entity.getUser().getId() != null && entity.getOrganization().getId() != null) { + // Find existing by user and org to update instead of creating new if it exists + Optional existing = find("user.id = ?1 and organization.id = ?2", + entity.getUser().getId(), entity.getOrganization().getId()).firstResultOptional(); + if (existing.isPresent()) { + UserOrganizationEntity existingEntity = existing.get(); + existingEntity.setRole(entity.getRole()); + existingEntity.setStatus(entity.getStatus()); + getEntityManager().merge(existingEntity); + return; + } + } persist(entity); } } diff --git a/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/repository/UserRepositoryImpl.java b/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/repository/UserRepositoryImpl.java index 79fbb98..63af959 100644 --- a/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/repository/UserRepositoryImpl.java +++ b/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/repository/UserRepositoryImpl.java @@ -8,6 +8,7 @@ import net.beetechgroup.beetask.entities.User; import net.beetechgroup.beetask.frameworks.persistence.entities.UserEntity; import net.beetechgroup.beetask.frameworks.persistence.mapper.UserEntityMapper; import net.beetechgroup.beetask.entities.organization.UserOrganization; +import net.beetechgroup.beetask.entities.organization.UserOrganizationStatus; import net.beetechgroup.beetask.usecase.repository.UserRepository; import net.beetechgroup.beetask.usecase.repository.UserOrganizationRepository; @@ -46,6 +47,6 @@ public class UserRepositoryImpl implements UserRepository, PanacheRepository findUserOrganizations(Long userId) { - return userOrganizationRepository.findByUserId(userId); + return userOrganizationRepository.findByUserIdAndStatus(userId, UserOrganizationStatus.ACTIVE); } } diff --git a/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/organization/OrganizationController.java b/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/organization/OrganizationController.java index 975012e..373187b 100644 --- a/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/organization/OrganizationController.java +++ b/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/organization/OrganizationController.java @@ -4,11 +4,23 @@ import io.quarkus.security.Authenticated; import io.quarkus.security.identity.SecurityIdentity; import org.eclipse.microprofile.openapi.annotations.Operation; import jakarta.inject.Inject; +import jakarta.ws.rs.GET; import jakarta.ws.rs.POST; +import jakarta.ws.rs.PATCH; import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.QueryParam; import net.beetechgroup.beetask.usecase.organization.create.CreateOrganizationInput; import net.beetechgroup.beetask.usecase.organization.create.CreateOrganizationOutput; import net.beetechgroup.beetask.usecase.organization.create.CreateOrganizationUseCase; +import net.beetechgroup.beetask.usecase.organization.search.SearchOrganizationsUseCase; +import net.beetechgroup.beetask.usecase.organization.search.SearchOrganizationOutput; +import net.beetechgroup.beetask.usecase.organization.join.RequestJoinOrganizationUseCase; +import net.beetechgroup.beetask.usecase.organization.requests.ListPendingRequestsUseCase; +import net.beetechgroup.beetask.usecase.organization.requests.JoinRequestOutput; +import net.beetechgroup.beetask.usecase.organization.requests.HandleJoinRequestUseCase; + +import java.util.List; @Path("/organizations") public class OrganizationController { @@ -16,6 +28,18 @@ public class OrganizationController { @Inject CreateOrganizationUseCase createOrganizationUseCase; + @Inject + SearchOrganizationsUseCase searchOrganizationsUseCase; + + @Inject + RequestJoinOrganizationUseCase requestJoinOrganizationUseCase; + + @Inject + ListPendingRequestsUseCase listPendingRequestsUseCase; + + @Inject + HandleJoinRequestUseCase handleJoinRequestUseCase; + @Inject SecurityIdentity securityIdentity; @@ -28,4 +52,48 @@ public class OrganizationController { CreateOrganizationOutput output = createOrganizationUseCase.execute(input); return OrganizationControllerMapper.toCreateOrganizationResponse(output); } + + @GET + @Path("/search") + @Authenticated + @Operation(summary = "Search organizations", description = "Search for organizations by name") + public List search(@QueryParam("q") String query) { + return searchOrganizationsUseCase.execute(query); + } + + @POST + @Path("/{id}/join") + @Authenticated + @Operation(summary = "Request to join", description = "Request to join an organization") + public void requestJoin(@PathParam("id") Long organizationId) { + String userEmail = securityIdentity.getPrincipal().getName(); + requestJoinOrganizationUseCase.execute(organizationId, userEmail); + } + + @GET + @Path("/{id}/requests") + @Authenticated + @Operation(summary = "List pending requests", description = "List all pending join requests for an organization (Owner only)") + public List listRequests(@PathParam("id") Long organizationId) { + String userEmail = securityIdentity.getPrincipal().getName(); + return listPendingRequestsUseCase.execute(userEmail, organizationId, null); + } + + @PATCH + @Path("/{id}/requests/{userId}/approve") + @Authenticated + @Operation(summary = "Approve request", description = "Approve a join request (Owner only)") + public void approveRequest(@PathParam("id") Long organizationId, @PathParam("userId") Long userId) { + String userEmail = securityIdentity.getPrincipal().getName(); + handleJoinRequestUseCase.execute(userEmail, organizationId, new HandleJoinRequestUseCase.Input(userId, true)); + } + + @PATCH + @Path("/{id}/requests/{userId}/reject") + @Authenticated + @Operation(summary = "Reject request", description = "Reject a join request (Owner only)") + public void rejectRequest(@PathParam("id") Long organizationId, @PathParam("userId") Long userId) { + String userEmail = securityIdentity.getPrincipal().getName(); + handleJoinRequestUseCase.execute(userEmail, organizationId, new HandleJoinRequestUseCase.Input(userId, false)); + } } diff --git a/backend/src/main/java/net/beetechgroup/beetask/usecase/organization/auth/AuthenticatedOrganizationUseCase.java b/backend/src/main/java/net/beetechgroup/beetask/usecase/organization/auth/AuthenticatedOrganizationUseCase.java new file mode 100644 index 0000000..0838bef --- /dev/null +++ b/backend/src/main/java/net/beetechgroup/beetask/usecase/organization/auth/AuthenticatedOrganizationUseCase.java @@ -0,0 +1,16 @@ +package net.beetechgroup.beetask.usecase.organization.auth; + +public abstract class AuthenticatedOrganizationUseCase { + protected final AuthorizeOrganizationAdminUseCase authorizer; + + protected AuthenticatedOrganizationUseCase(AuthorizeOrganizationAdminUseCase authorizer) { + this.authorizer = authorizer; + } + + public O execute(String userEmail, Long organizationId, I input) { + authorizer.execute(userEmail, organizationId); + return doExecute(organizationId, input); + } + + protected abstract O doExecute(Long organizationId, I input); +} diff --git a/backend/src/main/java/net/beetechgroup/beetask/usecase/organization/auth/AuthorizeOrganizationAdminUseCase.java b/backend/src/main/java/net/beetechgroup/beetask/usecase/organization/auth/AuthorizeOrganizationAdminUseCase.java new file mode 100644 index 0000000..8f89f4c --- /dev/null +++ b/backend/src/main/java/net/beetechgroup/beetask/usecase/organization/auth/AuthorizeOrganizationAdminUseCase.java @@ -0,0 +1,39 @@ +package net.beetechgroup.beetask.usecase.organization.auth; + +import net.beetechgroup.beetask.entities.User; +import net.beetechgroup.beetask.entities.organization.UserOrganization; +import net.beetechgroup.beetask.entities.organization.UserOrganizationRole; +import net.beetechgroup.beetask.usecase.repository.UserOrganizationRepository; +import net.beetechgroup.beetask.usecase.repository.UserRepository; + +import java.util.Optional; + +public class AuthorizeOrganizationAdminUseCase { + private final UserRepository userRepository; + private final UserOrganizationRepository userOrganizationRepository; + + public AuthorizeOrganizationAdminUseCase(UserRepository userRepository, + UserOrganizationRepository userOrganizationRepository) { + this.userRepository = userRepository; + this.userOrganizationRepository = userOrganizationRepository; + } + + public void execute(String userEmail, Long organizationId) { + Optional userOptional = userRepository.findByEmail(userEmail); + if (userOptional.isEmpty()) { + throw new RuntimeException("Unauthorized: User not found"); + } + + User user = userOptional.get(); + Optional association = userOrganizationRepository.findByUserAndOrganization(user.getId(), organizationId); + + if (association.isEmpty()) { + throw new RuntimeException("Forbidden: Not a member of this organization"); + } + + UserOrganization uo = association.get(); + if (uo.getRole() != UserOrganizationRole.OWNER && uo.getRole() != UserOrganizationRole.ADMIN) { + throw new RuntimeException("Forbidden: Only owners or admins can perform this action"); + } + } +} diff --git a/backend/src/main/java/net/beetechgroup/beetask/usecase/organization/create/CreateOrganizationUseCase.java b/backend/src/main/java/net/beetechgroup/beetask/usecase/organization/create/CreateOrganizationUseCase.java index 4c8363a..21dec34 100644 --- a/backend/src/main/java/net/beetechgroup/beetask/usecase/organization/create/CreateOrganizationUseCase.java +++ b/backend/src/main/java/net/beetechgroup/beetask/usecase/organization/create/CreateOrganizationUseCase.java @@ -4,6 +4,7 @@ import net.beetechgroup.beetask.entities.User; import net.beetechgroup.beetask.entities.organization.Organization; import net.beetechgroup.beetask.entities.organization.UserOrganization; import net.beetechgroup.beetask.entities.organization.UserOrganizationRole; +import net.beetechgroup.beetask.entities.organization.UserOrganizationStatus; import net.beetechgroup.beetask.usecase.repository.OrganizationRepository; import net.beetechgroup.beetask.usecase.repository.UserOrganizationRepository; import net.beetechgroup.beetask.usecase.repository.UserRepository; @@ -38,6 +39,7 @@ public class CreateOrganizationUseCase { userOrganization.setUser(user); userOrganization.setOrganization(organization); userOrganization.setRole(UserOrganizationRole.OWNER); + userOrganization.setStatus(UserOrganizationStatus.ACTIVE); userOrganizationRepository.save(userOrganization); return CreateOrganizationMapper.toCreateOrganizationOutput(organization); diff --git a/backend/src/main/java/net/beetechgroup/beetask/usecase/organization/join/RequestJoinOrganizationUseCase.java b/backend/src/main/java/net/beetechgroup/beetask/usecase/organization/join/RequestJoinOrganizationUseCase.java new file mode 100644 index 0000000..34c8c57 --- /dev/null +++ b/backend/src/main/java/net/beetechgroup/beetask/usecase/organization/join/RequestJoinOrganizationUseCase.java @@ -0,0 +1,56 @@ +package net.beetechgroup.beetask.usecase.organization.join; + +import net.beetechgroup.beetask.entities.User; +import net.beetechgroup.beetask.entities.organization.Organization; +import net.beetechgroup.beetask.entities.organization.UserOrganization; +import net.beetechgroup.beetask.entities.organization.UserOrganizationRole; +import net.beetechgroup.beetask.entities.organization.UserOrganizationStatus; +import net.beetechgroup.beetask.usecase.repository.OrganizationRepository; +import net.beetechgroup.beetask.usecase.repository.UserOrganizationRepository; +import net.beetechgroup.beetask.usecase.repository.UserRepository; + +import java.util.Optional; + +public class RequestJoinOrganizationUseCase { + private final OrganizationRepository organizationRepository; + private final UserRepository userRepository; + private final UserOrganizationRepository userOrganizationRepository; + + public RequestJoinOrganizationUseCase(OrganizationRepository organizationRepository, + UserRepository userRepository, + UserOrganizationRepository userOrganizationRepository) { + this.organizationRepository = organizationRepository; + this.userRepository = userRepository; + this.userOrganizationRepository = userOrganizationRepository; + } + + public void execute(Long organizationId, String userEmail) { + Optional userOptional = userRepository.findByEmail(userEmail); + Optional orgOptional = organizationRepository.findOrganizationById(organizationId); + + if (userOptional.isEmpty() || orgOptional.isEmpty()) { + throw new RuntimeException("User or Organization not found"); + } + + User user = userOptional.get(); + Organization organization = orgOptional.get(); + + Optional existing = userOrganizationRepository.findByUserAndOrganization(user.getId(), organization.getId()); + if (existing.isPresent()) { + if (existing.get().getStatus() == UserOrganizationStatus.ACTIVE) { + throw new RuntimeException("User is already a member of this organization"); + } else if (existing.get().getStatus() == UserOrganizationStatus.PENDING) { + throw new RuntimeException("A join request is already pending for this organization"); + } + // If REJECTED, we allow a new request (or we could just update the existing one to PENDING) + } + + UserOrganization request = new UserOrganization(); + request.setUser(user); + request.setOrganization(organization); + request.setRole(UserOrganizationRole.MEMBER); + request.setStatus(UserOrganizationStatus.PENDING); + + userOrganizationRepository.save(request); + } +} diff --git a/backend/src/main/java/net/beetechgroup/beetask/usecase/organization/requests/HandleJoinRequestUseCase.java b/backend/src/main/java/net/beetechgroup/beetask/usecase/organization/requests/HandleJoinRequestUseCase.java new file mode 100644 index 0000000..8e6198d --- /dev/null +++ b/backend/src/main/java/net/beetechgroup/beetask/usecase/organization/requests/HandleJoinRequestUseCase.java @@ -0,0 +1,39 @@ +package net.beetechgroup.beetask.usecase.organization.requests; + +import net.beetechgroup.beetask.entities.organization.UserOrganization; +import net.beetechgroup.beetask.entities.organization.UserOrganizationStatus; +import net.beetechgroup.beetask.usecase.organization.auth.AuthenticatedOrganizationUseCase; +import net.beetechgroup.beetask.usecase.organization.auth.AuthorizeOrganizationAdminUseCase; +import net.beetechgroup.beetask.usecase.repository.UserOrganizationRepository; + +import java.util.Optional; + +public class HandleJoinRequestUseCase extends AuthenticatedOrganizationUseCase { + private final UserOrganizationRepository userOrganizationRepository; + + public HandleJoinRequestUseCase(AuthorizeOrganizationAdminUseCase authorizer, + UserOrganizationRepository userOrganizationRepository) { + super(authorizer); + this.userOrganizationRepository = userOrganizationRepository; + } + + @Override + protected Void doExecute(Long organizationId, Input input) { + Optional associationOptional = userOrganizationRepository.findByUserAndOrganization(input.userId(), organizationId); + + if (associationOptional.isEmpty()) { + throw new RuntimeException("Request not found"); + } + + UserOrganization association = associationOptional.get(); + if (association.getStatus() != UserOrganizationStatus.PENDING) { + throw new RuntimeException("Request is not pending"); + } + + association.setStatus(input.approved() ? UserOrganizationStatus.ACTIVE : UserOrganizationStatus.REJECTED); + userOrganizationRepository.save(association); + return null; + } + + public record Input(Long userId, boolean approved) {} +} diff --git a/backend/src/main/java/net/beetechgroup/beetask/usecase/organization/requests/JoinRequestOutput.java b/backend/src/main/java/net/beetechgroup/beetask/usecase/organization/requests/JoinRequestOutput.java new file mode 100644 index 0000000..56599a7 --- /dev/null +++ b/backend/src/main/java/net/beetechgroup/beetask/usecase/organization/requests/JoinRequestOutput.java @@ -0,0 +1,3 @@ +package net.beetechgroup.beetask.usecase.organization.requests; + +public record JoinRequestOutput(Long userId, String name, String email, String photo) {} diff --git a/backend/src/main/java/net/beetechgroup/beetask/usecase/organization/requests/ListPendingRequestsUseCase.java b/backend/src/main/java/net/beetechgroup/beetask/usecase/organization/requests/ListPendingRequestsUseCase.java new file mode 100644 index 0000000..10b692d --- /dev/null +++ b/backend/src/main/java/net/beetechgroup/beetask/usecase/organization/requests/ListPendingRequestsUseCase.java @@ -0,0 +1,33 @@ +package net.beetechgroup.beetask.usecase.organization.requests; + +import net.beetechgroup.beetask.entities.organization.UserOrganization; +import net.beetechgroup.beetask.entities.organization.UserOrganizationStatus; +import net.beetechgroup.beetask.usecase.organization.auth.AuthenticatedOrganizationUseCase; +import net.beetechgroup.beetask.usecase.organization.auth.AuthorizeOrganizationAdminUseCase; +import net.beetechgroup.beetask.usecase.repository.UserOrganizationRepository; + +import java.util.List; +import java.util.stream.Collectors; + +public class ListPendingRequestsUseCase extends AuthenticatedOrganizationUseCase> { + private final UserOrganizationRepository userOrganizationRepository; + + public ListPendingRequestsUseCase(AuthorizeOrganizationAdminUseCase authorizer, + UserOrganizationRepository userOrganizationRepository) { + super(authorizer); + this.userOrganizationRepository = userOrganizationRepository; + } + + @Override + protected List doExecute(Long organizationId, Void input) { + List pending = userOrganizationRepository.findByOrganizationIdAndStatus(organizationId, UserOrganizationStatus.PENDING); + return pending.stream() + .map(uo -> new JoinRequestOutput( + uo.getUser().getId(), + uo.getUser().getName(), + uo.getUser().getEmail(), + uo.getUser().getPhoto() + )) + .collect(Collectors.toList()); + } +} diff --git a/backend/src/main/java/net/beetechgroup/beetask/usecase/organization/search/SearchOrganizationOutput.java b/backend/src/main/java/net/beetechgroup/beetask/usecase/organization/search/SearchOrganizationOutput.java new file mode 100644 index 0000000..124cfd6 --- /dev/null +++ b/backend/src/main/java/net/beetechgroup/beetask/usecase/organization/search/SearchOrganizationOutput.java @@ -0,0 +1,3 @@ +package net.beetechgroup.beetask.usecase.organization.search; + +public record SearchOrganizationOutput(Long id, String name) {} diff --git a/backend/src/main/java/net/beetechgroup/beetask/usecase/organization/search/SearchOrganizationsUseCase.java b/backend/src/main/java/net/beetechgroup/beetask/usecase/organization/search/SearchOrganizationsUseCase.java new file mode 100644 index 0000000..4183942 --- /dev/null +++ b/backend/src/main/java/net/beetechgroup/beetask/usecase/organization/search/SearchOrganizationsUseCase.java @@ -0,0 +1,22 @@ +package net.beetechgroup.beetask.usecase.organization.search; + +import net.beetechgroup.beetask.entities.organization.Organization; +import net.beetechgroup.beetask.usecase.repository.OrganizationRepository; + +import java.util.List; +import java.util.stream.Collectors; + +public class SearchOrganizationsUseCase { + private final OrganizationRepository organizationRepository; + + public SearchOrganizationsUseCase(OrganizationRepository organizationRepository) { + this.organizationRepository = organizationRepository; + } + + public List execute(String query) { + List results = organizationRepository.search(query); + return results.stream() + .map(org -> new SearchOrganizationOutput(org.getId(), org.getName())) + .collect(Collectors.toList()); + } +} diff --git a/backend/src/main/java/net/beetechgroup/beetask/usecase/repository/OrganizationRepository.java b/backend/src/main/java/net/beetechgroup/beetask/usecase/repository/OrganizationRepository.java index 23aa71a..75aba94 100644 --- a/backend/src/main/java/net/beetechgroup/beetask/usecase/repository/OrganizationRepository.java +++ b/backend/src/main/java/net/beetechgroup/beetask/usecase/repository/OrganizationRepository.java @@ -2,7 +2,11 @@ package net.beetechgroup.beetask.usecase.repository; import net.beetechgroup.beetask.entities.organization.Organization; +import java.util.List; +import java.util.Optional; public interface OrganizationRepository { Organization saveOrganization(Organization organization); + List search(String query); + Optional findOrganizationById(Long id); } \ No newline at end of file diff --git a/backend/src/main/java/net/beetechgroup/beetask/usecase/repository/UserOrganizationRepository.java b/backend/src/main/java/net/beetechgroup/beetask/usecase/repository/UserOrganizationRepository.java index 4260bbe..2c17ac0 100644 --- a/backend/src/main/java/net/beetechgroup/beetask/usecase/repository/UserOrganizationRepository.java +++ b/backend/src/main/java/net/beetechgroup/beetask/usecase/repository/UserOrganizationRepository.java @@ -1,9 +1,14 @@ package net.beetechgroup.beetask.usecase.repository; import net.beetechgroup.beetask.entities.organization.UserOrganization; +import net.beetechgroup.beetask.entities.organization.UserOrganizationStatus; import java.util.List; +import java.util.Optional; public interface UserOrganizationRepository { List findByUserId(Long userId); + List findByUserIdAndStatus(Long userId, UserOrganizationStatus status); + List findByOrganizationIdAndStatus(Long organizationId, UserOrganizationStatus status); + Optional findByUserAndOrganization(Long userId, Long organizationId); void save(UserOrganization userOrganization); } diff --git a/backend/src/main/java/net/beetechgroup/beetask/usecase/user/profile/GetUserProfileUseCase.java b/backend/src/main/java/net/beetechgroup/beetask/usecase/user/profile/GetUserProfileUseCase.java index a79a0b8..a41d720 100644 --- a/backend/src/main/java/net/beetechgroup/beetask/usecase/user/profile/GetUserProfileUseCase.java +++ b/backend/src/main/java/net/beetechgroup/beetask/usecase/user/profile/GetUserProfileUseCase.java @@ -27,12 +27,16 @@ public class GetUserProfileUseCase { List userOrganizations = userRepository.findUserOrganizations(user.getId()); List orgs = userOrganizations.stream() - .collect(Collectors.groupingBy(uo -> uo.getOrganization().getName())) + .collect(Collectors.groupingBy(uo -> uo.getOrganization().getId())) .entrySet().stream() - .map(entry -> new UserProfileOutput.OrganizationProfileOutput( + .map(entry -> { + UserOrganization firstUo = entry.getValue().get(0); + return new UserProfileOutput.OrganizationProfileOutput( entry.getKey(), + firstUo.getOrganization().getName(), entry.getValue().stream().map(uo -> uo.getRole().name()).collect(Collectors.toList()) - )) + ); + }) .collect(Collectors.toList()); return new UserProfileOutput( diff --git a/backend/src/main/java/net/beetechgroup/beetask/usecase/user/profile/UserProfileOutput.java b/backend/src/main/java/net/beetechgroup/beetask/usecase/user/profile/UserProfileOutput.java index 667f61b..e139dc4 100644 --- a/backend/src/main/java/net/beetechgroup/beetask/usecase/user/profile/UserProfileOutput.java +++ b/backend/src/main/java/net/beetechgroup/beetask/usecase/user/profile/UserProfileOutput.java @@ -9,6 +9,7 @@ public record UserProfileOutput( List organizations ) { public record OrganizationProfileOutput( + Long id, String name, List roles ) {}