Modal do primeiro acesso

This commit is contained in:
Gabriel Santana de Menezes 2026-05-03 17:26:12 -04:00
parent e99e1cd586
commit 22760ba661
22 changed files with 417 additions and 4 deletions

View file

@ -6,6 +6,7 @@ public class UserOrganization {
private User user; private User user;
private Organization organization; private Organization organization;
private UserOrganizationRole role; private UserOrganizationRole role;
private UserOrganizationStatus status;
public User getUser() { public User getUser() {
return user; return user;
@ -30,4 +31,12 @@ public class UserOrganization {
public void setRole(UserOrganizationRole role) { public void setRole(UserOrganizationRole role) {
this.role = role; this.role = role;
} }
public UserOrganizationStatus getStatus() {
return status;
}
public void setStatus(UserOrganizationStatus status) {
this.status = status;
}
} }

View file

@ -0,0 +1,7 @@
package net.beetechgroup.beetask.entities.organization;
public enum UserOrganizationStatus {
PENDING,
ACTIVE,
REJECTED
}

View file

@ -3,6 +3,11 @@ package net.beetechgroup.beetask.frameworks.config;
import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Produces; import jakarta.enterprise.inject.Produces;
import net.beetechgroup.beetask.usecase.organization.create.CreateOrganizationUseCase; 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.OrganizationRepository;
import net.beetechgroup.beetask.usecase.repository.UserOrganizationRepository; import net.beetechgroup.beetask.usecase.repository.UserOrganizationRepository;
import net.beetechgroup.beetask.usecase.repository.UserRepository; import net.beetechgroup.beetask.usecase.repository.UserRepository;
@ -10,10 +15,40 @@ import net.beetechgroup.beetask.usecase.repository.UserRepository;
@ApplicationScoped @ApplicationScoped
public class OrganizationUseCaseConfig { public class OrganizationUseCaseConfig {
@Produces
public AuthorizeOrganizationAdminUseCase authorizeOrganizationAdminUseCase(UserRepository userRepository,
UserOrganizationRepository userOrganizationRepository) {
return new AuthorizeOrganizationAdminUseCase(userRepository, userOrganizationRepository);
}
@Produces @Produces
public CreateOrganizationUseCase createOrganizationUseCase(OrganizationRepository organizationRepository, public CreateOrganizationUseCase createOrganizationUseCase(OrganizationRepository organizationRepository,
UserRepository userRepository, UserRepository userRepository,
UserOrganizationRepository userOrganizationRepository) { UserOrganizationRepository userOrganizationRepository) {
return new CreateOrganizationUseCase(organizationRepository, userRepository, 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);
}
} }

View file

@ -8,6 +8,9 @@ import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne; import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table; import jakarta.persistence.Table;
import net.beetechgroup.beetask.entities.organization.UserOrganizationRole; import net.beetechgroup.beetask.entities.organization.UserOrganizationRole;
import net.beetechgroup.beetask.entities.organization.UserOrganizationStatus;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
@Entity @Entity
@Table(name = "user_organizations") @Table(name = "user_organizations")
@ -21,7 +24,10 @@ public class UserOrganizationEntity {
@ManyToOne @ManyToOne
@JoinColumn(name = "organization_id") @JoinColumn(name = "organization_id")
private OrganizationEntity organization; private OrganizationEntity organization;
@Enumerated(EnumType.STRING)
private UserOrganizationRole role; private UserOrganizationRole role;
@Enumerated(EnumType.STRING)
private UserOrganizationStatus status;
public UserEntity getUser() { public UserEntity getUser() {
return user; return user;
@ -46,4 +52,12 @@ public class UserOrganizationEntity {
public void setRole(UserOrganizationRole role) { public void setRole(UserOrganizationRole role) {
this.role = role; this.role = role;
} }
public UserOrganizationStatus getStatus() {
return status;
}
public void setStatus(UserOrganizationStatus status) {
this.status = status;
}
} }

View file

@ -11,6 +11,7 @@ public class UserOrganizationEntityMapper {
domain.setUser(UserEntityMapper.toDomain(entity.getUser())); domain.setUser(UserEntityMapper.toDomain(entity.getUser()));
domain.setOrganization(OrganizationEntityMapper.toDomain(entity.getOrganization())); domain.setOrganization(OrganizationEntityMapper.toDomain(entity.getOrganization()));
domain.setRole(entity.getRole()); domain.setRole(entity.getRole());
domain.setStatus(entity.getStatus());
return domain; return domain;
} }
@ -20,6 +21,7 @@ public class UserOrganizationEntityMapper {
entity.setUser(UserEntityMapper.toEntity(domain.getUser())); entity.setUser(UserEntityMapper.toEntity(domain.getUser()));
entity.setOrganization(OrganizationEntityMapper.toEntity(domain.getOrganization())); entity.setOrganization(OrganizationEntityMapper.toEntity(domain.getOrganization()));
entity.setRole(domain.getRole()); entity.setRole(domain.getRole());
entity.setStatus(domain.getStatus());
return entity; return entity;
} }
} }

View file

@ -8,6 +8,9 @@ import net.beetechgroup.beetask.frameworks.persistence.entities.OrganizationEnti
import net.beetechgroup.beetask.frameworks.persistence.mapper.OrganizationEntityMapper; import net.beetechgroup.beetask.frameworks.persistence.mapper.OrganizationEntityMapper;
import net.beetechgroup.beetask.usecase.repository.OrganizationRepository; import net.beetechgroup.beetask.usecase.repository.OrganizationRepository;
import java.util.List;
import java.util.Optional;
@ApplicationScoped @ApplicationScoped
public class OrganizationRepositoryImpl implements OrganizationRepository, PanacheRepository<OrganizationEntity> { public class OrganizationRepositoryImpl implements OrganizationRepository, PanacheRepository<OrganizationEntity> {
@ -23,4 +26,16 @@ public class OrganizationRepositoryImpl implements OrganizationRepository, Panac
return OrganizationEntityMapper.toDomain(entity); return OrganizationEntityMapper.toDomain(entity);
} }
@Override
public List<Organization> search(String query) {
return find("name like ?1", "%" + query + "%").stream()
.map(OrganizationEntityMapper::toDomain)
.toList();
}
@Override
public Optional<Organization> findOrganizationById(Long id) {
return findByIdOptional(id)
.map(OrganizationEntityMapper::toDomain);
}
} }

View file

@ -4,11 +4,13 @@ import io.quarkus.hibernate.orm.panache.PanacheRepository;
import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.context.ApplicationScoped;
import jakarta.transaction.Transactional; import jakarta.transaction.Transactional;
import net.beetechgroup.beetask.entities.organization.UserOrganization; 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.entities.UserOrganizationEntity;
import net.beetechgroup.beetask.frameworks.persistence.mapper.UserOrganizationEntityMapper; import net.beetechgroup.beetask.frameworks.persistence.mapper.UserOrganizationEntityMapper;
import net.beetechgroup.beetask.usecase.repository.UserOrganizationRepository; import net.beetechgroup.beetask.usecase.repository.UserOrganizationRepository;
import java.util.List; import java.util.List;
import java.util.Optional;
@ApplicationScoped @ApplicationScoped
public class UserOrganizationRepositoryImpl implements UserOrganizationRepository, PanacheRepository<UserOrganizationEntity> { public class UserOrganizationRepositoryImpl implements UserOrganizationRepository, PanacheRepository<UserOrganizationEntity> {
@ -20,10 +22,43 @@ public class UserOrganizationRepositoryImpl implements UserOrganizationRepositor
.toList(); .toList();
} }
@Override
public List<UserOrganization> findByUserIdAndStatus(Long userId, UserOrganizationStatus status) {
return find("user.id = ?1 and status = ?2", userId, status).stream()
.map(UserOrganizationEntityMapper::toDomain)
.toList();
}
@Override
public List<UserOrganization> findByOrganizationIdAndStatus(Long organizationId, UserOrganizationStatus status) {
return find("organization.id = ?1 and status = ?2", organizationId, status).stream()
.map(UserOrganizationEntityMapper::toDomain)
.toList();
}
@Override
public Optional<UserOrganization> findByUserAndOrganization(Long userId, Long organizationId) {
return find("user.id = ?1 and organization.id = ?2", userId, organizationId)
.firstResultOptional()
.map(UserOrganizationEntityMapper::toDomain);
}
@Override @Override
@Transactional @Transactional
public void save(UserOrganization userOrganization) { public void save(UserOrganization userOrganization) {
UserOrganizationEntity entity = UserOrganizationEntityMapper.toEntity(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<UserOrganizationEntity> 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); persist(entity);
} }
} }

View file

@ -8,6 +8,7 @@ import net.beetechgroup.beetask.entities.User;
import net.beetechgroup.beetask.frameworks.persistence.entities.UserEntity; import net.beetechgroup.beetask.frameworks.persistence.entities.UserEntity;
import net.beetechgroup.beetask.frameworks.persistence.mapper.UserEntityMapper; import net.beetechgroup.beetask.frameworks.persistence.mapper.UserEntityMapper;
import net.beetechgroup.beetask.entities.organization.UserOrganization; 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.UserRepository;
import net.beetechgroup.beetask.usecase.repository.UserOrganizationRepository; import net.beetechgroup.beetask.usecase.repository.UserOrganizationRepository;
@ -46,6 +47,6 @@ public class UserRepositoryImpl implements UserRepository, PanacheRepository<Use
@Override @Override
public List<UserOrganization> findUserOrganizations(Long userId) { public List<UserOrganization> findUserOrganizations(Long userId) {
return userOrganizationRepository.findByUserId(userId); return userOrganizationRepository.findByUserIdAndStatus(userId, UserOrganizationStatus.ACTIVE);
} }
} }

View file

@ -4,11 +4,23 @@ import io.quarkus.security.Authenticated;
import io.quarkus.security.identity.SecurityIdentity; import io.quarkus.security.identity.SecurityIdentity;
import org.eclipse.microprofile.openapi.annotations.Operation; import org.eclipse.microprofile.openapi.annotations.Operation;
import jakarta.inject.Inject; import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST; import jakarta.ws.rs.POST;
import jakarta.ws.rs.PATCH;
import jakarta.ws.rs.Path; 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.CreateOrganizationInput;
import net.beetechgroup.beetask.usecase.organization.create.CreateOrganizationOutput; import net.beetechgroup.beetask.usecase.organization.create.CreateOrganizationOutput;
import net.beetechgroup.beetask.usecase.organization.create.CreateOrganizationUseCase; 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") @Path("/organizations")
public class OrganizationController { public class OrganizationController {
@ -16,6 +28,18 @@ public class OrganizationController {
@Inject @Inject
CreateOrganizationUseCase createOrganizationUseCase; CreateOrganizationUseCase createOrganizationUseCase;
@Inject
SearchOrganizationsUseCase searchOrganizationsUseCase;
@Inject
RequestJoinOrganizationUseCase requestJoinOrganizationUseCase;
@Inject
ListPendingRequestsUseCase listPendingRequestsUseCase;
@Inject
HandleJoinRequestUseCase handleJoinRequestUseCase;
@Inject @Inject
SecurityIdentity securityIdentity; SecurityIdentity securityIdentity;
@ -28,4 +52,48 @@ public class OrganizationController {
CreateOrganizationOutput output = createOrganizationUseCase.execute(input); CreateOrganizationOutput output = createOrganizationUseCase.execute(input);
return OrganizationControllerMapper.toCreateOrganizationResponse(output); return OrganizationControllerMapper.toCreateOrganizationResponse(output);
} }
@GET
@Path("/search")
@Authenticated
@Operation(summary = "Search organizations", description = "Search for organizations by name")
public List<SearchOrganizationOutput> 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<JoinRequestOutput> 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));
}
} }

View file

@ -0,0 +1,16 @@
package net.beetechgroup.beetask.usecase.organization.auth;
public abstract class AuthenticatedOrganizationUseCase<I, O> {
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);
}

View file

@ -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<User> userOptional = userRepository.findByEmail(userEmail);
if (userOptional.isEmpty()) {
throw new RuntimeException("Unauthorized: User not found");
}
User user = userOptional.get();
Optional<UserOrganization> 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");
}
}
}

View file

@ -4,6 +4,7 @@ import net.beetechgroup.beetask.entities.User;
import net.beetechgroup.beetask.entities.organization.Organization; import net.beetechgroup.beetask.entities.organization.Organization;
import net.beetechgroup.beetask.entities.organization.UserOrganization; import net.beetechgroup.beetask.entities.organization.UserOrganization;
import net.beetechgroup.beetask.entities.organization.UserOrganizationRole; 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.OrganizationRepository;
import net.beetechgroup.beetask.usecase.repository.UserOrganizationRepository; import net.beetechgroup.beetask.usecase.repository.UserOrganizationRepository;
import net.beetechgroup.beetask.usecase.repository.UserRepository; import net.beetechgroup.beetask.usecase.repository.UserRepository;
@ -38,6 +39,7 @@ public class CreateOrganizationUseCase {
userOrganization.setUser(user); userOrganization.setUser(user);
userOrganization.setOrganization(organization); userOrganization.setOrganization(organization);
userOrganization.setRole(UserOrganizationRole.OWNER); userOrganization.setRole(UserOrganizationRole.OWNER);
userOrganization.setStatus(UserOrganizationStatus.ACTIVE);
userOrganizationRepository.save(userOrganization); userOrganizationRepository.save(userOrganization);
return CreateOrganizationMapper.toCreateOrganizationOutput(organization); return CreateOrganizationMapper.toCreateOrganizationOutput(organization);

View file

@ -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<User> userOptional = userRepository.findByEmail(userEmail);
Optional<Organization> 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<UserOrganization> 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);
}
}

View file

@ -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<HandleJoinRequestUseCase.Input, Void> {
private final UserOrganizationRepository userOrganizationRepository;
public HandleJoinRequestUseCase(AuthorizeOrganizationAdminUseCase authorizer,
UserOrganizationRepository userOrganizationRepository) {
super(authorizer);
this.userOrganizationRepository = userOrganizationRepository;
}
@Override
protected Void doExecute(Long organizationId, Input input) {
Optional<UserOrganization> 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) {}
}

View file

@ -0,0 +1,3 @@
package net.beetechgroup.beetask.usecase.organization.requests;
public record JoinRequestOutput(Long userId, String name, String email, String photo) {}

View file

@ -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<Void, List<JoinRequestOutput>> {
private final UserOrganizationRepository userOrganizationRepository;
public ListPendingRequestsUseCase(AuthorizeOrganizationAdminUseCase authorizer,
UserOrganizationRepository userOrganizationRepository) {
super(authorizer);
this.userOrganizationRepository = userOrganizationRepository;
}
@Override
protected List<JoinRequestOutput> doExecute(Long organizationId, Void input) {
List<UserOrganization> 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());
}
}

View file

@ -0,0 +1,3 @@
package net.beetechgroup.beetask.usecase.organization.search;
public record SearchOrganizationOutput(Long id, String name) {}

View file

@ -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<SearchOrganizationOutput> execute(String query) {
List<Organization> results = organizationRepository.search(query);
return results.stream()
.map(org -> new SearchOrganizationOutput(org.getId(), org.getName()))
.collect(Collectors.toList());
}
}

View file

@ -2,7 +2,11 @@ package net.beetechgroup.beetask.usecase.repository;
import net.beetechgroup.beetask.entities.organization.Organization; import net.beetechgroup.beetask.entities.organization.Organization;
import java.util.List;
import java.util.Optional;
public interface OrganizationRepository { public interface OrganizationRepository {
Organization saveOrganization(Organization organization); Organization saveOrganization(Organization organization);
List<Organization> search(String query);
Optional<Organization> findOrganizationById(Long id);
} }

View file

@ -1,9 +1,14 @@
package net.beetechgroup.beetask.usecase.repository; package net.beetechgroup.beetask.usecase.repository;
import net.beetechgroup.beetask.entities.organization.UserOrganization; import net.beetechgroup.beetask.entities.organization.UserOrganization;
import net.beetechgroup.beetask.entities.organization.UserOrganizationStatus;
import java.util.List; import java.util.List;
import java.util.Optional;
public interface UserOrganizationRepository { public interface UserOrganizationRepository {
List<UserOrganization> findByUserId(Long userId); List<UserOrganization> findByUserId(Long userId);
List<UserOrganization> findByUserIdAndStatus(Long userId, UserOrganizationStatus status);
List<UserOrganization> findByOrganizationIdAndStatus(Long organizationId, UserOrganizationStatus status);
Optional<UserOrganization> findByUserAndOrganization(Long userId, Long organizationId);
void save(UserOrganization userOrganization); void save(UserOrganization userOrganization);
} }

View file

@ -27,12 +27,16 @@ public class GetUserProfileUseCase {
List<UserOrganization> userOrganizations = userRepository.findUserOrganizations(user.getId()); List<UserOrganization> userOrganizations = userRepository.findUserOrganizations(user.getId());
List<UserProfileOutput.OrganizationProfileOutput> orgs = userOrganizations.stream() List<UserProfileOutput.OrganizationProfileOutput> orgs = userOrganizations.stream()
.collect(Collectors.groupingBy(uo -> uo.getOrganization().getName())) .collect(Collectors.groupingBy(uo -> uo.getOrganization().getId()))
.entrySet().stream() .entrySet().stream()
.map(entry -> new UserProfileOutput.OrganizationProfileOutput( .map(entry -> {
UserOrganization firstUo = entry.getValue().get(0);
return new UserProfileOutput.OrganizationProfileOutput(
entry.getKey(), entry.getKey(),
firstUo.getOrganization().getName(),
entry.getValue().stream().map(uo -> uo.getRole().name()).collect(Collectors.toList()) entry.getValue().stream().map(uo -> uo.getRole().name()).collect(Collectors.toList())
)) );
})
.collect(Collectors.toList()); .collect(Collectors.toList());
return new UserProfileOutput( return new UserProfileOutput(

View file

@ -9,6 +9,7 @@ public record UserProfileOutput(
List<OrganizationProfileOutput> organizations List<OrganizationProfileOutput> organizations
) { ) {
public record OrganizationProfileOutput( public record OrganizationProfileOutput(
Long id,
String name, String name,
List<String> roles List<String> roles
) {} ) {}